Skip to content

Conversation

@Micro66
Copy link
Collaborator

@Micro66 Micro66 commented Nov 25, 2025

Summary

This PR integrates Dify as the third agent execution engine in Wegent, alongside ClaudeCode and Agno. It follows Wegent's Kubernetes-style CRD architecture pattern and enables users to leverage Dify's powerful workflow and agent capabilities.

Key Features

  • DifyAgent Implementation: Full integration with Dify API v1/chat-messages endpoint

    • Supports all Dify application types (chatbot, workflow, agent, chatflow)
    • Streaming response handling with conversation management
    • Dynamic configuration via Model environment variables and Team bot_prompt
  • Configuration Management:

    • DIFY_API_KEY: Required API key from Dify cloud or self-hosted instance
    • DIFY_BASE_URL: Dify service endpoint
    • DIFY_APP_ID: Default application ID (can be overridden in bot_prompt)
    • Dynamic app selection and parameters via Team.members[].prompt JSON
  • Architecture Compliance:

    • Follows existing Agent base class pattern
    • Registered in AgentFactory for seamless runtime selection
    • Added Dify Shell to public shells configuration
    • Enforces constraint: Dify Teams must have exactly one Bot
  • Comprehensive Testing:

    • Unit tests for DifyAgent core functionality
    • Tests for configuration validation and API call handling
    • Updated AgentFactory tests to include Dify support

Changes Overview

Backend:

  • executor/agents/dify/dify_agent.py: Core DifyAgent implementation
  • executor/agents/factory.py: Registered DifyAgent
  • backend/init_data/02-public-shells.yaml: Added Dify Shell configuration
  • backend/app/services/adapters/team_kinds.py: Added Dify single-bot validation

Testing:

  • executor/tests/agents/test_dify_agent.py: Comprehensive DifyAgent tests
  • executor/tests/agents/test_factory.py: Updated factory tests

Technical Highlights

  1. Conversation Management: Automatically manages Dify conversation_id for multi-turn dialogues
  2. Error Handling: Robust error handling for API failures, configuration issues, and streaming errors
  3. Flexible Configuration: Supports both default config in Model and dynamic override via bot_prompt
  4. Validation: Ensures Dify Teams have exactly one Bot (architectural constraint)

Test Plan

  • Unit tests pass for DifyAgent
  • AgentFactory correctly creates Dify agents
  • Team validation enforces single-bot constraint for Dify
  • Configuration validation works correctly
  • API call mocking tests streaming responses

Future Enhancements (Not in this PR)

Phase 2 features as outlined in the requirements:

  • Frontend Dify app selector component
  • Dynamic params form in chat interface
  • API endpoint to fetch user's Dify app list
  • LocalStorage caching for app selection and params

Summary by CodeRabbit

  • New Features

    • End-to-end Dify integration: backend API, runtime agent, frontend app selector, parameters form, and Dify-specific bot settings; new Accordion UI component.
  • Bug Fixes

    • Enforced single-bot constraint for external-api teams.
    • Final prompt field renamed to final_prompt; improved memo dependencies and searchable-select accessibility.
  • Tests

    • Added comprehensive tests for the Dify agent and agent factory.
  • Chores

    • Added frontend dependency for accordion component.

✏️ Tip: You can customize this high-level summary in your review settings.

This commit integrates Dify as a third agent runtime alongside ClaudeCode and Agno, following the CRD-based architecture pattern.

Changes:
- Implemented DifyAgent class with full Dify API integration
  - Supports streaming API responses from Dify chat-messages endpoint
  - Manages conversation IDs for multi-turn dialogues
  - Parses bot_prompt JSON for dynamic app ID and parameter configuration
  - Validates required configuration (API key, base URL, app ID)

- Registered DifyAgent in AgentFactory for runtime selection

- Added Dify Shell configuration to public shells initialization data

- Implemented Team validation constraint: Dify teams must have exactly one bot
  - Added validation in TeamKindsService._validate_bots method
  - Checks shell runtime and enforces single-bot rule for Dify

- Created comprehensive unit tests
  - test_dify_agent.py: Tests for DifyAgent functionality including initialization, configuration validation, API calls, and conversation management
  - Updated test_factory.py: Added Dify agent creation tests

Technical highlights:
- Follows existing agent architecture patterns (base.Agent inheritance)
- Configuration extracted from Model.env (DIFY_API_KEY, DIFY_BASE_URL, DIFY_APP_ID)
- Supports dynamic app ID override via Team.members[].prompt JSON
- Handles Dify API streaming responses with proper error handling
- Maintains conversation context across multiple task executions
@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Walkthrough

Adds Dify integration across backend, executor, tests, and frontend: new DifyAgent and factory registration, shellType schema and external_api single-bot validation, adapter endpoints for Dify app discovery/parameters, frontend app/params UI and types, utilities, tests, and UI helpers.

Changes

Cohort / File(s) Summary
Dify Agent Implementation
executor/agents/dify/__init__.py, executor/agents/dify/dify_agent.py
New DifyAgent class and package export: streaming calls to Dify API, per-task conversation_id storage, config parsing/validation, execute/pre_execute, and conversation lifecycle management.
Agent Factory & Tests
executor/agents/factory.py, executor/tests/agents/test_dify_agent.py, executor/tests/agents/test_factory.py
Register "dify"DifyAgent in factory; add helpers to classify agent types; add tests covering parsing, streaming behavior, conversation persistence, and factory instantiation.
Backend ShellType & Validation
backend/app/schemas/kind.py, backend/app/services/adapters/shell_utils.py, backend/app/services/adapters/team_kinds.py
Add optional shellType to ShellSpec (default local_engine); new shell utils to detect shell type; enforce single-bot-per-team when shellType is external_api (validation raises HTTP 400).
Backend Public Shells Data
backend/init_data/02-public-shells.yaml
Add/update public Shell entries: add Dify with shellType: external_api, annotate others with shellType: local_engine.
Backend API Endpoints
backend/app/api/api.py, backend/app/api/endpoints/adapter/dify.py
Mount /dify router and add endpoints to list Dify apps and fetch app parameters by resolving DIFY config from Kind models and proxying/normalizing Dify API responses with error mapping.
Frontend API Client
frontend/src/apis/dify.ts
Add getDifyApps() and getDifyAppParameters(appId) wrappers calling backend /dify endpoints.
Frontend Dify Components
frontend/src/features/tasks/components/DifyAppSelector.tsx, frontend/src/features/tasks/components/DifyParamsForm.tsx
Add DifyAppSelector (searchable app selection) and DifyParamsForm (JSON editor, per-field quick edits, localStorage persistence).
Frontend Types & Consumers
frontend/src/types/api.ts, frontend/src/features/tasks/components/FinalPromptMessage.tsx, frontend/src/features/tasks/components/MessagesArea.tsx
Add Dify-related TypeScript interfaces; rename FinalPromptData.promptFinalPromptData.final_prompt and update consumers.
Frontend Settings UI
frontend/src/features/settings/components/DifyBotConfig.tsx, frontend/src/features/settings/components/BotEdit.tsx
Add DifyBotConfig component for configuring API key/base URL/app; update BotEdit to conditionally handle Dify-specific UI and save logic.
Frontend UI Foundation
frontend/src/components/ui/accordion.tsx, frontend/package.json
Add Radix-based Accordion UI component and dependency @radix-ui/react-accordion.
Accessibility & Minor UI Fixes
frontend/src/components/ui/searchable-select.tsx, frontend/src/features/tasks/components/TaskSidebar.tsx
Add aria-controls to SearchableSelect trigger; remove viewStatusVersion from TaskSidebar memo deps.

Sequence Diagram(s)

sequenceDiagram
    participant TaskExec as Task Executor
    participant DifyAgent as DifyAgent
    participant Store as ConversationStore
    participant DifyAPI as Dify API

    TaskExec->>DifyAgent: execute(task_data)
    activate DifyAgent
    DifyAgent->>DifyAgent: parse bot_prompt / extract config
    DifyAgent->>DifyAgent: validate config
    DifyAgent->>Store: _get_conversation_id(task_id)
    alt have conversation_id
        Store-->>DifyAgent: conversation_id
    else none
        Store-->>DifyAgent: empty
    end
    DifyAgent->>DifyAPI: POST /v1/chat-messages (streaming)
    activate DifyAPI
    DifyAPI-->>DifyAgent: streaming events (content / error / conversation_id)
    DifyAgent->>DifyAgent: accumulate answer, extract conversation_id
    DifyAgent->>Store: _save_conversation_id(new_id)
    DifyAgent-->>TaskExec: COMPLETED or FAILED (ExecutionResult)
    deactivate DifyAgent
    deactivate DifyAPI
Loading
sequenceDiagram
    participant UI as Frontend
    participant API as Backend /dify
    participant DB as Database (Kind Models)
    participant DifyAPI as Dify API

    UI->>UI: detect Dify team via bot prompt
    UI->>API: GET /dify/apps
    activate API
    API->>DB: query Kind Models for user active shells
    API->>API: extract DIFY_API_KEY, DIFY_BASE_URL
    API->>DifyAPI: GET /v1/apps
    DifyAPI-->>API: apps list
    API-->>UI: simplified apps list
    deactivate API

    UI->>API: GET /dify/apps/{app_id}/parameters
    activate API
    API->>DifyAPI: GET /v1/parameters?app_id={app_id}
    DifyAPI-->>API: parameter schema
    API-->>UI: {user_input_form: [...]}
    deactivate API

    UI->>UI: DifyParamsForm renders & persists params to localStorage
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Files/areas needing close attention:
    • Streaming parsing, error handling, and conversation_id persistence in executor/agents/dify/dify_agent.py (_call_dify_api).
    • Shell type resolution and the new single-bot validation in backend/app/services/adapters/shell_utils.py and backend/app/services/adapters/team_kinds.py.
    • Backend adapter endpoints (backend/app/api/endpoints/adapter/dify.py) for correct config discovery and HTTP error mapping.
    • Frontend type rename (promptfinal_prompt) propagation across consumers and tests.

Possibly related PRs

Suggested reviewers

  • feifei325

Poem

🐰 A rabbit hops through Dify's door,

Streams of answers tumble o'er,
Apps picked, params snug and bright,
Conversation IDs tucked in tight,
Hooray — new agents make things soar! 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Dify Agent Support to Wegent' accurately summarizes the main objective of the PR: integrating Dify as a new agent execution engine.
Docstring Coverage ✅ Passed Docstring coverage is 86.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch weagent/add-dify-agent-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
executor/agents/dify/dify_agent.py (2)

27-28: Consider annotating class variable with ClassVar.

The _conversations dictionary is shared across all instances and should be annotated with typing.ClassVar for better type safety and clarity.

Apply this diff:

+from typing import Dict, Any, Optional, ClassVar
+
 class DifyAgent(Agent):
     """
     Dify Agent that integrates with Dify API
     Supports Dify chatbot, workflow, agent, and chatflow applications
     """
 
     # Static dictionary for storing conversation IDs per task
-    _conversations: Dict[str, str] = {}
+    _conversations: ClassVar[Dict[str, str]] = {}

161-260: API call implementation handles streaming responses correctly.

The streaming response processing properly accumulates answer chunks and tracks conversation state. The 5-minute timeout is appropriate for potentially long-running Dify workflows.

Consider using exception chaining for better debugging:

             logger.error(error_msg)
-            raise Exception(error_msg)
+            raise Exception(error_msg) from e

Apply similar changes at lines 255 and 259 to preserve the original exception context.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1583fa and 2b4acd8.

📒 Files selected for processing (7)
  • backend/app/services/adapters/team_kinds.py (2 hunks)
  • backend/init_data/02-public-shells.yaml (1 hunks)
  • executor/agents/dify/__init__.py (1 hunks)
  • executor/agents/dify/dify_agent.py (1 hunks)
  • executor/agents/factory.py (2 hunks)
  • executor/tests/agents/test_dify_agent.py (1 hunks)
  • executor/tests/agents/test_factory.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must be PEP 8 compliant, use Black formatter (line length: 88), isort for imports, and type hints required
Use descriptive names for functions and variables in Python code
Include docstrings for all public functions and classes in Python
Extract magic numbers to constants in Python code
Keep Python functions to a maximum of 50 lines (preferred)

