Fundamental_Analysis/frontend/archive/v1_report/ExecutionDetails.tsx
Lv, Qi 0cb31e363e Refactor E2E tests and improve error handling in Orchestrator
- 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).
2025-11-21 20:44:32 +08:00

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>
);
}