diff --git a/src/admin/components/odin/OdinChat.tsx b/src/admin/components/odin/OdinChat.tsx index 53f50cb..c890d88 100644 --- a/src/admin/components/odin/OdinChat.tsx +++ b/src/admin/components/odin/OdinChat.tsx @@ -216,6 +216,8 @@ export default function OdinChat() { // tools over system data); attachments keep the invoice-extract path. setBusy(true); const prevTurns = turns; + const prevInput = input; + const prevAttachments = attachments; const userContent = [ text, @@ -228,6 +230,10 @@ export default function OdinChat() { { role: "user", content: userContent }, ]; setTurns(optimistic); + // Clear the composer immediately — the message now lives in the thread + // as the optimistic bubble; a failure restores both below. + setInput(""); + setAttachments([]); try { if (files.length > 0) { @@ -268,8 +274,6 @@ export default function OdinChat() { setReview((prev) => [...prev, ...reviews]); const note = summaryNote(reviews); setTurns((t) => [...t, { role: "assistant", content: note }]); - setInput(""); - setAttachments([]); // Create the conversation now (first successful message) and persist. const convId = await ensureActive(); if (convId != null) @@ -293,7 +297,6 @@ export default function OdinChat() { ? body.data.tool_trace : undefined; setTurns((t) => [...t, { role: "assistant", content: reply, tools }]); - setInput(""); // Create the conversation now (first successful message) and persist. const convId = await ensureActive(); if (convId != null) @@ -305,6 +308,9 @@ export default function OdinChat() { } } catch (e) { setTurns(prevTurns); + // Give the user their message back to retry/edit. + setInput(prevInput); + setAttachments(prevAttachments); const fallback = files.length > 0 ? "Chyba čtení faktur" : "Chyba AI"; alert.error(e instanceof Error ? e.message : fallback); } finally { diff --git a/src/admin/components/odin/OdinThread.tsx b/src/admin/components/odin/OdinThread.tsx index 9cb57e5..1d381e1 100644 --- a/src/admin/components/odin/OdinThread.tsx +++ b/src/admin/components/odin/OdinThread.tsx @@ -196,6 +196,15 @@ export default function OdinThread({ borderRadius: 2, boxShadow: 1, bgcolor: isUser ? "primary.main" : "background.paper", + // The global ::selection is primary-on-white — invisible on + // this primary-filled bubble; invert it so selected/copied + // text stays readable. + ...(isUser && { + "& ::selection": { + backgroundColor: "common.white", + color: "primary.main", + }, + }), }} > {/* Tools the assistant consulted for this turn (Phase 2a). */}