代码修改

This commit is contained in:
fengxiangdebaozi committed 2026-05-27 19:43:15 +08:00
1 parent 8f5af46ec2
commit 8f75e12871
168 files changed
+76575

No files matched your search

+996
View File
@@ -0,0 +1,996 @@
import React, { useState, useEffect } from 'react';
import { useTheme } from '../contexts/ThemeContext';
import {
ArrowLeft,
Save,
Download,
Send,
RotateCcw,
PlayCircle,
CheckCircle2,
Circle,
Loader2,
ChevronDown,
ChevronRight,
FileText,
Link2,
Upload,
Clock,
AlertTriangle,
Sparkles,
Users,
Copy,
CheckCheck,
RefreshCw,
Database,
BarChart3,
PieChart,
LineChart,
Table,
User,
MessageSquare,
Network,
Target,
BookOpen,
Calendar,
Search,
} from 'lucide-react';
import { LightAppType } from './LightAppSquare';
// 应用配置接口
interface AppConfig {
id: LightAppType;
name: string;
description: string;
icon: React.ElementType;
iconColor: string;
bgColor: string;
inputs: {
name: string;
label: string;
placeholder: string;
type: 'text' | 'textarea' | 'link' | 'upload';
required: boolean;
}[];
params: {
name: string;
label: string;
options: { value: string; label: string }[];
default: string;
}[];
dimensions: {
name: string;
label: string;
default: boolean;
}[];
steps: string[];
previewFields: { key: string; label: string }[];
}
interface LightAppRunProps {
onNavigate: (page: string, appId?: string) => void;
appId?: LightAppType;
}
// 应用配置数据
const appConfigs: Record<LightAppType, AppConfig> = {
'info-ty': {
id: 'info-ty',
name: '信息TY',
description: '围绕单条或多条信息进行TY向分析',
icon: FileText,
iconColor: 'text-blue-500',
bgColor: 'bg-blue-500/10',
inputs: [
{ name: 'mainInput', label: '信息文本', placeholder: '请输入信息内容...', type: 'textarea', required: true },
{ name: 'link', label: '信息链接', placeholder: '可选,粘贴信息链接', type: 'link', required: false },
{ name: 'materials', label: '上传材料', placeholder: '支持 txt, pdf, docx', type: 'upload', required: false },
],
params: [
{ name: 'timeRange', label: '时间范围', options: [
{ value: '7d', label: '近7天' },
{ value: '30d', label: '近30天' },
{ value: '90d', label: '近90天' },
{ value: 'all', label: '全部' },
], default: '30d' },
{ name: 'dataRange', label: '数据范围', options: [
{ value: 'all', label: '全部' },
{ value: '国内', label: '国内' },
{ value: '境外', label: '境外' },
], default: 'all' },
{ name: 'depth', label: '输出深度', options: [
{ value: 'brief', label: '简略' },
{ value: 'standard', label: '标准' },
{ value: 'detailed', label: '详细' },
], default: 'standard' },
],
dimensions: [
{ name: 'basic', label: '基础分析', default: true },
{ name: 'risk', label: '风险提示', default: true },
{ name: 'summary', label: '摘要结论', default: true },
],
steps: ['已识别输入对象', '已获取数据', '已完成核心分析', '已生成结果'],
previewFields: [
{ key: 'title', label: '信息标题' },
{ key: 'time', label: '发布时间' },
{ key: 'source', label: '信息来源' },
],
},
'info-pg': {
id: 'info-pg',
name: '信息PG',
description: '围绕单条或多条信息进行PG向分析',
icon: User,
iconColor: 'text-cyan-500',
bgColor: 'bg-cyan-500/10',
inputs: [
{ name: 'mainInput', label: '信息文本', placeholder: '请输入信息内容...', type: 'textarea', required: true },
{ name: 'materials', label: '上传材料', placeholder: '支持 txt, pdf, docx', type: 'upload', required: false },
],
params: [
{ name: 'timeRange', label: '时间范围', options: [
{ value: '30d', label: '近30天' },
{ value: '90d', label: '近90天' },
{ value: '180d', label: '近半年' },
{ value: 'all', label: '全部' },
], default: '30d' },
{ name: 'count', label: '抓取条数', options: [
{ value: '50', label: '50条' },
{ value: '100', label: '100条' },
{ value: '200', label: '200条' },
{ value: '500', label: '500条' },
], default: '100' },
],
dimensions: [
{ name: 'profile', label: '画像分析', default: true },
{ name: 'tendency', label: '内容倾向', default: true },
{ name: 'behavior', label: '行为特征', default: true },
],
steps: ['已识别输入对象', '已获取数据', '已完成画像分析', '已生成结果'],
previewFields: [
{ key: 'title', label: '信息标题' },
{ key: 'author', label: '发布者' },
{ key: 'type', label: '内容类型' },
],
},
'info-spread': {
id: 'info-spread',
name: '信息传播分析',
description: '分析信息传播路径、传播范围与传播表现',
icon: Network,
iconColor: 'text-teal-500',
bgColor: 'bg-teal-500/10',
inputs: [
{ name: 'link', label: '信息链接', placeholder: '请粘贴信息链接', type: 'link', required: true },
{ name: 'text', label: '信息文本', placeholder: '可选,补充信息文本', type: 'textarea', required: false },
],
params: [
{ name: 'scope', label: '传播范围', options: [
{ value: 'all', label: '全部' },
{ value: 'domestic', label: '国内' },
{ value: 'overseas', label: '境外' },
], default: 'all' },
{ name: 'timeRange', label: '时间范围', options: [
{ value: '7d', label: '近7天' },
{ value: '30d', label: '近30天' },
{ value: '90d', label: '近90天' },
], default: '7d' },
],
dimensions: [
{ name: 'path', label: '传播路径', default: true },
{ name: 'nodes', label: '传播节点', default: true },
{ name: 'performance', label: '传播表现', default: true },
{ name: 'impact', label: '影响摘要', default: true },
],
steps: ['已识别信息', '已获取传播数据', '已完成传播路径分析', '已生成结果'],
previewFields: [
{ key: 'title', label: '信息标题' },
{ key: 'originalPlatform', label: '原始平台' },
{ key: 'totalSpread', label: '总传播量' },
],
},
'account-pg': {
id: 'account-pg',
name: '账号PG',
description: '围绕账号内容与行为特征进行PG分析',
icon: Users,
iconColor: 'text-purple-500',
bgColor: 'bg-purple-500/10',
inputs: [
{ name: 'accountId', label: '账号ID/链接', placeholder: '请输入账号ID或粘贴账号链接', type: 'text', required: true },
{ name: 'materials', label: '上传内容包', placeholder: '支持 json, csv, zip', type: 'upload', required: false },
],
params: [
{ name: 'timeRange', label: '时间范围', options: [
{ value: '30d', label: '近30天' },
{ value: '90d', label: '近90天' },
{ value: '180d', label: '近半年' },
{ value: 'all', label: '全部' },
], default: '30d' },
{ name: 'count', label: '抓取条数', options: [
{ value: '50', label: '50条' },
{ value: '100', label: '100条' },
{ value: '200', label: '200条' },
{ value: '500', label: '500条' },
], default: '100' },
],
dimensions: [
{ name: 'profile', label: '账号画像', default: true },
{ name: 'tendency', label: '内容倾向', default: true },
{ name: 'behavior', label: '行为特征', default: true },
{ name: 'impact', label: '影响判断', default: true },
],
steps: ['已定位账号', '已抓取内容', '已完成内容倾向分析', '已完成账号画像分析', '已生成结果'],
previewFields: [
{ key: 'accountName', label: '账号名称' },
{ key: 'accountType', label: '账号类型' },
{ key: 'followers', label: '粉丝数' },
],
},
'sentiment': {
id: 'sentiment',
name: '三情分析',
description: '对事件进行舆情、社情、民情综合分析',
icon: MessageSquare,
iconColor: 'text-orange-500',
bgColor: 'bg-orange-500/10',
inputs: [
{ name: 'eventName', label: '事件名称', placeholder: '请输入事件名称', type: 'text', required: true },
{ name: 'eventLink', label: '事件链接', placeholder: '可选,粘贴事件相关链接', type: 'link', required: false },
{ name: 'materials', label: '上传材料', placeholder: '支持上传相关素材', type: 'upload', required: false },
],
params: [
{ name: 'timeRange', label: '时间范围', options: [
{ value: '7d', label: '近7天' },
{ value: '30d', label: '近30天' },
{ value: '90d', label: '近90天' },
], default: '7d' },
{ name: 'dataRange', label: '数据范围', options: [
{ value: 'all', label: '全部' },
{ value: '国内', label: '国内' },
{ value: '境外', label: '境外' },
], default: 'all' },
],
dimensions: [
{ name: 'publicSentiment', label: '舆情分析', default: true },
{ name: 'socialSentiment', label: '社情分析', default: true },
{ name: 'publicOpinion', label: '民情分析', default: true },
{ name: 'riskAlert', label: '风险提示', default: true },
],
steps: ['已识别事件', '已聚合相关内容', '已完成舆情分析', '已完成社情分析', '已完成民情分析', '已生成结果'],
previewFields: [
{ key: 'eventName', label: '事件名称' },
{ key: 'eventTime', label: '事件时间' },
{ key: 'relatedCount', label: '相关内容数' },
],
},
'event': {
id: 'event',
name: '事件分析',
description: '围绕某个事件进行整体分析',
icon: Target,
iconColor: 'text-red-500',
bgColor: 'bg-red-500/10',
inputs: [
{ name: 'eventName', label: '事件名称', placeholder: '请输入事件名称', type: 'text', required: true },
{ name: 'eventDesc', label: '事件说明', placeholder: '请补充事件相关说明', type: 'textarea', required: false },
{ name: 'materials', label: '上传材料', placeholder: '支持上传事件相关材料', type: 'upload', required: false },
],
params: [
{ name: 'timeRange', label: '时间范围', options: [
{ value: '30d', label: '近30天' },
{ value: '90d', label: '近90天' },
{ value: '180d', label: '近半年' },
{ value: 'all', label: '全部' },
], default: '30d' },
{ name: 'depth', label: '输出深度', options: [
{ value: 'brief', label: '简略' },
{ value: 'standard', label: '标准' },
{ value: 'detailed', label: '详细' },
], default: 'standard' },
],
dimensions: [
{ name: 'context', label: '事件脉络', default: true },
{ name: 'keyPoints', label: '关键节点', default: true },
{ name: 'risk', label: '风险判断', default: true },
{ name: 'suggestions', label: '初步建议', default: true },
],
steps: ['已识别事件', '已整理事件材料', '已完成脉络分析', '已完成风险判断', '已生成结果'],
previewFields: [
{ key: 'eventName', label: '事件名称' },
{ key: 'eventStage', label: '所处阶段' },
{ key: 'keyPlayers', label: '关键参与者' },
],
},
'topic': {
id: 'topic',
name: '专题分析',
description: '对某专题做集中分析',
icon: BookOpen,
iconColor: 'text-green-500',
bgColor: 'bg-green-500/10',
inputs: [
{ name: 'topicName', label: '专题名称', placeholder: '请输入专题名称', type: 'text', required: true },
{ name: 'materials', label: '上传材料', placeholder: '支持上传专题相关文本集合', type: 'upload', required: false },
],
params: [
{ name: 'depth', label: '分析深度', options: [
{ value: 'surface', label: '表面' },
{ value: 'standard', label: '标准' },
{ value: 'deep', label: '深度' },
], default: 'standard' },
{ name: 'sourceRange', label: '来源范围', options: [
{ value: 'all', label: '全部' },
{ value: '国内', label: '国内' },
{ value: '境外', label: '境外' },
], default: 'all' },
],
dimensions: [
{ name: 'structure', label: '专题结构', default: true },
{ name: 'coreIssues', label: '核心问题', default: true },
{ name: 'viewpoints', label: '观点聚合', default: true },
{ name: 'conclusions', label: '结论建议', default: true },
],
steps: ['已识别专题', '已获取相关材料', '已完成结构分析', '已聚合核心观点', '已生成结果'],
previewFields: [
{ key: 'topicName', label: '专题名称' },
{ key: 'relatedDocs', label: '相关文档' },
{ key: 'coverage', label: '覆盖范围' },
],
},
};
type ResultTab = 'overview' | 'details' | 'raw';
const LightAppRun: React.FC<LightAppRunProps> = ({ onNavigate, appId }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
const [currentAppId, setCurrentAppId] = useState<LightAppType>(appId || 'sentiment');
const [resultTab, setResultTab] = useState<ResultTab>('overview');
const [isRunning, setIsRunning] = useState(false);
const [currentStep, setCurrentStep] = useState(0);
const [copied, setCopied] = useState(false);
const [formData, setFormData] = useState<Record<string, string>>({});
const [params, setParams] = useState<Record<string, string>>({});
const [dimensions, setDimensions] = useState<Record<string, boolean>>({});
const [hasResult, setHasResult] = useState(false);
const [result, setResult] = useState<any>(null);
const config = appConfigs[currentAppId];
const Icon = config.icon;
// 初始化表单数据
useEffect(() => {
console.log('当前应用:', currentAppId);
if (config) {
const initialForm: Record<string, string> = {};
config.inputs.forEach(input => {
initialForm[input.name] = '';
});
setFormData(initialForm);
const initialParams: Record<string, string> = {};
config.params.forEach(param => {
initialParams[param.name] = param.default;
});
setParams(initialParams);
const initialDims: Record<string, boolean> = {};
config.dimensions.forEach(dim => {
initialDims[dim.name] = dim.default;
});
setDimensions(initialDims);
}
}, [currentAppId]);
// 执行分析
const handleRun = async () => {
setIsRunning(true);
setCurrentStep(0);
setHasResult(false);
// 模拟执行步骤
for (let i = 0; i < config.steps.length; i++) {
await new Promise(resolve => setTimeout(resolve, 800));
setCurrentStep(i + 1);
}
// 生成结果
await new Promise(resolve => setTimeout(resolve, 500));
setResult(generateMockResult());
setHasResult(true);
setIsRunning(false);
};
// 生成模拟结果
const generateMockResult = () => {
switch (currentAppId) {
case 'sentiment':
return {
overview: {
overallSentiment: '中偏负',
mainConcerns: ['活动形式', '品牌调性', '执行成本'],
riskAlert: '讨论热度上升,负向占比增加',
sentimentTrend: '下行',
},
details: {
publicSentiment: '舆情方面,主要关注点是活动本身的创新性和参与体验。部分用户对活动细节提出质疑,整体讨论呈现多元化特征。',
socialSentiment: '社情方面,该事件引发了关于活动公平性的讨论,社交媒体上相关话题讨论量显著上升。',
publicOpinion: '民情方面,公众对该活动的看法分化明显,支持和反对声音并存,需关注后续舆情走向。',
riskAssessment: '当前风险等级:中。建议持续关注舆情变化,准备应对预案。',
},
raw: [
{ type: '正面评论', content: '活动创意很好,期待参与', count: 234 },
{ type: '中性评论', content: '等待活动正式开始', count: 567 },
{ type: '负面评论', content: '对活动细节表示质疑', count: 189 },
],
};
case 'event':
return {
overview: {
eventStage: '发酵期',
keyNodes: '活动公布期,关注度持续上升',
riskLevel: '中',
suggestion: '建议加强信息透明度,及时回应公众关切',
},
details: {
context: '该事件起因于...,经过...,目前处于...阶段。',
keyPoints: ['关键节点一:...', '关键节点二:...', '关键节点三:...'],
riskJudgment: '风险可控,但需关注...',
suggestions: '1. ... 2. ... 3. ...',
},
raw: [
{ time: '2026-03-18', title: '事件起因', content: '...' },
{ time: '2026-03-19', title: '初步发酵', content: '...' },
{ time: '2026-03-20', title: '关注度上升', content: '...' },
],
};
case 'account-pg':
return {
overview: {
accountProfile: '该账号为...类型账号',
contentTendency: '内容倾向明显偏...',
influenceJudgment: '具有一定影响力,需关注...',
},
details: {
profileAnalysis: '账号画像分析...',
contentAnalysis: '内容倾向分析...',
behaviorAnalysis: '行为特征分析...',
influenceAnalysis: '影响判断分析...',
},
raw: [
{ date: '2026-03-15', content: '...' },
{ date: '2026-03-16', content: '...' },
{ date: '2026-03-17', content: '...' },
],
};
default:
return {
overview: { summary: '分析结果摘要...' },
details: { analysis: '详细分析内容...' },
raw: [],
};
}
};
// 重置
const handleReset = () => {
const initialForm: Record<string, string> = {};
config.inputs.forEach(input => {
initialForm[input.name] = '';
});
setFormData(initialForm);
const initialParams: Record<string, string> = {};
config.params.forEach(param => {
initialParams[param.name] = param.default;
});
setParams(initialParams);
const initialDims: Record<string, boolean> = {};
config.dimensions.forEach(dim => {
initialDims[dim.name] = dim.default;
});
setDimensions(initialDims);
setHasResult(false);
setResult(null);
setCurrentStep(0);
};
// 复制结果
const handleCopy = () => {
navigator.clipboard.writeText(JSON.stringify(result, null, 2));
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
// 保存快照
const handleSaveSnapshot = () => {
// 模拟保存
alert('快照已保存到运行记录');
onNavigate('light-app-records');
};
// 导出
const handleExport = () => {
const blob = new Blob([JSON.stringify(result, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${config.name}_结果_${new Date().toISOString().split('T')[0]}.json`;
a.click();
};
return (
<div className="space-y-4">
{/* 顶部操作栏 */}
<div className={`rounded-xl border p-4 ${
isDark ? 'bg-slate-800/50 border-slate-700' : 'bg-white border-gray-200'
}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<button
onClick={() => onNavigate('light-app-square')}
className={`p-2 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700 text-slate-400' : 'hover:bg-gray-100 text-gray-500'
}`}
>
<ArrowLeft className="w-5 h-5" />
</button>
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${config.bgColor}`}>
<Icon className={`w-5 h-5 ${config.iconColor}`} />
</div>
<div>
<h1 className={`text-lg font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>
{config.name}
</h1>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
{config.description}
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={handleSaveSnapshot}
disabled={!hasResult}
className={`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 transition-colors ${
hasResult
? isDark
? 'bg-slate-700 text-slate-300 hover:bg-slate-600'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
: isDark
? 'bg-slate-800 text-slate-600 cursor-not-allowed'
: 'bg-gray-50 text-gray-400 cursor-not-allowed'
}`}
>
<Save className="w-4 h-4" />
保存快照
</button>
<button
onClick={handleExport}
disabled={!hasResult}
className={`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 transition-colors ${
hasResult
? isDark
? 'bg-slate-700 text-slate-300 hover:bg-slate-600'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
: isDark
? 'bg-slate-800 text-slate-600 cursor-not-allowed'
: 'bg-gray-50 text-gray-400 cursor-not-allowed'
}`}
>
<Download className="w-4 h-4" />
导出结果
</button>
<button
onClick={() => onNavigate('task-create')}
disabled={!hasResult}
className={`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 transition-colors ${
hasResult
? isDark
? 'bg-blue-500/20 text-blue-400 hover:bg-blue-500/30 border border-blue-500/30'
: 'bg-red-50 text-red-600 hover:bg-red-100 border border-red-200'
: isDark
? 'bg-slate-800 text-slate-600 cursor-not-allowed border border-transparent'
: 'bg-gray-50 text-gray-400 cursor-not-allowed border border-transparent'
}`}
>
<Send className="w-4 h-4" />
转正式任务
</button>
</div>
</div>
</div>
{/* 主体内容区 - 三栏布局 */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-4">
{/* 左侧:输入与配置 */}
<div className={`lg:col-span-4 rounded-xl border p-4 ${
isDark ? 'bg-slate-800/50 border-slate-700' : 'bg-white border-gray-200'
}`}>
<div className="space-y-6">
{/* 输入对象 */}
<div>
<h3 className={`text-sm font-medium mb-3 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
输入对象
</h3>
<div className="space-y-3">
{config.inputs.map(input => (
<div key={input.name}>
<label className={`block text-xs mb-1.5 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
{input.label}
{input.required && <span className="text-red-500 ml-1">*</span>}
</label>
{input.type === 'textarea' ? (
<textarea
value={formData[input.name] || ''}
onChange={(e) => setFormData({ ...formData, [input.name]: e.target.value })}
placeholder={input.placeholder}
rows={4}
className={`w-full px-3 py-2 rounded-lg border text-sm transition-colors focus:outline-none focus:ring-2 ${
isDark
? 'bg-slate-900 border-slate-600 text-slate-200 placeholder-slate-500 focus:border-blue-500'
: 'bg-gray-50 border-gray-300 text-gray-900 placeholder-gray-400 focus:border-red-500'
}`}
/>
) : input.type === 'upload' ? (
<div className={`border-2 border-dashed rounded-lg p-4 text-center cursor-pointer transition-colors ${
isDark
? 'border-slate-600 hover:border-blue-500/50'
: 'border-gray-300 hover:border-red-500/50'
}`}>
<Upload className={`w-6 h-6 mx-auto mb-2 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
<p className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
{input.placeholder}
</p>
</div>
) : (
<input
type="text"
value={formData[input.name] || ''}
onChange={(e) => setFormData({ ...formData, [input.name]: e.target.value })}
placeholder={input.placeholder}
className={`w-full px-3 py-2 rounded-lg border text-sm transition-colors focus:outline-none focus:ring-2 ${
isDark
? 'bg-slate-900 border-slate-600 text-slate-200 placeholder-slate-500 focus:border-blue-500'
: 'bg-gray-50 border-gray-300 text-gray-900 placeholder-gray-400 focus:border-red-500'
}`}
/>
)}
</div>
))}
</div>
</div>
{/* 参数配置 */}
<div>
<h3 className={`text-sm font-medium mb-3 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
参数配置
</h3>
<div className="space-y-3">
{config.params.map(param => (
<div key={param.name}>
<label className={`block text-xs mb-1.5 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
{param.label}
</label>
<select
value={params[param.name] || param.default}
onChange={(e) => setParams({ ...params, [param.name]: e.target.value })}
className={`w-full px-3 py-2 rounded-lg border text-sm transition-colors focus:outline-none focus:ring-2 ${
isDark
? 'bg-slate-900 border-slate-600 text-slate-200 focus:border-blue-500'
: 'bg-gray-50 border-gray-300 text-gray-900 focus:border-red-500'
}`}
>
{param.options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
))}
</div>
</div>
{/* 分析维度 */}
<div>
<h3 className={`text-sm font-medium mb-3 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
分析维度
</h3>
<div className="space-y-2">
{config.dimensions.map(dim => (
<label
key={dim.name}
className={`flex items-center gap-2 p-2 rounded-lg cursor-pointer transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-gray-50'
}`}
>
<input
type="checkbox"
checked={dimensions[dim.name] ?? dim.default}
onChange={(e) => setDimensions({ ...dimensions, [dim.name]: e.target.checked })}
className="w-4 h-4 rounded border-gray-300"
/>
<span className={`text-sm ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
{dim.label}
</span>
</label>
))}
</div>
</div>
</div>
</div>
{/* 中间:执行过程/对象预览 */}
<div className={`lg:col-span-4 rounded-xl border p-4 ${
isDark ? 'bg-slate-800/50 border-slate-700' : 'bg-white border-gray-200'
}`}>
<div className="space-y-6">
{/* 执行过程 */}
<div>
<h3 className={`text-sm font-medium mb-3 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
执行过程
</h3>
<div className="space-y-2">
{config.steps.map((step, index) => (
<div key={index} className="flex items-center gap-3">
{isRunning && index === currentStep ? (
<Loader2 className={`w-4 h-4 animate-spin ${isDark ? 'text-blue-400' : 'text-red-500'}`} />
) : index < currentStep || hasResult ? (
<CheckCircle2 className={`w-4 h-4 ${isDark ? 'text-green-400' : 'text-green-500'}`} />
) : (
<Circle className={`w-4 h-4 ${isDark ? 'text-slate-600' : 'text-gray-300'}`} />
)}
<span className={`text-sm ${
index < currentStep || hasResult
? isDark ? 'text-slate-300' : 'text-gray-700'
: isDark ? 'text-slate-500' : 'text-gray-400'
}`}>
{index + 1}. {step}
</span>
</div>
))}
</div>
</div>
{/* 对象预览 */}
<div>
<h3 className={`text-sm font-medium mb-3 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
对象预览
</h3>
<div className={`rounded-lg p-4 ${
isDark ? 'bg-slate-900/50' : 'bg-gray-50'
}`}>
{formData.mainInput || formData.eventName || formData.accountId || formData.link || formData.topicName ? (
<div className="space-y-2">
{config.previewFields.map(field => (
<div key={field.key} className="flex items-start gap-2">
<span className={`text-xs font-medium min-w-[60px] ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
{field.label}:
</span>
<span className={`text-sm ${isDark ? 'text-slate-300' : 'text-gray-600'}`}>
{formData.mainInput?.slice(0, 30) || formData.eventName || formData.accountId || formData.link || formData.topicName}
</span>
</div>
))}
</div>
) : (
<p className={`text-sm text-center ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
请在左侧输入对象后预览
</p>
)}
</div>
</div>
</div>
</div>
{/* 右侧:结果区 */}
<div className={`lg:col-span-4 rounded-xl border p-4 ${
isDark ? 'bg-slate-800/50 border-slate-700' : 'bg-white border-gray-200'
}`}>
<div className="flex items-center justify-between mb-4">
<h3 className={`text-sm font-medium ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
分析结果
</h3>
{hasResult && (
<div className="flex items-center gap-1">
<button
onClick={handleCopy}
className={`p-1.5 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700 text-slate-400' : 'hover:bg-gray-100 text-gray-500'
}`}
>
{copied ? <CheckCheck className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
</button>
</div>
)}
</div>
{/* 结果标签页 */}
{hasResult ? (
<>
<div className="flex gap-1 mb-4">
{(['overview', 'details', 'raw'] as ResultTab[]).map(tab => (
<button
key={tab}
onClick={() => setResultTab(tab)}
className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${
resultTab === tab
? isDark
? 'bg-blue-500/20 text-blue-400'
: 'bg-red-50 text-red-600'
: isDark
? 'text-slate-400 hover:text-slate-300'
: 'text-gray-500 hover:text-gray-700'
}`}
>
{tab === 'overview' ? '概览' : tab === 'details' ? '详细结果' : '原始数据'}
</button>
))}
</div>
{/* 结果内容 */}
<div className={`rounded-lg p-4 ${
isDark ? 'bg-slate-900/50' : 'bg-gray-50'
}`}>
{resultTab === 'overview' && (
<div className="space-y-3">
{Object.entries(result?.overview || {}).map(([key, value]) => (
<div key={key}>
<span className={`text-xs font-medium ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
{key === 'overallSentiment' ? '总体情绪' :
key === 'mainConcerns' ? '主要关注点' :
key === 'riskAlert' ? '风险提示' :
key === 'sentimentTrend' ? '情绪趋势' :
key === 'eventStage' ? '事件阶段' :
key === 'keyNodes' ? '关键节点' :
key === 'riskLevel' ? '风险等级' :
key === 'suggestion' ? '建议' :
key === 'accountProfile' ? '账号画像' :
key === 'contentTendency' ? '内容倾向' :
key === 'influenceJudgment' ? '影响判断' :
key === 'summary' ? '结果摘要' : key}:
</span>
<p className={`text-sm mt-1 ${isDark ? 'text-slate-300' : 'text-gray-600'}`}>
{Array.isArray(value) ? value.join('、') : String(value)}
</p>
</div>
))}
</div>
)}
{resultTab === 'details' && (
<div className="space-y-3">
{Object.entries(result?.details || {}).map(([key, value]) => (
<div key={key}>
<span className={`text-xs font-medium ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
{key === 'publicSentiment' ? '舆情分析' :
key === 'socialSentiment' ? '社情分析' :
key === 'publicOpinion' ? '民情分析' :
key === 'riskAssessment' ? '风险评估' :
key === 'context' ? '事件脉络' :
key === 'keyPoints' ? '关键节点' :
key === 'riskJudgment' ? '风险判断' :
key === 'suggestions' ? '建议' :
key === 'profileAnalysis' ? '画像分析' :
key === 'contentAnalysis' ? '内容分析' :
key === 'behaviorAnalysis' ? '行为分析' :
key === 'influenceAnalysis' ? '影响分析' :
key === 'analysis' ? '分析' : key}:
</span>
<p className={`text-sm mt-1 ${isDark ? 'text-slate-300' : 'text-gray-600'}`}>
{Array.isArray(value) ? value.join('\n') : String(value)}
</p>
</div>
))}
</div>
)}
{resultTab === 'raw' && (
<div className="space-y-2">
{(result?.raw || []).length > 0 ? (
result.raw.map((item: any, index: number) => (
<div key={index} className={`p-2 rounded ${isDark ? 'bg-slate-800/50' : 'bg-white'}`}>
<div className="flex items-center gap-2 mb-1">
{item.type && (
<span className={`px-2 py-0.5 rounded text-xs ${
isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-blue-50 text-blue-600'
}`}>
{item.type}
</span>
)}
{item.time && (
<span className={`text-xs ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
{item.time}
</span>
)}
{item.date && (
<span className={`text-xs ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
{item.date}
</span>
)}
</div>
<p className={`text-sm ${isDark ? 'text-slate-300' : 'text-gray-600'}`}>
{item.content || item.title || JSON.stringify(item)}
</p>
</div>
))
) : (
<p className={`text-sm text-center ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
无原始数据
</p>
)}
</div>
)}
</div>
</>
) : (
<div className="text-center py-12">
<Sparkles className={`w-12 h-12 mx-auto mb-4 ${isDark ? 'text-slate-600' : 'text-gray-300'}`} />
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
{isRunning ? '分析中...' : '点击下方按钮开始分析'}
</p>
</div>
)}
</div>
</div>
{/* 底部操作区 */}
<div className={`rounded-xl border p-4 ${
isDark ? 'bg-slate-800/50 border-slate-700' : 'bg-white border-gray-200'
}`}>
<div className="flex items-center justify-between">
<button
onClick={handleReset}
disabled={isRunning}
className={`px-6 py-2.5 rounded-lg font-medium flex items-center gap-2 transition-colors ${
isRunning
? isDark
? 'bg-slate-800 text-slate-600 cursor-not-allowed'
: 'bg-gray-50 text-gray-400 cursor-not-allowed'
: isDark
? 'bg-slate-700 text-slate-300 hover:bg-slate-600'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
<RotateCcw className="w-4 h-4" />
重置
</button>
<button
onClick={handleRun}
disabled={isRunning || !formData.mainInput && !formData.eventName && !formData.accountId && !formData.link && !formData.topicName}
className={`px-8 py-2.5 rounded-lg font-medium flex items-center gap-2 transition-all ${
isRunning
? isDark
? 'bg-blue-500/20 text-blue-400 cursor-wait'
: 'bg-red-50 text-red-400 cursor-wait'
: formData.mainInput || formData.eventName || formData.accountId || formData.link || formData.topicName
? isDark
? 'bg-blue-600 text-white hover:bg-blue-500 shadow-lg shadow-blue-500/25'
: 'bg-red-500 text-white hover:bg-red-600 shadow-lg shadow-red-500/25'
: isDark
? 'bg-slate-700 text-slate-500 cursor-not-allowed'
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
}`}
>
{isRunning ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
分析中...
</>
) : (
<>
<PlayCircle className="w-4 h-4" />
开始分析
</>
)}
</button>
</div>
</div>
</div>
);
};
export default LightAppRun;