Files:

  • executor/agents/factory.py
  • executor/tests/agents/test_dify_agent.py
  • executor/tests/agents/test_factory.py
  • backend/app/services/adapters/team_kinds.py
  • executor/agents/dify/dify_agent.py
  • executor/agents/dify/__init__.py
backend/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Backend must encrypt Git tokens before database storage

Files:

  • backend/app/services/adapters/team_kinds.py
🧬 Code graph analysis (4)
executor/agents/factory.py (1)
executor/agents/dify/dify_agent.py (1)
  • DifyAgent (21-337)
executor/tests/agents/test_dify_agent.py (1)
executor/agents/dify/dify_agent.py (8)
  • DifyAgent (21-337)
  • _parse_bot_prompt (92-115)
  • _validate_config (140-159)
  • _call_dify_api (161-259)
  • execute (261-324)
  • _save_conversation_id (129-138)
  • clear_conversation (327-337)
  • get_name (30-31)
executor/agents/dify/dify_agent.py (2)
shared/models/task.py (1)
  • ExecutionResult (58-66)
executor/tests/agents/test_dify_agent.py (1)
  • task_data (16-43)
executor/agents/dify/__init__.py (1)
executor/agents/dify/dify_agent.py (1)
  • DifyAgent (21-337)
🪛 Ruff (0.14.5)
executor/agents/factory.py

25-25: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

executor/tests/agents/test_dify_agent.py

177-177: Do not assert blind exception: Exception

(B017)

backend/app/services/adapters/team_kinds.py

546-546: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


568-568: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)

executor/agents/dify/dify_agent.py

1-1: Shebang is present but file is not executable

(EXE001)


28-28: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


109-109: Consider moving this statement to an else block

(TRY300)


113-113: Do not catch blind exception: Exception

(BLE001)


232-232: Create your own exception

(TRY002)


232-232: Avoid specifying long messages outside the exception class

(TRY003)


241-244: Consider moving this statement to an else block

(TRY300)


252-252: Do not use bare except

(E722)


252-253: try-except-pass detected, consider logging the exception

(S110)


254-254: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


255-255: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


255-255: Create your own exception

(TRY002)


258-258: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


259-259: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


259-259: Create your own exception

(TRY002)

executor/agents/dify/__init__.py

1-1: Shebang is present but file is not executable

(EXE001)

🔇 Additional comments (17)
backend/init_data/02-public-shells.yaml (1)

28-38: LGTM!

The Dify Shell resource definition follows the established pattern and is consistent with the existing ClaudeCode and Agno shells. All required fields are properly configured.

backend/app/services/adapters/team_kinds.py (2)

518-527: LGTM!

The docstring update clearly documents the new Dify runtime constraint, making the validation behavior explicit.


558-580: Code logic is correct—no changes needed.

The shell existence validation occurs elsewhere in the codebase (as confirmed by kind_impl.py line 93, which raises NotFoundException for missing shells). The silent skip when shell is None at line 571 is intentional and correct: the Dify constraint only applies when a Dify runtime is actually in use. If a shell doesn't exist for a particular bot, that bot simply isn't using Dify, and the constraint doesn't apply.

executor/agents/dify/dify_agent.py (6)

33-62: LGTM!

The initialization logic is well-structured, extracting configuration from multiple sources with appropriate fallbacks and logging the initialization state.


63-91: LGTM!

Configuration extraction uses defensive programming with .get() calls and proper defaults, ensuring safe access even with missing or malformed data.


92-116: LGTM!

The JSON parsing includes appropriate error handling for user-provided input. The defensive approach with broad exception catching is reasonable for parsing external configuration data, ensuring the agent gracefully degrades to defaults rather than failing initialization.


117-160: LGTM!

Conversation ID management and configuration validation are well-implemented. Using task_id as the key ensures proper isolation of conversations per task, and validation provides clear error messages for missing configuration.


261-325: LGTM!

The execution flow is well-structured with clear progress reporting, proper error handling, and appropriate status transitions. Treating an empty answer as a failure is correct behavior.


326-337: LGTM!

The conversation cleanup method is correctly implemented as a classmethod, allowing external cleanup of conversation state when needed.

executor/agents/factory.py (1)

15-15: LGTM!

DifyAgent is properly imported and registered in the factory, following the established pattern for ClaudeCode and Agno agents. The factory will now correctly instantiate Dify agents via get_agent("dify", task_data).

Also applies to: 25-25

executor/agents/dify/__init__.py (1)

9-11: LGTM!

The module properly exports DifyAgent via __all__, following Python package conventions.

executor/tests/agents/test_factory.py (2)

62-76: LGTM!

The test cases for DifyAgent follow the established testing pattern and provide appropriate coverage for agent instantiation and case-insensitive type matching.


89-96: LGTM!

The registry validation tests ensure that DifyAgent is properly registered in the factory mapping, complementing the instantiation tests.

executor/tests/agents/test_dify_agent.py (4)

15-91: LGTM!

The test fixture provides comprehensive test data, and the initialization and parsing tests properly cover both success paths and error handling scenarios, including JSON parsing failures and empty inputs.


92-179: LGTM!

Configuration validation tests thoroughly cover all required fields, and API interaction tests properly mock streaming responses and error scenarios. The exception handling tests appropriately verify failure paths.


180-233: LGTM!

The execution flow tests comprehensively cover success and failure scenarios, including configuration validation failures, empty responses, and API exceptions. Proper mocking ensures isolated testing of execution logic.


234-257: LGTM!

The conversation management test excellently validates the class-level conversation state persistence across agent instances and proper cleanup. This is critical for verifying multi-turn conversation support.

This commit implements the frontend components and API integration for Dify agent support, completing Phase 2 of the Dify integration.

Backend Changes:
- Created /api/v1/dify/apps endpoint to fetch user's Dify applications
- Created /api/v1/dify/apps/{app_id}/parameters endpoint for app parameter schemas
- Registered Dify router in main API configuration
- Fetches apps using user's configured DIFY_API_KEY and DIFY_BASE_URL from Model

Frontend Changes:
- Added DifyApp, DifyBotPrompt, DifyParameterField, DifyParametersSchema types
- Created DifyAppSelector component:
  - Fetches and displays available Dify applications
  - Auto-detects Dify teams based on bot_prompt structure
  - Provides dropdown selector for switching between apps
  - Displays app icon, name, and mode (chat/workflow/agent/chatflow)

- Created DifyParamsForm component:
  - Provides JSON editor for Dify application parameters
  - Includes quick edit interface for individual parameters
  - Persists parameters to localStorage per team (dify_params_team_{team_id})
  - Collapsible accordion UI for clean integration
  - Auto-loads cached parameters on team selection

- Created dify.ts API client with getDifyApps() and getDifyAppParameters() functions

Technical Highlights:
- Components auto-detect Dify teams by checking bot_prompt JSON structure
- Parameters are cached in localStorage for persistence across sessions
- Clean UI integration using existing design system components
- Type-safe API calls with proper TypeScript interfaces
- Error handling with user-friendly messages

Integration Notes:
- DifyAppSelector can be integrated into ChatArea or Workbench header
- DifyParamsForm should be placed near ChatInput or in a sidebar
- Components automatically show/hide based on selected team runtime
- Ready for final integration into main chat interface
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
frontend/src/apis/dify.ts (1)

5-20: Typed Dify API wrappers look good; consider centralizing usage

The typed wrappers for /dify/apps and /dify/apps/{appId}/parameters are clean and match the shared types. Consider updating callers (e.g. DifyAppSelector) to use these helpers instead of hitting apiClient directly so the endpoint path and response typing stay centralized.

frontend/src/features/tasks/components/DifyAppSelector.tsx (1)

7-12: Harden Dify team detection and reuse the shared Dify API helper

Two small improvements here:

  1. Guard promptData before using the in operator

If bot_prompt JSON happens to be a primitive (e.g. "123" or 42), 'difyAppId' in promptData can throw. A light type guard avoids that:

-    const firstBot = selectedTeam.bots[0];
-    try {
-      if (firstBot.bot_prompt) {
-        const promptData = JSON.parse(firstBot.bot_prompt);
-        return 'difyAppId' in promptData || 'params' in promptData;
-      }
-    } catch {
-      // Not a JSON, not a Dify team
-    }
+    const firstBot = selectedTeam.bots[0];
+    try {
+      if (firstBot.bot_prompt) {
+        const promptData = JSON.parse(firstBot.bot_prompt);
+        if (promptData && typeof promptData === 'object' && !Array.isArray(promptData)) {
+          return 'difyAppId' in promptData || 'params' in promptData;
+        }
+      }
+    } catch {
+      // Not valid JSON or not an object, not a Dify team
+    }
  1. Prefer the shared getDifyApps wrapper over direct apiClient usage

Since frontend/src/apis/dify.ts already exposes a typed getDifyApps, you can simplify the fetch and avoid duplicating the URL:

-import { apiClient } from '@/apis/client';
+import { getDifyApps } from '@/apis/dify';
@@
-        const response = await apiClient.get<DifyApp[]>('/dify/apps');
-        setApps(response);
+        const response = await getDifyApps();
+        setApps(response);

The rest of the component, particularly the use of SearchableSelect from the shared UI library, aligns well with the existing design system. Based on learnings, this keeps the Dify UI consistent with other task components.

Also applies to: 33-49, 59-69

frontend/src/features/tasks/components/DifyParamsForm.tsx (1)

90-141: Consider more forgiving JSON editing & form validation for params

Right now handleJsonChange silently ignores invalid JSON and the textarea value is always derived from paramsJson, so any partially typed but invalid JSON is immediately reverted to the last valid state. That’s safe but can feel jarring when editing larger payloads.

Longer‑term, you might want to:

  • Keep the raw textarea value in state, validate separately, and surface parse errors inline; and/or
  • Move this into the project’s standard react-hook-form + zod stack for better schema‑based validation and error messages.

Not blocking, but worth considering as the Dify params UX matures.

frontend/src/types/api.ts (1)

303-327: Dify type shapes align with usage; consider tightening any in follow‑ups

The new Dify types (DifyApp, DifyBotPrompt, DifyParameterField, DifyParametersSchema) match how the frontend components and API wrappers use them and look coherent.

If you want stricter typing later, you could:

  • Change params?: Record<string, any> to Record<string, unknown> or a generic Record<string, string | number | boolean>, and
  • Mirror the exact Dify parameter schema (e.g. discriminated unions by type) once that stabilizes.

Not required for this PR, but it would improve type‑safety around Dify parameter handling over time.

backend/app/api/endpoints/adapter/dify.py (1)

37-61: Factor out Dify config lookup to avoid duplication & keep filters consistent

Both endpoints duplicate the same “find user’s Dify Model config” logic (Kind query + CRD parsing + env extraction). Functionally this works, but it’s easy for the two copies to drift over time (e.g. when adding DIFY_APP_ID or additional fields).

Consider extracting a small helper, e.g.:

def _get_dify_config(db: Session, current_user: User) -> dict[str, str]:
    models = (
        db.query(Kind)
        .filter(
            Kind.user_id == current_user.id,
            Kind.kind == "Model",
            Kind.is_active == True,  # or Kind.is_active.is_(True) for SQLAlchemy
        )
        .all()
    )

    for model in models:
        try:
            model_crd = Model.model_validate(model.json)
            env = model_crd.spec.modelConfig.get("env", {})
            if "DIFY_API_KEY" in env and "DIFY_BASE_URL" in env:
                return {"api_key": env["DIFY_API_KEY"], "base_url": env["DIFY_BASE_URL"]}
        except Exception as e:
            logger.warning("Failed to parse model %s: %s", model.id, e)

    raise HTTPException(
        status_code=404,
        detail="No Dify configuration found. Please configure DIFY_API_KEY and DIFY_BASE_URL in a Model.",
    )

Then both get_dify_apps and get_dify_app_parameters can call this helper, which keeps the selection/filtering rules in one place.

Also applies to: 142-169

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b4acd8 and 2eea503.

📒 Files selected for processing (6)
  • backend/app/api/api.py (2 hunks)
  • backend/app/api/endpoints/adapter/dify.py (1 hunks)
  • frontend/src/apis/dify.ts (1 hunks)
  • frontend/src/features/tasks/components/DifyAppSelector.tsx (1 hunks)
  • frontend/src/features/tasks/components/DifyParamsForm.tsx (1 hunks)
  • frontend/src/types/api.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: TypeScript code must use strict mode, functional components with hooks, Prettier formatter, ESLint with Next.js config, single quotes, and no semicolons
