Add analysis feature

This commit is contained in:
Xin Wang
2026-08-07 10:59:47 +08:00
parent 485ad623d4
commit 56a32e81ca
23 changed files with 768 additions and 223 deletions
+106 -3
View File
@@ -425,6 +425,35 @@ export function HistoryPage() {
}
}, []);
useEffect(() => {
if (
!dialogOpen ||
!detail ||
!["pending", "processing"].includes(detail.analysis.status)
) {
return;
}
let stopped = false;
let loadingLatest = false;
const timer = window.setInterval(() => {
if (loadingLatest) return;
loadingLatest = true;
void conversationsApi
.get(detail.id)
.then((latest) => {
if (!stopped) setDetail(latest);
})
.catch(() => undefined)
.finally(() => {
loadingLatest = false;
});
}, 2000);
return () => {
stopped = true;
window.clearInterval(timer);
};
}, [detail, dialogOpen]);
const remove = useCallback(
async (conversation: Conversation) => {
const label = conversation.assistantName || "调试会话";
@@ -729,7 +758,9 @@ export function HistoryPage() {
{detail && detailTab === "session" && (
<SessionInfoPanel detail={detail} />
)}
{detail && detailTab === "analysis" && <AnalysisPanel />}
{detail && detailTab === "analysis" && (
<AnalysisPanel analysis={detail.analysis} />
)}
</div>
</aside>
@@ -879,7 +910,79 @@ function SessionInfoPanel({ detail }: { detail: ConversationDetail }) {
);
}
function AnalysisPanel() {
function analysisValue(value: unknown): string {
if (value === null || value === undefined || value === "") return "未提取到";
if (typeof value === "boolean") return value ? "是" : "否";
if (typeof value === "object") return JSON.stringify(value);
return String(value);
}
function AnalysisPanel({
analysis,
}: {
analysis: ConversationDetail["analysis"];
}) {
if (analysis.status === "pending" || analysis.status === "processing") {
return (
<div className="flex min-h-48 items-center justify-center gap-2 text-sm text-muted-foreground">
<Loader2 size={16} className="animate-spin" />
正在分析通话内容
</div>
);
}
if (analysis.status === "failed") {
return (
<div className="rounded-2xl border border-destructive/25 bg-destructive/5 px-4 py-4">
<div className="text-sm font-medium text-destructive">分析失败</div>
<p className="mt-1.5 break-words text-xs leading-5 text-muted-foreground">
{analysis.error || "分析服务没有返回有效结果。"}
</p>
</div>
);
}
if (analysis.status === "completed") {
return (
<div className="space-y-4">
<div>
<div className="caption-label text-muted-soft">关键信息</div>
{analysis.completedAt && (
<div className="mt-1 text-xs text-muted-foreground">
完成于 {formatDate(analysis.completedAt)}
</div>
)}
</div>
<div className="divide-y divide-hairline overflow-hidden rounded-2xl border border-hairline bg-background">
{analysis.fields.map((field) => (
<div
key={field.name}
className="grid grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] gap-4 px-4 py-3"
>
<div className="min-w-0">
<div className="break-all text-sm font-medium text-foreground">
{field.name}
</div>
<div className="mt-0.5 text-[11px] text-muted-soft">
{field.type}
</div>
</div>
<div
className={cn(
"break-words text-sm text-foreground",
(field.value === null || field.value === undefined) &&
"text-muted-soft",
)}
>
{analysisValue(field.value)}
</div>
</div>
))}
</div>
</div>
);
}
return (
<div className="flex min-h-48 flex-col items-center justify-center rounded-2xl border border-dashed border-hairline-strong bg-canvas-soft px-5 py-10 text-center">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-surface-strong text-foreground">
@@ -889,7 +992,7 @@ function AnalysisPanel() {
暂无分析结果
</div>
<p className="mt-1.5 max-w-xs text-xs leading-5 text-muted-foreground">
通话后分析接入后,将在这里展示从对话中提取的关键信息字段。
该会话未开启通话后分析,或没有可分析的对话内容。
</p>
</div>
);