Remove corpus counts from chat chrome
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
CornerDownRight,
|
||||
} from "lucide-react";
|
||||
import { cn } from "./lib/utils";
|
||||
import { citationSectionLabel } from "./CitationCard";
|
||||
|
||||
interface ChatBubbleProps {
|
||||
message: ChatMessage;
|
||||
@@ -167,98 +168,110 @@ export function ChatBubble({
|
||||
});
|
||||
};
|
||||
|
||||
const renderAnswerBlocks = () => {
|
||||
if (!message.blocks?.length) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
{message.blocks.map((block, blockIndex) => {
|
||||
const warning = block.kind === "warning" && message.answerPlan?.needsWarning;
|
||||
const showHeading = message.blocks!.length > 1 || message.answerPlan?.showHeading;
|
||||
const sourceIds = Array.from(new Set(block.claims.flatMap((claim) => claim.sourceIds)));
|
||||
const sources = sourceIds.flatMap((sourceId) => {
|
||||
const index = message.citations?.findIndex((item) => item.chunkId === sourceId) ?? -1;
|
||||
const citation = index >= 0 ? message.citations?.[index] : undefined;
|
||||
return citation ? [{ citation, index }] : [];
|
||||
});
|
||||
return (
|
||||
<section
|
||||
key={`${block.title}-${blockIndex}`}
|
||||
className={cn(
|
||||
warning && "rounded-lg border-l-2 border-status-warning bg-status-warning-bg/25 px-3 py-2"
|
||||
)}
|
||||
>
|
||||
{showHeading && (
|
||||
<h4 className="mb-2 flex items-center gap-2 text-sm font-bold text-txt-primary">
|
||||
{warning && <AlertTriangle className="h-4 w-4 shrink-0 text-status-warning" />}
|
||||
{block.title}
|
||||
</h4>
|
||||
)}
|
||||
<ul className="list-none space-y-2 p-0" style={{ listStyle: "none" }}>
|
||||
{block.claims.map((claim, claimIndex) => (
|
||||
<li
|
||||
key={`${blockIndex}-${claimIndex}`}
|
||||
className="text-[0.96rem] leading-relaxed text-txt-primary"
|
||||
style={{ listStyle: "none" }}
|
||||
>
|
||||
<div className="flex items-start gap-2.5">
|
||||
{message.answerPlan?.layout !== "prose" && (
|
||||
<span className="mt-[0.6rem] h-1.5 w-1.5 shrink-0 rounded-full bg-accent-primary" />
|
||||
)}
|
||||
<div>{formatBoldText(claim.text)}</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{sources.length > 0 && (
|
||||
<div className="mt-2.5 flex flex-wrap gap-x-3 gap-y-1">
|
||||
{sources.map(({ citation, index }) => (
|
||||
<button
|
||||
key={citation.chunkId}
|
||||
onClick={() => onCitationClick?.(citation, index + 1, message.citations ?? [])}
|
||||
className="inline-flex items-center gap-1 text-[0.7rem] font-medium text-txt-muted transition-colors hover:text-accent-primary"
|
||||
>
|
||||
<BookOpen className="h-3 w-3" />
|
||||
Xem căn cứ · {citation.drugName} · {citationSectionLabel(citation.sectionType)} · tr. {citation.sourcePageRange[0]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<article
|
||||
className={cn(
|
||||
"my-4 w-full rounded-2xl border transition-all shadow-sm glass-content-card",
|
||||
message.grounded !== false
|
||||
? "border-border-subtle bg-surface"
|
||||
: "border-status-warning/30 bg-status-warning-bg/20",
|
||||
"my-5 w-full",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Intelligence Document Header */}
|
||||
<header className="flex flex-wrap items-center justify-between gap-2 border-b border-border-subtle bg-surface-elevated px-4 py-2.5 rounded-t-2xl">
|
||||
<header className="mb-2.5 flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-7 w-7 items-center justify-center rounded-xl bg-accent-soft text-accent-primary">
|
||||
<Pill className="h-4 w-4" />
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-accent-soft text-accent-primary">
|
||||
<BookOpen className="h-3.5 w-3.5" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="m-0 text-xs font-bold tracking-tight text-txt-primary flex items-center gap-1.5">
|
||||
<span>Báo Cáo Tra Cứu Chuyên Luận Dược Thư</span>
|
||||
{message.resolvedDrugId && (
|
||||
<span className="rounded-md bg-accent-soft px-1.5 py-0.5 text-[0.68rem] font-bold text-accent-primary uppercase">
|
||||
{message.resolvedDrugId}
|
||||
</span>
|
||||
)}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{message.decision === "answerable" && message.grounded !== false ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-status-success/30 bg-status-success-bg px-2.5 py-0.5 text-[0.65rem] font-extrabold text-status-success">
|
||||
<span className="text-xs font-semibold text-txt-secondary">
|
||||
{message.resolvedDrugId ? message.resolvedDrugId.replace(/_/g, " ").toUpperCase() : "Trợ lý Dược thư"}
|
||||
</span>
|
||||
{message.decision === "answerable" && message.grounded !== false && (
|
||||
<span className="inline-flex items-center gap-1 text-[0.68rem] font-medium text-status-success">
|
||||
<ShieldCheck className="h-3 w-3" />
|
||||
ENTAILED & GROUNDED
|
||||
</span>
|
||||
) : message.decision === "verify_pdf" ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-status-warning/30 bg-status-warning-bg px-2.5 py-0.5 text-[0.65rem] font-extrabold text-status-warning">
|
||||
<AlertTriangle className="h-3 w-3" />
|
||||
CẦN ĐỐI CHIẾU PDF GỐC
|
||||
</span>
|
||||
) : message.decision === "clarify" ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-border-accent/40 bg-accent-soft px-2.5 py-0.5 text-[0.65rem] font-extrabold text-accent-primary">
|
||||
<Info className="h-3 w-3" />
|
||||
CẦN LÀM RÕ CÂU HỎI
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-status-warning/30 bg-status-warning-bg px-2.5 py-0.5 text-[0.65rem] font-extrabold text-status-warning">
|
||||
<Info className="h-3 w-3" />
|
||||
THÔNG TIN TRA CỨU MỞ RỘNG
|
||||
Có căn cứ Dược thư
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* How the answer was produced from the retrieved evidence — only
|
||||
meaningful for a real answerable turn (rag/answer.py's two
|
||||
operating modes: LLM paraphrase vs. verbatim quote). A clarify
|
||||
or verify_pdf turn is neither, so it gets no source-mode pill. */}
|
||||
{message.decision === "answerable" && message.grounded !== false && message.generated !== undefined && (
|
||||
<span
|
||||
title={
|
||||
message.generated
|
||||
? "Câu trả lời do LLM diễn giải lại từ chuyên luận gốc, đã qua kiểm tra grounding + entailment."
|
||||
: "Trích dẫn nguyên văn từ chuyên luận gốc, không qua diễn giải của LLM."
|
||||
}
|
||||
className="inline-flex items-center gap-1 rounded-full border border-border-subtle bg-surface-elevated px-2.5 py-0.5 text-[0.65rem] font-bold text-txt-secondary"
|
||||
>
|
||||
{message.generated ? (
|
||||
<>
|
||||
<Sparkles className="h-3 w-3 text-accent-primary" />
|
||||
AI diễn giải, đã kiểm chứng
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BookOpen className="h-3 w-3 text-accent-primary" />
|
||||
Trích dẫn nguyên văn
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<time className="text-[0.68rem] text-txt-muted hidden sm:inline">
|
||||
{new Date(message.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
||||
</time>
|
||||
</div>
|
||||
<time className="hidden text-[0.68rem] text-txt-muted sm:inline">
|
||||
{new Date(message.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
||||
</time>
|
||||
</header>
|
||||
|
||||
{/* Document Body */}
|
||||
<div className="p-4 sm:p-5 medical-document-body">
|
||||
{renderStructuredContent(message.content, message.citations)}
|
||||
<div className={cn(
|
||||
"medical-document-body rounded-2xl px-4 py-3.5 sm:px-5",
|
||||
message.grounded === false ? "bg-status-warning-bg/25" : "bg-surface"
|
||||
)}>
|
||||
{message.blocks?.length
|
||||
? renderAnswerBlocks()
|
||||
: renderStructuredContent(message.content, message.citations)}
|
||||
</div>
|
||||
|
||||
{/* Quick-reply chips — only for a clarify turn the model gave a few
|
||||
natural discrete answers to; free text always still works. */}
|
||||
{message.quickReplies && message.quickReplies.length > 0 && (
|
||||
{onQuickReply && message.quickReplies && message.quickReplies.length > 0 && (
|
||||
<div className="px-4 pb-3 flex flex-wrap gap-2">
|
||||
{message.quickReplies.map((reply, idx) => (
|
||||
<button
|
||||
@@ -273,27 +286,7 @@ export function ChatBubble({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Disclaimer Section inside document */}
|
||||
{message.disclaimer && (
|
||||
<div className="mx-4 mb-3 rounded-xl border border-border-subtle bg-surface-elevated/50 p-2.5 text-[0.72rem] text-txt-muted flex items-start gap-2">
|
||||
<Info className="h-3.5 w-3.5 text-accent-primary shrink-0 mt-0.5" />
|
||||
<span>{message.disclaimer}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Document Footer & Actions */}
|
||||
<footer className="flex flex-wrap items-center justify-between gap-3 border-t border-border-subtle bg-surface-elevated/40 px-4 py-2.5 rounded-b-2xl text-xs text-txt-muted">
|
||||
<div className="flex items-center gap-2">
|
||||
{message.citations && message.citations.length > 0 && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<BookOpen className="h-3.5 w-3.5 text-accent-primary" />
|
||||
<span className="font-semibold text-txt-secondary text-[0.72rem]">
|
||||
{message.citations.length} Nguồn trích dẫn chính thức
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<footer className="mt-1 flex justify-end gap-1 text-xs text-txt-muted">
|
||||
<div className="flex items-center gap-2">
|
||||
{onRetry && (
|
||||
<button
|
||||
|
||||
@@ -40,6 +40,10 @@ const SECTION_LABELS: Record<string, string> = {
|
||||
thong_tin_quy_che: "Thông tin quy chế",
|
||||
};
|
||||
|
||||
export function citationSectionLabel(sectionType: string): string {
|
||||
return SECTION_LABELS[sectionType] ?? (sectionType.replace(/_/g, " ") || "Chuyên luận");
|
||||
}
|
||||
|
||||
export function CitationCard({
|
||||
citation,
|
||||
index,
|
||||
@@ -47,7 +51,7 @@ export function CitationCard({
|
||||
onSelect,
|
||||
className,
|
||||
}: CitationCardProps) {
|
||||
const sectionLabel = SECTION_LABELS[citation.sectionType] ?? citation.sectionType ?? "Chuyên luận";
|
||||
const sectionLabel = citationSectionLabel(citation.sectionType);
|
||||
const pageRangeText = citation.sourcePageRange
|
||||
? `Trang ${citation.sourcePageRange[0]}${
|
||||
citation.sourcePageRange[1] && citation.sourcePageRange[1] !== citation.sourcePageRange[0]
|
||||
|
||||
Reference in New Issue
Block a user