fix(web): 移除 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 重置机制
This commit is contained in:
parent
3910bc324b
commit
a9ce308c05
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1728,7 +1728,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "picobot"
|
||||
version = "0.3.3"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
||||
@ -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 单元测试。
|
||||
|
||||
|
||||
@ -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 (
|
||||
<div className="flex h-screen flex-col bg-[var(--bg-primary)] text-[var(--text-primary)] overflow-hidden">
|
||||
@ -906,9 +906,9 @@ function App() {
|
||||
)}
|
||||
<div className="flex-1 min-h-0">
|
||||
<ChatContainer
|
||||
key={selectedTopic ?? 'no-topic'}
|
||||
messages={chatMessages}
|
||||
isLoading={isLoading}
|
||||
topicId={selectedTopic}
|
||||
isReadOnly={subAgentView || schedulerView ? true : isReadOnly}
|
||||
channelName={
|
||||
schedulerView
|
||||
|
||||
@ -24,6 +24,8 @@ interface ChatContainerProps {
|
||||
onOpenSettings?: () => 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}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -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<HTMLTextAreaElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const wasLoadingRef = useRef(false);
|
||||
const prevTopicIdRef = useRef<string | null | undefined>(topicId);
|
||||
|
||||
// 切换话题时清空草稿(替代原来通过 key remount 的重置机制)
|
||||
useEffect(() => {
|
||||
if (prevTopicIdRef.current !== topicId) {
|
||||
prevTopicIdRef.current = topicId;
|
||||
setContent('');
|
||||
setAttachments([]);
|
||||
}
|
||||
}, [topicId]);
|
||||
|
||||
useEffect(() => {
|
||||
const textarea = textareaRef.current;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user