fix: 修复 Flow Monitor 响应内容显示和提供商显示问题 (#75)

- 修复凭证池模式下非流式响应的 content 和 body 字段未保存问题
- 从响应 JSON 中提取 content、tool_calls 和 usage 信息
- 添加 provider_id 字段用于显示实际的提供商名称
- 优化清理完成弹窗样式,使用自定义 Modal 组件
- 美化排序下拉框,使用 Radix UI Select 组件
- 添加详细的调试日志用于追踪 Flow 显示问题
- 修复所有测试代码中缺少 provider_id 字段的问题
This commit is contained in:
Chiron
2026-01-10 22:27:06 +08:00
parent 85bfc56037
commit 3ca4bfe7f6
13 changed files with 431 additions and 68 deletions
+115 -8
View File
@@ -35,6 +35,12 @@ export function CleanupDialog({
const [cleanupType, setCleanupType] = useState<CleanupType>("ByTime");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showSuccess, setShowSuccess] = useState(false);
const [cleanupResult, setCleanupResult] = useState<{
cleaned_count: number;
cleaned_files: number;
freed_bytes: number;
} | null>(null);
// 时间清理选项
const [retentionDays, setRetentionDays] = useState(7);
@@ -105,14 +111,9 @@ export function CleanupDialog({
const result = await flowMonitorApi.cleanupFlows(request);
// 显示清理结果
const sizeText = formatBytes(result.freed_bytes);
alert(
`清理完成!\n删除了 ${result.cleaned_count} 条记录\n清理了 ${result.cleaned_files} 个文件\n释放了 ${sizeText} 空间`,
);
onSuccess();
onClose();
// 保存清理结果并显示成功提示
setCleanupResult(result);
setShowSuccess(true);
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
} finally {
@@ -120,6 +121,13 @@ export function CleanupDialog({
}
};
const handleSuccessClose = () => {
setShowSuccess(false);
setCleanupResult(null);
onSuccess();
onClose();
};
const formatBytes = (bytes: number): string => {
if (bytes === 0) return "0 B";
const k = 1024;
@@ -149,6 +157,105 @@ export function CleanupDialog({
"IFlow",
];
// 如果显示成功提示,渲染成功对话框
if (showSuccess && cleanupResult) {
return (
<Modal isOpen={isOpen} onClose={handleSuccessClose} maxWidth="max-w-md">
<div className="px-6 py-8">
{/* 成功图标 */}
<div className="flex justify-center mb-6">
<div className="relative">
<div className="w-20 h-20 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center">
<svg
className="w-10 h-10 text-green-600 dark:text-green-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
</div>
{/* 动画圆环 */}
<div className="absolute inset-0 w-20 h-20 border-4 border-green-200 dark:border-green-800 rounded-full animate-ping opacity-75"></div>
</div>
</div>
{/* 标题 */}
<h3 className="text-2xl font-bold text-center mb-2">清理完成!</h3>
<p className="text-center text-muted-foreground mb-6">
已成功清理日志数据
</p>
{/* 清理统计 */}
<div className="space-y-3 mb-6">
<div className="flex items-center justify-between p-4 bg-muted/50 rounded-lg">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center">
<Hash className="w-5 h-5 text-blue-600 dark:text-blue-400" />
</div>
<span className="text-sm text-muted-foreground">删除记录</span>
</div>
<span className="text-lg font-semibold">
{cleanupResult.cleaned_count} 条
</span>
</div>
<div className="flex items-center justify-between p-4 bg-muted/50 rounded-lg">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center">
<svg
className="w-5 h-5 text-purple-600 dark:text-purple-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
</div>
<span className="text-sm text-muted-foreground">清理文件</span>
</div>
<span className="text-lg font-semibold">
{cleanupResult.cleaned_files} 个
</span>
</div>
<div className="flex items-center justify-between p-4 bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 rounded-lg border border-green-200 dark:border-green-800">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-green-100 dark:bg-green-900/50 rounded-lg flex items-center justify-center">
<HardDrive className="w-5 h-5 text-green-600 dark:text-green-400" />
</div>
<span className="text-sm font-medium text-green-900 dark:text-green-100">
释放空间
</span>
</div>
<span className="text-lg font-bold text-green-600 dark:text-green-400">
{formatBytes(cleanupResult.freed_bytes)}
</span>
</div>
</div>
{/* 确认按钮 */}
<button
onClick={handleSuccessClose}
className="w-full py-3 bg-primary hover:bg-primary/90 text-white font-medium rounded-lg transition-colors"
>
完成
</button>
</div>
</Modal>
);
}
return (
<Modal isOpen={isOpen} onClose={onClose} maxWidth="max-w-2xl">
{/* 标题 */}
+4 -1
View File
@@ -383,7 +383,10 @@ function FlowDetailHeader({
<div className="rounded-lg border bg-card p-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<InfoItem label="模型" value={flow.request.model} />
<InfoItem label="提供商" value={flow.metadata.provider} />
<InfoItem
label="提供商"
value={flow.metadata.provider_id || flow.metadata.provider}
/>
<InfoItem label="类型" value={formatFlowType(flow.flow_type)} />
<InfoItem
label="创建时间"
+53 -8
View File
@@ -1,4 +1,5 @@
import React, { useState, useEffect, useCallback } from "react";
import * as Select from "@radix-ui/react-select";
import {
CheckCircle2,
XCircle,
@@ -21,6 +22,7 @@ import {
Activity,
Bell,
BellOff,
Check,
} from "lucide-react";
import {
flowMonitorApi,
@@ -570,16 +572,59 @@ export function FlowList({
)}
</button>
)}
<select
className="rounded border bg-background px-2 py-1 text-sm"
<Select.Root
value={sortBy}
onChange={(e) => setSortBy(e.target.value as FlowSortBy)}
onValueChange={(value) => setSortBy(value as FlowSortBy)}
>
<option value="created_at">按时间</option>
<option value="duration">按耗时</option>
<option value="total_tokens">按 Token</option>
<option value="model">按模型</option>
</select>
<Select.Trigger className="inline-flex min-w-[120px] items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-1.5 text-sm shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-ring">
<Select.Value />
<Select.Icon>
<ChevronDown className="h-4 w-4 opacity-50" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="relative z-50 min-w-[120px] overflow-hidden rounded-md border border-border bg-white dark:bg-gray-900 text-foreground shadow-lg animate-in fade-in-80 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2">
<Select.Viewport className="p-1 bg-white dark:bg-gray-900">
<Select.Item
value="created_at"
className="relative flex cursor-pointer select-none items-center rounded-sm px-8 py-2 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
>
<Select.ItemIndicator className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<Check className="h-4 w-4" />
</Select.ItemIndicator>
<Select.ItemText>按时间</Select.ItemText>
</Select.Item>
<Select.Item
value="duration"
className="relative flex cursor-pointer select-none items-center rounded-sm px-8 py-2 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
>
<Select.ItemIndicator className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<Check className="h-4 w-4" />
</Select.ItemIndicator>
<Select.ItemText>按耗时</Select.ItemText>
</Select.Item>
<Select.Item
value="total_tokens"
className="relative flex cursor-pointer select-none items-center rounded-sm px-8 py-2 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
>
<Select.ItemIndicator className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<Check className="h-4 w-4" />
</Select.ItemIndicator>
<Select.ItemText>按 Token</Select.ItemText>
</Select.Item>
<Select.Item
value="model"
className="relative flex cursor-pointer select-none items-center rounded-sm px-8 py-2 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
>
<Select.ItemIndicator className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<Check className="h-4 w-4" />
</Select.ItemIndicator>
<Select.ItemText>按模型</Select.ItemText>
</Select.Item>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
<button
onClick={() => setSortDesc(!sortDesc)}
className="rounded border px-2 py-1 text-sm hover:bg-muted"
+1
View File
@@ -333,6 +333,7 @@ export interface RoutingInfo {
*/
export interface FlowMetadata {
provider: ProviderType;
provider_id?: string; // 实际的 provider ID(如 "deepseek", "moonshot" 等)
credential_id?: string;
credential_name?: string;
retry_count: number;