Use const over let, never use var in TypeScript/React code
Prefer functional patterns in TypeScript/React components
Use PascalCase for component names and kebab-case for component files in React

Files:

  • frontend/src/features/tasks/components/DifyAppSelector.tsx
  • frontend/src/apis/dify.ts
  • frontend/src/types/api.ts
  • frontend/src/features/tasks/components/DifyParamsForm.tsx
frontend/**/*.{ts,tsx,jsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS with design system colors and spacing units defined in CSS variables for frontend UI development

Files:

  • frontend/src/features/tasks/components/DifyAppSelector.tsx
  • frontend/src/apis/dify.ts
  • frontend/src/types/api.ts
  • frontend/src/features/tasks/components/DifyParamsForm.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: Apply responsive design with mobile-first approach using Tailwind CSS breakpoints (sm, md, lg)
Use shadcn/ui components from frontend/src/components/ui/ for consistent component library usage
Use react-hook-form with zod validation for form handling in React

Files:

  • frontend/src/features/tasks/components/DifyAppSelector.tsx
  • frontend/src/apis/dify.ts
  • frontend/src/types/api.ts
  • frontend/src/features/tasks/components/DifyParamsForm.tsx
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must be PEP 8 compliant, use Black formatter (line length: 88), isort for imports, and type hints required
Use descriptive names for functions and variables in Python code
Include docstrings for all public functions and classes in Python
Extract magic numbers to constants in Python code
Keep Python functions to a maximum of 50 lines (preferred)

Files:

  • backend/app/api/api.py
  • backend/app/api/endpoints/adapter/dify.py
backend/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Backend must encrypt Git tokens before database storage

Files:

  • backend/app/api/api.py
  • backend/app/api/endpoints/adapter/dify.py
🧠 Learnings (1)
📚 Learning: 2025-11-25T01:57:13.462Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T01:57:13.462Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use shadcn/ui components from `frontend/src/components/ui/` for consistent component library usage

Applied to files:

  • frontend/src/features/tasks/components/DifyAppSelector.tsx
🧬 Code graph analysis (2)
frontend/src/apis/dify.ts (2)
frontend/src/types/api.ts (2)
  • DifyApp (304-310)
  • DifyParametersSchema (326-328)
frontend/src/apis/client.ts (1)
  • apiClient (105-105)
frontend/src/features/tasks/components/DifyParamsForm.tsx (4)
frontend/src/types/api.ts (1)
  • Team (39-53)
frontend/src/components/ui/label.tsx (1)
  • Label (21-21)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (31-31)
frontend/src/components/ui/input.tsx (1)
  • Input (28-28)
🪛 Biome (2.1.2)
frontend/src/features/tasks/components/DifyAppSelector.tsx

[error] 88-88: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 Ruff (0.14.5)
backend/app/api/endpoints/adapter/dify.py

24-24: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


25-25: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


41-41: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


57-57: Do not catch blind exception: Exception

(BLE001)


102-102: Consider moving this statement to an else block

(TRY300)


110-110: Do not use bare except

(E722)


110-111: try-except-pass detected, consider logging the exception

(S110)


112-112: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


113-113: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


116-116: Use explicit conversion flag

Replace with conversion flag

(RUF010)


117-117: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


118-118: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


120-120: Do not catch blind exception: Exception

(BLE001)


121-121: Use explicit conversion flag

Replace with conversion flag

(RUF010)


122-122: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


123-123: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


129-129: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


130-130: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


146-146: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


161-161: Do not catch blind exception: Exception

(BLE001)


194-194: Consider moving this statement to an else block

(TRY300)


203-203: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


204-204: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


206-206: Do not catch blind exception: Exception

(BLE001)


207-207: Use explicit conversion flag

Replace with conversion flag

(RUF010)


208-208: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


209-209: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (1)
backend/app/api/api.py (1)

6-22: Dify router wiring matches existing adapter pattern

Importing dify and mounting dify.router under /dify with its own tag is consistent with the other adapter routers; no issues here.

Comment on lines +171 to +205
# Call Dify API to get app parameters
try:
api_url = f"{dify_config['base_url']}/v1/parameters"
headers = {
"Authorization": f"Bearer {dify_config['api_key']}",
"Content-Type": "application/json"
}

params = {"app_id": app_id}

logger.info(f"Fetching parameters for Dify app: {app_id}")

response = requests.get(
api_url,
headers=headers,
params=params,
timeout=10
)

response.raise_for_status()
data = response.json()

logger.info(f"Successfully fetched parameters for app {app_id}")
return data

except requests.exceptions.HTTPError as e:
# If parameters endpoint doesn't exist, return empty schema
if e.response is not None and e.response.status_code == 404:
logger.info(f"Parameters endpoint not available for app {app_id}, returning empty schema")
return {"user_input_form": []}

error_msg = f"Dify API HTTP error: {e}"
logger.error(error_msg)
raise HTTPException(status_code=502, detail=error_msg)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Align error handling in get_dify_app_parameters with get_dify_apps (handle RequestException)

get_dify_app_parameters currently handles requests.exceptions.HTTPError and then falls back to a broad except Exception. Network‑level errors like timeouts or DNS failures (subclasses of RequestException but not HTTPError) will hit the generic Exception path and be surfaced as 500s, whereas get_dify_apps treats them as 502s with a clearer message.

Bringing the parameters endpoint in line with the apps endpoint improves consistency and diagnostics:

     except requests.exceptions.HTTPError as e:
         # If parameters endpoint doesn't exist, return empty schema
         if e.response is not None and e.response.status_code == 404:
             logger.info(f"Parameters endpoint not available for app {app_id}, returning empty schema")
             return {"user_input_form": []}
 
-        error_msg = f"Dify API HTTP error: {e}"
-        logger.error(error_msg)
-        raise HTTPException(status_code=502, detail=error_msg)
-
-    except Exception as e:
-        error_msg = f"Failed to fetch app parameters: {str(e)}"
-        logger.error(error_msg)
-        raise HTTPException(status_code=500, detail=error_msg)
+        error_msg = f"Dify API HTTP error: {e}"
+        logger.exception(error_msg)
+        raise HTTPException(status_code=502, detail=error_msg) from e
+
+    except requests.exceptions.RequestException as e:
+        error_msg = f"Failed to connect to Dify API: {e}"
+        logger.exception(error_msg)
+        raise HTTPException(status_code=502, detail=error_msg) from e
+
+    except Exception as e:
+        error_msg = f"Failed to fetch app parameters: {e}"
+        logger.exception(error_msg)
+        raise HTTPException(status_code=500, detail=error_msg) from e

This keeps the special 404 → empty schema behavior, while treating network issues the same way for both endpoints and preserving the original stack traces in the logs.


🏁 Script executed:

#!/bin/bash
# Locate the dify.py file and inspect both functions
fd "dify.py" --type f

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Extract get_dify_apps and get_dify_app_parameters functions with context
rg -n "def get_dify_app" backend/app/api/endpoints/adapter/dify.py -A 50 -B 2

Length of output: 3599


🏁 Script executed:

#!/bin/bash
# Verify RequestException is a requests exception class
rg -n "RequestException\|HTTPError" backend/app/api/endpoints/adapter/dify.py -B 2 -A 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get full try-except block for get_dify_apps (starting at line 68)
sed -n '68,125p' backend/app/api/endpoints/adapter/dify.py

Length of output: 1950


🏁 Script executed:

#!/bin/bash
# Get full try-except block for get_dify_app_parameters (starting at line 171 to end)
sed -n '171,210p' backend/app/api/endpoints/adapter/dify.py

Length of output: 1326


🏁 Script executed:

#!/bin/bash
# Check imports at top of file to see exception handling approach
head -30 backend/app/api/endpoints/adapter/dify.py

Length of output: 793


Add explicit RequestException handling to get_dify_app_parameters for consistency

The get_dify_apps endpoint (lines 68–125) explicitly handles requests.exceptions.RequestException with a 502 status, while get_dify_app_parameters (lines 171–210) lacks this handler. Connection errors like timeouts will fall through to the generic Exception block and return 500 instead of 502, creating inconsistent error handling between similar endpoints and making diagnostics harder.

Add an explicit RequestException handler before the generic exception:

    except requests.exceptions.HTTPError as e:
        # If parameters endpoint doesn't exist, return empty schema
        if e.response is not None and e.response.status_code == 404:
            logger.info(f"Parameters endpoint not available for app {app_id}, returning empty schema")
            return {"user_input_form": []}

        error_msg = f"Dify API HTTP error: {e}"
        logger.error(error_msg)
        raise HTTPException(status_code=502, detail=error_msg)

+    except requests.exceptions.RequestException as e:
+        error_msg = f"Failed to connect to Dify API: {e}"
+        logger.error(error_msg)
+        raise HTTPException(status_code=502, detail=error_msg)
+
    except Exception as e:
        error_msg = f"Failed to fetch app parameters: {str(e)}"
         logger.error(error_msg)
         raise HTTPException(status_code=500, detail=error_msg)

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.5)

194-194: Consider moving this statement to an else block

(TRY300)


203-203: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


204-204: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
In backend/app/api/endpoints/adapter/dify.py around lines 171 to 205, add an
explicit except block for requests.exceptions.RequestException placed after the
existing HTTPError handler and before the generic Exception handler; in that
block log the error (including details) and raise HTTPException(status_code=502,
detail="Dify API request error: <error>") so timeouts/connection errors return
502 consistently with get_dify_apps, leaving the current 404 handling intact.

Comment on lines +39 to +54
// Check if selected team is using Dify runtime
const isDifyTeam = useMemo(() => {
if (!selectedTeam || !selectedTeam.bots || selectedTeam.bots.length === 0) {
return false;
}
const firstBot = selectedTeam.bots[0];
try {
if (firstBot.bot_prompt) {
const promptData = JSON.parse(firstBot.bot_prompt);
return 'difyAppId' in promptData || 'params' in promptData;
}
} catch {
// Not a JSON, not a Dify team
}
return false;
}, [selectedTeam]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a small type guard when inspecting parsed bot_prompt JSON

Here too, if selectedTeam.bots[0].bot_prompt parses to a primitive (e.g. "123"), using the in operator on it can throw. Mirroring the guard suggested for DifyAppSelector makes this more robust:

-    const firstBot = selectedTeam.bots[0];
-    try {
-      if (firstBot.bot_prompt) {
-        const promptData = JSON.parse(firstBot.bot_prompt);
-        return 'difyAppId' in promptData || 'params' in promptData;
-      }
-    } catch {
-      // Not a JSON, not a Dify team
-    }
+    const firstBot = selectedTeam.bots[0];
+    try {
+      if (firstBot.bot_prompt) {
+        const promptData = JSON.parse(firstBot.bot_prompt);
+        if (promptData && typeof promptData === 'object' && !Array.isArray(promptData)) {
+          return 'difyAppId' in promptData || 'params' in promptData;
+        }
+      }
+    } catch {
+      // Not valid JSON or not an object, not a Dify team
+    }

This keeps the “is Dify team?” heuristic intact while avoiding edge‑case runtime errors.

🤖 Prompt for AI Agents
frontend/src/features/tasks/components/DifyParamsForm.tsx around lines 39 to 54:
the parsed bot_prompt may be a primitive (e.g. "123") so using the 'in' operator
can throw; inside the try block, add a guard that promptData is an object and
not null (e.g. typeof promptData === 'object' && promptData !== null) before
checking 'difyAppId' or 'params' with the 'in' operator, then return the result
of those checks; keep the existing try/catch and fallthrough return false.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/features/tasks/components/TaskSidebar.tsx (1)

93-115: Unread counters can become stale after removing viewStatusVersion from useMemo dependencies

Both groupTasksByDate and totalUnreadCount now only recompute when tasks or getUnreadCount change. When markAllTasksAsViewed() is called, it updates localStorage and increments viewStatusVersion to signal a view-state change, but the tasks array identity and getUnreadCount function reference remain unchanged. This means the memoized unread counts (todayUnread, thisWeekUnread, earlierUnread, totalUnreadCount) stay stale—returning cached results even though localStorage view status has been updated.

Since isTaskUnread reads from localStorage to determine unread status, and viewStatusVersion is explicitly used as the change signal for view-state updates in the context provider, viewStatusVersion should be included in the dependency arrays.

Proposed fix:

-  }, [tasks, getUnreadCount])
+  }, [tasks, getUnreadCount, viewStatusVersion])-  }, [tasks, getUnreadCount])
+  }, [tasks, getUnreadCount, viewStatusVersion])

