From 5c616a9d0306dba3673e2f16e19bb0416786f4e5 Mon Sep 17 00:00:00 2001 From: Anais Saraiva Date: Mon, 21 Sep 2026 22:00:24 +0200 Subject: [PATCH] Update App.tsx --- frontend/src/App.tsx | 80 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 71c0339..8c368a7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -102,6 +102,10 @@ export function App() { const [filePreview, setFilePreview] = useState(null); const [previewBusy, setPreviewBusy] = useState(false); const [ocrAvailable, setOcrAvailable] = useState(false); + // Model-error retry: which trailing message is the error bubble (if any), + // its conversation, and how many times this prompt was already retried. + const [failed, setFailed] = useState<{ index: number; convId: string } | null>(null); + const [retryCount, setRetryCount] = useState(0); const { t } = useT(); // Public routes: no auth needed. IDs are constant for the page lifetime. @@ -199,6 +203,8 @@ export function App() { // Load messages for active conversation useEffect(() => { + setFailed(null); + setRetryCount(0); if (!activeId) { setMessages([]); return; @@ -248,6 +254,8 @@ export function App() { setChecking(false); setConvs([]); setMessages([]); + setFailed(null); + setRetryCount(0); setActiveId(null); setArchivedConv(null); setAttachments([]); @@ -265,6 +273,27 @@ export function App() { } } + /** Max footer-arrow retries for the same failed prompt. */ + const MAX_RETRIES = 3; + + /** Stream one assistant reply for an exact history; resolves its text. */ + async function streamReply(history: ChatMessage[], convId: string): Promise { + // "Thinking" feature flag: decorate the outgoing payload only — the + // system message is never shown nor persisted client-side. + const outMessages = featureEnabled("thinking") + ? [ + { role: "system", content: THINKING_SYSTEM_PROMPT } as ChatMessage, + ...history, + ] + : history; + let full = ""; + await api.chatStream({ model, messages: outMessages, conversationId: convId }, (t) => { + full += t; + setStreaming(full); + }); + return full; + } + async function send(text: string) { if (sending || !model || archivedConv) return; let convId = activeId; @@ -291,25 +320,12 @@ export function App() { const history = [...messages, userMsg]; setMessages(history); setAttachments([]); + setFailed(null); + setRetryCount(0); setSending(true); setStreaming(""); - let full = ""; try { - // "Thinking" feature flag: decorate the outgoing payload only — the - // system message is never shown nor persisted client-side. - const outMessages = featureEnabled("thinking") - ? [ - { role: "system", content: THINKING_SYSTEM_PROMPT } as ChatMessage, - ...history, - ] - : history; - await api.chatStream( - { model, messages: outMessages, conversationId: convId! }, - (t) => { - full += t; - setStreaming(full); - }, - ); + const full = await streamReply(history, convId!); setMessages([...history, { role: "assistant", content: full }]); setStreaming(""); await refreshConvs(); @@ -318,6 +334,38 @@ export function App() { ...history, { role: "assistant", content: t("attach.chatError") }, ]); + setFailed({ index: history.length, convId: convId! }); + setStreaming(""); + } finally { + setSending(false); + } + } + + /** + * Re-run the same prompt after a model error (footer retry arrow on the + * error bubble). The trailing error message is dropped and the untouched + * history is streamed again — at most MAX_RETRIES times per prompt. + */ + async function retryFailed() { + if (sending || !failed || retryCount >= MAX_RETRIES || !model || archivedConv) return; + const history = messages.slice(0, failed.index); + const convId = failed.convId; + setMessages(history); + setFailed(null); + setRetryCount((c) => c + 1); + setSending(true); + setStreaming(""); + try { + const full = await streamReply(history, convId); + setMessages([...history, { role: "assistant", content: full }]); + setRetryCount(0); + await refreshConvs(); + } catch { + setMessages([ + ...history, + { role: "assistant", content: t("attach.chatError") }, + ]); + setFailed({ index: history.length, convId }); setStreaming(""); } finally { setSending(false);