Remove corpus counts from chat chrome

This commit is contained in:
2026-08-10 17:26:58 +07:00
parent 46469468bb
commit 97cb6d16f4
31 changed files with 2192 additions and 424 deletions
+45 -16
View File
@@ -19,7 +19,10 @@ import { cn } from "@duoc-thu/ui";
interface ChatPanelProps {
sessionId: string;
messages: ChatMessage[];
onMessagesChange: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
initialQuery?: string;
initialQueryToken?: number;
onCitationClick?: (citation: Citation, index: number, allCitations: Citation[]) => void;
onCitationsLoaded?: (citations: Citation[]) => void;
activeCitationIndex?: number | null;
@@ -28,42 +31,45 @@ interface ChatPanelProps {
const STARTER_QUESTIONS = [
{
category: "Liều Dùng Lâm Sàng",
query: "Liều dùng Paracetamol người lớn và trẻ em theo cân nặng là bao nhiêu?",
category: "Chỉ Định",
query: "Levetiracetam được chỉ định trong những trường hợp nào?",
icon: Pill,
},
{
category: "Chống Chỉ Định",
query: "Chống chỉ định tuyệt đối và tương đối của Amoxicillin là gì?",
query: "Chống chỉ định của Metformin là gì?",
icon: Stethoscope,
},
{
category: "Tương Tác Thuốc",
query: "Tương tác giữa Metformin và thuốc cản quang chứa iốt xử trí thế nào?",
category: "ADR Theo Tần Suất",
query: "Tác dụng không mong muốn của Zolpidem là gì?",
icon: Activity,
},
{
category: "Thận Trọng & ADR",
query: "Thận trọng khi dùng Aspirin cho bệnh nhân có tiền sử loét dạ dày?",
category: "Thời Kỳ Mang Thai",
query: "Có thể dùng Fluoxetin trong thời kỳ mang thai không?",
icon: Zap,
},
];
export function ChatPanel({
sessionId,
messages,
onMessagesChange: setMessages,
initialQuery,
initialQueryToken,
onCitationClick,
onCitationsLoaded,
activeCitationIndex = null,
className,
}: ChatPanelProps) {
const { resolvedTheme } = useTheme();
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const abortControllerRef = useRef<AbortController | null>(null);
const initialQuerySentRef = useRef<string | undefined>(undefined);
const initialQuerySentRef = useRef<number | undefined>(undefined);
const stopRequestedRef = useRef(false);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -87,8 +93,12 @@ export function ChatPanel({
setMessages((prev) => [...prev, userMsg]);
setIsLoading(true);
stopRequestedRef.current = false;
abortControllerRef.current = new AbortController();
const timeoutId = window.setTimeout(() => {
abortControllerRef.current?.abort();
}, 25_000);
try {
const res = await fetch("/api/chat", {
@@ -115,10 +125,16 @@ export function ChatPanel({
}
} catch (err: any) {
if (err.name === "AbortError") {
setError(
stopRequestedRef.current
? "Đã dừng chờ trên giao diện. Tác vụ đang chạy có thể cần vài giây để kết thúc an toàn."
: "Yêu cầu vượt quá 25 giây và đã được dừng. Vui lòng thử lại với câu hỏi cụ thể hơn."
);
return;
}
setError("Không thể kết nối đến máy chủ AI Service. Vui lòng kiểm tra lại dịch vụ backend.");
} finally {
window.clearTimeout(timeoutId);
setIsLoading(false);
abortControllerRef.current = null;
}
@@ -126,12 +142,15 @@ export function ChatPanel({
const handleStop = () => {
if (abortControllerRef.current) {
stopRequestedRef.current = true;
abortControllerRef.current.abort();
setIsLoading(false);
abortControllerRef.current = null;
}
};
useEffect(() => {
return () => abortControllerRef.current?.abort();
}, []);
useEffect(() => {
// Guard against firing twice for the same query: React 18 Strict Mode
// (dev only) runs this effect setup twice on mount, and with no guard
@@ -140,12 +159,16 @@ export function ChatPanel({
// muốn của Aspirin" turns in the trace). The ref persists across the
// Strict Mode replay, so the second invocation for the same
// `initialQuery` is a no-op; a genuinely new query still sends once.
if (initialQuery && initialQuerySentRef.current !== initialQuery) {
initialQuerySentRef.current = initialQuery;
if (
initialQuery &&
initialQueryToken !== undefined &&
initialQuerySentRef.current !== initialQueryToken
) {
initialQuerySentRef.current = initialQueryToken;
handleSendMessage(initialQuery);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialQuery]);
}, [initialQuery, initialQueryToken]);
// Empty state renderer per theme
const renderEmptyState = () => {
@@ -165,7 +188,7 @@ export function ChatPanel({
Tra Cứu Dược Thư Quốc Gia Việt Nam
</h2>
<p className="text-xs sm:text-sm text-txt-secondary mt-1.5 leading-relaxed max-w-xl mx-auto">
Hệ thống AI y tế tra cứu chính xác theo 684 chuyên luận chính thức. Mọi thông tin đu đưc xác thực suy luận (Entailment Verification) kèm trích dẫn trang in PDF.
Tra cứu 684 chuyên luận Dược thư Quốc gia Việt Nam 2018 với căn cứ theo trang in. Khi cần, bác thể tiếp tục trao đi đ làm dữ kiện đi chiếu với bối cảnh lâm sàng.
</p>
</div>
@@ -321,7 +344,13 @@ export function ChatPanel({
}
activeCitationIndex={activeCitationIndex}
onRetry={retryQuery ? () => handleSendMessage(retryQuery) : undefined}
onQuickReply={(text) => handleSendMessage(text)}
onQuickReply={
msgIdx === messages.length - 1 &&
msg.decision === "clarify" &&
!isLoading
? (text) => handleSendMessage(text)
: undefined
}
/>
);
})