Compare commits
1 Commits
pubsub-buf
...
fix/useref
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51c539ee46 |
@@ -469,17 +469,13 @@ export const AgentChatInput = memo<AgentChatInputProps>(
|
||||
|
||||
// Re-focus the editor after a send completes (isLoading goes
|
||||
// from true → false) so the user can immediately type again.
|
||||
// Uses the "store previous value in state" pattern recommended
|
||||
// by React for responding to prop changes during render.
|
||||
const [prevIsLoading, setPrevIsLoading] = useState(isLoading);
|
||||
if (prevIsLoading !== isLoading) {
|
||||
setPrevIsLoading(isLoading);
|
||||
if (prevIsLoading && !isLoading) {
|
||||
if (!isMobileViewport()) {
|
||||
internalRef.current?.focus();
|
||||
}
|
||||
const prevIsLoadingRef = useRef(isLoading);
|
||||
useEffect(() => {
|
||||
if (prevIsLoadingRef.current && !isLoading && !isMobileViewport()) {
|
||||
internalRef.current?.focus();
|
||||
}
|
||||
}
|
||||
prevIsLoadingRef.current = isLoading;
|
||||
}, [isLoading]);
|
||||
|
||||
const isUploading = attachments.some(
|
||||
(f) => uploadStates?.get(f)?.status === "uploading",
|
||||
|
||||
@@ -245,14 +245,6 @@ export const AgentCreateForm: FC<AgentCreateFormProps> = ({
|
||||
lastUsedModelID,
|
||||
]);
|
||||
|
||||
// Keep a mutable ref to selectedWorkspaceId and selectedModel so
|
||||
// that the onSend callback always sees the latest values without
|
||||
// the shared input component re-rendering on every change.
|
||||
const selectedWorkspaceIdRef = useRef(selectedWorkspaceId);
|
||||
selectedWorkspaceIdRef.current = selectedWorkspaceId;
|
||||
const selectedModelRef = useRef(selectedModel);
|
||||
selectedModelRef.current = selectedModel;
|
||||
|
||||
const handleWorkspaceChange = (value: string) => {
|
||||
if (value === autoCreateWorkspaceValue) {
|
||||
setSelectedWorkspaceId(null);
|
||||
@@ -278,15 +270,15 @@ export const AgentCreateForm: FC<AgentCreateFormProps> = ({
|
||||
await onCreateChat({
|
||||
message,
|
||||
fileIDs,
|
||||
workspaceId: selectedWorkspaceIdRef.current ?? undefined,
|
||||
model: selectedModelRef.current || undefined,
|
||||
workspaceId: selectedWorkspaceId ?? undefined,
|
||||
model: selectedModel || undefined,
|
||||
}).catch(() => {
|
||||
// Re-enable draft persistence so the user can edit
|
||||
// and retry after a failed send attempt.
|
||||
resetDraft();
|
||||
});
|
||||
},
|
||||
[submitDraft, resetDraft, onCreateChat],
|
||||
[submitDraft, resetDraft, onCreateChat, selectedWorkspaceId, selectedModel],
|
||||
);
|
||||
|
||||
const selectedWorkspace = selectedWorkspaceId
|
||||
|
||||
@@ -436,7 +436,10 @@ export const useChatStore = (
|
||||
} = options;
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const storeRef = useRef<ChatStore>(createChatStore());
|
||||
const storeRef = useRef<ChatStore | null>(null);
|
||||
if (storeRef.current === null) {
|
||||
storeRef.current = createChatStore();
|
||||
}
|
||||
const streamResetFrameRef = useRef<number | null>(null);
|
||||
const queuedMessagesHydratedChatIDRef = useRef<string | null>(null);
|
||||
// Tracks whether the WebSocket has delivered a queue_update for the
|
||||
@@ -449,7 +452,7 @@ export const useChatStore = (
|
||||
const activeChatIDRef = useRef<string | null>(null);
|
||||
const prevChatIDRef = useRef<string | undefined>(chatID);
|
||||
|
||||
const store = storeRef.current;
|
||||
const store = storeRef.current!;
|
||||
|
||||
// Compute the last REST-fetched message ID so the stream can
|
||||
// skip messages the client already has. We use a ref so the
|
||||
@@ -457,10 +460,12 @@ export const useChatStore = (
|
||||
// chatMessages in its dependency array (which would cause
|
||||
// unnecessary reconnections).
|
||||
const lastMessageIdRef = useRef<number | undefined>(undefined);
|
||||
lastMessageIdRef.current =
|
||||
chatMessages && chatMessages.length > 0
|
||||
? chatMessages[chatMessages.length - 1].id
|
||||
: undefined;
|
||||
useEffect(() => {
|
||||
lastMessageIdRef.current =
|
||||
chatMessages && chatMessages.length > 0
|
||||
? chatMessages[chatMessages.length - 1].id
|
||||
: undefined;
|
||||
}, [chatMessages]);
|
||||
|
||||
// True once the initial REST page has resolved for the current
|
||||
// chat. The WebSocket effect gates on this so that
|
||||
|
||||
@@ -31,14 +31,16 @@ export function useWorkspaceCreationWatcher({
|
||||
const streamState = useChatSelector(store, selectStreamState);
|
||||
const processedToolCallIdsRef = useRef<Set<string>>(new Set());
|
||||
|
||||
// Reset processed IDs when chatID changes during render,
|
||||
// before effects run.
|
||||
// Reset processed IDs when chatID changes.
|
||||
const [previousChatID, setPreviousChatID] = useState(chatID);
|
||||
if (previousChatID !== chatID) {
|
||||
setPreviousChatID(chatID);
|
||||
processedToolCallIdsRef.current = new Set();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
processedToolCallIdsRef.current = new Set();
|
||||
}, [chatID]);
|
||||
|
||||
// Watch stream tool results for create_workspace completions.
|
||||
useEffect(() => {
|
||||
if (!streamState || !chatID) {
|
||||
|
||||
@@ -519,9 +519,13 @@ const ScrollAnchoredContainer: FC<{
|
||||
const sentinelRef = useRef<HTMLDivElement>(null);
|
||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
||||
const isFetchingRef = useRef(isFetchingMoreMessages);
|
||||
isFetchingRef.current = isFetchingMoreMessages;
|
||||
useEffect(() => {
|
||||
isFetchingRef.current = isFetchingMoreMessages;
|
||||
}, [isFetchingMoreMessages]);
|
||||
const onFetchRef = useRef(onFetchMoreMessages);
|
||||
onFetchRef.current = onFetchMoreMessages;
|
||||
useEffect(() => {
|
||||
onFetchRef.current = onFetchMoreMessages;
|
||||
}, [onFetchMoreMessages]);
|
||||
|
||||
// Sentinel observer — triggers loading older messages.
|
||||
// All changing values are read from refs so the observer
|
||||
|
||||
@@ -61,7 +61,9 @@ const AgentEmbedPage: FC = () => {
|
||||
bootstrapChatEmbedSession({ checks: permissionChecks }, queryClient),
|
||||
);
|
||||
const latestEmbedSessionMutationRef = useRef(embedSessionMutation);
|
||||
latestEmbedSessionMutationRef.current = embedSessionMutation;
|
||||
useEffect(() => {
|
||||
latestEmbedSessionMutationRef.current = embedSessionMutation;
|
||||
}, [embedSessionMutation]);
|
||||
const inFlightBootstrapRef = useRef<Promise<unknown> | null>(null);
|
||||
|
||||
const [chatErrorReasons, setChatErrorReasons] = useState<
|
||||
|
||||
@@ -341,7 +341,9 @@ const AgentsPage: FC = () => {
|
||||
// WebSocket handler can read it without re-subscribing on
|
||||
// every navigation.
|
||||
const activeChatIDRef = useRef(agentId);
|
||||
activeChatIDRef.current = agentId;
|
||||
useEffect(() => {
|
||||
activeChatIDRef.current = agentId;
|
||||
}, [agentId]);
|
||||
|
||||
useEffect(() => {
|
||||
return createReconnectingWebSocket({
|
||||
|
||||
@@ -46,6 +46,7 @@ export function useDesktopConnection({
|
||||
}: UseDesktopConnectionOptions): UseDesktopConnectionResult {
|
||||
const [status, setStatus] = useState<DesktopConnectionStatus>("idle");
|
||||
const [hasConnected, setHasConnected] = useState(false);
|
||||
const [rfb, setRfb] = useState<RFB | null>(null);
|
||||
|
||||
const rfbRef = useRef<RFB | null>(null);
|
||||
const offscreenContainerRef = useRef<HTMLElement | null>(null);
|
||||
@@ -66,6 +67,9 @@ export function useDesktopConnection({
|
||||
// Ignore errors during disconnect.
|
||||
}
|
||||
rfbRef.current = null;
|
||||
if (!disposedRef.current) {
|
||||
setRfb(null);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -109,6 +113,7 @@ export function useDesktopConnection({
|
||||
rfb.addEventListener("disconnect", () => {
|
||||
if (disposedRef.current) return;
|
||||
rfbRef.current = null;
|
||||
setRfb(null);
|
||||
|
||||
if (!sessionConnected && !hasConnectedRef.current) {
|
||||
// The VNC handshake never completed and the desktop
|
||||
@@ -140,10 +145,12 @@ export function useDesktopConnection({
|
||||
rfb.addEventListener("securityfailure", () => {
|
||||
if (disposedRef.current) return;
|
||||
rfbRef.current = null;
|
||||
setRfb(null);
|
||||
setStatus("error");
|
||||
});
|
||||
|
||||
rfbRef.current = rfb;
|
||||
setRfb(rfb);
|
||||
} catch {
|
||||
setStatus("error");
|
||||
}
|
||||
@@ -203,6 +210,6 @@ export function useDesktopConnection({
|
||||
connect,
|
||||
disconnect,
|
||||
attach,
|
||||
rfb: rfbRef.current,
|
||||
rfb,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,7 +34,9 @@ export function useFileAttachments(
|
||||
|
||||
// Revoke blob URLs on unmount to prevent memory leaks.
|
||||
const previewUrlsRef = useRef(previewUrls);
|
||||
previewUrlsRef.current = previewUrls;
|
||||
useEffect(() => {
|
||||
previewUrlsRef.current = previewUrls;
|
||||
}, [previewUrls]);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
for (const [, url] of previewUrlsRef.current) {
|
||||
|
||||
Reference in New Issue
Block a user