diff --git a/Cargo.lock b/Cargo.lock index 057944c..507e911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1728,7 +1728,7 @@ dependencies = [ [[package]] name = "picobot" -version = "0.3.3" +version = "0.3.5" dependencies = [ "anyhow", "async-trait", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4ecd51d..132ebea 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,7 +4,7 @@ ## [0.3.5] - 2026-08-12 -较 [0.3.4] 的 4 个 commit 迭代,聚焦 **可观测性**、**并发性能** 与 **外部集成健壮性** 三大方向。 +较 [0.3.4] 的 5 个 commit 迭代,聚焦 **可观测性**、**并发性能** 与 **外部集成健壮性** 三大方向。 ### 新增功能 @@ -36,6 +36,13 @@ - 清空输入框时回退到默认值 300 而非 0(0 表示不超时,与用户预期不符)。 - 新增两个 tokio 测试覆盖有超时和无超时(error pass-through)路径。 +#### 移除 ChatContainer key remount 修复新建话题列表刷新延迟 +- 话题切换时 `key={selectedTopic}` 导致整个 `ChatContainer` 子树卸载重建,触发 `ExpertSelector` / `ModelSelector` 重新挂载并发起 3 次冗余 HTTP 请求(`getSelectedExpert` / `listModelOptions` / 模型选择刷新),引入 200-500ms 延迟。 +- 改用 `topicId` prop 透传话题 ID: + - `App.tsx`:移除 key,`viewKey` 纳入 `selectedTopic` 保持各话题独立滚动位置。 + - `ChatContainer`:新增 `topicId` prop 透传给 `MessageInput`。 + - `MessageInput`:监听 `topicId` 变化清空草稿,替代原 key remount 重置机制。 + ### 测试 - MCP 超时路径新增 2 个 tokio 单元测试。 diff --git a/web/src/App.tsx b/web/src/App.tsx index 6ea51c6..07739f1 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -611,8 +611,8 @@ function App() { const viewKey = useMemo(() => { if (schedulerView) return `scheduler:${schedulerView.jobId}`; if (subAgentView) return `subagent:${subAgentView.taskId}`; - return 'main'; - }, [schedulerView, subAgentView]); + return `main:${selectedTopic ?? ''}`; + }, [schedulerView, subAgentView, selectedTopic]); return (
@@ -906,9 +906,9 @@ function App() { )}
void; /** 设置弹窗关闭信号(每次关闭递增,用于触发 ExpertSelector 刷新) */ settingsClosedTick?: number; + /** 当前话题 ID,用于切换话题时清空输入框草稿 */ + topicId?: string | null; } export function ChatContainer({ @@ -40,6 +42,7 @@ export function ChatContainer({ sessionId, onOpenSettings, settingsClosedTick, + topicId, }: ChatContainerProps) { const [selectedExpert, setSelectedExpert] = useState<{ name: string; @@ -74,6 +77,7 @@ export function ChatContainer({ isReadOnly={isReadOnly} channelName={channelName} selectedExpert={selectedExpert} + topicId={topicId} />
); diff --git a/web/src/components/Chat/MessageInput.tsx b/web/src/components/Chat/MessageInput.tsx index 87478e2..df97dd9 100644 --- a/web/src/components/Chat/MessageInput.tsx +++ b/web/src/components/Chat/MessageInput.tsx @@ -25,6 +25,8 @@ interface MessageInputProps { isReadOnly?: boolean; channelName?: string; selectedExpert?: { name: string; description: string } | null; + /** 当前话题 ID,切换话题时自动清空草稿 */ + topicId?: string | null; } interface FileAttachment { @@ -51,6 +53,7 @@ export function MessageInput({ isReadOnly = false, channelName, selectedExpert, + topicId, }: MessageInputProps) { const effectivePlaceholder = placeholder ?? @@ -62,6 +65,16 @@ export function MessageInput({ const textareaRef = useRef(null); const fileInputRef = useRef(null); const wasLoadingRef = useRef(false); + const prevTopicIdRef = useRef(topicId); + + // 切换话题时清空草稿(替代原来通过 key remount 的重置机制) + useEffect(() => { + if (prevTopicIdRef.current !== topicId) { + prevTopicIdRef.current = topicId; + setContent(''); + setAttachments([]); + } + }, [topicId]); useEffect(() => { const textarea = textareaRef.current;