Also applies to: 139-143

frontend/src/components/ui/searchable-select.tsx (1)

97-103: Wire aria-controls to an actual element id

The trigger now sets aria-controls="searchable-select-popover", but no element in this component has that id, so the ARIA relationship is dangling. Consider assigning the same id to the controlled list (or popover content), e.g.:

-            <CommandList className="max-h-[300px] overflow-y-auto">
+            <CommandList
+              id="searchable-select-popover"
+              className="max-h-[300px] overflow-y-auto"
+            >

This keeps the a11y improvement while making the reference valid.

♻️ Duplicate comments (2)
frontend/src/types/api.ts (1)

298-327: Consider keeping a backward‑compatible prompt alias on FinalPromptData

FinalPromptData now only exposes final_prompt: string. The updated consumers in this PR are aligned, but any existing code (or backend payloads) still using a prompt field will now be out of sync.

To make this rename safer during migration, you can keep a deprecated alias:

 export interface FinalPromptData {
   type: 'final_prompt';
-  final_prompt: string;
+  final_prompt: string;
+  /**
+   * Backward‑compat alias for older payloads using `prompt`.
+   * Prefer `final_prompt` for new code.
+   */
+  prompt?: string;
 }

This lets you read either field at runtime while nudging new code toward final_prompt. The new Dify types (DifyApp, DifyBotPrompt, DifyParameterField, DifyParametersSchema) otherwise look consistent with how they’re consumed.

You can re‑scan for any external or backend references to prompt to gauge how much old usage remains:

#!/bin/bash
rg -n "final_prompt" . -S
rg -n "FinalPromptData" . -S
rg -n "\bprompt\b" backend executor -S
frontend/src/features/tasks/components/DifyParamsForm.tsx (1)

40-54: Add an object guard for parsed bot_prompt and consider tightening effect deps

  1. Guard parsed bot_prompt before using in

As previously noted in an earlier review, JSON.parse(firstBot.bot_prompt) can return a primitive or array; using 'difyAppId' in promptData on such a value will throw. Mirror the defensive guard used elsewhere:

-    const firstBot = selectedTeam.bots[0];
-    try {
-      if (firstBot.bot_prompt) {
-        const promptData = JSON.parse(firstBot.bot_prompt);
-        return 'difyAppId' in promptData || 'params' in promptData;
-      }
-    } catch {
-      // Not a JSON, not a Dify team
-    }
+    const firstBot = selectedTeam.bots[0];
+    try {
+      if (firstBot.bot_prompt) {
+        const promptData = JSON.parse(firstBot.bot_prompt);
+        if (promptData && typeof promptData === 'object' && !Array.isArray(promptData)) {
+          return 'difyAppId' in promptData || 'params' in promptData;
+        }
+      }
+    } catch {
+      // Not valid JSON or not an object, not a Dify team
+    }
  1. Optional: align the load effect’s deps with its usage

The load‑from‑localStorage effect uses onParamsChange but omits it from the deps array and disables react-hooks/exhaustive-deps. You could either:

  • Add onParamsChange to the deps array and drop the disable, or
  • Add a brief comment explaining why the deps lint is intentionally suppressed.

Also, _selectedAppId is currently unused — if you don’t plan to use it soon, consider removing it from props to keep the surface minimal.

Also applies to: 57-81

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2eea503 and 49c79b7.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • frontend/package.json (1 hunks)
  • frontend/src/components/ui/accordion.tsx (1 hunks)
  • frontend/src/components/ui/searchable-select.tsx (1 hunks)
  • frontend/src/features/settings/components/BotEdit.tsx (1 hunks)
  • frontend/src/features/tasks/components/DifyAppSelector.tsx (1 hunks)
  • frontend/src/features/tasks/components/DifyParamsForm.tsx (1 hunks)
  • frontend/src/features/tasks/components/FinalPromptMessage.tsx (3 hunks)
  • frontend/src/features/tasks/components/MessagesArea.tsx (1 hunks)
  • frontend/src/features/tasks/components/TaskSidebar.tsx (2 hunks)
  • frontend/src/types/api.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: TypeScript code must use strict mode, functional components with hooks, Prettier formatter, ESLint with Next.js config, single quotes, and no semicolons
Use const over let, never use var in TypeScript/React code
Prefer functional patterns in TypeScript/React components
Use PascalCase for component names and kebab-case for component files in React

Files:

  • frontend/src/features/tasks/components/FinalPromptMessage.tsx
  • frontend/src/features/tasks/components/TaskSidebar.tsx
  • frontend/src/components/ui/searchable-select.tsx
  • frontend/src/features/settings/components/BotEdit.tsx
  • frontend/src/features/tasks/components/MessagesArea.tsx
  • frontend/src/features/tasks/components/DifyAppSelector.tsx
  • frontend/src/components/ui/accordion.tsx
  • frontend/src/types/api.ts
  • frontend/src/features/tasks/components/DifyParamsForm.tsx
