feat: 添加对话应用选择和知识库切换功能

- 新增对话应用管理模块(dify-chat-apps),支持获取和切换对话应用
- 优化对话应用切换后自动刷新会话列表功能
- 知识库管理页面新增下拉选择器,支持切换不同知识库
- API 层支持 app_id 参数传递,实现多应用会话隔离

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-08 01:44:34 +08:00
parent 27aff59152
commit 3f5c23123b
27 changed files with 925 additions and 167 deletions
+4 -1
View File
@@ -91,6 +91,7 @@ export async function action({ request }: ActionFunctionArgs) {
files,
conversation_id: conversationId,
response_mode: responseMode,
app_id: appId, // 支持前端传递应用 ID
} = body;
console.log('客戶端調用remix路由_Chat Messages API - 收到请求:', {
@@ -98,6 +99,7 @@ export async function action({ request }: ActionFunctionArgs) {
queryPreview: query?.substring(0, 100) + (query?.length > 100 ? '...' : ''),
conversationId,
responseMode,
appId, // 记录应用 ID
hasInputs: !!inputs,
hasFiles: !!files && files.length > 0,
filesCount: files?.length || 0,
@@ -110,7 +112,8 @@ export async function action({ request }: ActionFunctionArgs) {
responseMode,
conversationId,
files,
frontendJWT // 传递 JWT
frontendJWT, // 传递 JWT
appId // 传递应用 ID
);
// 对于流式响应,直接返回流
+7 -1
View File
@@ -23,7 +23,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
);
}
const data = await difyClient.getConversations(frontendJWT);
// 从 URL 参数获取 app_id
const url = new URL(request.url);
const appId = url.searchParams.get('app_id') || undefined;
console.log('[API] Conversations - 获取会话列表:', { appId });
const data = await difyClient.getConversations(frontendJWT, appId);
return json(data, {
headers: {
+5 -5
View File
@@ -6,7 +6,6 @@
import { type LoaderFunctionArgs, json } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
import { getUserSession } from '~/api/login/auth.server';
import { request as backendRequest } from '~/api/axios-client';
export async function loader({ request, params }: LoaderFunctionArgs) {
return json({ error: 'Method not allowed' }, { status: 405 });
@@ -35,11 +34,11 @@ export async function action({ request, params }: LoaderFunctionArgs) {
console.log(`[API V3] Update Area Dataset: ${id}`, body);
const apiUrl = `${API_BASE_URL}/v3/dify/area-datasets/${id}`;
const response = await backendRequest(apiUrl, {
const response = await fetch(apiUrl, {
method: 'PUT',
headers: {
Authorization: `Bearer ${frontendJWT}`,
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
body: JSON.stringify(body),
});
@@ -52,10 +51,11 @@ export async function action({ request, params }: LoaderFunctionArgs) {
console.log(`[API V3] Delete Area Dataset: ${id}`);
const apiUrl = `${API_BASE_URL}/v3/dify/area-datasets/${id}`;
const response = await backendRequest(apiUrl, {
const response = await fetch(apiUrl, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${frontendJWT}`,
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
@@ -5,7 +5,6 @@
import { type LoaderFunctionArgs, json } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
import { getUserSession } from '~/api/login/auth.server';
import { request as backendRequest } from '~/api/axios-client';
export async function loader({ request }: LoaderFunctionArgs) {
try {
@@ -22,10 +21,11 @@ export async function loader({ request }: LoaderFunctionArgs) {
// 转发请求到后端
const apiUrl = `${API_BASE_URL}/v3/dify/area-datasets/areas`;
const response = await backendRequest(apiUrl, {
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
Authorization: `Bearer ${frontendJWT}`,
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
+3 -3
View File
@@ -5,7 +5,6 @@
import { type LoaderFunctionArgs, json } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
import { getUserSession } from '~/api/login/auth.server';
import { request as backendRequest } from '~/api/axios-client';
export async function loader({ request }: LoaderFunctionArgs) {
try {
@@ -22,10 +21,11 @@ export async function loader({ request }: LoaderFunctionArgs) {
// 转发请求到后端
const apiUrl = `${API_BASE_URL}/v3/dify/area-datasets/my`;
const response = await backendRequest(apiUrl, {
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
Authorization: `Bearer ${frontendJWT}`,
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
+5 -5
View File
@@ -6,7 +6,6 @@
import { type LoaderFunctionArgs, json } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
import { getUserSession } from '~/api/login/auth.server';
import { request as backendRequest } from '~/api/axios-client';
/**
* GET - 获取所有知识库绑定列表
@@ -40,10 +39,11 @@ export async function loader({ request }: LoaderFunctionArgs) {
// 转发请求到后端
const apiUrl = `${API_BASE_URL}/v3/dify/area-datasets?${params}`;
const response = await backendRequest(apiUrl, {
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
Authorization: `Bearer ${frontendJWT}`,
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
@@ -78,11 +78,11 @@ export async function action({ request }: LoaderFunctionArgs) {
// 转发创建请求到后端
const apiUrl = `${API_BASE_URL}/v3/dify/area-datasets`;
const response = await backendRequest(apiUrl, {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
Authorization: `Bearer ${frontendJWT}`,
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
body: JSON.stringify(body),
});
@@ -0,0 +1,47 @@
/**
* GET /api/v3/dify/chat-apps/default - 获取默认对话应用
*
* 转发请求到后端 API,后端从配置文件读取默认对话应用
* 参考文档:docs/new-dify/dify_api_doc.md - 对话应用多实例支持
*/
import { LoaderFunctionArgs, json } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
import { getUserSession } from '~/api/login/auth.server';
export async function loader({ request }: LoaderFunctionArgs) {
try {
const { frontendJWT } = await getUserSession(request);
if (!frontendJWT) {
return json(
{ code: 401, message: 'JWT认证失败,请重新登录', data: null },
{ status: 401 }
);
}
console.log('[API] Get Default Chat App - Forwarding to backend');
// 转发请求到后端
const apiUrl = `${API_BASE_URL}/v3/dify/chat-apps/default`;
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
const data = await response.json();
console.log('[API] Get Default Chat App - Backend response:', data);
return json(data, { status: response.status });
} catch (error: any) {
console.error('[API] Get Default Chat App - Error:', error.message);
return json(
{ code: 500, message: error.message || 'Failed to get default chat app', data: null },
{ status: 500 }
);
}
}
+48
View File
@@ -0,0 +1,48 @@
/**
* GET /api/v3/dify/chat-apps/my - 获取当前用户可访问的对话应用列表
*
* 转发请求到后端 API,后端从配置文件读取对话应用列表
* 参考文档:docs/new-dify/dify_api_doc.md - 对话应用多实例支持
*/
import { LoaderFunctionArgs, json } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
import { getUserSession } from '~/api/login/auth.server';
export async function loader({ request }: LoaderFunctionArgs) {
try {
const { frontendJWT } = await getUserSession(request);
if (!frontendJWT) {
return json(
{ code: 401, message: 'JWT认证失败,请重新登录', data: { data: [], total: 0 } },
{ status: 401 }
);
}
console.log('[API] Get My Chat Apps - Forwarding to backend');
// 转发请求到后端 - 使用正确的接口路径
// 根据文档:GET /api/v3/dify/chat-apps
const apiUrl = `${API_BASE_URL}/v3/dify/chat-apps`;
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
const data = await response.json();
console.log('[API] Get My Chat Apps - Backend response:', data);
return json(data, { status: response.status });
} catch (error: any) {
console.error('[API] Get My Chat Apps - Error:', error.message);
return json(
{ code: 500, message: error.message || 'Failed to get chat apps', data: { data: [], total: 0 } },
{ status: 500 }
);
}
}
+36 -30
View File
@@ -1,36 +1,42 @@
import { Tabs } from 'antd';
import DatasetManager from "~/components/dify-dataset-manager";
import AreaDatasetConfig from "~/components/dify-dataset-manager/area-dataset-config";
import { Spin } from 'antd';
import { useState, useEffect } from 'react';
/**
* 知识库管理首页 - 带标签页导航
* 标签1: 知识库列表 - 进入单个知识库管理
* 标签2: 知识库配置管理 - 地区-知识库绑定管理
* 知识库管理页面
* 动态加载 DatasetManager 组件避免 SSR 问题
*/
export default function DatasetManagerIndex() {
const items = [
{
key: 'dataset-list',
label: '知识库列表',
children: <DatasetManager />,
},
{
key: 'area-config',
label: '知识库配置管理',
children: <AreaDatasetConfig />,
},
];
export default function DatasetManagerPage() {
const [DatasetManager, setDatasetManager] = useState<React.ComponentType | null>(null);
const [mounted, setMounted] = useState(false);
// 使用Tabs作为顶层导航,默认选中第一个
const defaultActiveTab = 'dataset-list';
useEffect(() => {
setMounted(true);
// 只在客户端动态导入组件
import("~/components/dify-dataset-manager").then((mod) => {
setDatasetManager(() => mod.default);
}).catch(err => {
console.error('加载知识库管理组件失败:', err);
});
}, []);
return (
<div className="dataset-manager-container">
<Tabs
defaultActiveKey={defaultActiveTab}
items={items}
className="dataset-manager-tabs"
/>
</div>
);
// 服务端渲染时显示简单加载状态
if (!mounted) {
return (
<div style={{ padding: '40px', textAlign: 'center' }}>
<div>...</div>
</div>
);
}
// 客户端加载中
if (!DatasetManager) {
return (
<div style={{ padding: '40px', textAlign: 'center' }}>
<Spin size="large" />
<p style={{ marginTop: 16 }}>...</p>
</div>
);
}
return <DatasetManager />;
}