- 将庞大的 page.tsx 拆分为多个独立组件 (components/) - 提取业务逻辑到 Hooks (hooks/) - 提取工具函数到 utils.ts - 优化代码结构和可维护性
116 lines
3.7 KiB
TypeScript
116 lines
3.7 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);
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<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}
|
||
>
|
||
<RotateCw className="size-4" />
|
||
重新生成分析
|
||
</Button>
|
||
)}
|
||
</div>
|
||
|
||
{state.error && (
|
||
<p className="text-red-500">加载失败: {state.error}</p>
|
||
)}
|
||
|
||
{(state.loading || 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>
|
||
{state.loading && (
|
||
<span className="inline-flex items-center gap-2 mt-2 text-muted-foreground">
|
||
<Spinner className="size-3" />
|
||
<span className="text-sm">正在生成中...</span>
|
||
</span>
|
||
)}
|
||
</article>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|