Spaces:
Sleeping
fix(frontend): PDF upload — in-chat ack + inline scorecard card + proceed-choice prompt
Browse filesReported symptom: user uploaded a PDF and saw no acknowledgment in
chat — only a transient 8-second banner above the composer, which is
easy to miss. The chat transcript itself showed no trace of the
upload, so the user had no signal the indexing succeeded or that
they could now ask the bot about the document.
CHANGES
─────────────────────────────────────────────────────────────────────
handleFile (frontend/src/app/page.tsx):
1. pushUser("📎 Uploaded: <filename>") — breadcrumb so the chat
transcript records the action AND so the next /api/chat turn's
chat_history includes it (helps the brain disambiguate "this
policy" references).
2. On success: pushAssistant with the upload-ack text AND a
`citations` array carrying the uploaded policy_id. The existing
chat renderer reads citations from an assistant message and
fires getScorecard(policy_id, session_id) per cited policy, so
the scorecard card now appears INLINE under the bubble — same
treatment as a recommendation card.
3. Followed by a second pushAssistant carrying the proceed-choice
prompt: "Finish your profile" vs "Dive into the PDF first",
with the explicit note that a fuller profile makes the
discussion more useful.
4. On error: pushAssistant the error text (was banner-only;
equally easy to miss as the success case).
frontend/src/lib/i18n.ts — three new keys (en + hi):
- upload.user_msg "📎 Uploaded: ${name}"
- upload.chat_ack "Got it — I've indexed **${name}**…
Here's how it grades against what we know
about you so far:"
- upload.chat_choice proceed-choice prompt
BACKEND
─────────────────────────────────────────────────────────────────────
Unchanged. The /api/upload-policy endpoint already returns policy_id
+ policy_name + chunks_added + pages_indexed + elapsed_ms; retrieval
already pulls from the per-session quarantine collection via
rag.retrieve(session_id=...); /api/policies/{id}/scorecard already
honors uploaded policies (registered in UPLOADED_DOCS_DIR + included
in /api/policies/all).
VERIFY
─────────────────────────────────────────────────────────────────────
- Typecheck clean (npx tsc --noEmit, no errors)
- Live audit deferred to deploy commit — Playwright drives a file
upload + asserts (a) ack message appears in chat, (b) scorecard
card renders inline, (c) proceed-choice prompt appears.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- frontend/src/app/page.tsx +40 -5
- frontend/src/lib/i18n.ts +6 -0
|
@@ -1440,6 +1440,10 @@ export default function Page() {
|
|
| 1440 |
async function handleFile(ev: React.ChangeEvent<HTMLInputElement>) {
|
| 1441 |
const f = ev.target.files?.[0];
|
| 1442 |
if (!f) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1443 |
setUploadStatus(t("upload.indexing", { name: f.name }));
|
| 1444 |
try {
|
| 1445 |
// Pass the live chat session so the backend scopes the uploaded doc
|
|
@@ -1454,14 +1458,45 @@ export default function Page() {
|
|
| 1454 |
secs: (r.elapsed_ms / 1000).toFixed(1),
|
| 1455 |
}),
|
| 1456 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1457 |
// Refresh coverage so the uploaded doc shows up
|
| 1458 |
getCoverage().then(setCoverage).catch(() => {});
|
| 1459 |
} catch (e: unknown) {
|
| 1460 |
-
|
| 1461 |
-
|
| 1462 |
-
|
| 1463 |
-
|
| 1464 |
-
);
|
| 1465 |
} finally {
|
| 1466 |
if (fileInputRef.current) fileInputRef.current.value = "";
|
| 1467 |
setTimeout(() => setUploadStatus(null), 8000);
|
|
|
|
| 1440 |
async function handleFile(ev: React.ChangeEvent<HTMLInputElement>) {
|
| 1441 |
const f = ev.target.files?.[0];
|
| 1442 |
if (!f) return;
|
| 1443 |
+
// Push a user-side breadcrumb so the chat transcript shows the upload
|
| 1444 |
+
// happened (helps the user track context — and it's part of the
|
| 1445 |
+
// history the next /api/chat turn sends back to the brain).
|
| 1446 |
+
pushUser(t("upload.user_msg", { name: f.name }));
|
| 1447 |
setUploadStatus(t("upload.indexing", { name: f.name }));
|
| 1448 |
try {
|
| 1449 |
// Pass the live chat session so the backend scopes the uploaded doc
|
|
|
|
| 1458 |
secs: (r.elapsed_ms / 1000).toFixed(1),
|
| 1459 |
}),
|
| 1460 |
);
|
| 1461 |
+
// ── In-chat acknowledgment + inline scorecard card ──────────────
|
| 1462 |
+
// Push two assistant messages:
|
| 1463 |
+
// 1. The "got it, here's the card" ack with a `citations` array
|
| 1464 |
+
// carrying the uploaded policy_id. The existing chat renderer
|
| 1465 |
+
// reads citations from an assistant message and fires
|
| 1466 |
+
// `getScorecard(policy_id, session_id)` per cited policy, so
|
| 1467 |
+
// the scorecard card appears inline under the bubble — same
|
| 1468 |
+
// treatment as a recommendation card.
|
| 1469 |
+
// 2. The proceed-choice prompt — telling the user they can
|
| 1470 |
+
// finish their profile OR dive into the PDF, and noting that
|
| 1471 |
+
// a fuller profile makes the policy discussion more useful.
|
| 1472 |
+
const ackText = t("upload.chat_ack", {
|
| 1473 |
+
name: r.policy_name,
|
| 1474 |
+
chunks: r.chunks_added,
|
| 1475 |
+
pages: r.pages_indexed,
|
| 1476 |
+
secs: (r.elapsed_ms / 1000).toFixed(1),
|
| 1477 |
+
});
|
| 1478 |
+
pushAssistant(ackText, {
|
| 1479 |
+
citations: [
|
| 1480 |
+
{
|
| 1481 |
+
policy_id: r.policy_id,
|
| 1482 |
+
policy_name: r.policy_name,
|
| 1483 |
+
insurer_slug: "user-upload",
|
| 1484 |
+
page_start: 1,
|
| 1485 |
+
page_end: r.pages_indexed,
|
| 1486 |
+
source_url: "",
|
| 1487 |
+
score: 1.0,
|
| 1488 |
+
},
|
| 1489 |
+
],
|
| 1490 |
+
});
|
| 1491 |
+
pushAssistant(t("upload.chat_choice"));
|
| 1492 |
// Refresh coverage so the uploaded doc shows up
|
| 1493 |
getCoverage().then(setCoverage).catch(() => {});
|
| 1494 |
} catch (e: unknown) {
|
| 1495 |
+
const errMsg = e instanceof Error ? e.message : String(e);
|
| 1496 |
+
setUploadStatus(t("upload.error", { err: errMsg }));
|
| 1497 |
+
// Surface the failure in chat too — a transient banner alone is
|
| 1498 |
+
// easy to miss (originally reported as "no acknowledgment").
|
| 1499 |
+
pushAssistant(t("upload.error", { err: errMsg }));
|
| 1500 |
} finally {
|
| 1501 |
if (fileInputRef.current) fileInputRef.current.value = "";
|
| 1502 |
setTimeout(() => setUploadStatus(null), 8000);
|
|
@@ -41,6 +41,9 @@ export const UI_STRINGS = {
|
|
| 41 |
"upload.indexing": "Indexing ${name}…",
|
| 42 |
"upload.success": "✓ Indexed “${name}” — ${chunks} chunks from ${pages} pages (${secs}s). It's now searchable in this chat. Ask me about it.",
|
| 43 |
"upload.error": "✗ Upload failed: ${err}",
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
// Marketplace panel
|
| 46 |
"mp.heading": "Health insurance marketplace",
|
|
@@ -159,6 +162,9 @@ export const UI_STRINGS = {
|
|
| 159 |
"upload.indexing": "${name} index हो रही है…",
|
| 160 |
"upload.success": "✓ “${name}” index हो गई — ${pages} पेज से ${chunks} chunks (${secs}s)। अब यह इसी chat में search हो सकती है। इसके बारे में पूछिए।",
|
| 161 |
"upload.error": "✗ Upload विफल: ${err}",
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
"mp.heading": "स्वास्थ्य बीमा बाज़ार",
|
| 164 |
"mp.summary": "${total} पॉलिसियाँ, ${insurers} प्रमुख भारतीय बीमाकर्ताओं से। पूरी रेटिंग और source document के लिए किसी भी पॉलिसी पर click करें।",
|
|
|
|
| 41 |
"upload.indexing": "Indexing ${name}…",
|
| 42 |
"upload.success": "✓ Indexed “${name}” — ${chunks} chunks from ${pages} pages (${secs}s). It's now searchable in this chat. Ask me about it.",
|
| 43 |
"upload.error": "✗ Upload failed: ${err}",
|
| 44 |
+
"upload.user_msg": "📎 Uploaded: ${name}",
|
| 45 |
+
"upload.chat_ack": "Got it — I've indexed **${name}** (${chunks} chunks from ${pages} pages, in ${secs}s). Here's how it grades against what we know about you so far:",
|
| 46 |
+
"upload.chat_choice": "How would you like to proceed?\n\n• **Tell me more about yourself** — finish the short profile (age, family, location, budget, health) so I can speak to this policy more personally.\n• **Dive into the PDF first** — ask questions about coverage, waiting periods, exclusions, anything in the document.\n\nEither works. The more I know about you, the more useful the discussion of this policy will be.",
|
| 47 |
|
| 48 |
// Marketplace panel
|
| 49 |
"mp.heading": "Health insurance marketplace",
|
|
|
|
| 162 |
"upload.indexing": "${name} index हो रही है…",
|
| 163 |
"upload.success": "✓ “${name}” index हो गई — ${pages} पेज से ${chunks} chunks (${secs}s)। अब यह इसी chat में search हो सकती है। इसके बारे में पूछिए।",
|
| 164 |
"upload.error": "✗ Upload विफल: ${err}",
|
| 165 |
+
"upload.user_msg": "📎 Upload किया: ${name}",
|
| 166 |
+
"upload.chat_ack": "मिल गया — **${name}** index हो गई (${pages} पेज से ${chunks} chunks, ${secs}s में)। यह policy आपके profile के हिसाब से कैसी है:",
|
| 167 |
+
"upload.chat_choice": "आगे कैसे बढ़ें?\n\n• **अपने बारे में बताएं** — short profile पूरा करें (उम्र, परिवार, location, बजट, health) ताकि मैं इस policy पर आपको personally बात कर सकूं।\n• **पहले PDF पर बात करें** — coverage, waiting periods, exclusions — कुछ भी पूछें।\n\nदोनों ठीक हैं। जितना मैं आपके बारे में जानूंगा, इस policy की चर्चा उतनी useful होगी।",
|
| 168 |
|
| 169 |
"mp.heading": "स्वास्थ्य बीमा बाज़ार",
|
| 170 |
"mp.summary": "${total} पॉलिसियाँ, ${insurers} प्रमुख भारतीय बीमाकर्ताओं से। पूरी रेटिंग और source document के लिए किसी भी पॉलिसी पर click करें।",
|