From 3f6b2bbab6fbb41461fe05f461868030933184e8 Mon Sep 17 00:00:00 2001 From: xuyaqist Date: Thu, 8 Oct 2026 11:14:47 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=E5=9C=A8=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E8=BF=87=E7=A8=8B=E4=B8=AD=E6=97=A0=E6=B3=95=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E6=98=BE=E7=A4=BA=E6=8C=87=E7=A4=BA=E7=81=AF=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E2=80=9C=E7=BB=BF=E8=89=B2=E2=80=9D?= =?UTF-8?q?=E4=BB=A3=E8=A1=A8=E6=AD=A3=E5=9C=A8=E5=9B=9E=E7=AD=94=EF=BC=8C?= =?UTF-8?q?=E2=80=9C=E8=93=9D=E8=89=B2=E2=80=9D=E4=BB=A3=E8=A1=A8=E5=9B=9E?= =?UTF-8?q?=E7=AD=94=E5=AE=8C=E6=88=90=202.=20=E4=BF=AE=E5=A4=8D=E5=9C=A8?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E8=BF=87=E7=A8=8B=E4=B8=AD=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=8F=91=E9=80=81=E7=9A=84=E6=B6=88=E6=81=AF=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=AD=A3=E5=B8=B8=E6=98=BE=E7=A4=BA=E6=8D=A2=E8=A1=8C?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=203.=20=E4=BF=AE=E5=A4=8D=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=AD=A3=E5=B8=B8=E9=87=8D=E5=91=BD=E5=90=8D=E2=80=9C?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E4=BC=9A=E8=AF=9D=E2=80=9D=E7=9A=84=E6=A0=87?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../newchat/assistant-ui/thread-list.tsx | 69 +++++++++++++++++-- .../assistant-ui/threadlist-sidebar.tsx | 9 +++ frontend/app/[locale]/newchat/page.tsx | 13 ++++ .../interaction/user-message-bubble.tsx | 2 +- frontend/features/workbench/WorkbenchPage.tsx | 13 ++++ 5 files changed, 99 insertions(+), 7 deletions(-) diff --git a/frontend/app/[locale]/newchat/assistant-ui/thread-list.tsx b/frontend/app/[locale]/newchat/assistant-ui/thread-list.tsx index 8c2233e4d4..19eefbe5cc 100644 --- a/frontend/app/[locale]/newchat/assistant-ui/thread-list.tsx +++ b/frontend/app/[locale]/newchat/assistant-ui/thread-list.tsx @@ -30,7 +30,9 @@ import { createContext, useCallback, useContext, + useEffect, useMemo, + useRef, useState, type ReactNode, } from "react"; @@ -303,17 +305,47 @@ export const BatchSidebarFooter: FC<{ onSwitchToLegacy?: () => void }> = ({ ); }; +type RenameSuccessHandler = ( + threadId: string, + remoteId: string | undefined, + title: string +) => void; + interface ThreadListProps { generatedTitles?: ReadonlyMap; serverConversationIds?: ReadonlyMap; + onRenameSuccess?: RenameSuccessHandler; } export const ThreadList: FC = ({ generatedTitles, serverConversationIds, + onRenameSuccess, }) => { const { t } = useTranslation(); - const completedConversations = useMemo(() => new Set(), []); + const [completedConversations, setCompletedConversations] = useState( + () => new Set() + ); + const isThreadRunning = useAuiState((s) => s.thread.isRunning); + const mainThreadId = useAuiState((s) => s.threads.mainThreadId); + const runningThreadId = useRef(undefined); + + useEffect(() => { + if (isThreadRunning) { + runningThreadId.current = mainThreadId; + return; + } + + if (runningThreadId.current) { + const completedThreadId = runningThreadId.current; + setCompletedConversations((previous) => { + const next = new Set(previous); + next.add(completedThreadId); + return next; + }); + runningThreadId.current = undefined; + } + }, [isThreadRunning, mainThreadId]); const isLoading = useAuiState((s) => s.threads.isLoading); const isLoadingMore = useAuiState((s) => s.threads.isLoadingMore); const hasMore = useAuiState((s) => s.threads.hasMore); @@ -335,6 +367,7 @@ export const ThreadList: FC = ({ )} @@ -373,12 +406,14 @@ const ThreadListEmpty: FC = () => { interface ThreadListItemsProps { completedConversations: Set; generatedTitles?: ReadonlyMap; + onRenameSuccess?: RenameSuccessHandler; visibleThreadIds: string[]; } const ThreadListItems: FC = ({ completedConversations, generatedTitles, + onRenameSuccess, visibleThreadIds, }) => { const { t } = useTranslation(); @@ -392,10 +427,11 @@ const ThreadListItems: FC = ({ ); }, - [completedConversations, generatedTitles] + [completedConversations, generatedTitles, onRenameSuccess] ); // Render each thread by index so we can interleave group labels between @@ -529,17 +565,20 @@ const ThreadListSkeleton: FC = () => { interface ThreadListItemProps { completedConversations: Set; generatedTitles?: ReadonlyMap; + onRenameSuccess?: RenameSuccessHandler; } const ThreadListItem: FC = ({ completedConversations, generatedTitles, + onRenameSuccess, }) => { return ( ); @@ -548,11 +587,13 @@ const ThreadListItem: FC = ({ interface ThreadListItemContentProps { completedConversations: Set; generatedTitles?: ReadonlyMap; + onRenameSuccess?: RenameSuccessHandler; } const ThreadListItemContent: FC = ({ completedConversations, generatedTitles, + onRenameSuccess, }) => { const aui = useAui(); const { t } = useTranslation(); @@ -575,6 +616,7 @@ const ThreadListItemContent: FC = ({ setPendingThreadOperationId(thread.id); try { await threadListItem.rename(newTitle); + onRenameSuccess?.(thread.id, thread.remoteId, newTitle); log.log(`[ThreadList] Renamed thread to "${newTitle}"`); setIsEditing(false); } catch (error) { @@ -584,7 +626,7 @@ const ThreadListItemContent: FC = ({ setPendingThreadOperationId(undefined); } }, - [thread.id, threadListItem, t] + [thread.id, thread.remoteId, threadListItem, onRenameSuccess, t] ); const handleRenameClick = useCallback(() => { @@ -697,11 +739,26 @@ const ConversationStatusIndicatorWrapper: FC<{ completedConversations: Set; }> = ({ completedConversations }) => { const aui = useAui(); - const status = aui.threadListItem().getState().status as string; - const isRunning = status === "running" || status === "streaming"; + const threadId = aui.threadListItem().getState().id; return ( - + <> + + s.thread.isRunning && s.threads.mainThreadId === threadId + } + > + + + + completedConversations.has(threadId) && + !(s.thread.isRunning && s.threads.mainThreadId === threadId) + } + > + + + ); }; diff --git a/frontend/app/[locale]/newchat/assistant-ui/threadlist-sidebar.tsx b/frontend/app/[locale]/newchat/assistant-ui/threadlist-sidebar.tsx index 3731bc5e6d..a35e865dfd 100644 --- a/frontend/app/[locale]/newchat/assistant-ui/threadlist-sidebar.tsx +++ b/frontend/app/[locale]/newchat/assistant-ui/threadlist-sidebar.tsx @@ -24,12 +24,19 @@ import { useIsMobile } from "@/hooks/use-mobile"; import { cn } from "@/lib/utils"; import { useTranslation } from "react-i18next"; +type RenameSuccessHandler = ( + threadId: string, + remoteId: string | undefined, + title: string +) => void; + interface ThreadListSidebarProps extends SidebarProps { className?: string; generatedTitles?: ReadonlyMap; serverConversationIds?: ReadonlyMap; onPrepareNewConversation?: () => void; onNewConversation?: () => void | Promise; + onRenameSuccess?: RenameSuccessHandler; showLegacySwitch?: boolean; } @@ -38,6 +45,7 @@ export function ThreadListSidebar({ serverConversationIds, onPrepareNewConversation, onNewConversation, + onRenameSuccess, showLegacySwitch = true, ...props }: ThreadListSidebarProps) { @@ -144,6 +152,7 @@ export function ThreadListSidebar({ diff --git a/frontend/app/[locale]/newchat/page.tsx b/frontend/app/[locale]/newchat/page.tsx index c5698da367..1eb3c408cd 100644 --- a/frontend/app/[locale]/newchat/page.tsx +++ b/frontend/app/[locale]/newchat/page.tsx @@ -281,6 +281,18 @@ const HomeContent: FC<{ [chatMode] ); + const handleConversationRenamed = useCallback( + (threadId: string, remoteId: string | undefined, title: string) => { + setGeneratedTitles((titles) => { + const next = new Map(titles); + next.set(threadId, title); + if (remoteId) next.set(remoteId, title); + return next; + }); + }, + [] + ); + const handleGenerationStopped = useCallback((conversationId: number) => { // A user-initiated stop transitions assistant-ui to idle before the // backend has persisted the terminal message. Do not mistake that short @@ -781,6 +793,7 @@ const HomeContent: FC<{ diff --git a/frontend/components/interaction/user-message-bubble.tsx b/frontend/components/interaction/user-message-bubble.tsx index 0926ec2af3..7c14bbd3fd 100644 --- a/frontend/components/interaction/user-message-bubble.tsx +++ b/frontend/components/interaction/user-message-bubble.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from "react"; export function UserMessageBubble({ children }: { children: ReactNode }) { return ( -
+
{children}
); diff --git a/frontend/features/workbench/WorkbenchPage.tsx b/frontend/features/workbench/WorkbenchPage.tsx index ee0d3b6c00..1f423b6830 100644 --- a/frontend/features/workbench/WorkbenchPage.tsx +++ b/frontend/features/workbench/WorkbenchPage.tsx @@ -253,6 +253,18 @@ const HomeContent: FC = () => { [chatMode, dispatchWorkbench, workbenchState.configVersion] ); + const handleConversationRenamed = useCallback( + (threadId: string, remoteId: string | undefined, title: string) => { + setGeneratedTitles((titles) => { + const next = new Map(titles); + next.set(threadId, title); + if (remoteId) next.set(remoteId, title); + return next; + }); + }, + [] + ); + const handleGenerationStopped = useCallback((conversationId: number) => { // A user-initiated stop transitions assistant-ui to idle before the // backend has persisted the terminal message. Do not mistake that short @@ -1168,6 +1180,7 @@ const HomeContent: FC = () => { showLegacySwitch={false} generatedTitles={generatedTitles} serverConversationIds={serverConversationIds} + onRenameSuccess={handleConversationRenamed} onPrepareNewConversation={handlePrepareNewConversation} onNewConversation={handleNewConversation} />