"use client" import { useMemo, useState } from "react" import { SearchIcon } from "lucide-react" import { fetchKnowledgeRetrieveLogs, type KnowledgeRetrieveLog, } from "@/lib/api/admin" import { KnowledgeAnswerStatus, KnowledgeRetrieveChannel, KnowledgeRetrieveScene, } from "@/lib/generated/enums" import { getKnowledgeAnswerStatusLabel, getKnowledgeChunkProviderLabel, getKnowledgeRetrieveChannelLabel, getKnowledgeRetrieveSceneLabel, } from "@/lib/knowledge-i18n" import { formatDateTime } from "@/lib/utils" import { useI18n } from "@/i18n/provider" import { DashboardListPage } from "@/components/dashboard/list" import { Badge } from "@/components/ui/badge" import { RetrieveLogDetailDrawer } from "./retrieve-log-detail" type RetrieveLogListProps = { knowledgeBaseId: number | null } type TFunction = (key: string, values?: Record) => string function getChannelOptions(t: TFunction) { return [ { value: "all", label: t("knowledge.allChannels") }, { value: KnowledgeRetrieveChannel.IM, label: t("knowledge.channelIM") }, { value: KnowledgeRetrieveChannel.AgentAssist, label: t("knowledge.channelAgentAssist") }, { value: KnowledgeRetrieveChannel.API, label: t("knowledge.channelAPI") }, { value: KnowledgeRetrieveChannel.Debug, label: t("knowledge.channelDebug") }, ] } function getSceneOptions(t: TFunction) { return [ { value: "all", label: t("knowledge.allScenes") }, { value: KnowledgeRetrieveScene.FirstResponse, label: t("knowledge.sceneFirstResponse") }, { value: KnowledgeRetrieveScene.Assist, label: t("knowledge.sceneAssist") }, { value: KnowledgeRetrieveScene.QA, label: t("knowledge.sceneQA") }, ] } function getAnswerStatusOptions(t: TFunction) { return [ { value: "all", label: t("knowledge.allAnswerStatus") }, { value: String(KnowledgeAnswerStatus.Normal), label: t("knowledge.answerNormal") }, { value: String(KnowledgeAnswerStatus.NoAnswer), label: t("knowledge.answerNoAnswer") }, { value: String(KnowledgeAnswerStatus.Fallback), label: t("knowledge.answerFallback") }, { value: String(KnowledgeAnswerStatus.Blocked), label: t("knowledge.answerBlocked") }, ] } function getProviderOptions(t: TFunction) { return [ { value: "all", label: t("knowledge.allChunkProviders") }, { value: "fixed", label: t("knowledge.chunkFixed") }, { value: "structured", label: t("knowledge.chunkStructured") }, { value: "faq", label: t("knowledge.chunkFAQ") }, { value: "semantic", label: t("knowledge.chunkSemantic") }, ] } function getRerankOptions(t: TFunction) { return [ { value: "all", label: t("knowledge.allRerank") }, { value: "1", label: t("knowledge.rerankEnabled") }, { value: "0", label: t("knowledge.rerankDisabled") }, ] } function channelLabel(value: string, fallback: string, t: TFunction) { return getKnowledgeRetrieveChannelLabel(value, fallback, t) } function sceneLabel(value: string, fallback: string, t: TFunction) { return getKnowledgeRetrieveSceneLabel(value, fallback, t) } function answerStatusLabel(value: number, fallback: string, t: TFunction) { return getKnowledgeAnswerStatusLabel(value, fallback, t) } function providerLabel(value: string, t: TFunction) { return getKnowledgeChunkProviderLabel(value, t) } function getAnswerStatusVariant(status: number): "default" | "secondary" | "outline" | "destructive" { switch (status) { case 1: return "default" case 2: return "secondary" case 3: return "outline" case 4: return "destructive" default: return "outline" } } export function RetrieveLogList({ knowledgeBaseId, }: RetrieveLogListProps) { const t = useI18n() const [detailOpen, setDetailOpen] = useState(false) const [selectedLogId, setSelectedLogId] = useState(null) const [selectedKnowledgeBaseId, setSelectedKnowledgeBaseId] = useState(null) const channelOptions = useMemo(() => getChannelOptions(t), [t]) const sceneOptions = useMemo(() => getSceneOptions(t), [t]) const answerStatusOptions = useMemo(() => getAnswerStatusOptions(t), [t]) const providerOptions = useMemo(() => getProviderOptions(t), [t]) const rerankOptions = useMemo(() => getRerankOptions(t), [t]) function handleOpenDetail(logId: number) { setSelectedLogId(logId) setSelectedKnowledgeBaseId(knowledgeBaseId) setDetailOpen(true) } if (!knowledgeBaseId) { return (
{t("knowledge.selectBaseForLogs")}
) } return ( <>
layout="fragment" filters={[ { name: "question", label: t("knowledge.filterQuestion"), placeholder: t("knowledge.filterQuestion"), defaultValue: "", trim: true, className: "min-w-0 xl:min-w-[280px]", inputClassName: "pl-9", icon: , }, { name: "channel", label: t("knowledge.selectChannel"), type: "select", defaultValue: "all", allValue: "all", options: channelOptions, placeholder: t("knowledge.selectChannel"), }, { name: "scene", label: t("knowledge.selectScene"), type: "select", defaultValue: "all", allValue: "all", options: sceneOptions, placeholder: t("knowledge.selectScene"), }, { name: "answerStatus", label: t("knowledge.answerStatus"), type: "select", defaultValue: "all", allValue: "all", valueType: "number", options: answerStatusOptions, placeholder: t("knowledge.answerStatus"), }, { name: "chunkProvider", label: t("knowledge.chunkStrategy"), type: "select", defaultValue: "all", allValue: "all", options: providerOptions, placeholder: t("knowledge.chunkStrategy"), }, { name: "rerankEnabled", label: "Rerank", type: "select", defaultValue: "all", allValue: "all", valueType: "number", options: rerankOptions, placeholder: "Rerank", }, ]} fetchList={(query) => fetchKnowledgeRetrieveLogs({ knowledgeBaseId, ...query })} reloadKey={knowledgeBaseId} getItemId={(item) => item.id} getRowClassName={() => "cursor-pointer"} onRowClick={(item) => handleOpenDetail(item.id)} columns={[ { key: "time", label: t("knowledge.time"), className: "w-42 text-xs text-muted-foreground", render: (item) => formatDateTime(item.createdAt), }, { key: "question", label: t("knowledge.question"), render: (item) => (
{item.question || "-"}
{channelLabel(item.channel, item.channelName, t)} {sceneLabel(item.scene, item.sceneName, t)} {item.knowledgeBaseName ? {item.knowledgeBaseName} : null}
), }, { key: "answerStatus", label: t("knowledge.answerStatus"), className: "w-28", render: (item) => ( {answerStatusLabel(item.answerStatus, item.answerStatusName, t)} ), }, { key: "hitCount", label: t("knowledge.hitCount"), className: "w-24 text-right", render: (item) => item.hitCount, }, { key: "topScore", label: "TopScore", className: "w-24 text-right font-mono text-xs", render: (item) => item.topScore.toFixed(4), }, { key: "provider", label: "Provider", className: "w-28", render: (item) => item.chunkProvider ? providerLabel(item.chunkProvider, t) : "-", }, { key: "rerank", label: "Rerank", className: "w-24", render: (item) => item.rerankEnabled ? `${t("knowledge.yes")} (${item.rerankLimit})` : t("knowledge.no"), }, { key: "citations", label: t("knowledge.citations"), className: "w-24 text-right", render: (item) => item.citationCount, }, { key: "duration", label: t("knowledge.duration"), className: "w-28 text-right", render: (item) => `${item.latencyMs} ms`, }, ]} labels={{ query: t("knowledge.filter"), loading: t("knowledge.loadingRetrieveLogs"), empty: t("knowledge.emptyRetrieveLogs"), loadFailed: t("knowledge.loadRetrieveLogsFailed"), }} />
{ setDetailOpen(open) if (!open) { setSelectedLogId(null) setSelectedKnowledgeBaseId(null) } }} /> ) }