frontend/**/*.{ts,tsx,jsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS with design system colors and spacing units defined in CSS variables for frontend UI development

Files:

  • frontend/src/features/tasks/components/FinalPromptMessage.tsx
  • frontend/src/features/tasks/components/TaskSidebar.tsx
  • frontend/src/components/ui/searchable-select.tsx
  • frontend/src/features/settings/components/BotEdit.tsx
  • frontend/src/features/tasks/components/MessagesArea.tsx
  • frontend/src/features/tasks/components/DifyAppSelector.tsx
  • frontend/src/components/ui/accordion.tsx
  • frontend/src/types/api.ts
  • frontend/src/features/tasks/components/DifyParamsForm.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: Apply responsive design with mobile-first approach using Tailwind CSS breakpoints (sm, md, lg)
Use shadcn/ui components from frontend/src/components/ui/ for consistent component library usage
Use react-hook-form with zod validation for form handling in React

Files:

  • frontend/src/features/tasks/components/FinalPromptMessage.tsx
  • frontend/src/features/tasks/components/TaskSidebar.tsx
  • frontend/src/components/ui/searchable-select.tsx
  • frontend/src/features/settings/components/BotEdit.tsx
  • frontend/src/features/tasks/components/MessagesArea.tsx
  • frontend/src/features/tasks/components/DifyAppSelector.tsx
  • frontend/src/components/ui/accordion.tsx
  • frontend/src/types/api.ts
  • frontend/src/features/tasks/components/DifyParamsForm.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-25T01:57:13.462Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T01:57:13.462Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use shadcn/ui components from `frontend/src/components/ui/` for consistent component library usage

Applied to files:

  • frontend/package.json
  • frontend/src/features/tasks/components/DifyAppSelector.tsx
  • frontend/src/components/ui/accordion.tsx
📚 Learning: 2025-11-25T01:57:13.462Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T01:57:13.462Z
Learning: Applies to **/*.{ts,tsx} : Prefer functional patterns in TypeScript/React components

Applied to files:

  • frontend/src/features/tasks/components/DifyAppSelector.tsx
🧬 Code graph analysis (3)
frontend/src/features/tasks/components/TaskSidebar.tsx (1)
frontend/src/utils/taskViewStatus.ts (1)
  • getUnreadCount (121-123)
frontend/src/features/tasks/components/DifyAppSelector.tsx (3)
frontend/src/types/api.ts (2)
  • Team (39-53)
  • DifyApp (304-310)
frontend/src/apis/client.ts (1)
  • apiClient (105-105)
frontend/src/components/ui/searchable-select.tsx (2)
  • SearchableSelectItem (20-26)
  • SearchableSelect (48-203)
frontend/src/components/ui/accordion.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (8-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Executor
🔇 Additional comments (5)
frontend/src/features/settings/components/BotEdit.tsx (1)

204-214: LGTM! Good dependency optimization.

Correctly updated both useCallback dependency arrays to include only i18n.language instead of t. Since neither callback uses the translation function, removing it from the dependencies prevents unnecessary re-memoization and follows React best practices.

frontend/src/features/tasks/components/FinalPromptMessage.tsx (1)

36-48: FinalPromptMessage correctly migrated to data.final_prompt

All internal uses of the final prompt (clipboard, sessionStorage payload, Markdown rendering) now read from data.final_prompt, matching the updated API type. The local promptData.prompt field in sessionStorage is just an internal key, so it doesn’t conflict.

Also applies to: 78-80, 106-110

frontend/src/components/ui/accordion.tsx (1)

13-58: Accordion wrappers look consistent with Radix/shadcn patterns

The Accordion, AccordionItem, AccordionTrigger, and AccordionContent wrappers correctly preserve Radix types, use forwardRef, and apply animation/chevron styles in a shadcn‑style way. I don’t see any functional or a11y issues here.

If you want to be extra sure, you can compare against the latest shadcn accordion snippet for your Tailwind/React version.

frontend/src/features/tasks/components/MessagesArea.tsx (1)

586-605: parseMarkdownFinalPrompt change is compatible with FinalPromptData

Verification confirms no issues:

  • FinalPromptData correctly defines final_prompt: string
  • The updated parseMarkdownFinalPrompt returns the correct shape: { type: 'final_prompt', final_prompt: … }
  • The .prompt usages found (in ChatArea.tsx and MessagesArea.tsx) are on different types: sessionStorage JSON objects and TaskDetailSubtask, not FinalPromptData
  • No breaking changes or remaining incompatibilities detected
frontend/package.json (1)

16-21: Verification complete: @radix-ui/react-accordion addition is sound

All original concerns have been verified:

  • Single version (1.2.12) installed with no duplicates
  • Package actively imported and used in frontend/src/components/ui/accordion.tsx
  • Version compatible with current Radix stack—follows the established v1 pattern consistent with other accordion/form components (checkbox, dialog, popover, etc.)

Comment on lines +33 to +49
const isDifyTeam = useMemo(() => {
if (!selectedTeam || !selectedTeam.bots || selectedTeam.bots.length === 0) {
return false;
}
// In a real implementation, we would check the bot's shell runtime
// For now, we assume it's a Dify team if bot_prompt contains difyAppId
const firstBot = selectedTeam.bots[0];
try {
if (firstBot.bot_prompt) {
const promptData = JSON.parse(firstBot.bot_prompt);
return 'difyAppId' in promptData || 'params' in promptData;
}
} catch {
// Not a JSON, not a Dify team
}
return false;
}, [selectedTeam]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Harden Dify detection and fix stale dependencies in the fetch effect

Two things here are worth tightening up:

  1. Guard against non‑object bot_prompt JSON before using in

If bot_prompt parses to a primitive or array, using 'difyAppId' in promptData will throw at runtime. Add a simple object check before the in operator:

-    const firstBot = selectedTeam.bots[0];
-    try {
-      if (firstBot.bot_prompt) {
-        const promptData = JSON.parse(firstBot.bot_prompt);
-        return 'difyAppId' in promptData || 'params' in promptData;
-      }
-    } catch {
-      // Not a JSON, not a Dify team
-    }
+    const firstBot = selectedTeam.bots[0];
+    try {
+      if (firstBot.bot_prompt) {
+        const promptData = JSON.parse(firstBot.bot_prompt);
+        if (promptData && typeof promptData === 'object' && !Array.isArray(promptData)) {
+          return 'difyAppId' in promptData || 'params' in promptData;
+        }
+      }
+    } catch {
+      // Not valid JSON or not an object, not a Dify team
+    }
  1. Align useEffect dependencies with the values it closes over

The fetch effect uses selectedAppId and onAppChange but only lists [isDifyTeam] and disables react-hooks/exhaustive-deps. This can lead to stale reads if the parent updates selectedAppId or swaps the callback.

You can re‑enable the lint rule and keep behavior predictable by including those in the deps:

-  useEffect(() => {
+  useEffect(() => {
     if (!isDifyTeam) {
       setApps([]);
       setError(null);
       return;
     }
@@
-    fetchApps();
-  }, [isDifyTeam]); // eslint-disable-line react-hooks-exhaustive-deps
+    fetchApps();
+  }, [isDifyTeam, selectedAppId, onAppChange]);

This may trigger one extra fetch when selectedAppId changes from null to a real id, but avoids hidden coupling to stale closure state and lets you drop the eslint disable.

(Optionally, either make use of _t = useTranslation('common') for the user‑visible strings or remove the hook if you don’t plan to localize this component yet.)

To confirm hook deps, you can re‑enable the rule and run lint:

Also applies to: 52-81


🏁 Script executed:

#!/bin/bash
cd frontend
npm run lint -- DifyAppSelector.tsx

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Run ESLint directly via npx to catch missing hook deps and other lint issues
npx eslint frontend/src/features/tasks/components/DifyAppSelector.tsx --format unix || true

# Verify whether the `useTranslation` hook is actually used
rg -n "useTranslation" frontend/src/features/tasks/components/DifyAppSelector.tsx

Length of output: 690


🏁 Script executed:

#!/bin/bash
# Get the full component to verify useEffect deps and bot_prompt handling
cat frontend/src/features/tasks/components/DifyAppSelector.tsx

# Also check if there are any other linting configs we can use
fd -e "eslint" -e ".eslintrc*" frontend/ || echo "No eslint config found in frontend"

Length of output: 5851


Guard against non-object bot_prompt JSON and fix stale dependencies in the fetch effect

Code inspection confirms both issues in the current implementation:

  1. Add object guard before using in operator on parsed JSON (lines 38–42)

Using in on primitives or arrays throws a TypeError. The JSON.parse(firstBot.bot_prompt) call can return any JSON type, so add the guard:

    const firstBot = selectedTeam.bots[0];
    try {
      if (firstBot.bot_prompt) {
        const promptData = JSON.parse(firstBot.bot_prompt);
+       if (promptData && typeof promptData === 'object' && !Array.isArray(promptData)) {
          return 'difyAppId' in promptData || 'params' in promptData;
+       }
      }
    } catch {
-     // Not a JSON, not a Dify team
+     // Not valid JSON or not an object, not a Dify team
    }
  1. Include selectedAppId and onAppChange in useEffect dependency array (lines 52–81)

The effect reads both values (lines 60–61) but only lists [isDifyTeam] with the lint rule disabled. This creates a stale closure; add the missing dependencies:

    fetchApps();
-  }, [isDifyTeam]); // eslint-disable-line react-hooks/exhaustive-deps
+  }, [isDifyTeam, selectedAppId, onAppChange]);

(The useTranslation hook at line 27 is unused; either wire it for the UI strings or remove the import.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isDifyTeam = useMemo(() => {
if (!selectedTeam || !selectedTeam.bots || selectedTeam.bots.length === 0) {
return false;
}
// In a real implementation, we would check the bot's shell runtime
// For now, we assume it's a Dify team if bot_prompt contains difyAppId
const firstBot = selectedTeam.bots[0];
try {
if (firstBot.bot_prompt) {
const promptData = JSON.parse(firstBot.bot_prompt);
return 'difyAppId' in promptData || 'params' in promptData;
}
} catch {
// Not a JSON, not a Dify team
}
return false;
}, [selectedTeam]);
const isDifyTeam = useMemo(() => {
if (!selectedTeam || !selectedTeam.bots || selectedTeam.bots.length === 0) {
return false;
}
// In a real implementation, we would check the bot's shell runtime
// For now, we assume it's a Dify team if bot_prompt contains difyAppId
const firstBot = selectedTeam.bots[0];
try {
if (firstBot.bot_prompt) {
const promptData = JSON.parse(firstBot.bot_prompt);
if (promptData && typeof promptData === 'object' && !Array.isArray(promptData)) {
return 'difyAppId' in promptData || 'params' in promptData;
}
}
} catch {
// Not valid JSON or not an object, not a Dify team
}
return false;
}, [selectedTeam]);
🤖 Prompt for AI Agents
In frontend/src/features/tasks/components/DifyAppSelector.tsx around lines 33 to
49 and 52 to 81, guard against parsed bot_prompt being a non-object before using
the "in" operator (after JSON.parse, check typeof === 'object' and not null and
!Array.isArray) so primitives/arrays won't throw, and update the fetch useEffect
dependency array to include selectedAppId and onAppChange (in addition to
isDifyTeam) to avoid stale closures; also remove or use the unused
useTranslation import at line ~27.

Introduced shellType field to distinguish between local execution engines and external API services.
This architectural improvement provides better separation of concerns and enables runtime optimization.

Key changes:

Backend:
- Added shellType field to Shell schema (local_engine | external_api)
- Updated all Shell configurations with explicit type classification
- Refactored Team validation to use generic shellType instead of hardcoded runtime checks
- Created shell_utils.py with utility functions for shell type detection

Executor:
- Added AGENT_TYPE constant to DifyAgent for type classification
- Implemented optimized pre_execute method for external API agents (skips code download)
- Enhanced AgentFactory with type classification methods (is_external_api_agent, get_agent_type)

Frontend:
- Added Shell TypeScript interface with shell_type field

Benefits:
- Clearer architectural boundaries between local engines and external APIs
- Performance optimization for external API agents (skip unnecessary operations)
- Extensible design for future agent types
- Improved code maintainability and readability
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
executor/agents/dify/dify_agent.py (1)

254-267: Consider improving exception handling practices.

Two improvements would enhance debuggability:

  1. Use logger.exception() instead of logger.error() at lines 262 and 266 to automatically include stack traces.
  2. Use raise ... from err at lines 263 and 267 to preserve exception context and make the error chain visible.

Apply this diff:

            except requests.exceptions.HTTPError as e:
                error_msg = f"Dify API HTTP error: {e}"
                if e.response is not None:
                    try:
                        error_data = e.response.json()
                        error_msg = f"Dify API error: {error_data.get('message', str(e))}"
-                    except:
-                        pass
-                logger.error(error_msg)
-                raise Exception(error_msg)
+                    except (ValueError, KeyError):
+                        logger.debug(f"Failed to parse error response JSON: {e.response.text[:200]}")
+                logger.exception(error_msg)
+                raise Exception(error_msg) from e
            except requests.exceptions.RequestException as e:
                error_msg = f"Dify API request failed: {e}"
-                logger.error(error_msg)
-                raise Exception(error_msg)
+                logger.exception(error_msg)
+                raise Exception(error_msg) from e
backend/app/services/adapters/team_kinds.py (1)

518-583: Clarify external_api constraint semantics and simplify shellType handling

The runtime constraint logic looks correct (blocking teams with multiple bots if any bot uses an external_api shell), but a few small improvements would make this more robust and maintainable:

  • The docstring says “Dify Teams must have exactly one bot”, while the code enforces the rule for any external_api shell (e.g., future non‑Dify shells). Consider rewording to “Teams using external API shells must have exactly one bot” to match behavior.
  • ShellSpec.shellType is now defined on ShellSpec, so hasattr(shell_crd.spec, "shellType") will always be true and the dict-style fallback is effectively dead code (and would fail at runtime if ever reached, since shell_crd.spec is a model, not a dict). You can simplify to something like:
-            if shell:
-                shell_crd = Shell.model_validate(shell.json)
-                # Check if shell is external API type
-                shell_type = shell_crd.spec.shellType if hasattr(shell_crd.spec, 'shellType') else shell_crd.spec.get("shellType", "local_engine")
-
-                if shell_type == "external_api":
+            if shell:
+                shell_crd = Shell.model_validate(shell.json)
+                shell_type = shell_crd.spec.shellType or "local_engine"
+
+                if shell_type == "external_api":
                     # External API shells (like Dify) can only have one bot per team
                     if len(bots) > 1:
                         raise HTTPException(
                             status_code=400,
                             detail=f"Teams using external API shells ({shell_crd.spec.runtime}) must have exactly one bot. Found {len(bots)} bots."
                         )
  • You already introduced get_shell_type / is_external_api_shell in backend/app/services/adapters/shell_utils.py; using those helpers here instead of re-querying and re-parsing shells would avoid duplication and keep external_api detection consistent if the semantics evolve.
backend/app/services/adapters/shell_utils.py (1)

16-50: Tighten shellType resolution to avoid unreachable / fragile branch

Functionally this works, but ShellSpec.shellType is now a declared field with a default, so the current branching is more complex than needed and the dict fallback is unlikely to ever be valid:

  • hasattr(shell_crd.spec, "shellType") will always be true for a ShellSpec, so the isinstance(shell_crd.spec, dict) branch is effectively dead and would fail if spec stopped being a dict-like.
  • You can simplify and make the defaulting explicit:
-    shell_crd = Shell.model_validate(shell.json)
-
-    # Get shellType from spec, default to local_engine for backward compatibility
-    if hasattr(shell_crd.spec, 'shellType'):
-        return shell_crd.spec.shellType
-
-    # Try dict-style access for backward compatibility
-    if isinstance(shell_crd.spec, dict):
-        return shell_crd.spec.get("shellType", "local_engine")
-
-    return "local_engine"
+    shell_crd = Shell.model_validate(shell.json)
+
+    # Use explicit default for backward compatibility
+    shell_type = getattr(shell_crd.spec, "shellType", None)
+    return shell_type or "local_engine"

This keeps the same external behavior while reducing branches and avoiding a path that would break if spec is ever not dict-like.

backend/app/schemas/kind.py (1)

90-95: Strengthen shellType typing to reflect allowed values

Right now shellType is Optional[str] with a comment listing allowed values. To have Pydantic and type checkers enforce this, consider constraining it:

from typing import Literal, Optional

class ShellSpec(BaseModel):
    """Shell specification"""
    runtime: str
    shellType: Optional[Literal["local_engine", "external_api"]] = "local_engine"
    supportModel: Optional[List[str]] = None

This makes invalid values impossible at the schema level instead of only being documented in a comment.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 49c79b7 and a3c717d.

📒 Files selected for processing (7)
  • backend/app/schemas/kind.py (1 hunks)
  • backend/app/services/adapters/shell_utils.py (1 hunks)
  • backend/app/services/adapters/team_kinds.py (2 hunks)
  • backend/init_data/02-public-shells.yaml (2 hunks)
  • executor/agents/dify/dify_agent.py (1 hunks)
  • executor/agents/factory.py (2 hunks)
  • frontend/src/types/api.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must be PEP 8 compliant, use Black formatter (line length: 88), isort for imports, and type hints required
Use descriptive names for functions and variables in Python code
Include docstrings for all public functions and classes in Python
Extract magic numbers to constants in Python code
Keep Python functions to a maximum of 50 lines (preferred)

Files:

  • backend/app/services/adapters/team_kinds.py
  • backend/app/schemas/kind.py
  • backend/app/services/adapters/shell_utils.py
  • executor/agents/dify/dify_agent.py
  • executor/agents/factory.py
backend/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Backend must encrypt Git tokens before database storage

Files:

  • backend/app/services/adapters/team_kinds.py
  • backend/app/schemas/kind.py
  • backend/app/services/adapters/shell_utils.py
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: TypeScript code must use strict mode, functional components with hooks, Prettier formatter, ESLint with Next.js config, single quotes, and no semicolons
Use const over let, never use var in TypeScript/React code
Prefer functional patterns in TypeScript/React components
Use PascalCase for component names and kebab-case for component files in React

Files:

  • frontend/src/types/api.ts
frontend/**/*.{ts,tsx,jsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS with design system colors and spacing units defined in CSS variables for frontend UI development

Files:

  • frontend/src/types/api.ts
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: Apply responsive design with mobile-first approach using Tailwind CSS breakpoints (sm, md, lg)
Use shadcn/ui components from frontend/src/components/ui/ for consistent component library usage
Use react-hook-form with zod validation for form handling in React

Files:

  • frontend/src/types/api.ts
🧬 Code graph analysis (4)
frontend/src/types/api.ts (1)
backend/app/schemas/kind.py (1)
  • Shell (102-108)
backend/app/services/adapters/shell_utils.py (2)
backend/app/schemas/kind.py (1)
  • Shell (102-108)
frontend/src/types/api.ts (1)
  • Shell (40-49)
executor/agents/dify/dify_agent.py (2)
shared/models/task.py (1)
  • ExecutionResult (58-66)
executor/tests/agents/test_dify_agent.py (1)
  • task_data (16-43)
executor/agents/factory.py (1)
executor/agents/dify/dify_agent.py (1)
  • DifyAgent (21-360)
🪛 Ruff (0.14.5)
backend/app/services/adapters/team_kinds.py

546-546: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


568-568: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)

backend/app/services/adapters/shell_utils.py

34-34: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)

executor/agents/dify/dify_agent.py

1-1: Shebang is present but file is not executable

(EXE001)


36-36: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


117-117: Consider moving this statement to an else block

(TRY300)


