-
Notifications
You must be signed in to change notification settings - Fork 33
Add Dify Agent Support to Wegent #168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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
WalkthroughAdds 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
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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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
_conversationsdictionary is shared across all instances and should be annotated withtyping.ClassVarfor 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 eApply similar changes at lines 255 and 259 to preserve the original exception context.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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.pyexecutor/tests/agents/test_dify_agent.pyexecutor/tests/agents/test_factory.pybackend/app/services/adapters/team_kinds.pyexecutor/agents/dify/dify_agent.pyexecutor/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.pyline 93, which raisesNotFoundExceptionfor missing shells). The silent skip whenshellisNoneat 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
There was a problem hiding this 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 usageThe typed wrappers for
/dify/appsand/dify/apps/{appId}/parametersare clean and match the shared types. Consider updating callers (e.g.DifyAppSelector) to use these helpers instead of hittingapiClientdirectly 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 helperTwo small improvements here:
- Guard
promptDatabefore using theinoperatorIf
bot_promptJSON happens to be a primitive (e.g."123"or42),'difyAppId' in promptDatacan 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 + }
- Prefer the shared
getDifyAppswrapper over directapiClientusageSince
frontend/src/apis/dify.tsalready exposes a typedgetDifyApps, 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
SearchableSelectfrom 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 paramsRight now
handleJsonChangesilently ignores invalid JSON and the textarea value is always derived fromparamsJson, 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 tighteninganyin follow‑upsThe 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>toRecord<string, unknown>or a genericRecord<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 consistentBoth 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_IDor 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_appsandget_dify_app_parameterscan 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
📒 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
Useconstoverlet, never usevarin 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.tsxfrontend/src/apis/dify.tsfrontend/src/types/api.tsfrontend/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.tsxfrontend/src/apis/dify.tsfrontend/src/types/api.tsfrontend/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 fromfrontend/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.tsxfrontend/src/apis/dify.tsfrontend/src/types/api.tsfrontend/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.pybackend/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.pybackend/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 patternImporting
difyand mountingdify.routerunder/difywith its own tag is consistent with the other adapter routers; no issues here.
| # 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) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 eThis 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 fLength 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 2Length 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 2Length 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.pyLength 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.pyLength 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.pyLength 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.
| // 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]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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 removingviewStatusVersionfromuseMemodependenciesBoth
groupTasksByDateandtotalUnreadCountnow only recompute whentasksorgetUnreadCountchange. WhenmarkAllTasksAsViewed()is called, it updates localStorage and incrementsviewStatusVersionto signal a view-state change, but thetasksarray identity andgetUnreadCountfunction 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
isTaskUnreadreads from localStorage to determine unread status, andviewStatusVersionis explicitly used as the change signal for view-state updates in the context provider,viewStatusVersionshould 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: Wirearia-controlsto an actual element idThe 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‑compatiblepromptalias on FinalPromptData
FinalPromptDatanow only exposesfinal_prompt: string. The updated consumers in this PR are aligned, but any existing code (or backend payloads) still using apromptfield 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
promptto gauge how much old usage remains:#!/bin/bash rg -n "final_prompt" . -S rg -n "FinalPromptData" . -S rg -n "\bprompt\b" backend executor -Sfrontend/src/features/tasks/components/DifyParamsForm.tsx (1)
40-54: Add an object guard for parsedbot_promptand consider tightening effect deps
- Guard parsed
bot_promptbefore usinginAs previously noted in an earlier review,
JSON.parse(firstBot.bot_prompt)can return a primitive or array; using'difyAppId' in promptDataon 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 + }
- Optional: align the load effect’s deps with its usage
The load‑from‑localStorage effect uses
onParamsChangebut omits it from the deps array and disablesreact-hooks/exhaustive-deps. You could either:
- Add
onParamsChangeto the deps array and drop the disable, or- Add a brief comment explaining why the deps lint is intentionally suppressed.
Also,
_selectedAppIdis 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
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis 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
Useconstoverlet, never usevarin 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.tsxfrontend/src/features/tasks/components/TaskSidebar.tsxfrontend/src/components/ui/searchable-select.tsxfrontend/src/features/settings/components/BotEdit.tsxfrontend/src/features/tasks/components/MessagesArea.tsxfrontend/src/features/tasks/components/DifyAppSelector.tsxfrontend/src/components/ui/accordion.tsxfrontend/src/types/api.tsfrontend/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.tsxfrontend/src/features/tasks/components/TaskSidebar.tsxfrontend/src/components/ui/searchable-select.tsxfrontend/src/features/settings/components/BotEdit.tsxfrontend/src/features/tasks/components/MessagesArea.tsxfrontend/src/features/tasks/components/DifyAppSelector.tsxfrontend/src/components/ui/accordion.tsxfrontend/src/types/api.tsfrontend/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 fromfrontend/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.tsxfrontend/src/features/tasks/components/TaskSidebar.tsxfrontend/src/components/ui/searchable-select.tsxfrontend/src/features/settings/components/BotEdit.tsxfrontend/src/features/tasks/components/MessagesArea.tsxfrontend/src/features/tasks/components/DifyAppSelector.tsxfrontend/src/components/ui/accordion.tsxfrontend/src/types/api.tsfrontend/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.jsonfrontend/src/features/tasks/components/DifyAppSelector.tsxfrontend/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
useCallbackdependency arrays to include onlyi18n.languageinstead oft. 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 todata.final_promptAll internal uses of the final prompt (clipboard, sessionStorage payload, Markdown rendering) now read from
data.final_prompt, matching the updated API type. The localpromptData.promptfield 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 patternsThe 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 FinalPromptDataVerification confirms no issues:
FinalPromptDatacorrectly definesfinal_prompt: string- The updated
parseMarkdownFinalPromptreturns the correct shape:{ type: 'final_prompt', final_prompt: … }- The
.promptusages found (in ChatArea.tsx and MessagesArea.tsx) are on different types: sessionStorage JSON objects andTaskDetailSubtask, notFinalPromptData- No breaking changes or remaining incompatibilities detected
frontend/package.json (1)
16-21: Verification complete: @radix-ui/react-accordion addition is soundAll 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.)
| 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]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Harden Dify detection and fix stale dependencies in the fetch effect
Two things here are worth tightening up:
- Guard against non‑object
bot_promptJSON before usingin
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
+ }- Align
useEffectdependencies 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.tsxLength 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.tsxLength 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:
- Add object guard before using
inoperator 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
}- Include
selectedAppIdandonAppChangeinuseEffectdependency 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.
| 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
There was a problem hiding this 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:
- Use
logger.exception()instead oflogger.error()at lines 262 and 266 to automatically include stack traces.- Use
raise ... from errat 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 ebackend/app/services/adapters/team_kinds.py (1)
518-583: Clarify external_api constraint semantics and simplify shellType handlingThe runtime constraint logic looks correct (blocking teams with multiple bots if any bot uses an
external_apishell), 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_apishell (e.g., future non‑Dify shells). Consider rewording to “Teams using external API shells must have exactly one bot” to match behavior.ShellSpec.shellTypeis now defined onShellSpec, sohasattr(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, sinceshell_crd.specis 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_shellinbackend/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 branchFunctionally this works, but
ShellSpec.shellTypeis 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 aShellSpec, so theisinstance(shell_crd.spec, dict)branch is effectively dead and would fail ifspecstopped 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
specis ever not dict-like.backend/app/schemas/kind.py (1)
90-95: StrengthenshellTypetyping to reflect allowed valuesRight now
shellTypeisOptional[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]] = NoneThis 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
📒 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.pybackend/app/schemas/kind.pybackend/app/services/adapters/shell_utils.pyexecutor/agents/dify/dify_agent.pyexecutor/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.pybackend/app/schemas/kind.pybackend/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
Useconstoverlet, never usevarin 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 fromfrontend/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_executeimplementation 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
ExecutionResultaligns 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_TYPEattribute and correctly identifies external API agents. The use ofhasattrpreventsAttributeErrorfor agents that don't defineAGENT_TYPE.
66-82: LGTM!The method provides good backward compatibility by defaulting to "local_engine" for agents without an explicit
AGENT_TYPEattribute. 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 newshellTypesemanticsThe 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] = {} |
There was a problem hiding this comment.
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, ClassVarThen:
- _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.
| except: | ||
| pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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} |
There was a problem hiding this comment.
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, ClassVarThen:
- _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.
| // 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[]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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:
-
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'. -
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 -C2Length 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 -50Length 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.tsLength 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 -250Length 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
There was a problem hiding this 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.
DifyBotPromptDatais declared but never used in this file.-interface DifyBotPromptData { - difyAppId?: string; - params?: Record<string, unknown>; -}
37-37: Remove unused state.
difyParamsis 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
selectedAppIdchanges), 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:
toastandtare 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
📒 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
Useconstoverlet, never usevarin 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.tsxfrontend/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.tsxfrontend/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 fromfrontend/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.tsxfrontend/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
isDifyAgentflag cleanly encapsulates the Dify detection logic. WhileuseMemofor 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, nott.
560-568: LGTM!Clean conditional rendering that delegates Dify-specific configuration to the dedicated
DifyBotConfigcomponent 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_promptfor 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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 pyLength 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 -40Length 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 tsxLength 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.tsxLength 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 -C2Length 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.
| 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.
There was a problem hiding this 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/appsendpoint call (line 68) still does not passdifyApiKeyordifyBaseUrlto 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/appsaccepts 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
📒 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
Useconstoverlet, never usevarin 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 fromfrontend/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.
There was a problem hiding this 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/appsto avoid stale/invalid config
fetchAppsvalidatesdifyApiKey/difyBaseUrlbut never sends them with the/dify/appsrequest, 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 entireagentConfig/ env; merge in Dify keys and skip empty values
updateAgentConfigalways replacesagentConfigwith a new object that only hasenvand the three DIFY_* keys, and it writesDIFY_APP_IDeven when empty. IfagentConfigever 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 patternsThe 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 sharedInputcomponent (if available under@/components/ui/input) and, longer‑term, wiring them into a smallreact-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 aboveThe preview Textarea renders the raw
agentConfig, which includesDIFY_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
previewConfigthat replacesenv.DIFY_API_KEYwith'********'before stringifying, while still persisting the real value viaonAgentConfigChange.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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
Useconstoverlet, never usevarin 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 fromfrontend/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
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
Configuration Management:
Architecture Compliance:
Comprehensive Testing:
Changes Overview
Backend:
Testing:
Technical Highlights
Test Plan
Future Enhancements (Not in this PR)
Phase 2 features as outlined in the requirements:
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.