- Fix `simple_test_analysis` template in E2E test setup to align with Orchestrator's data fetch logic.
- Implement and verify additional E2E scenarios:
- Scenario C: Partial Provider Failure (verified error propagation fix in Orchestrator).
- Scenario D: Invalid Symbol input.
- Scenario E: Analysis Module failure.
- Update `WorkflowStateMachine::handle_report_failed` to correctly scope error broadcasting to the specific task instead of failing effectively silently or broadly.
- Update testing strategy documentation to reflect completed Phase 4 testing.
- Skip Scenario B (Orchestrator Restart) as persistence is not yet implemented (decision made to defer persistence).
148 lines
5.8 KiB
TypeScript
148 lines
5.8 KiB
TypeScript
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
|
import { Spinner } from '@/components/ui/spinner';
|
|
import { Button } from '@/components/ui/button';
|
|
import { CheckCircle, XCircle, RotateCw } from 'lucide-react';
|
|
import { formatMs } from '../utils';
|
|
|
|
interface AnalysisRecord {
|
|
type: string;
|
|
name: string;
|
|
status: 'pending' | 'running' | 'done' | 'error';
|
|
start_ts?: string;
|
|
end_ts?: string;
|
|
duration_ms?: number;
|
|
tokens?: {
|
|
prompt_tokens: number;
|
|
completion_tokens: number;
|
|
total_tokens: number;
|
|
};
|
|
error?: string;
|
|
}
|
|
|
|
interface ExecutionDetailsProps {
|
|
financials: any;
|
|
isLoading: boolean;
|
|
error: any;
|
|
analysisRecords: AnalysisRecord[];
|
|
currentAnalysisTask: string | null;
|
|
totalElapsedMs: number;
|
|
retryAnalysis: (type: string) => void;
|
|
}
|
|
|
|
export function ExecutionDetails({
|
|
financials,
|
|
isLoading,
|
|
error,
|
|
analysisRecords,
|
|
currentAnalysisTask,
|
|
totalElapsedMs,
|
|
retryAnalysis,
|
|
}: ExecutionDetailsProps) {
|
|
return (
|
|
<div className="space-y-4">
|
|
<h2 className="text-lg font-medium">执行详情</h2>
|
|
|
|
{/* 执行概况卡片 */}
|
|
{financials && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">执行概况</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* 财务数据状态 */}
|
|
<div>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
{isLoading ? (
|
|
<Spinner className="size-4" />
|
|
) : error ? (
|
|
<XCircle className="size-4 text-red-500" />
|
|
) : (
|
|
<CheckCircle className="size-4 text-green-600" />
|
|
)}
|
|
<span className="font-medium">财务数据</span>
|
|
</div>
|
|
{financials?.meta && (
|
|
<div className="ml-6 text-sm text-muted-foreground space-y-1">
|
|
<div>耗时: {formatMs(financials.meta.elapsed_ms)}</div>
|
|
<div>API调用: {financials.meta.api_calls_total} 次</div>
|
|
<div>开始时间: {financials.meta.started_at}</div>
|
|
{financials.meta.finished_at && (
|
|
<div>结束时间: {financials.meta.finished_at}</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 分析任务状态 */}
|
|
{analysisRecords.length > 0 && (
|
|
<div className="pt-3 border-t">
|
|
<div className="font-medium mb-2">分析任务</div>
|
|
<div className="ml-6 text-sm text-muted-foreground space-y-2">
|
|
{analysisRecords.map((record, idx) => (
|
|
<div key={idx} className="space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
{record.status === 'running' && <Spinner className="size-3" />}
|
|
{record.status === 'done' && <CheckCircle className="size-3 text-green-600" />}
|
|
{record.status === 'error' && <XCircle className="size-3 text-red-500" />}
|
|
<span className="font-medium">{record.name}</span>
|
|
<span className="text-xs text-muted-foreground">
|
|
{record.status === 'running' ? '运行中' : record.status === 'done' ? '已完成' : record.status === 'error' ? '失败' : '待继续'}
|
|
</span>
|
|
{record.status === 'error' && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-6 px-2 text-xs"
|
|
onClick={() => {
|
|
retryAnalysis(record.type);
|
|
}}
|
|
disabled={currentAnalysisTask !== null}
|
|
>
|
|
<RotateCw className="size-3" />
|
|
重试
|
|
</Button>
|
|
)}
|
|
</div>
|
|
{record.duration_ms !== undefined && (
|
|
<div className="ml-5">耗时: {formatMs(record.duration_ms)}</div>
|
|
)}
|
|
{record.tokens && (
|
|
<div className="ml-5">
|
|
Token: {record.tokens.total_tokens}
|
|
(Prompt: {record.tokens.prompt_tokens},
|
|
Completion: {record.tokens.completion_tokens})
|
|
</div>
|
|
)}
|
|
{record.error && (
|
|
<div className="ml-5 text-red-500">错误: {record.error}</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 总体统计 */}
|
|
<div className="pt-3 border-t">
|
|
<div className="font-medium mb-2">总体统计</div>
|
|
<div className="ml-6 text-sm text-muted-foreground space-y-1">
|
|
<div>总耗时: {formatMs(totalElapsedMs)}</div>
|
|
{financials?.meta?.steps && (
|
|
<div>财务数据完成步骤: {(financials.meta.steps as any[]).filter((s: any) => s?.status === 'done').length}/{(financials.meta.steps as any[]).length}</div>
|
|
)}
|
|
{analysisRecords.length > 0 && (
|
|
<>
|
|
<div>分析任务: {analysisRecords.filter(r => r.status === 'done').length}/{analysisRecords.length} 已完成</div>
|
|
<div>总Token消耗: {analysisRecords.reduce((sum, r) => sum + (r.tokens?.total_tokens || 0), 0)}</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|