Replace AGENT_CHAT_UNKNOWN_THREAD_ID with null for thread state (#19552)
## Summary This PR refactors the AI chat thread state management to use `null` instead of a sentinel string value (`AGENT_CHAT_UNKNOWN_THREAD_ID`) to represent an uninitialized or new chat thread. This improves type safety and makes the code more idiomatic by using `null` to represent the absence of a value. ## Key Changes - **Removed sentinel constant**: Deleted `AGENT_CHAT_UNKNOWN_THREAD_ID` constant and replaced all usages with `null` - **Updated state types**: Changed `currentAIChatThreadState`, `agentChatLastDiffSyncedThreadState`, and `agentChatDisplayedThreadState` to use `string | null` type with `null` as default value - **Updated component family states**: Modified message-related state families to accept `threadId: string | null` instead of `threadId: string` - **Refined null checks**: Added explicit `null` checks in: - `AgentChatMessagesFetchEffect`: Updated `isNewThread` logic to check for `null` first - `useAIChatThreadClick` and `useSwitchToNewAIChat`: Added guards to only save drafts when `currentAIChatThread !== null` - `AgentChatThreadInitializationEffect`: Added null check before UUID validation - `useEnsureAgentChatThreadIdForSend`: Added null check before comparing with draft key - **Updated fallback logic**: Used nullish coalescing operator (`??`) in `AIChatTab` and `useAIChatEditor` to default to `AGENT_CHAT_NEW_THREAD_DRAFT_KEY` when thread is null - **Enhanced refetch safety**: Added early return in `handleRefetchMessages` to prevent refetching when in new thread state ## Implementation Details - The change maintains backward compatibility by treating `null` the same way the code previously treated `AGENT_CHAT_UNKNOWN_THREAD_ID` - All draft saving operations now safely check for null before attempting to store drafts - The nullish coalescing pattern (`currentAIChatThread ?? AGENT_CHAT_NEW_THREAD_DRAFT_KEY`) ensures proper fallback behavior when accessing draft storage https://claude.ai/code/session_01Pz8KCygSNgBPYsndbMq8f7 --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { readUIMessageStream, type UIMessageChunk } from 'ai';
|
||||
import { print, type ExecutionResult } from 'graphql';
|
||||
@@ -18,8 +18,6 @@ import { agentChatFirstLiveSeqState } from '@/ai/states/agentChatFirstLiveSeqSta
|
||||
import { agentChatHandleEventCallbackState } from '@/ai/states/agentChatHandleEventCallbackState';
|
||||
import { agentChatIsStreamingState } from '@/ai/states/agentChatIsStreamingState';
|
||||
import { agentChatMessagesComponentFamilyState } from '@/ai/states/agentChatMessagesComponentFamilyState';
|
||||
import { agentChatStreamWriterState } from '@/ai/states/agentChatStreamWriterState';
|
||||
import { agentChatSubscriptionDisposeState } from '@/ai/states/agentChatSubscriptionDisposeState';
|
||||
import { agentChatUsageState } from '@/ai/states/agentChatUsageState';
|
||||
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
|
||||
import { dispatchBrowserEvent } from '@/browser-event/utils/dispatchBrowserEvent';
|
||||
@@ -98,47 +96,38 @@ export const useAgentChatSubscription = (threadId: string | null) => {
|
||||
const store = useStore();
|
||||
const sseClient = useAtomStateValue(sseClientState);
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
const writer = store.get(agentChatStreamWriterState.atom);
|
||||
|
||||
if (isDefined(writer)) {
|
||||
writer.close().catch(() => {});
|
||||
store.set(agentChatStreamWriterState.atom, null);
|
||||
}
|
||||
|
||||
const dispose = store.get(agentChatSubscriptionDisposeState.atom);
|
||||
|
||||
if (isDefined(dispose)) {
|
||||
dispose();
|
||||
store.set(agentChatSubscriptionDisposeState.atom, null);
|
||||
}
|
||||
|
||||
if (store.get(agentChatIsStreamingState.atom)) {
|
||||
store.set(agentChatIsStreamingState.atom, false);
|
||||
}
|
||||
}, [store]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(threadId)) {
|
||||
cleanup();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDefined(sseClient)) {
|
||||
if (!isDefined(threadId) || !isDefined(sseClient)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let bridge: TransformStream<UIMessageChunk> | null = null;
|
||||
let throttleTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let latestMessage: ExtendedUIMessage | null = null;
|
||||
let writer: WritableStreamDefaultWriter<UIMessageChunk> | null = null;
|
||||
let disposed = false;
|
||||
|
||||
store.set(agentChatFirstLiveSeqState.atom, null);
|
||||
|
||||
const closeWriter = () => {
|
||||
if (isDefined(writer)) {
|
||||
writer.close().catch(() => {});
|
||||
writer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupStream = () => {
|
||||
closeWriter();
|
||||
|
||||
if (store.get(agentChatIsStreamingState.atom)) {
|
||||
store.set(agentChatIsStreamingState.atom, false);
|
||||
}
|
||||
};
|
||||
|
||||
const flushToAtom = () => {
|
||||
const messageToFlush = latestMessage;
|
||||
|
||||
if (!isDefined(messageToFlush) || !isDefined(threadId)) {
|
||||
if (!isDefined(messageToFlush)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -244,7 +233,9 @@ export const useAgentChatSubscription = (threadId: string | null) => {
|
||||
}
|
||||
flushToAtom();
|
||||
|
||||
store.set(agentChatIsStreamingState.atom, false);
|
||||
if (!disposed) {
|
||||
store.set(agentChatIsStreamingState.atom, false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEvent = (event: AgentChatSubscriptionEvent) => {
|
||||
@@ -261,22 +252,19 @@ export const useAgentChatSubscription = (threadId: string | null) => {
|
||||
store.set(agentChatIsStreamingState.atom, true);
|
||||
|
||||
bridge = new TransformStream<UIMessageChunk>();
|
||||
store.set(
|
||||
agentChatStreamWriterState.atom,
|
||||
bridge.writable.getWriter(),
|
||||
);
|
||||
writer = bridge.writable.getWriter();
|
||||
|
||||
const adaptedReadable = bridge.readable.pipeThrough(
|
||||
createMidStreamAdapter(),
|
||||
);
|
||||
|
||||
startReadLoop(adaptedReadable).catch(() => {
|
||||
store.set(agentChatIsStreamingState.atom, false);
|
||||
if (!disposed) {
|
||||
store.set(agentChatIsStreamingState.atom, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const writer = store.get(agentChatStreamWriterState.atom);
|
||||
|
||||
if (isDefined(writer)) {
|
||||
writer.write(event.chunk as UIMessageChunk).catch(() => {});
|
||||
}
|
||||
@@ -284,13 +272,7 @@ export const useAgentChatSubscription = (threadId: string | null) => {
|
||||
}
|
||||
|
||||
case 'message-persisted': {
|
||||
const writer = store.get(agentChatStreamWriterState.atom);
|
||||
|
||||
if (isDefined(writer)) {
|
||||
writer.close().catch(() => {});
|
||||
store.set(agentChatStreamWriterState.atom, null);
|
||||
}
|
||||
|
||||
closeWriter();
|
||||
dispatchBrowserEvent(AGENT_CHAT_REFETCH_MESSAGES_EVENT_NAME);
|
||||
break;
|
||||
}
|
||||
@@ -308,13 +290,7 @@ export const useAgentChatSubscription = (threadId: string | null) => {
|
||||
streamError.code = event.code;
|
||||
store.set(agentChatErrorState.atom, streamError);
|
||||
|
||||
const writer = store.get(agentChatStreamWriterState.atom);
|
||||
|
||||
if (isDefined(writer)) {
|
||||
writer.close().catch(() => {});
|
||||
store.set(agentChatStreamWriterState.atom, null);
|
||||
}
|
||||
|
||||
closeWriter();
|
||||
store.set(agentChatIsStreamingState.atom, false);
|
||||
break;
|
||||
}
|
||||
@@ -340,19 +316,21 @@ export const useAgentChatSubscription = (threadId: string | null) => {
|
||||
// graphql-sse handles reconnection automatically
|
||||
},
|
||||
complete: () => {
|
||||
cleanup();
|
||||
if (!disposed) {
|
||||
cleanupStream();
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
store.set(agentChatSubscriptionDisposeState.atom, () => dispose);
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
store.set(agentChatHandleEventCallbackState.atom, null);
|
||||
if (isDefined(throttleTimer)) {
|
||||
clearTimeout(throttleTimer);
|
||||
}
|
||||
cleanup();
|
||||
cleanupStream();
|
||||
dispose();
|
||||
};
|
||||
}, [threadId, sseClient, store, cleanup]);
|
||||
}, [threadId, sseClient, store]);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user