- 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).
130 lines
4.5 KiB
TypeScript
130 lines
4.5 KiB
TypeScript
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>
|
||
);
|
||
}
|
||
|