Skip to content

Commit 2eea503

Browse files
committed
Add frontend Dify integration components (Phase 2)
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
1 parent 2b4acd8 commit 2eea503

File tree

6 files changed

+599
-2
lines changed

6 files changed

+599
-2
lines changed

backend/app/api/api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
from app.api.endpoints import auth, users, repository, oidc, quota, admin
6-
from app.api.endpoints.adapter import models, agents, bots, teams, tasks, executors
6+
from app.api.endpoints.adapter import models, agents, bots, teams, tasks, executors, dify
77
from app.api.endpoints.kind import k_router
88
from app.api.router import api_router
99

@@ -19,4 +19,5 @@
1919
api_router.include_router(repository.router, prefix="/git", tags=["repository"])
2020
api_router.include_router(executors.router, prefix="/executors", tags=["executors"])
2121
api_router.include_router(quota.router, prefix="/quota", tags=["quota"])
22+
api_router.include_router(dify.router, prefix="/dify", tags=["dify"])
2223
api_router.include_router(k_router)
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# SPDX-FileCopyrightText: 2025 Weibo, Inc.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
from fastapi import APIRouter, Depends, HTTPException
6+
from sqlalchemy.orm import Session
7+
from typing import List, Dict, Any
8+
import requests
9+
10+
from app.api.dependencies import get_db
11+
from app.core import security
12+
from app.models.user import User
13+
from app.models.kind import Kind
14+
from app.schemas.kind import Model
15+
from shared.logger import setup_logger
16+
17+
logger = setup_logger("dify_api")
18+
19+
router = APIRouter()
20+
21+
22+
@router.get("/apps")
23+
def get_dify_apps(
24+
db: Session = Depends(get_db),
25+
current_user: User = Depends(security.get_current_user)
26+
) -> List[Dict[str, Any]]:
27+
"""
28+
Get list of Dify applications for the current user
29+
30+
Retrieves Dify applications from the user's configured Dify instance.
31+
Requires DIFY_API_KEY and DIFY_BASE_URL to be configured in a Model.
32+
33+
Returns:
34+
List of Dify applications with id, name, mode, and icon
35+
"""
36+
37+
# Find user's Dify Model configuration
38+
models = db.query(Kind).filter(
39+
Kind.user_id == current_user.id,
40+
Kind.kind == "Model",
41+
Kind.is_active == True
42+
).all()
43+
44+
dify_config = None
45+
for model in models:
46+
try:
47+
model_crd = Model.model_validate(model.json)
48+
env = model_crd.spec.modelConfig.get("env", {})
49+
50+
# Check if this model has Dify configuration
51+
if "DIFY_API_KEY" in env and "DIFY_BASE_URL" in env:
52+
dify_config = {
53+
"api_key": env["DIFY_API_KEY"],
54+
"base_url": env["DIFY_BASE_URL"]
55+
}
56+
break
57+
except Exception as e:
58+
logger.warning(f"Failed to parse model {model.id}: {e}")
59+
continue
60+
61+
if not dify_config:
62+
raise HTTPException(
63+
status_code=404,
64+
detail="No Dify configuration found. Please configure DIFY_API_KEY and DIFY_BASE_URL in a Model."
65+
)
66+
67+
# Call Dify API to get applications
68+
try:
69+
api_url = f"{dify_config['base_url']}/v1/apps"
70+
headers = {
71+
"Authorization": f"Bearer {dify_config['api_key']}",
72+
"Content-Type": "application/json"
73+
}
74+
75+
logger.info(f"Fetching Dify apps from: {api_url}")
76+
77+
response = requests.get(
78+
api_url,
79+
headers=headers,
80+
timeout=10
81+
)
82+
83+
response.raise_for_status()
84+
data = response.json()
85+
86+
# Extract app list from response
87+
# Dify API returns: {"data": [{"id": "...", "name": "...", "mode": "...", "icon": "..."}]}
88+
apps = data.get("data", [])
89+
90+
# Transform to simplified format
91+
result = []
92+
for app in apps:
93+
result.append({
94+
"id": app.get("id", ""),
95+
"name": app.get("name", "Unnamed App"),
96+
"mode": app.get("mode", "chat"), # chat, workflow, agent, chatflow
97+
"icon": app.get("icon", ""),
98+
"icon_background": app.get("icon_background", "#1C64F2")
99+
})
100+
101+
logger.info(f"Successfully fetched {len(result)} Dify apps")
102+
return result
103+
104+
except requests.exceptions.HTTPError as e:
105+
error_msg = f"Dify API HTTP error: {e}"
106+
if e.response is not None:
107+
try:
108+
error_data = e.response.json()
109+
error_msg = f"Dify API error: {error_data.get('message', str(e))}"
110+
except:
111+
pass
112+
logger.error(error_msg)
113+
raise HTTPException(status_code=502, detail=error_msg)
114+
115+
except requests.exceptions.RequestException as e:
116+
error_msg = f"Failed to connect to Dify API: {str(e)}"
117+
logger.error(error_msg)
118+
raise HTTPException(status_code=502, detail=error_msg)
119+
120+
except Exception as e:
121+
error_msg = f"Unexpected error: {str(e)}"
122+
logger.error(error_msg)
123+
raise HTTPException(status_code=500, detail=error_msg)
124+
125+
126+
@router.get("/apps/{app_id}/parameters")
127+
def get_dify_app_parameters(
128+
app_id: str,
129+
db: Session = Depends(get_db),
130+
current_user: User = Depends(security.get_current_user)
131+
) -> Dict[str, Any]:
132+
"""
133+
Get parameters schema for a specific Dify application
134+
135+
Args:
136+
app_id: Dify application ID
137+
138+
Returns:
139+
Parameters schema with field definitions
140+
"""
141+
142+
# Find user's Dify Model configuration
143+
models = db.query(Kind).filter(
144+
Kind.user_id == current_user.id,
145+
Kind.kind == "Model",
146+
Kind.is_active == True
147+
).all()
148+
149+
dify_config = None
150+
for model in models:
151+
try:
152+
model_crd = Model.model_validate(model.json)
153+
env = model_crd.spec.modelConfig.get("env", {})
154+
155+
if "DIFY_API_KEY" in env and "DIFY_BASE_URL" in env:
156+
dify_config = {
157+
"api_key": env["DIFY_API_KEY"],
158+
"base_url": env["DIFY_BASE_URL"]
159+
}
160+
break
161+
except Exception as e:
162+
logger.warning(f"Failed to parse model {model.id}: {e}")
163+
continue
164+
165+
if not dify_config:
166+
raise HTTPException(
167+
status_code=404,
168+
detail="No Dify configuration found"
169+
)
170+
171+
# Call Dify API to get app parameters
172+
try:
173+
api_url = f"{dify_config['base_url']}/v1/parameters"
174+
headers = {
175+
"Authorization": f"Bearer {dify_config['api_key']}",
176+
"Content-Type": "application/json"
177+
}
178+
179+
params = {"app_id": app_id}
180+
181+
logger.info(f"Fetching parameters for Dify app: {app_id}")
182+
183+
response = requests.get(
184+
api_url,
185+
headers=headers,
186+
params=params,
187+
timeout=10
188+
)
189+
190+
response.raise_for_status()
191+
data = response.json()
192+
193+
logger.info(f"Successfully fetched parameters for app {app_id}")
194+
return data
195+
196+
except requests.exceptions.HTTPError as e:
197+
# If parameters endpoint doesn't exist, return empty schema
198+
if e.response is not None and e.response.status_code == 404:
199+
logger.info(f"Parameters endpoint not available for app {app_id}, returning empty schema")
200+
return {"user_input_form": []}
201+
202+
error_msg = f"Dify API HTTP error: {e}"
203+
logger.error(error_msg)
204+
raise HTTPException(status_code=502, detail=error_msg)
205+
206+
except Exception as e:
207+
error_msg = f"Failed to fetch app parameters: {str(e)}"
208+
logger.error(error_msg)
209+
raise HTTPException(status_code=500, detail=error_msg)

frontend/src/apis/dify.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-FileCopyrightText: 2025 Weibo, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
import { apiClient } from './client';
6+
import { DifyApp, DifyParametersSchema } from '@/types/api';
7+
8+
/**
9+
* Get list of Dify applications
10+
*/
11+
export async function getDifyApps(): Promise<DifyApp[]> {
12+
return apiClient.get<DifyApp[]>('/dify/apps');
13+
}
14+
15+
/**
16+
* Get parameters schema for a specific Dify application
17+
*/
18+
export async function getDifyAppParameters(appId: string): Promise<DifyParametersSchema> {
19+
return apiClient.get<DifyParametersSchema>(`/dify/apps/${appId}/parameters`);
20+
}

0 commit comments

Comments
 (0)