121-121: Do not catch blind exception: Exception

(BLE001)


240-240: Create your own exception

(TRY002)


240-240: Avoid specifying long messages outside the exception class

(TRY003)


249-252: Consider moving this statement to an else block

(TRY300)


260-260: Do not use bare except

(E722)


260-261: try-except-pass detected, consider logging the exception

(S110)


262-262: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


263-263: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


263-263: Create your own exception

(TRY002)


266-266: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


267-267: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


267-267: Create your own exception

(TRY002)

executor/agents/factory.py

29-29: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Executor
🔇 Additional comments (16)
executor/agents/dify/dify_agent.py (11)

1-19: LGTM!

The module setup and imports are well-organized and follow Python conventions.


38-39: LGTM!

Simple and correct implementation.


41-69: LGTM!

The initialization logic is well-structured with clear separation of configuration extraction, prompt parsing, and conversation management. The logging provides good visibility into the agent setup.


71-98: LGTM!

The configuration extraction handles missing data gracefully, with validation deferred to _validate_config. The safe access pattern for nested dictionaries is appropriate.


100-123: Defensive JSON parsing is appropriate.

The method correctly handles JSON parsing failures with fallback behavior and informative warnings. The broad exception catching at line 121 is reasonable for ensuring robustness when dealing with potentially malformed user input.


125-135: LGTM!

Simple and correct implementation for retrieving conversation state.


137-146: LGTM!

The conversation persistence logic is clear and includes appropriate logging.


148-167: LGTM!

The validation logic is thorough and provides clear error messages for each missing configuration requirement.


269-282: LGTM!

The pre_execute implementation correctly reflects the external API agent pattern, where no local environment setup is needed. The logging message clearly explains the agent type behavior.


284-347: LGTM!

The execution flow is well-structured with appropriate validation, progress reporting, and error handling. The use of ExecutionResult aligns with the codebase patterns shown in shared/models/task.py.


349-360: LGTM!

The conversation cleanup method is straightforward and includes appropriate existence checks and logging.

executor/agents/factory.py (3)

15-15: LGTM!

The import follows the established pattern for other agents.


50-64: LGTM!

The method safely checks for the AGENT_TYPE attribute and correctly identifies external API agents. The use of hasattr prevents AttributeError for agents that don't define AGENT_TYPE.


66-82: LGTM!

The method provides good backward compatibility by defaulting to "local_engine" for agents without an explicit AGENT_TYPE attribute. This ensures existing agents (ClaudeCode, Agno) continue to work without modification.

frontend/src/types/api.ts (1)

39-49: LGTM! Well-structured Shell interface.

The Shell interface is properly typed with appropriate optional fields and union types for shell_type. The structure follows TypeScript best practices and provides good type safety.

backend/init_data/02-public-shells.yaml (1)

13-41: Shell definitions are consistent with new shellType semantics

The updated public shells look coherent:

  • ClaudeCode and Agno correctly declare shellType: local_engine.
  • Dify is introduced with shellType: external_api, matching the new external‑API behavior and validation.
  • supportModel: [] is consistent with the existing schema.

No issues from a schema/validation standpoint.

AGENT_TYPE = "external_api"

# Static dictionary for storing conversation IDs per task
_conversations: Dict[str, str] = {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add ClassVar annotation for class-level mutable attribute.

The _conversations dictionary is a class-level mutable attribute that should be annotated with typing.ClassVar to indicate it's shared across all instances. This helps type checkers understand the intended usage and prevents potential confusion.

Apply this diff:

-    _conversations: Dict[str, str] = {}
+    from typing import ClassVar
+    _conversations: ClassVar[Dict[str, str]] = {}

Or add ClassVar to the existing import on line 11:

-from typing import Dict, Any, Optional
+from typing import Dict, Any, Optional, ClassVar

Then:

-    _conversations: Dict[str, str] = {}
+    _conversations: ClassVar[Dict[str, str]] = {}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.5)

36-36: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🤖 Prompt for AI Agents
In executor/agents/dify/dify_agent.py around line 36, the class-level mutable
attribute `_conversations: Dict[str, str] = {}` lacks a typing.ClassVar
annotation; update the declaration to `_conversations: ClassVar[Dict[str, str]]
= {}` and also add ClassVar to the typing imports on line 11 (or add a separate
import) so type checkers understand this is a shared class variable rather than
an instance attribute.

Comment on lines +260 to +261
except:
pass
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace bare except with specific exception handling.

The bare except: pass at line 260 silently suppresses all exceptions when parsing error response JSON, which could hide unexpected errors (e.g., KeyboardInterrupt). This makes debugging difficult and could mask important failures.

Apply this diff:

                try:
                    error_data = e.response.json()
                    error_msg = f"Dify API error: {error_data.get('message', str(e))}"
-                except:
-                    pass
+                except (ValueError, KeyError):
+                    logger.debug(f"Failed to parse error response JSON: {e.response.text[:200]}")
🧰 Tools
🪛 Ruff (0.14.5)

260-260: Do not use bare except

(E722)


260-261: try-except-pass detected, consider logging the exception

(S110)

🤖 Prompt for AI Agents
In executor/agents/dify/dify_agent.py around lines 260-261, replace the bare
"except: pass" that silently swallows all errors when parsing the error response
JSON with explicit exception handling for JSON parsing issues (e.g., except
(ValueError, json.JSONDecodeError, TypeError) as e:). Inside that block, handle
the error explicitly—log the exception or the raw response for debugging and
continue only for expected parse errors; do not catch BaseException or broad
Exception so KeyboardInterrupt/SystemExit propagate.

