Fundamental_Analysis/frontend/archive/v1_report/AnalysisContent.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

130 lines
4.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Spinner } from '@/components/ui/spinner';
import { Button } from '@/components/ui/button';
import { CheckCircle, XCircle, RotateCw } from 'lucide-react';
import { normalizeMarkdown, removeTitleFromContent } from '../utils';
interface AnalysisContentProps {
analysisType: string;
state: {
content: string;
loading: boolean;
error: string | null;
};
financials: any;
analysisConfig: any;
retryAnalysis: (type: string) => void;
currentAnalysisTask: string | null;
}
export function AnalysisContent({
analysisType,
state,
financials,
analysisConfig,
retryAnalysis,
currentAnalysisTask,
}: AnalysisContentProps) {
const analysisName = analysisType === 'company_profile'
? '公司简介'
: (analysisConfig?.analysis_modules?.[analysisType]?.name || analysisType);
const modelName = analysisConfig?.analysis_modules?.[analysisType]?.model;
// Process content
const contentWithoutTitle = removeTitleFromContent(state.content, analysisName);
const normalizedContent = normalizeMarkdown(contentWithoutTitle);
const isGenerating = state.loading;
return (
<div className="space-y-4 relative">
<h2 className="text-lg font-medium">{analysisName} {modelName || 'AI'}</h2>
{!financials && (
<p className="text-sm text-muted-foreground">...</p>
)}
{financials && (
<>
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3 text-sm">
{state.loading ? (
<Spinner className="size-4" />
) : state.error ? (
<XCircle className="size-4 text-red-500" />
) : state.content ? (
<CheckCircle className="size-4 text-green-600" />
) : null}
<div className="text-muted-foreground">
{state.loading
? `正在生成${analysisName}...`
: state.error
? '生成失败'
: state.content
? '生成完成'
: '待开始'}
</div>
</div>
{/* 重新生成按钮 */}
{!state.loading && (
<Button
variant="ghost"
size="sm"
onClick={() => retryAnalysis(analysisType)}
disabled={currentAnalysisTask !== null || isGenerating}
>
<RotateCw className={`size-4 ${isGenerating ? 'animate-spin' : ''}`} />
{isGenerating ? '生成中...' : '重新生成分析'}
</Button>
)}
</div>
{state.error && (
<p className="text-red-500">: {state.error}</p>
)}
{/* Content Area with Overlay */}
<div className="relative min-h-[200px]">
{/* Overlay when generating */}
{isGenerating && (
<div className="absolute inset-0 bg-background/80 backdrop-blur-sm z-10 flex flex-col items-center justify-center space-y-4 rounded-lg border">
<Spinner className="size-8 text-primary" />
<p className="text-sm font-medium text-muted-foreground animate-pulse">
...
</p>
</div>
)}
{/* Existing Content or Placeholder */}
{state.content ? (
<div className="space-y-4">
<div className="border rounded-lg p-6 bg-card">
<article className="markdown-body" style={{
boxSizing: 'border-box',
minWidth: '200px',
maxWidth: '980px',
margin: '0 auto',
padding: '0'
}}>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
>
{normalizedContent}
</ReactMarkdown>
</article>
</div>
</div>
) : !isGenerating && (
<div className="flex items-center justify-center h-full text-muted-foreground border rounded-lg p-12 border-dashed">
</div>
)}
</div>
</>
)}
</div>
);
}