"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" 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 } } function CitationList({ hits }: { hits: KnowledgeRetrieveHit[] }) { const citations = hits.filter((item) => item.isCitation) if (citations.length === 0) { return
暂无引用来源
} return (
{citations.map((item) => (
{getHitSourceLabel(item)} Chunk #{item.chunkNo}
{item.sectionPath || item.title || "未记录章节"}
{item.snippet || "-"}
))}
) } export function RetrieveLogDetailDrawer({ open, retrieveLogId, onOpenChange, }: RetrieveLogDetailDrawerProps) { 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 : "加载检索日志详情失败") } finally { setLoading(false) } }, [retrieveLogId]) useEffect(() => { if (open && retrieveLogId) { void loadDetail() } }, [open, retrieveLogId, loadDetail]) const traceData = useMemo(() => safeParseJSON(detail?.log.traceData ?? ""), [detail?.log.traceData]) if (!open) { return null } return ( 检索日志详情 {detail?.log.question || (loading ? "加载中..." : "未找到检索日志")} {!detail ? (
{loading ? "正在加载详情..." : "暂无详情数据"}
) : (

请求信息

知识库
{detail.log.knowledgeBaseName || `#${detail.log.knowledgeBaseId}`}
创建时间
{formatDateTime(detail.log.createdAt)}
渠道 / 场景
{detail.log.channelName} / {detail.log.sceneName}
Request ID
{detail.log.requestId || "-"}
原始问题
{detail.log.question || "-"}
改写问题
{detail.log.rewriteQuestion || "-"}
回答内容
{detail.log.answer || "-"}

检索策略

结果概览

引用来源

命中详情

{detail.hits.length} 条
{detail.hits.map((item) => (
#{item.rankNo} {getHitSourceLabel(item)} {item.usedInAnswer ? "已入上下文" : "未入上下文"} {item.isCitation ? 引用 : null}
章节:{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) { if (item.faqQuestion) { return item.faqQuestion } if (item.documentTitle) { return item.documentTitle } if (item.faqId > 0) { return `FAQ #${item.faqId}` } return `文档 #${item.documentId}` } function Metric({ label, value, mono = false, }: { label: string value: string | number mono?: boolean }) { return (
{label}
{String(value || value === 0 ? value : "-")}
) }