"""

_agents = {"claudecode": ClaudeCodeAgent, "agno": AgnoAgent}
_agents = {"claudecode": ClaudeCodeAgent, "agno": AgnoAgent, "dify": DifyAgent}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add ClassVar annotation for class-level mutable attribute.

Similar to the DifyAgent, the _agents dictionary should be annotated with typing.ClassVar to clearly indicate it's a class-level attribute shared across instances.

Apply this diff:

-from typing import Dict, Any, Optional
+from typing import Dict, Any, Optional, ClassVar

Then:

-    _agents = {"claudecode": ClaudeCodeAgent, "agno": AgnoAgent, "dify": DifyAgent}
+    _agents: ClassVar[Dict[str, type[Agent]]] = {"claudecode": ClaudeCodeAgent, "agno": AgnoAgent, "dify": DifyAgent}
🧰 Tools
🪛 Ruff (0.14.5)

29-29: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🤖 Prompt for AI Agents
In executor/agents/factory.py around line 29, the class-level mutable _agents
dict needs a typing.ClassVar annotation; change the declaration to annotate
_agents as a ClassVar (e.g. _agents: ClassVar[dict[str, Type[AgentBase]]] =
{...}) and add the required imports (from typing import ClassVar, Type or adjust
existing typing imports) so static checkers clearly see this is a shared class
attribute.

Comment on lines +315 to 340
// Dify Types
export interface DifyApp {
id: string;
name: string;
mode: 'chat' | 'workflow' | 'agent' | 'chatflow';
icon: string;
icon_background: string;
}

export interface DifyBotPrompt {
difyAppId?: string;
params?: Record<string, unknown>;
}

export interface DifyParameterField {
variable: string;
label: string;
type: 'text-input' | 'select' | 'paragraph';
required?: boolean;
default?: string;
options?: Array<{ label: string; value: string }>;
}

export interface DifyParametersSchema {
user_input_form: DifyParameterField[];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify Dify API field values match actual API specification.

The Dify type definitions look well-structured, but there's a potential discrepancy that needs verification:

  1. DifyApp.mode uses 'chat' but the PR objectives mention 'chatbot' as one of the supported Dify application types. Verify whether the Dify API returns 'chat' or 'chatbot'.

  2. DifyParameterField.type values ('text-input' | 'select' | 'paragraph') should be confirmed against the actual Dify API v1/chat-messages parameter schema.


🌐 Web query:

What are the valid application mode values returned by the Dify API for app listings, and what are the valid parameter field types in Dify's user input form schema?

💡 Result:

I couldn't find any documented "application_mode" values in Dify's public docs. Do you want me to keep searching (I can dig into the OpenAPI spec or a specific /apps endpoint response)?

Meanwhile, Dify’s docs do list valid parameter field types used in tool/app parameter schemas (user input form schema). Common types shown in the docs include: string, number, boolean, file, and secret-input (and the docs also show using form: llm or form to control UI extraction). [1][2]

Sources:
[1] Dify — API access / overview. docs.dify.ai.
[2] Dify — plugin/tool parameter examples showing parameter types (string/number/boolean/file/secret-input). docs.dify.ai.


I need to understand whether these Dify types are direct bindings to the API or if they're custom abstractions. Let me check their actual usage in the codebase.


🏁 Script executed:

#!/bin/bash
# Search for usage of DifyParameterField and DifyApp.mode
echo "=== Searching for DifyParameterField usage ==="
rg -n "DifyParameterField" --type ts --type tsx -C2

echo ""
echo "=== Searching for DifyApp usage ==="
rg -n "DifyApp\b" --type ts --type tsx -C2

echo ""
echo "=== Searching for difyAppId or references to Dify types ==="
rg -n "difyAppId\|DifyBotPrompt\|DifyParametersSchema" --type ts --type tsx -C2

Length of output: 228



🏁 Script executed:

#!/bin/bash
# Search for usage of Dify types without restrictive type flags
echo "=== Searching for DifyParameterField usage ==="
rg -n "DifyParameterField" -C2

echo ""
echo "=== Searching for DifyApp usage ==="
rg -n "DifyApp\b" -C2

echo ""
echo "=== Searching for DifyBotPrompt usage ==="
rg -n "DifyBotPrompt" -C2

echo ""
echo "=== Checking where these types are imported from ==="
rg -n "from.*api" frontend/src --type ts -C1 | head -50

Length of output: 6541



🏁 Script executed:

#!/bin/bash
# Search for DifyParametersSchema usage
echo "=== Searching for DifyParametersSchema usage ==="
rg -n "DifyParametersSchema" -C3

echo ""
echo "=== Checking if any Dify responses are actually processed/parsed ==="
rg -n "user_input_form" -C3

echo ""
echo "=== Looking at the full dify.ts file ==="
cat frontend/src/apis/dify.ts

Length of output: 2771



🏁 Script executed:

#!/bin/bash
# Examine the backend Dify adapter
echo "=== Checking backend Dify adapter implementation ==="
cat backend/app/api/endpoints/adapter/dify.py | head -250

Length of output: 6588


Fix TypeScript type definitions to match actual Dify API responses for parameter field types.

The DifyApp.mode type is correct and matches the Dify API. However, DifyParameterField.type has a critical mismatch:

The Dify API returns parameter field types as: string, number, boolean, file, and secret-input, but the TypeScript interface defines: 'text-input' | 'select' | 'paragraph'. Since the backend returns Dify API response data directly without transformation, this type mismatch will cause runtime errors when processing parameter schemas.

Update DifyParameterField.type in frontend/src/types/api.ts (line 333) to match the actual Dify API field types: 'string' | 'number' | 'boolean' | 'file' | 'secret-input'.

(Note: PR objectives reference 'chatbot' but the correct Dify app mode value is 'chat', which is already correctly defined in the types.)

🤖 Prompt for AI Agents
In frontend/src/types/api.ts around lines 315 to 340, DifyParameterField.type is
incorrectly defined as 'text-input' | 'select' | 'paragraph' while the backend
returns Dify API types; update the union type for DifyParameterField.type to
match the actual API values ('string' | 'number' | 'boolean' | 'file' |
'secret-input') so TypeScript reflects the runtime responses and prevents type
errors when processing parameter schemas.

Redesigned bot edit page to provide specialized UI for Dify mode, hiding irrelevant configuration options.

Key changes:

Frontend - DifyBotConfig Component:
- Created dedicated DifyBotConfig component for Dify-specific settings
- Provides user-friendly interface for Dify API credentials (API key, base URL)
- Integrated Dify app selector with real-time app fetching
- Added informational banner explaining Dify external API mode
- Includes configuration preview for transparency

Frontend - BotEdit Component:
- Added isDifyAgent detection based on selected agent type
- Implemented conditional rendering: Dify mode vs standard mode
- Dify mode shows only: Bot name, Agent selection, Dify configuration
- Standard mode shows: Bot name, Agent selection, Model config, MCP config, System prompt
- Updated save logic to skip system_prompt and MCP config for Dify bots
- Enhanced validation to handle Dify configuration requirements

User Experience:
- Cleaner, focused UI when editing Dify bots
- No confusing options like system prompt or MCP for external API agents
- Clear visual distinction between local engines and external API services
- Guided workflow with helpful documentation links

Benefits:
- Reduced cognitive load - only show relevant fields
- Prevents configuration errors from invalid field combinations
- Aligns UI with architectural distinction between agent types
- Improved usability for Dify integration
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
frontend/src/features/settings/components/BotEdit.tsx (1)

338-380: Reduce code duplication in agent config validation.

Lines 338–358 (Dify path) and 361–380 (non-Dify custom model path) contain nearly identical JSON parsing and error handling logic. Consider extracting a shared helper function.

+  // Helper to parse and validate agent config JSON
+  const parseAgentConfigJson = (config: string): unknown | null => {
+    const trimmed = config.trim();
+    if (!trimmed) {
+      setAgentConfigError(true);
+      toast({
+        variant: 'destructive',
+        title: t('bot.errors.agent_config_json'),
+      });
+      return null;
+    }
+    try {
+      setAgentConfigError(false);
+      return JSON.parse(trimmed);
+    } catch {
+      setAgentConfigError(true);
+      toast({
+        variant: 'destructive',
+        title: t('bot.errors.agent_config_json'),
+      });
+      return null;
+    }
+  };

   // Then in handleSave:
-  if (isDifyAgent) {
-    const trimmedConfig = agentConfig.trim();
-    if (!trimmedConfig) {
-      setAgentConfigError(true);
-      toast({...});
-      return;
-    }
-    try {
-      parsedAgentConfig = JSON.parse(trimmedConfig);
-      setAgentConfigError(false);
-    } catch {
-      setAgentConfigError(true);
-      toast({...});
-      return;
-    }
-  } else if (isCustomModel) {
-    // Similar duplicated logic...
-  }
+  if (isDifyAgent || isCustomModel) {
+    parsedAgentConfig = parseAgentConfigJson(agentConfig);
+    if (parsedAgentConfig === null) return;
+  } else {
+    parsedAgentConfig = { private_model: selectedModel };
+  }
frontend/src/features/settings/components/DifyBotConfig.tsx (4)

23-26: Remove unused interface.

DifyBotPromptData is declared but never used in this file.

-interface DifyBotPromptData {
-  difyAppId?: string;
-  params?: Record<string, unknown>;
-}

37-37: Remove unused state.

difyParams is declared but never read or updated.

-  const [difyParams, setDifyParams] = useState<Record<string, unknown>>({});

109-111: Consider guarding against unnecessary config updates on mount.

When mounting with existing config, the first effect parses and sets state, then this effect immediately regenerates and emits a new config string. Even if semantically equivalent, this triggers unnecessary parent updates. Consider comparing the new config with the existing one before calling onAgentConfigChange.

   useEffect(() => {
+    const newConfig = JSON.stringify({
+      env: {
+        DIFY_API_KEY: difyApiKey,
+        DIFY_BASE_URL: difyBaseUrl,
+        DIFY_APP_ID: selectedAppId,
+      },
+    }, null, 2);
+    
+    // Avoid unnecessary updates if config hasn't changed
+    if (newConfig !== agentConfig) {
+      onAgentConfigChange(newConfig);
+    }
-    updateAgentConfig();
-  }, [updateAgentConfig]);
+  }, [difyApiKey, difyBaseUrl, selectedAppId, agentConfig, onAgentConfigChange]);

94-94: Add explanation for the eslint-disable.

The suppression is reasonable (avoiding refetch when selectedAppId changes), but a brief comment explaining the intent would help future maintainers.

-  }, [difyApiKey, difyBaseUrl]); // eslint-disable-line react-hooks/exhaustive-deps
+  // eslint-disable-next-line react-hooks/exhaustive-deps -- Only refetch when credentials change, not on app selection
+  }, [difyApiKey, difyBaseUrl, toast, t]);

Note: toast and t are stable references and won't cause unnecessary refetches, but including them satisfies the linter without changing behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3c717d and 0722be9.

📒 Files selected for processing (2)
  • frontend/src/features/settings/components/BotEdit.tsx (8 hunks)
  • frontend/src/features/settings/components/DifyBotConfig.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: TypeScript code must use strict mode, functional components with hooks, Prettier formatter, ESLint with Next.js config, single quotes, and no semicolons
Use const over let, never use var in TypeScript/React code
Prefer functional patterns in TypeScript/React components
Use PascalCase for component names and kebab-case for component files in React

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
  • frontend/src/features/settings/components/BotEdit.tsx
frontend/**/*.{ts,tsx,jsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS with design system colors and spacing units defined in CSS variables for frontend UI development

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
  • frontend/src/features/settings/components/BotEdit.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: Apply responsive design with mobile-first approach using Tailwind CSS breakpoints (sm, md, lg)
Use shadcn/ui components from frontend/src/components/ui/ for consistent component library usage
Use react-hook-form with zod validation for form handling in React

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
  • frontend/src/features/settings/components/BotEdit.tsx
🧬 Code graph analysis (2)
frontend/src/features/settings/components/DifyBotConfig.tsx (7)
frontend/src/types/api.ts (1)
  • DifyApp (316-322)
frontend/src/apis/client.ts (1)
  • apiClient (105-105)
frontend/src/hooks/use-toast.ts (1)
  • toast (189-189)
frontend/src/components/ui/searchable-select.tsx (2)
  • SearchableSelectItem (20-26)
  • SearchableSelect (48-203)
frontend/src/components/ui/button.tsx (1)
  • Button (55-55)
frontend/src/components/ui/label.tsx (1)
  • Label (21-21)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (31-31)
frontend/src/features/settings/components/BotEdit.tsx (5)
frontend/src/hooks/use-toast.ts (1)
  • toast (189-189)
frontend/src/apis/bots.ts (1)
  • CreateBotRequest (9-15)
frontend/src/components/ui/switch.tsx (1)
  • Switch (33-33)
frontend/src/components/ui/button.tsx (1)
  • Button (55-55)
frontend/src/components/ui/select.tsx (5)
  • Select (147-147)
  • SelectTrigger (150-150)
  • SelectValue (149-149)
  • SelectContent (151-151)
  • SelectItem (153-153)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Executor
🔇 Additional comments (6)
frontend/src/features/settings/components/BotEdit.tsx (5)

17-17: LGTM!

Clean import of the new DifyBotConfig component.


81-82: LGTM!

The isDifyAgent flag cleanly encapsulates the Dify detection logic. While useMemo for a simple comparison is technically unnecessary, it maintains consistency with the component's style.


207-218: LGTM!

Correct dependency optimization — the callbacks only use i18n.language, not t.


560-568: LGTM!

Clean conditional rendering that delegates Dify-specific configuration to the dedicated DifyBotConfig component while preserving the existing flow for other agents.


768-787: LGTM!

Correctly hides the prompt editor for Dify agents, consistent with the save logic that clears system_prompt for Dify.

frontend/src/features/settings/components/DifyBotConfig.tsx (1)

142-277: LGTM!

Well-structured UI with proper loading states, empty states, accessibility attributes (htmlFor), and informative hints. The info banner with documentation link is a nice touch for user guidance.

Comment on lines 71 to 94
const fetchApps = async () => {
setIsLoadingApps(true);
try {
const response = await apiClient.get<DifyApp[]>('/dify/apps');
setApps(response);

// Auto-select first app if none selected
if (!selectedAppId && response.length > 0) {
setSelectedAppId(response[0].id);
}
} catch (error) {
console.error('Failed to fetch Dify apps:', error);
toast({
variant: 'destructive',
title: t('bot.errors.fetch_dify_apps_failed') || 'Failed to load Dify applications',
});
setApps([]);
} finally {
setIsLoadingApps(false);
}
};

fetchApps();
}, [difyApiKey, difyBaseUrl]); // eslint-disable-line react-hooks/exhaustive-deps
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

API call missing credentials — backend cannot authenticate to Dify.

The fetch call to /dify/apps doesn't pass difyApiKey or difyBaseUrl to the backend. The backend needs these credentials to call the Dify API on the user's behalf. As written, this will either fail or use incorrect/missing credentials.

Consider passing the credentials as query params or in headers:

       const fetchApps = async () => {
         setIsLoadingApps(true);
         try {
-          const response = await apiClient.get<DifyApp[]>('/dify/apps');
+          const response = await apiClient.get<DifyApp[]>('/dify/apps', {
+            params: {
+              api_key: difyApiKey,
+              base_url: difyBaseUrl,
+            },
+          });
           setApps(response);

Alternatively, ensure the backend endpoint is designed to accept these credentials securely (e.g., in request body or headers rather than query params for the API key).


🏁 Script executed:

#!/bin/bash
# Check backend endpoint implementation to understand expected request format
ast-grep --pattern 'def $_(request$_):
  $$$
  dify
  $$$
'

# Also search for the /dify/apps route definition
rg -n '/dify/apps' --type py

Length of output: 0


I need to search more broadly for backend implementation and the repository structure. Let me verify this further:


🏁 Script executed:

#!/bin/bash
# Check repository structure
echo "=== Repository structure ==="
find . -maxdepth 2 -type d | head -20

# Search for dify-related code in all file types
echo -e "\n=== Dify-related files ==="
fd -i dify

# Search for /dify/apps or dify endpoints across all languages
echo -e "\n=== Dify endpoint definitions ==="
rg -n 'dify.*apps|/dify/apps' --type-list | head -5
rg -n 'dify.*apps|/dify/apps' -C3

# Check apiClient implementation
echo -e "\n=== ApiClient usage ==="
fd apiClient
rg -n 'apiClient.*get.*dify|const apiClient'

Length of output: 5447


🏁 Script executed:

#!/bin/bash
# Look for backend configuration or separate backend directory
echo "=== Checking for backend/server directories ==="
find . -maxdepth 2 -type d \( -name backend -o -name server -o -name api -o -name src \)

# Check for package.json or other config indicating backend structure
echo -e "\n=== Root level configuration files ==="
ls -la | grep -E '(package.json|tsconfig|backend|server)'

# Search all files for credential passing patterns in API calls
echo -e "\n=== Credential passing patterns ==="
rg -n 'params:|headers:|Authorization' --type ts --type tsx -C2 | head -40

Length of output: 253


🏁 Script executed:

#!/bin/bash
# Check repository structure
echo "=== Repository structure ==="
ls -la

# Search for dify-related code in all file types
echo -e "\n=== Dify-related files ==="
fd -i dify

# Search for /dify/apps or dify endpoints across all languages
echo -e "\n=== Dify endpoint definitions ==="
rg -n '/dify/apps|dify.*apps' -C3

# Check apiClient implementation
echo -e "\n=== ApiClient implementation ==="
fd -n 'apiClient|api' --type ts --type tsx | head -10
rg -n 'export.*apiClient|const apiClient' --type ts --type tsx

Length of output: 5865


🏁 Script executed:

#!/bin/bash
# Look for how API credentials are handled elsewhere
echo "=== Credential handling patterns ==="
rg -n 'difyApiKey|difyBaseUrl' --type ts --type tsx -C2

# Check for environment variables or configuration
echo -e "\n=== Environment/config references ==="
rg -n 'DIFY|dify' --type env --type json --type yaml

# Look for backend routes or endpoints configuration
echo -e "\n=== Backend route patterns ==="
rg -n 'router\.|Route\(|@.*Route' --type ts --type tsx --type js | grep -i dify

# Check imports in the component file
echo -e "\n=== DifyBotConfig imports and context ==="
head -30 frontend/src/features/settings/components/DifyBotConfig.tsx

Length of output: 1225


Let me examine the backend implementation to understand where the Dify credentials come from:


🏁 Script executed:

#!/bin/bash
# View the full backend dify.py endpoint implementation
echo "=== Backend Dify endpoint implementation ==="
cat backend/app/api/endpoints/adapter/dify.py

# Check for dify_config initialization
echo -e "\n=== Search for dify_config source ==="
rg -n 'dify_config' backend/

Length of output: 7478


🏁 Script executed:

#!/bin/bash
# Check frontend component for how difyApiKey and difyBaseUrl are managed
echo "=== DifyBotConfig component full view ==="
cat frontend/src/features/settings/components/DifyBotConfig.tsx | head -100

# Check how credentials are stored in state
echo -e "\n=== Credential state management ==="
rg -n 'setDifyApiKey|setDifyBaseUrl|difyApiKey.*=' frontend/src/features/settings/components/DifyBotConfig.tsx -C2

Length of output: 4678


Pass Dify credentials from frontend to backend — backend currently uses stale database values instead of user input.

The frontend component allows users to edit difyApiKey and difyBaseUrl, and triggers a fetch when these change. However, the /dify/apps call doesn't pass these credentials to the backend. Instead, the backend queries the database for the user's Model configuration, which may have stale or outdated values. During credential editing, this causes a mismatch: the backend uses old credentials while the frontend displays new ones.

Pass credentials to the backend:

       const response = await apiClient.get<DifyApp[]>('/dify/apps', {
-        
+        params: {
+          api_key: difyApiKey,
+          base_url: difyBaseUrl,
+        },
       });

Or preferably via headers to avoid exposing sensitive keys in URLs. Ensure the backend endpoint accepts and uses these credentials instead of querying the database.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fetchApps = async () => {
setIsLoadingApps(true);
try {
const response = await apiClient.get<DifyApp[]>('/dify/apps');
setApps(response);
// Auto-select first app if none selected
if (!selectedAppId && response.length > 0) {
setSelectedAppId(response[0].id);
}
} catch (error) {
console.error('Failed to fetch Dify apps:', error);
toast({
variant: 'destructive',
title: t('bot.errors.fetch_dify_apps_failed') || 'Failed to load Dify applications',
});
setApps([]);
} finally {
setIsLoadingApps(false);
}
};
fetchApps();
}, [difyApiKey, difyBaseUrl]); // eslint-disable-line react-hooks/exhaustive-deps
const fetchApps = async () => {
setIsLoadingApps(true);
try {
const response = await apiClient.get<DifyApp[]>('/dify/apps', {
params: {
api_key: difyApiKey,
base_url: difyBaseUrl,
},
});
setApps(response);
// Auto-select first app if none selected
if (!selectedAppId && response.length > 0) {
setSelectedAppId(response[0].id);
}
} catch (error) {
console.error('Failed to fetch Dify apps:', error);
toast({
variant: 'destructive',
title: t('bot.errors.fetch_dify_apps_failed') || 'Failed to load Dify applications',
});
setApps([]);
} finally {
setIsLoadingApps(false);
}
};
fetchApps();
}, [difyApiKey, difyBaseUrl]); // eslint-disable-line react-hooks/exhaustive-deps
🤖 Prompt for AI Agents
In frontend/src/features/settings/components/DifyBotConfig.tsx around lines
71-94 the fetchApps call does not forward the user-edited difyApiKey and
difyBaseUrl to the backend so the server falls back to stale DB values; modify
the request to include the current difyApiKey and difyBaseUrl in request headers
(not the URL) when calling /dify/apps and update the backend endpoint to read
those headers and use them for the Dify API call instead of querying the
database; ensure headers are only sent when values are present and
sanitize/validate them on the server.

- Remove unused DifyBotPromptData interface
- Remove unused difyParams and setDifyParams state variables

These were leftover from initial development and are not needed in the final implementation.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
frontend/src/features/settings/components/DifyBotConfig.tsx (1)

65-88: Critical issue remains unresolved: API call missing Dify credentials.

This is the same issue flagged in the previous review. The /dify/apps endpoint call (line 68) still does not pass difyApiKey or difyBaseUrl to the backend. The backend will query the database for potentially stale credentials instead of using the values the user is currently editing in the UI.

Pass the current credentials to the backend. As suggested in the previous review, use headers (preferred for security) rather than query params:

       const fetchApps = async () => {
         setIsLoadingApps(true);
         try {
-          const response = await apiClient.get<DifyApp[]>('/dify/apps');
+          const response = await apiClient.get<DifyApp[]>('/dify/apps', {
+            headers: {
+              'X-Dify-API-Key': difyApiKey,
+              'X-Dify-Base-URL': difyBaseUrl,
+            },
+          });
           setApps(response);

Ensure the backend endpoint /dify/apps accepts and uses these headers instead of querying the database.

🧹 Nitpick comments (1)
frontend/src/features/settings/components/DifyBotConfig.tsx (1)

36-56: Consider notifying users when config parsing fails.

When JSON parsing fails, only a console error is logged. Users won't receive feedback about malformed configuration, which could cause confusion.

Add a toast notification in the catch block:

     } catch (error) {
       console.error('Failed to parse agent config:', error);
+      toast({
+        variant: 'destructive',
+        title: t('bot.errors.invalid_config') || 'Invalid configuration format',
+      });
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0722be9 and e71de5d.

📒 Files selected for processing (1)
  • frontend/src/features/settings/components/DifyBotConfig.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: TypeScript code must use strict mode, functional components with hooks, Prettier formatter, ESLint with Next.js config, single quotes, and no semicolons
Use const over let, never use var in TypeScript/React code
Prefer functional patterns in TypeScript/React components
Use PascalCase for component names and kebab-case for component files in React

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
frontend/**/*.{ts,tsx,jsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS with design system colors and spacing units defined in CSS variables for frontend UI development

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: Apply responsive design with mobile-first approach using Tailwind CSS breakpoints (sm, md, lg)
Use shadcn/ui components from frontend/src/components/ui/ for consistent component library usage
Use react-hook-form with zod validation for form handling in React

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
🧬 Code graph analysis (1)
frontend/src/features/settings/components/DifyBotConfig.tsx (4)
frontend/src/types/api.ts (1)
  • DifyApp (316-322)
frontend/src/apis/client.ts (1)
  • apiClient (105-105)
frontend/src/hooks/use-toast.ts (1)
  • toast (189-189)
frontend/src/components/ui/searchable-select.tsx (2)
  • SearchableSelectItem (20-26)
  • SearchableSelect (48-203)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Executor

Fixed the auto-loading issue that caused errors when switching to Dify mode.

Changes:
- Changed from auto-loading to manual loading with button trigger
- Added 'Load Applications' button with loading state
- Improved empty state with helpful guidance
- Added success toast when apps are loaded
- Added better error messages with actionable descriptions
- Prevents unnecessary API calls before credentials are entered

User experience improvements:
- No more automatic error messages on mode switch
- Clear guidance on what to do next
- Manual control over when to fetch apps
- Better feedback on loading and success states

Fixes the 'bot.errors.fetch_dify_apps_failed' error that appeared when
switching to Dify mode before entering API credentials.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
frontend/src/features/settings/components/DifyBotConfig.tsx (1)

58-97: Pass current Dify credentials when calling /dify/apps to avoid stale/invalid config

fetchApps validates difyApiKey/difyBaseUrl but never sends them with the /dify/apps request, so the backend can only rely on whatever is already stored (or nothing for new bots). This means the “Load Applications” flow will use stale or missing credentials and likely fail, especially for newly configured bots.

Consider sending the credentials in headers (preferable to query params) and wiring the backend endpoint to consume them instead of (or in addition to) DB-stored values, e.g.:

-      const response = await apiClient.get<DifyApp[]>('/dify/apps');
+      const response = await apiClient.get<DifyApp[]>('/dify/apps', {
+        headers: {
+          ...(difyApiKey ? { 'x-dify-api-key': difyApiKey } : {}),
+          ...(difyBaseUrl ? { 'x-dify-base-url': difyBaseUrl } : {}),
+        },
+      });

and then drop/resolve the TODO once the backend supports this path.

🧹 Nitpick comments (4)
frontend/src/features/settings/components/DifyBotConfig.tsx (4)

99-115: Don’t overwrite entire agentConfig / env; merge in Dify keys and skip empty values

updateAgentConfig always replaces agentConfig with a new object that only has env and the three DIFY_* keys, and it writes DIFY_APP_ID even when empty. If agentConfig ever contains other settings (e.g. additional env vars, metadata), those will be lost as soon as this component runs, and an empty app ID may be treated as a configured-but-invalid value server‑side.

A safer pattern is to parse/merge the existing config and only inject non‑empty Dify values:

-  const updateAgentConfig = useCallback(() => {
-    const config = {
-      env: {
-        DIFY_API_KEY: difyApiKey,
-        DIFY_BASE_URL: difyBaseUrl,
-        DIFY_APP_ID: selectedAppId,
-      },
-    };
-
-    onAgentConfigChange(JSON.stringify(config, null, 2));
-  }, [difyApiKey, difyBaseUrl, selectedAppId, onAgentConfigChange]);
+  const updateAgentConfig = useCallback(() => {
+    let existing: any = {}
+    try {
+      existing = agentConfig.trim() ? JSON.parse(agentConfig) : {}
+    } catch {
+      // ignore parse errors and start fresh
+    }
+
+    const env = {
+      ...(existing.env || {}),
+      ...(difyApiKey ? { DIFY_API_KEY: difyApiKey } : {}),
+      ...(difyBaseUrl ? { DIFY_BASE_URL: difyBaseUrl } : {}),
+      ...(selectedAppId ? { DIFY_APP_ID: selectedAppId } : {}),
+    }
+
+    onAgentConfigChange(JSON.stringify({ ...existing, env }, null, 2))
+  }, [agentConfig, difyApiKey, difyBaseUrl, selectedAppId, onAgentConfigChange]);

This preserves unrelated configuration and avoids persisting empty DIFY_* values.


141-143: Harden external docs link against reverse‑tabnabbing

window.open(..., '_blank') can expose you to reverse‑tabnabbing. Consider:

-  const handleOpenDifyDocs = useCallback(() => {
-    window.open('https://docs.dify.ai/guides/application-publishing/developing-with-apis', '_blank');
-  }, []);
+  const handleOpenDifyDocs = useCallback(() => {
+    const win = window.open(
+      'https://docs.dify.ai/guides/application-publishing/developing-with-apis',
+      '_blank'
+    );
+    if (win) win.opener = null;
+  }, []);

171-207: Align inputs with shadcn/ui and shared form patterns

The API key and base URL fields use raw <input> elements while the rest of the UI relies on shadcn/ui components. For consistency with the design system and the project guidelines, consider switching these to the shared Input component (if available under @/components/ui/input) and, longer‑term, wiring them into a small react-hook-form + zod schema for basic validation (non‑empty key, valid URL).

This would also centralize styling and error handling with the rest of the app.


297-307: Config preview exposes API key in plain text despite password masking above

The preview Textarea renders the raw agentConfig, which includes DIFY_API_KEY. This effectively undoes the masking from the password input and makes it easy to leak the key via screenshots or screen‑sharing.

If the goal is to keep a human‑readable preview while still treating the key as sensitive, consider redacting it just for display, e.g. creating a derived previewConfig that replaces env.DIFY_API_KEY with '********' before stringifying, while still persisting the real value via onAgentConfigChange.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e71de5d and db315fd.

📒 Files selected for processing (1)
  • frontend/src/features/settings/components/DifyBotConfig.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: TypeScript code must use strict mode, functional components with hooks, Prettier formatter, ESLint with Next.js config, single quotes, and no semicolons
Use const over let, never use var in TypeScript/React code
Prefer functional patterns in TypeScript/React components
Use PascalCase for component names and kebab-case for component files in React

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
frontend/**/*.{ts,tsx,jsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS with design system colors and spacing units defined in CSS variables for frontend UI development

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: Apply responsive design with mobile-first approach using Tailwind CSS breakpoints (sm, md, lg)
Use shadcn/ui components from frontend/src/components/ui/ for consistent component library usage
Use react-hook-form with zod validation for form handling in React

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
🧬 Code graph analysis (1)
frontend/src/features/settings/components/DifyBotConfig.tsx (8)
frontend/src/types/api.ts (1)
  • DifyApp (316-322)
frontend/src/hooks/use-toast.ts (1)
  • toast (189-189)
frontend/public/mockServiceWorker.js (1)
  • response (123-123)
frontend/src/apis/client.ts (1)
  • apiClient (105-105)
frontend/src/components/ui/searchable-select.tsx (2)
  • SearchableSelectItem (20-26)
  • SearchableSelect (48-203)
frontend/src/components/ui/button.tsx (1)
  • Button (55-55)
frontend/src/components/ui/label.tsx (1)
  • Label (21-21)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (31-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Executor

@Micro66 Micro66 closed this Nov 25, 2025
@qdaxb qdaxb deleted the weagent/add-dify-agent-support branch November 26, 2025 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants