harden PWA resume and title fallback
This commit is contained in:
@@ -162,20 +162,18 @@ const EMPTY_ACTIVE_RUNS: ActiveRunsState = {
|
||||
|
||||
function waitForAppForeground() {
|
||||
if (typeof document === "undefined" || typeof window === "undefined") return Promise.resolve();
|
||||
if (document.visibilityState !== "hidden" && navigator.onLine !== false) return Promise.resolve();
|
||||
if (document.visibilityState !== "hidden") return Promise.resolve();
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
const finishIfReady = () => {
|
||||
if (document.visibilityState === "hidden" || navigator.onLine === false) return;
|
||||
if (document.visibilityState === "hidden") return;
|
||||
document.removeEventListener("visibilitychange", finishIfReady);
|
||||
window.removeEventListener("pageshow", finishIfReady);
|
||||
window.removeEventListener("online", finishIfReady);
|
||||
resolve();
|
||||
};
|
||||
|
||||
document.addEventListener("visibilitychange", finishIfReady);
|
||||
window.addEventListener("pageshow", finishIfReady);
|
||||
window.addEventListener("online", finishIfReady);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1179,7 +1177,7 @@ export default function App() {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (message.includes("bearer token")) {
|
||||
handleAuthFailure(message);
|
||||
} else {
|
||||
} else if (!isRecoverableStreamDisconnect(err)) {
|
||||
setError(message);
|
||||
}
|
||||
} finally {
|
||||
@@ -1197,7 +1195,7 @@ export default function App() {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (message.includes("bearer token")) {
|
||||
handleAuthFailure(message);
|
||||
} else {
|
||||
} else if (!isRecoverableStreamDisconnect(err)) {
|
||||
setError(message);
|
||||
}
|
||||
} finally {
|
||||
@@ -1234,7 +1232,15 @@ export default function App() {
|
||||
suspendChatStreams();
|
||||
return;
|
||||
}
|
||||
setError((current) => (current && isRecoverableStreamDisconnect(new Error(current)) ? null : current));
|
||||
void refreshActiveRuns();
|
||||
void refreshCollections(selectedItemRef.current ?? undefined, false);
|
||||
const currentSelection = selectedItemRef.current;
|
||||
if (currentSelection?.kind === "chat") {
|
||||
void refreshChat(currentSelection.id);
|
||||
} else if (currentSelection?.kind === "search") {
|
||||
void refreshSearch(currentSelection.id);
|
||||
}
|
||||
};
|
||||
const handlePageShow = () => {
|
||||
void refreshActiveRuns();
|
||||
@@ -2216,6 +2222,7 @@ export default function App() {
|
||||
|
||||
while (true) {
|
||||
let streamErrorMessage: string | null = null;
|
||||
let replayedAssistantText = "";
|
||||
const abortController = new AbortController();
|
||||
chatStreamAbortRefs.current.set(chatId, abortController);
|
||||
|
||||
@@ -2246,6 +2253,7 @@ export default function App() {
|
||||
},
|
||||
onDelta: (payload) => {
|
||||
if (!payload.text) return;
|
||||
replayedAssistantText += payload.text;
|
||||
setPendingChatStates((current) => {
|
||||
const pendingState = current[chatId];
|
||||
if (!pendingState) return current;
|
||||
@@ -2254,7 +2262,7 @@ export default function App() {
|
||||
const isTarget = index === all.length - 1 && message.id.startsWith("temp-assistant-");
|
||||
if (!isTarget) return message;
|
||||
updated = true;
|
||||
return { ...message, content: message.content + payload.text };
|
||||
return { ...message, content: replayedAssistantText };
|
||||
});
|
||||
return updated ? { ...current, [chatId]: { messages: nextMessages } } : current;
|
||||
});
|
||||
@@ -2312,35 +2320,23 @@ export default function App() {
|
||||
if (shouldResumeStream) {
|
||||
try {
|
||||
const persistedChat = await retryAfterAppResume(() => getChat(chatId));
|
||||
const userMessageWasAccepted = hasNewPersistedUserMessage(
|
||||
persistedChat,
|
||||
previousMessageIds,
|
||||
content,
|
||||
attachments
|
||||
);
|
||||
setError(null);
|
||||
if (isCurrentSelection(target)) {
|
||||
setSelectedChat(persistedChat);
|
||||
setSelectedSearch(null);
|
||||
}
|
||||
setPendingChatStates((current) => ({
|
||||
...current,
|
||||
[chatId]: {
|
||||
messages: persistedChat.messages.concat(
|
||||
...(userMessageWasAccepted ? [] : [optimisticUserMessage]),
|
||||
{
|
||||
...optimisticAssistantMessage,
|
||||
id: `temp-assistant-resume-${Date.now()}`,
|
||||
content: "",
|
||||
}
|
||||
),
|
||||
},
|
||||
}));
|
||||
|
||||
// Retrying the same idempotent request either starts an unaccepted
|
||||
// submission or replays the existing backend-owned stream.
|
||||
// submission or replays the existing backend-owned stream. Keep
|
||||
// the current optimistic transcript visible until replay begins.
|
||||
continue;
|
||||
} catch (resumeError) {
|
||||
if (isRecoverableStreamDisconnect(resumeError)) {
|
||||
setError(null);
|
||||
await waitForAppForeground();
|
||||
await waitForRetry(1000);
|
||||
continue;
|
||||
}
|
||||
err = resumeError;
|
||||
}
|
||||
} else if (streamErrorMessage) {
|
||||
|
||||
Reference in New Issue
Block a user