diff --git a/frontend-react/src/api/client.ts b/frontend-react/src/api/client.ts index d99964c..c93db00 100644 --- a/frontend-react/src/api/client.ts +++ b/frontend-react/src/api/client.ts @@ -1,26 +1,46 @@ import axios from 'axios'; -import type { DatasetResponse, GenerateRequest, GenerateResponse } from '@/types'; +// 导入类型 +import type { GenerateRequest, GenerateResponse, Message } from '@/types'; -// 1. 创建 Axios 实例 -// 注意:这里使用相对路径 '/api' 是为了配合 vite.config.ts 中的代理配置 -// 这样在 Docker 内部请求会被转发到 http://backend:8000 -// 如果您更倾向于直接请求宿主机端口,也可以改为 'http://localhost:3001' +// 定义角色列表接口 +// 结构: { "RoleName": ["file1.jsonl", "file2.jsonl"] } +export interface ChatRoleListResponse { + [roleName: string]: string[]; +} + +// 创建 Axios 实例 const apiClient = axios.create({ - baseURL: '/api', - timeout: 60000, // 本地生成可能较慢,设置 60s 超时 + baseURL: '/api', // 配合 Vite 代理,转发到 http://backend:8000 + timeout: 60000, headers: { 'Content-Type': 'application/json', }, }); -// 2. API 接口函数封装 +// ========================================== +// API 接口函数封装 +// ========================================== /** - * 获取所有数据集和聊天记录列表 + * 获取所有角色和聊天记录列表 * 对应后端: GET /get_all_role_and_chat */ -export const getDatasets = async (): Promise => { - const response = await apiClient.get('/get_all_role_and_chat'); +export const getChatRoleList = async (): Promise => { + const response = await apiClient.get('/get_all_role_and_chat'); + return response.data; +}; + +/** + * 获取特定聊天记录 + * 对应后端: GET /chat_history?file_path=xxx.jsonl + * + * @param filePath - 文件路径,如 "RoleA/chat1.jsonl" + * @returns Message[] - 消息数组 + */ +export const getChatHistory = async (filePath: string): Promise => { + const response = await apiClient.get('/chat_history', { + params: { file_path: filePath } + }); return response.data; }; @@ -56,10 +76,9 @@ export const uploadImage = async (file: File): Promise<{ url: string }> => { }; // 3. 预留:流式响应处理 (SSE) -// 由于 Axios 对 SSE 支持一般,后续这里可能会改用原生 fetch 或 EventSource export const generateReplyStream = async (payload: GenerateRequest): Promise => { // TODO: 待实现 SSE 逻辑 console.log('SSE stream logic placeholder', payload); }; -export default apiClient; \ No newline at end of file +export default apiClient; diff --git a/frontend-react/src/components/Layout.tsx b/frontend-react/src/components/Layout.tsx index 1f89388..9d86491 100644 --- a/frontend-react/src/components/Layout.tsx +++ b/frontend-react/src/components/Layout.tsx @@ -43,24 +43,90 @@ const Layout: React.FC = ({ left, middle, right }) => {
{/* --- 顶部工具栏 --- */} -
-
- - -
+
-

AI WorkFlow Engine

