-
Notifications
You must be signed in to change notification settings - Fork 0
Studio: Fix empty chat threads on navigation and stabilize new chat flow #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
7f9bbe7
e379b51
ec74cbc
f977819
74e74f5
cb41fc0
3a4c26d
ce90347
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -596,6 +596,15 @@ function ThreadHistoryProvider({ | |||||
|
|
||||||
| async append({ parentId, message }: ExportedMessageRepositoryItem) { | ||||||
| const { remoteId } = await aui.threadListItem().initialize(); | ||||||
| // Keep single-chat runtime state in sync once a new chat is first | ||||||
| // persisted. Compare panes intentionally do not write global activeThreadId. | ||||||
| const thread = await db.threads.get(remoteId); | ||||||
| if (thread?.modelType === "base" && !thread.pairId) { | ||||||
| const store = useChatRuntimeStore.getState(); | ||||||
| if (store.activeThreadId !== remoteId) { | ||||||
| store.setActiveThreadId(remoteId); | ||||||
| } | ||||||
| } | ||||||
| const content = cloneContent(message.content); | ||||||
| const attachments = | ||||||
| message.role === "user" ? cloneAttachments(message.attachments) : []; | ||||||
|
|
@@ -658,7 +667,11 @@ function useRuntimeHook(): ReturnType<typeof useLocalRuntime> { | |||||
|
|
||||||
| function ThreadAutoSwitch({ | ||||||
| threadId, | ||||||
| }: { threadId: string }): ReactElement | null { | ||||||
| syncActiveThreadId = true, | ||||||
| }: { | ||||||
| threadId: string; | ||||||
| syncActiveThreadId?: boolean; | ||||||
| }): ReactElement | null { | ||||||
| const aui = useAui(); | ||||||
| const isLoading = useAuiState(({ threads }) => threads.isLoading); | ||||||
| const mainThreadId = useAuiState(({ threads }) => threads.mainThreadId); | ||||||
|
|
@@ -669,6 +682,13 @@ function ThreadAutoSwitch({ | |||||
| } | ||||||
| }, [aui, isLoading, mainThreadId, threadId]); | ||||||
|
|
||||||
| useEffect(() => { | ||||||
| if (!syncActiveThreadId || isLoading || mainThreadId !== threadId) { | ||||||
| return; | ||||||
| } | ||||||
| useChatRuntimeStore.getState().setActiveThreadId(threadId); | ||||||
| }, [isLoading, mainThreadId, syncActiveThreadId, threadId]); | ||||||
|
|
||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -682,30 +702,10 @@ function ThreadNewChatSwitch({ | |||||
| if (isLoading) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| let cancelled = false; | ||||||
| // Clear immediately so the adapter never picks up a stale thread ID | ||||||
| // from a previous chat while we initialize the new one. | ||||||
| // Switch to a fresh local thread without persisting it yet. | ||||||
| // Persistence still happens on first message append. | ||||||
| void aui.threads().switchToNewThread(); | ||||||
| useChatRuntimeStore.getState().setActiveThreadId(null); | ||||||
|
|
||||||
| void (async () => { | ||||||
| try { | ||||||
| aui.threads().switchToNewThread(); | ||||||
| const { remoteId } = await aui.threadListItem().initialize(); | ||||||
| if (!cancelled) { | ||||||
| useChatRuntimeStore.getState().setActiveThreadId(remoteId); | ||||||
| } | ||||||
| } catch (error) { | ||||||
| if (!cancelled) { | ||||||
| useChatRuntimeStore.getState().setActiveThreadId(null); | ||||||
| } | ||||||
| console.error("Failed to initialize new chat thread", error); | ||||||
| } | ||||||
| })(); | ||||||
|
|
||||||
| return () => { | ||||||
| cancelled = true; | ||||||
| }; | ||||||
| }, [aui, isLoading, nonce]); | ||||||
|
|
||||||
| return null; | ||||||
|
|
@@ -733,12 +733,14 @@ export function ChatRuntimeProvider({ | |||||
| pairId, | ||||||
| initialThreadId, | ||||||
| newThreadNonce, | ||||||
| syncActiveThreadId = true, | ||||||
| }: { | ||||||
| children: ReactNode; | ||||||
| modelType?: ModelType; | ||||||
| pairId?: string; | ||||||
| initialThreadId?: string; | ||||||
| newThreadNonce?: string; | ||||||
| syncActiveThreadId?: boolean; | ||||||
| }): ReactElement { | ||||||
| const runtime = useRemoteThreadListRuntime({ | ||||||
| runtimeHook: useRuntimeHook, | ||||||
|
|
@@ -754,8 +756,15 @@ export function ChatRuntimeProvider({ | |||||
|
|
||||||
| return ( | ||||||
| <AssistantRuntimeProvider runtime={runtime} aui={aui}> | ||||||
| <ActiveThreadSync enabled={modelType === "base" && !pairId && !newThreadNonce} /> | ||||||
| {initialThreadId && <ThreadAutoSwitch threadId={initialThreadId} />} | ||||||
| <ActiveThreadSync | ||||||
| enabled={modelType === "base" && !pairId && !newThreadNonce && !initialThreadId} | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Disabling
Suggested change
|
||||||
| /> | ||||||
| {initialThreadId && ( | ||||||
| <ThreadAutoSwitch | ||||||
| threadId={initialThreadId} | ||||||
| syncActiveThreadId={syncActiveThreadId} | ||||||
| /> | ||||||
| )} | ||||||
| {!initialThreadId && newThreadNonce && ( | ||||||
| <ThreadNewChatSwitch nonce={newThreadNonce} /> | ||||||
| )} | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,7 +77,10 @@ export function ThreadSidebar({ | |
| showCompare: boolean; | ||
| }) { | ||
| const allThreads = useLiveQuery( | ||
| () => db.threads.orderBy("createdAt").reverse().toArray(), | ||
| async () => { | ||
| const rows = await db.threads.orderBy("createdAt").reverse().toArray(); | ||
| return rows.filter((t) => !t.archived); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filtering archived threads here makes the identical check inside the |
||
| }, | ||
| [], | ||
| ); | ||
| const items = groupThreads(allThreads ?? []); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The added logic in
handleNewThreadto check for existing messages is redundant and introduces a UX regression.ThreadNewChatSwitch(inruntime-provider.tsx), which now avoids callinginitialize()until the first message is appended.Since empty threads are no longer persisted automatically, it is better to allow the "New Chat" action to always proceed, ensuring the UI can be reset consistently.