代码修改
This commit is contained in:
1 parent
8f5af46ec2
commit
8f75e12871
168 files changed
+76575
No files matched your search
@@ -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;
|
||||
Reference in new issue
Block a user