"use client" import { useCallback, useEffect, useMemo, useState } from "react" import { toast } from "sonner" import { fetchKnowledgeRetrieveLog, type KnowledgeRetrieveHit, type KnowledgeRetrieveLogDetail, } from "@/lib/api/admin" import { formatDateTime } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer" import { ScrollArea } from "@/components/ui/scroll-area" import { Separator } from "@/components/ui/separator" import { useI18n } from "@/i18n/provider" import { getKnowledgeAnswerStatusLabel, getKnowledgeChunkProviderLabel, getKnowledgeRetrieveChannelLabel, getKnowledgeRetrieveSceneLabel, } from "@/lib/knowledge-i18n" type RetrieveLogDetailDrawerProps = { open: boolean retrieveLogId: number | null onOpenChange: (open: boolean) => void } function safeParseJSON(value: string) { if (!value) { return null } try { return JSON.parse(value) as Record } catch { return null } } type TFunction = (key: string, values?: Record) => string function CitationList({ hits, t }: { hits: KnowledgeRetrieveHit[]; t: TFunction }) { const citations = hits.filter((item) => item.isCitation) if (citations.length === 0) { return
{t("knowledge.noCitations")}
} return (
{citations.map((item) => (
{getHitSourceLabel(item, t)} Chunk #{item.chunkNo}
{item.sectionPath || item.title || t("knowledge.unrecordedSection")}
{item.snippet || "-"}
))}
) } export function RetrieveLogDetailDrawer({ open, retrieveLogId, onOpenChange, }: RetrieveLogDetailDrawerProps) { const t = useI18n() const [loading, setLoading] = useState(false) const [detail, setDetail] = useState(null) const loadDetail = useCallback(async () => { if (!retrieveLogId) { setDetail(null) return } setLoading(true) try { const data = await fetchKnowledgeRetrieveLog(retrieveLogId) setDetail(data) } catch (error) { toast.error(error instanceof Error ? error.message : t("knowledge.loadRetrieveLogDetailFailed")) } finally { setLoading(false) } }, [retrieveLogId, t]) useEffect(() => { if (open && retrieveLogId) { void loadDetail() } }, [open, retrieveLogId, loadDetail]) const traceData = useMemo(() => safeParseJSON(detail?.log.traceData ?? ""), [detail?.log.traceData]) if (!open) { return null } return ( {t("knowledge.retrieveLogDetail")} {detail?.log.question || (loading ? t("knowledge.loading") : t("knowledge.retrieveLogNotFound"))} {!detail ? (
{loading ? t("knowledge.loadingDetail") : t("knowledge.emptyDetail")}
) : (

{t("knowledge.requestInfo")}

{t("knowledge.title")}
{detail.log.knowledgeBaseName || `#${detail.log.knowledgeBaseId}`}
{t("knowledge.createdAt")}
{formatDateTime(detail.log.createdAt)}
{t("knowledge.channelScene")}
{getKnowledgeRetrieveChannelLabel(detail.log.channel, detail.log.channelName, t)} /{" "} {getKnowledgeRetrieveSceneLabel(detail.log.scene, detail.log.sceneName, t)}
Request ID
{detail.log.requestId || "-"}
{t("knowledge.originalQuestion")}
{detail.log.question || "-"}
{t("knowledge.rewriteQuestion")}
{detail.log.rewriteQuestion || "-"}
{t("knowledge.answerContent")}
{detail.log.answer || "-"}

{t("knowledge.retrieveStrategy")}

{t("knowledge.resultSummary")}

{t("knowledge.citationSources")}

{t("knowledge.hitDetails")}

{t("knowledge.itemsCount", { count: detail.hits.length })}
{detail.hits.map((item) => (
#{item.rankNo} {getHitSourceLabel(item, t)} {item.usedInAnswer ? t("knowledge.usedInContext") : t("knowledge.notUsedInContext")} {item.isCitation ? {t("knowledge.citations")} : null}
{t("knowledge.section", { section: item.sectionPath || item.title || "-" })} Chunk #{item.chunkNo} Provider:{item.provider || "-"} Score:{item.score.toFixed(4)} Rerank:{item.rerankScore ? item.rerankScore.toFixed(4) : "-"}
{item.snippet || "-"}
))}

TraceData

                    {traceData ? JSON.stringify(traceData, null, 2) : detail.log.traceData || "-"}
                  
)}
) } function getHitSourceLabel(item: KnowledgeRetrieveHit, t: TFunction) { if (item.faqQuestion) { return item.faqQuestion } if (item.documentTitle) { return item.documentTitle } if (item.faqId > 0) { return `FAQ #${item.faqId}` } return `${t("knowledge.document")} #${item.documentId}` } function Metric({ label, value, mono = false, }: { label: string value: string | number mono?: boolean }) { return (
{label}
{String(value || value === 0 ? value : "-")}
) }