+ {/* --- 左侧:双层下拉框 (替换原来的 打开/保存按钮) --- */} +
+ + {/* 1. 第一层:角色选择 */} + + + {/* 2. 第二层:文件选择 (仅在选择角色后显示) */} + {currentRole && datasets[currentRole] && ( +
{/* relative 用于定位菜单 */} + + + + {/* 3. 操作按钮 (重命名/删除) */} + + + {/* 悬浮菜单 */} + {showMenu && ( +
+ + +
+ )} +
+ )} +
+ + {/* --- 中间:标题 (替换原来的 AI WorkFlow Engine) --- */} + {/* 这里留空,或者您可以放一个小的 Logo/图标 */} +
+ {/* 可以在这里放图标,或者直接留空让布局更紧凑 */} +
+ + {/* --- 右侧:设置 (保留原来的代码) --- */} +
+ +
+
-
- -
-
{/* --- 主体区域:三栏布局 --- */}
diff --git a/frontend-react/src/store/useChatStore.ts b/frontend-react/src/store/useChatStore.ts index 0452868..91ade60 100644 --- a/frontend-react/src/store/useChatStore.ts +++ b/frontend-react/src/store/useChatStore.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import { Message, ChatState, SpliceBlock, GenerationParams } from '@/types'; +import type { Message, SpliceBlock, GenerationParams } from '@/types'; // --- 辅助函数:生成唯一 ID --- const generateId = (): string => { @@ -24,36 +24,71 @@ const initialGenParams: GenerationParams = { stream: true, }; +// --- 状态接口定义 --- +export interface ChatState { + // 1. 消息树状态 + messages: Record; + currentMessageId: string | null; + isLoading: boolean; + inputMessage: string; + + // 2. 设置状态 + renderHtml: boolean; + selectedFile: string | null; + spliceBlocks: SpliceBlock[]; + genParams: GenerationParams; + + // 3. 角色与文件状态 + datasets: Record; // 存储角色列表字典 + currentRole: string | null; + currentChatFile: string | null; +} + // --- Store 定义 --- export const useChatStore = create((set, get) => ({ + // --- 状态初始化 --- messages: {}, currentMessageId: null, - isLoading: false, inputMessage: "", - - renderHtml: true, // 默认开启 HTML 渲染,对应原 app.py + renderHtml: true, selectedFile: null, - spliceBlocks: initialSpliceBlocks, genParams: initialGenParams, + // --- 角色和文件列表 --- + datasets: {}, // 初始为空字典 + currentRole: null, // 初始未选中 + currentChatFile: null, // 初始未选中 + // --- Actions (操作方法) --- - // 1. 设置输入框内容 + // 1. 加载数据集列表 + loadDatasets: (data: Record) => set({ datasets: data }), + + // 2. 选择角色 + selectRole: (roleName: string) => set((state) => ({ + currentRole: roleName, + currentChatFile: null // 切换角色时,必须清空文件选择 + })), + + // 3. 选择聊天文件 + selectChatFile: (fileName: string) => set({ currentChatFile: fileName }), + + // 4. 设置输入框内容 setInputMessage: (text: string) => set({ inputMessage: text }), - // 2. 切换 HTML 渲染开关 + // 5. 切换 HTML 渲染开关 toggleRenderHtml: () => set((state) => ({ renderHtml: !state.renderHtml })), - // 3. 更新生成参数 (支持部分更新) + // 6. 更新生成参数 updateGenParams: (newParams: Partial) => set((state) => ({ genParams: { ...state.genParams, ...newParams }, })), - // 4. 切换拼接块状态 (即时生效) + // 7. 切换拼接块状态 toggleSpliceBlock: (id: string | number) => set((state) => ({ spliceBlocks: state.spliceBlocks.map((block) => @@ -61,7 +96,7 @@ export const useChatStore = create((set, get) => ({ ), })), - // 5. 添加新消息 (用户发送或 AI 完整回复后调用) + // 8. 添加新消息 addMessage: (role: 'user' | 'assistant', content: string, parentId: string | null = null) => { const newMessage: Message = { id: generateId(), @@ -70,13 +105,12 @@ export const useChatStore = create((set, get) => ({ role, content, timestamp: Date.now(), - isStreaming: false, // 初始为非流式状态 + isStreaming: false, }; set((state) => { const newMessages = { ...state.messages, [newMessage.id]: newMessage }; - // 如果有父节点,更新父节点的 childrenIds if (parentId) { const parent = state.messages[parentId]; if (parent) { @@ -93,10 +127,10 @@ export const useChatStore = create((set, get) => ({ }; }); - return newMessage.id; // 返回新消息 ID,方便后续操作 + return newMessage.id; }, - // 6. 更新流式消息内容 (打字机效果) + // 9. 更新流式消息内容 updateStreamingMessage: (messageId: string, contentChunk: string, isComplete: boolean = false) => { set((state) => { const currentMsg = state.messages[messageId]; @@ -108,21 +142,18 @@ export const useChatStore = create((set, get) => ({ [messageId]: { ...currentMsg, content: currentMsg.content + contentChunk, - isStreaming: !isComplete, // 如果完成,则结束流式状态 + isStreaming: !isComplete, }, }, }; }); }, - // 7. 切换当前消息分支 (点击历史消息时) + // 10. 切换当前消息分支 switchBranch: (messageId: string) => { set({ currentMessageId: messageId }); }, - // 8. 设置加载状态 + // 11. 设置加载状态 setLoading: (loading: boolean) => set({ isLoading: loading }), - - // 9. 设置当前选中的文件 - setSelectedFile: (filePath: string | null) => set({ selectedFile: filePath }), }));