Catch up on replies missed while the app was in the background
The reply catch-up read the page's stale face, so after an interim message it stopped while the thinking row stayed up. It now reads the live face, checks at once when the page comes back into view or online, and replays the bot's face and task too, so a reply that ended silently can't leave the bot working on screen. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
import type { BotMessageWithAttachments, WebSocketDataType } from "@/src/types";
|
||||
import type {
|
||||
BotMessageWithAttachments,
|
||||
SessionMessagesResponse,
|
||||
WebSocketDataType,
|
||||
} from "@/src/types";
|
||||
import postSessionApi from "@/src/functions/frontend/post-session-api";
|
||||
|
||||
type Params = {
|
||||
@@ -19,9 +23,13 @@ type Params = {
|
||||
* While a reply is due, checks the session's latest messages every
|
||||
* `AppData.BotReplyCatchUpMs`. Replies arrive live; this only catches one
|
||||
* whose push was missed (the socket was down at the time), so the
|
||||
* thinking row can't stay up until a reload. A missed message is replayed
|
||||
* thinking row can't stay up until a reload. It also checks at once when
|
||||
* the page comes back into view or online. A missed message is replayed
|
||||
* as the `server:bot-message` it would have been, so the thread, the rail
|
||||
* and the unread flag all take it the usual way.
|
||||
* and the unread flag all take it the usual way. The bot's face, status
|
||||
* line and active task are replayed too, as `server:bot-updated`, so a
|
||||
* reply that ended without a final message (its face push missed) can't
|
||||
* leave the bot working on screen.
|
||||
*/
|
||||
export default function useReplyCatchUp({ session_id, awaiting_reply, last_message_id }: Params) {
|
||||
const last_message = useRef(Number(last_message_id) || 0);
|
||||
@@ -33,28 +41,52 @@ export default function useReplyCatchUp({ session_id, awaiting_reply, last_messa
|
||||
|
||||
let stale = false;
|
||||
|
||||
const timer = setInterval(async () => {
|
||||
const res = await postSessionApi<BotMessageWithAttachments>({
|
||||
const replay = ({ data }: { data: WebSocketDataType }) =>
|
||||
window.dispatchEvent(new CustomEvent("wsDataEvent", { detail: { data, message: data } }));
|
||||
|
||||
const check = async () => {
|
||||
const res = (await postSessionApi<BotMessageWithAttachments>({
|
||||
path: "session-messages",
|
||||
body: { session_id },
|
||||
}).catch(() => null);
|
||||
}).catch(() => null)) as SessionMessagesResponse | null;
|
||||
|
||||
if (stale || !res?.success || !Array.isArray(res.payload)) return;
|
||||
|
||||
const missed = (res.payload as BotMessageWithAttachments[]).filter(
|
||||
const missed = res.payload.filter(
|
||||
(message) => Number(message.id) > last_message.current,
|
||||
);
|
||||
|
||||
for (const message of missed) {
|
||||
const data: WebSocketDataType = { event: "server:bot-message", data: message };
|
||||
|
||||
window.dispatchEvent(new CustomEvent("wsDataEvent", { detail: { data, message: data } }));
|
||||
replay({ data: { event: "server:bot-message", data: message } });
|
||||
}
|
||||
}, AppData.BotReplyCatchUpMs);
|
||||
|
||||
/**
|
||||
* After the messages, so a reply that ended silently (its last
|
||||
* word an interim one) drops the row once its face moves on.
|
||||
*/
|
||||
if (res.bot?.id) replay({ data: { event: "server:bot-updated", data: res.bot } });
|
||||
};
|
||||
|
||||
/**
|
||||
* Coming back to the app (a notification tapped, the PWA brought
|
||||
* forward) checks at once: the socket was likely down meanwhile.
|
||||
*/
|
||||
const onVisible = () => {
|
||||
if (document.visibilityState == "visible") check();
|
||||
};
|
||||
|
||||
const timer = setInterval(check, AppData.BotReplyCatchUpMs);
|
||||
|
||||
window.addEventListener("online", check);
|
||||
window.addEventListener("pageshow", check);
|
||||
document.addEventListener("visibilitychange", onVisible);
|
||||
|
||||
return () => {
|
||||
stale = true;
|
||||
clearInterval(timer);
|
||||
window.removeEventListener("online", check);
|
||||
window.removeEventListener("pageshow", check);
|
||||
document.removeEventListener("visibilitychange", onVisible);
|
||||
};
|
||||
}, [awaiting_reply, session_id]);
|
||||
}
|
||||
|
||||
@@ -197,11 +197,13 @@ export default function Conversation() {
|
||||
const last_thread_message = thread_messages[thread_messages.length - 1];
|
||||
/**
|
||||
* A session moved on from gets no reply here: the bot answered in the
|
||||
* new one.
|
||||
* new one. The face is the live one, as the thinking row reads it: the
|
||||
* page's is from when it rendered, so after an interim message the
|
||||
* catch-up would stop while the row stays up.
|
||||
*/
|
||||
const awaiting_reply =
|
||||
!moved_to_session_id &&
|
||||
isAwaitingReply({ last_message: last_thread_message, face: bot?.face });
|
||||
isAwaitingReply({ last_message: last_thread_message, face: live_bot?.face });
|
||||
|
||||
const reply_progress = useReplyProgress({
|
||||
bot_id: bot?.id || 0,
|
||||
|
||||
Reference in New Issue
Block a user