From 61c2fca2a76f7f58be4041a97441a94cc5ee47a1 Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Mon, 6 Jul 2026 18:08:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=B9=E8=AF=9D=E6=A1=86=E4=B8=8A?= =?UTF-8?q?=E6=96=B9=E9=9B=86=E6=88=90=E4=B8=93=E5=AE=B6=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=99=A8=EF=BC=8C=E6=94=AF=E6=8C=81=E5=AE=9E=E6=97=B6=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E4=B8=8E=E7=B3=BB=E7=BB=9F=E6=8F=90=E7=A4=BA=E8=AF=8D?= =?UTF-8?q?=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 ExpertSelector 组件,加载时查询 session 选中状态,失败降级显示无专家 - 下拉面板含无专家/专家列表/管理专家入口,乐观更新+失败回滚保证一致性 - ChatContainer 在 MessageList 与 MessageInput 之间渲染 ExpertSelector - MessageInput 接收 selectedExpert,选中时 placeholder 显示以专家身份对话 - App.tsx 传递 sessionId/onOpenSettings 给 ChatContainer,支持从对话框直达专家设置 - App.tsx 同时包含 subagent 导航 command 发送的连带修改 --- web/src/App.tsx | 35 ++- web/src/components/Chat/ChatContainer.tsx | 16 ++ web/src/components/Chat/ExpertSelector.tsx | 247 +++++++++++++++++++++ web/src/components/Chat/MessageInput.tsx | 8 +- 4 files changed, 298 insertions(+), 8 deletions(-) create mode 100644 web/src/components/Chat/ExpertSelector.tsx diff --git a/web/src/App.tsx b/web/src/App.tsx index 69365a1..6390b4e 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -166,6 +166,7 @@ function App() { }) const [configPageOpen, setConfigPageOpen] = useState(false) + const [configInitialTab, setConfigInitialTab] = useState<'providers' | 'experts'>('providers') const handleSaveConnection = useCallback((host: string, port: number) => { setGatewaySettings({ host, port }) @@ -355,8 +356,18 @@ function App() { ) const handleExitSubAgentView = useCallback(() => { - exitSubAgentView() - }, [exitSubAgentView]) + const command = exitSubAgentView() + if (command) { + sendMessage({ type: 'command', payload: JSON.stringify(command) }) + } + }, [exitSubAgentView, sendMessage]) + + const handleNavigateToSubAgentLevel = useCallback((index: number) => { + const command = navigateToSubAgentLevel(index) + if (command) { + sendMessage({ type: 'command', payload: JSON.stringify(command) }) + } + }, [navigateToSubAgentLevel, sendMessage]) // 切换到定时任务 tab 时自动获取列表 useEffect(() => { @@ -567,7 +578,10 @@ function App() { {/* Breadcrumb: 主会话 */} + {error && ( + {error} + )} + + + {open && ( +
+ {listLoading && expertList.length === 0 ? ( +
+ 加载中... +
+ ) : ( + <> + {/* 无专家 option */} + + {expertList.length > 0 && ( +
+ {expertList.map(expert => { + const isSelected = selectedExpert?.name === expert.name + return ( + + ) + })} +
+ )} +
+ +
+ + )} +
+ )} + + ) +} diff --git a/web/src/components/Chat/MessageInput.tsx b/web/src/components/Chat/MessageInput.tsx index ce8506f..e572ee4 100644 --- a/web/src/components/Chat/MessageInput.tsx +++ b/web/src/components/Chat/MessageInput.tsx @@ -12,6 +12,7 @@ interface MessageInputProps { placeholder?: string isReadOnly?: boolean channelName?: string + selectedExpert?: { name: string; description: string } | null } interface FileAttachment { @@ -33,10 +34,13 @@ export function MessageInput({ onStop, disabled = false, isLoading = false, - placeholder = '输入消息...按 / 查看命令', + placeholder, isReadOnly = false, channelName, + selectedExpert, }: MessageInputProps) { + const effectivePlaceholder = placeholder + ?? (selectedExpert ? `以 ${selectedExpert.name} 专家身份对话...` : '输入消息...按 / 查看命令') const [content, setContent] = useState('') const [attachments, setAttachments] = useState([]) const [isDragging, setIsDragging] = useState(false) @@ -360,7 +364,7 @@ export function MessageInput({ onChange={(e) => setContent(e.target.value)} onKeyDown={handleKeyDown} onPaste={handlePaste} - placeholder={placeholder} + placeholder={effectivePlaceholder} disabled={disabled} rows={1} className="w-full resize-none rounded-xl border border-[var(--border-color)] bg-[var(--bg-tertiary)] px-4 py-3 pr-12 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)] focus:border-[var(--accent-cyan)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--focus-ring)] disabled:opacity-50 transition-all self-center scrollbar-hide"