From 855a0c3f07a12dd80c34216cc2cf735bf9c04a51 Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Fri, 5 Jun 2026 21:30:41 +0800 Subject: [PATCH] style(webui): align prompt navigator with header actions --- .../src/components/thread/PromptNavigator.tsx | 28 ++++++------- webui/src/components/thread/ThreadHeader.tsx | 3 ++ webui/src/components/thread/ThreadShell.tsx | 12 +++++- .../src/components/thread/ThreadViewport.tsx | 22 +++++------ webui/src/tests/thread-viewport.test.tsx | 39 ++++++++++++------- 5 files changed, 61 insertions(+), 43 deletions(-) diff --git a/webui/src/components/thread/PromptNavigator.tsx b/webui/src/components/thread/PromptNavigator.tsx index 2e618bd18..aa1bf318a 100644 --- a/webui/src/components/thread/PromptNavigator.tsx +++ b/webui/src/components/thread/PromptNavigator.tsx @@ -47,23 +47,19 @@ export function PromptNavigator({ return ( <> -
setOpen(true)} > - -
+ + {sessionInfoAction} + {promptNavigatorAction} {!hideThemeButton ? ( (null); const pendingFirstRef = useRef(null); + const viewportRef = useRef(null); const messageCacheRef = useRef>(new Map()); /** Last chatId we associated with the in-memory thread (for cache-on-switch). */ const prevChatIdForCacheRef = useRef(null); @@ -717,6 +719,12 @@ export function ThreadShell({ const sessionInfoAction = historyKey ? ( ) : undefined; + const promptNavigatorAction = historyKey ? ( + viewportRef.current?.jumpToUserPrompt(promptId)} + /> + ) : undefined; return (
@@ -731,10 +739,12 @@ export function ThreadShell({ hostChromeTitleInset={hostChromeTitleInset} hideThemeButton={hideThemeButton} minimal={!session && !loading} + promptNavigatorAction={promptNavigatorAction} sessionInfoAction={sessionInfoAction} /> ) : null} void; +} + interface ThreadViewportProps { messages: UIMessage[]; isStreaming: boolean; @@ -54,7 +59,7 @@ export function windowMessages(messages: UIMessage[], visibleCount: number): UIM return messages.slice(start); } -export function ThreadViewport({ +export const ThreadViewport = forwardRef(function ThreadViewport({ messages, isStreaming, composer, @@ -65,7 +70,7 @@ export function ThreadViewport({ cliApps = [], mcpPresets = [], onOpenFilePreview, -}: ThreadViewportProps) { +}, ref) { const { t } = useTranslation(); const scrollRef = useRef(null); const contentRef = useRef(null); @@ -161,6 +166,8 @@ export function ThreadViewport({ setVisibleMessageCount((count) => Math.max(count, messages.length - index)); }, [messages]); + useImperativeHandle(ref, () => ({ jumpToUserPrompt }), [jumpToUserPrompt]); + const measureComposerDock = useCallback(() => { const el = composerDockRef.current; if (!el) return; @@ -330,13 +337,6 @@ export function ThreadViewport({ /> ) : null} - {hasMessages ? ( - - ) : null} - {showScrollToBottomButton && !atBottom && (