戒酒的李白

The newly added crawler visualization settings and the process visualization orc…

…hestration module have been adapted to support bilingual switching.
// 爬虫控制面板翻译资源文件
// 包含中文(zh-CN)和英文(en-US)的翻译
const spiderI18nResources = {
'zh-CN': {
translation: {
// 页面标题和导航
'page-title': '爬虫控制面板',
// 卡片标题
'topic-selection': '选择话题类型',
'spider-parameters': '爬虫参数配置',
'content-filters': '内容筛选配置',
'account-config': '账号配置',
'parallel-config': '并行配置',
'db-config': '数据库配置',
'ai-assistant': 'AI配置助手',
'spider-status': '爬虫状态',
// 话题选择部分
'add-custom-topic': '添加自定义话题',
'custom-topic-placeholder': '输入自定义话题',
'btn-add': '添加',
'selected-topics': '已选择的话题:',
// 爬虫参数部分
'crawl-depth': '爬取深度',
'crawl-depth-hint': '每个话题爬取的页数(1-10)',
'interval': '爬取间隔(秒)',
'interval-hint': '每次请求之间的间隔时间',
'max-retries': '最大重试次数',
'timeout': '请求超时时间(秒)',
// 筛选器部分
'help': '帮助',
'filter-conditions': '筛选条件说明:',
'filter-condition-1': '数值条件:设置大于某个值进行筛选,如点赞数>1000',
'filter-condition-2': '正则匹配:使用正则表达式匹配内容,如包含特定关键词',
'filter-condition-3': '多个条件之间是"与"的关系,即同时满足才会保留',
'filter-tip': '提示:合理设置筛选条件可以提高数据质量',
'interaction-filter': '互动数据筛选',
'likes-gt': '点赞数大于',
'comments-gt': '评论数大于',
'reposts-gt': '转发数大于',
'reads-gt': '阅读数大于',
'regex-filter': '内容正则筛选',
'add-regex-filter': '添加正则筛选',
'advanced-options': '高级选项',
'only-original': '仅爬取原创内容',
'must-have-media': '必须包含图片或视频',
'only-verified': '仅认证用户的内容',
// 账号配置部分
'btn-add-account': '添加账号',
'how-to-get-cookie': '如何获取Cookie?',
'cookie-step-1': '登录微博网页版',
'cookie-step-2': '按F12打开开发者工具',
'cookie-step-3': '切换到Network标签页',
'cookie-step-4': '刷新页面,找到请求头中的Cookie值',
'cookie-warning': '注意:请勿泄露您的Cookie信息!',
'account-tip': '提示:添加多个账号可以提高爬取效率,系统会自动在账号间轮换。',
'no-account-warning': '请至少添加一个账号',
'username': '用户名',
'username-placeholder': '微博用户名',
'password': '密码',
'password-placeholder': '微博密码',
'cookie': 'Cookie',
'cookie-placeholder': '请输入微博Cookie',
'save-cookie': '保存Cookie(加密存储)',
'status-pending': '状态:待验证',
'btn-validate-account': '验证账号',
'status-validating': '状态:验证中...',
'status-success': '状态:验证成功',
'status-failed': '状态:验证失败 - ',
'error-empty-cookie': 'Cookie不能为空',
// 正则筛选器
'regex-pattern': '正则表达式',
'regex-pattern-placeholder': '输入正则表达式',
'match-target': '匹配目标',
'target-content': '微博内容',
'target-author': '作者名',
'target-location': '发布位置',
'inverse-match': '反向匹配(不包含匹配项)',
// 并行配置
'max-concurrent': '最大并行数',
'max-concurrent-hint': '同时进行爬取的最大话题数(1-5)',
'requests-per-minute': '每分钟请求数限制',
'requests-per-minute-hint': '避免请求过于频繁(30-120)',
// 数据库配置
'db-type': '数据库类型',
'host': '主机地址',
'port': '端口',
'db-name': '数据库名',
'username-db': '用户名',
'password-db': '密码',
'btn-test-connection': '测试连接',
'db-connect-success': '数据库连接测试成功!',
'db-connect-fail': '数据库连接测试失败:',
'db-connect-error': '测试连接时发生错误:',
// AI配置助手
'ai-prompt-label': '用自然语言描述您的爬虫需求',
'ai-prompt-placeholder': '例如:我想爬取最近一周关于人工智能的热门微博,重点关注转发量超过1000的内容,每个话题爬取前5页内容。',
'btn-generate-config': '生成配置',
'auto-apply': '自动应用生成的配置',
'ai-suggestion': 'AI助手建议:',
'ai-config-applied': 'AI配置已自动应用',
'ai-config-error': '生成配置时出错:',
'empty-prompt-error': '请输入您的爬虫需求描述!',
// 操作按钮
'btn-start': '开始爬取',
'btn-save-config': '保存配置',
'config-saved': '配置已保存!',
'save-failed': '保存失败:',
'save-error': '保存出错:',
// 爬虫状态
'task-started': '爬虫任务已启动...',
'start-failed': '启动失败:',
'error': '错误:',
// 验证错误提示
'select-topic-error': '请至少选择一个话题!',
'invalid-regex-error': '正则表达式 "{0}" 格式无效!',
'need-account-error': '请至少添加一个账号!',
'empty-cookie-error': '存在未配置Cookie的账号,请检查!',
'concurrent-limit-error': '最大并行数必须在1-5之间!',
'request-limit-error': '每分钟请求数必须在30-120之间!',
'db-config-error': '请完整填写数据库配置信息!'
}
},
'en-US': {
translation: {
// Page title and navigation
'page-title': 'Spider Control Panel',
// Card titles
'topic-selection': 'Select Topic Types',
'spider-parameters': 'Spider Parameters',
'content-filters': 'Content Filters',
'account-config': 'Account Configuration',
'parallel-config': 'Parallel Configuration',
'db-config': 'Database Configuration',
'ai-assistant': 'AI Configuration Assistant',
'spider-status': 'Spider Status',
// Topic selection section
'add-custom-topic': 'Add Custom Topic',
'custom-topic-placeholder': 'Enter custom topic',
'btn-add': 'Add',
'selected-topics': 'Selected Topics:',
// Spider parameters section
'crawl-depth': 'Crawl Depth',
'crawl-depth-hint': 'Number of pages to crawl for each topic (1-10)',
'interval': 'Interval (seconds)',
'interval-hint': 'Time between requests',
'max-retries': 'Maximum Retries',
'timeout': 'Request Timeout (seconds)',
// Filters section
'help': 'Help',
'filter-conditions': 'Filter conditions:',
'filter-condition-1': 'Numeric conditions: Set values to filter by, e.g., likes > 1000',
'filter-condition-2': 'Regex matching: Use regular expressions to match content, e.g., contain specific keywords',
'filter-condition-3': 'Multiple conditions are combined with AND logic',
'filter-tip': 'Tip: Setting proper filters can improve data quality',
'interaction-filter': 'Interaction Data Filters',
'likes-gt': 'Likes greater than',
'comments-gt': 'Comments greater than',
'reposts-gt': 'Reposts greater than',
'reads-gt': 'Reads greater than',
'regex-filter': 'Content Regex Filters',
'add-regex-filter': 'Add Regex Filter',
'advanced-options': 'Advanced Options',
'only-original': 'Only crawl original content',
'must-have-media': 'Must contain images or videos',
'only-verified': 'Only content from verified users',
// Account configuration section
'btn-add-account': 'Add Account',
'how-to-get-cookie': 'How to get the Cookie?',
'cookie-step-1': 'Login to Weibo web version',
'cookie-step-2': 'Press F12 to open developer tools',
'cookie-step-3': 'Switch to Network tab',
'cookie-step-4': 'Refresh page and find Cookie value in request headers',
'cookie-warning': 'Warning: Do not expose your Cookie information!',
'account-tip': 'Tip: Adding multiple accounts can improve crawling efficiency, the system will automatically rotate between accounts.',
'no-account-warning': 'Please add at least one account',
'username': 'Username',
'username-placeholder': 'Weibo username',
'password': 'Password',
'password-placeholder': 'Weibo password',
'cookie': 'Cookie',
'cookie-placeholder': 'Please enter Weibo Cookie',
'save-cookie': 'Save Cookie (encrypted storage)',
'status-pending': 'Status: Pending verification',
'btn-validate-account': 'Validate Account',
'status-validating': 'Status: Validating...',
'status-success': 'Status: Validation successful',
'status-failed': 'Status: Validation failed - ',
'error-empty-cookie': 'Cookie cannot be empty',
// Regex filters
'regex-pattern': 'Regular Expression',
'regex-pattern-placeholder': 'Enter regular expression',
'match-target': 'Match Target',
'target-content': 'Weibo content',
'target-author': 'Author name',
'target-location': 'Posting location',
'inverse-match': 'Inverse match (exclude matches)',
// Parallel configuration
'max-concurrent': 'Maximum Concurrent Tasks',
'max-concurrent-hint': 'Maximum number of topics to crawl simultaneously (1-5)',
'requests-per-minute': 'Requests Per Minute Limit',
'requests-per-minute-hint': 'Avoid too frequent requests (30-120)',
// Database configuration
'db-type': 'Database Type',
'host': 'Host',
'port': 'Port',
'db-name': 'Database Name',
'username-db': 'Username',
'password-db': 'Password',
'btn-test-connection': 'Test Connection',
'db-connect-success': 'Database connection test successful!',
'db-connect-fail': 'Database connection test failed: ',
'db-connect-error': 'Error while testing connection: ',
// AI assistant
'ai-prompt-label': 'Describe your crawling requirements in natural language',
'ai-prompt-placeholder': 'For example: I want to crawl trending Weibo posts about AI from the past week, focusing on content with more than 1000 reposts, crawling the first 5 pages for each topic.',
'btn-generate-config': 'Generate Configuration',
'auto-apply': 'Auto-apply generated configuration',
'ai-suggestion': 'AI Assistant Suggestion:',
'ai-config-applied': 'AI configuration applied automatically',
'ai-config-error': 'Error generating configuration: ',
'empty-prompt-error': 'Please enter your crawler requirements!',
// Action buttons
'btn-start': 'Start Crawling',
'btn-save-config': 'Save Configuration',
'config-saved': 'Configuration saved!',
'save-failed': 'Save failed: ',
'save-error': 'Error saving: ',
// Spider status
'task-started': 'Crawler task started...',
'start-failed': 'Start failed: ',
'error': 'Error: ',
// Validation error messages
'select-topic-error': 'Please select at least one topic!',
'invalid-regex-error': 'Regular expression "{0}" is invalid!',
'need-account-error': 'Please add at least one account!',
'empty-cookie-error': 'There are accounts without Cookie configuration, please check!',
'concurrent-limit-error': 'Maximum concurrent tasks must be between 1-5!',
'request-limit-error': 'Requests per minute must be between 30-120!',
'db-config-error': 'Please complete all database configuration fields!'
}
}
};
\ No newline at end of file
... ...
// 翻译资源文件
// 包含中文(zh-CN)和英文(en-US)的翻译
const i18nResources = {
'zh-CN': {
translation: {
// 页面标题
'page-title': '工作流编辑器 - 微博舆情分析系统',
'navbar-brand': '工作流编辑器',
// 导航菜单
'nav-visual-editor': '可视化编辑',
'nav-template-mgmt': '模板管理',
'nav-task-list': '任务列表',
// 按钮
'btn-save': '保存',
'btn-run': '运行',
'btn-cancel': '取消',
'btn-close': '关闭',
'btn-create-new': '新建',
'btn-validate': '验证',
'btn-undo': '撤销',
'btn-redo': '重做',
'btn-zoom-in': '放大',
'btn-zoom-out': '缩小',
'btn-fit-view': '适应视图',
'btn-export': '导出工作流',
'btn-import': '导入工作流',
'btn-cancel-task': '取消任务',
'btn-view-full-result': '查看完整结果',
// 选项卡
'tab-components': '组件',
'tab-templates': '模板',
// 组件类别
'comp-data-source': '数据源',
'comp-data-processing': '数据处理',
'comp-model-analysis': '模型分析',
'comp-visualization': '可视化',
// 组件
'comp-database': '数据库',
'comp-file': '文件',
'comp-crawler': '爬虫',
'comp-filter': '过滤',
'comp-sort': '排序',
'comp-aggregate': '聚合',
'comp-sentiment': '情感分析',
'comp-topic': '话题分类',
'comp-keywords': '关键词提取',
'comp-summarize': '文本摘要',
'comp-chart': '图表',
'comp-table': '表格',
'comp-wordcloud': '词云',
// 模板相关
'templates-crawler': '爬虫模板',
'templates-analysis': '分析流程模板',
'modal-save-template': '保存为模板',
'template-name': '模板名称',
'template-description': '描述',
'template-icon': '图标',
// 图标名称
'icon-chart': '图表',
'icon-filter': '过滤',
'icon-crawler': '爬虫',
'icon-ai': 'AI分析',
'icon-database': '数据库',
'icon-wordcloud': '词云',
// 属性面板
'properties-title': '组件属性',
// 工作流状态
'workflow-status-message': '工作流就绪。拖拽左侧组件到画布创建节点。',
'nodes': '节点',
'connections': '连接',
// 运行工作流
'modal-run-workflow': '运行工作流',
'run-workflow-confirm': '确认要运行当前工作流吗?',
'save-before-run': '运行前保存工作流',
// 任务状态
'modal-task-status': '任务执行状态',
'task-progress': '进度',
'task-status-info': '状态信息',
'task-waiting': '等待中',
'task-id': '任务ID:',
'task-status': '状态:',
'task-start-time': '开始时间:',
'task-complete-time': '完成时间:',
'task-current-step': '当前步骤:',
'waiting-to-start': '等待开始',
'task-elapsed-time': '耗时:',
'task-result-preview': '结果预览',
'refresh-preview': '刷新预览',
'loading': '加载中...',
'task-running-preparing': '任务运行中,正在准备预览数据...',
'preview-after-task': '任务完成后将显示结果预览...',
'preview-error': '加载预览时发生错误'
}
},
'en-US': {
translation: {
// Page title
'page-title': 'Workflow Editor - Weibo Public Opinion Analysis System',
'navbar-brand': 'Workflow Editor',
// Navigation menu
'nav-visual-editor': 'Visual Editor',
'nav-template-mgmt': 'Template Management',
'nav-task-list': 'Task List',
// Buttons
'btn-save': 'Save',
'btn-run': 'Run',
'btn-cancel': 'Cancel',
'btn-close': 'Close',
'btn-create-new': 'Create New',
'btn-validate': 'Validate',
'btn-undo': 'Undo',
'btn-redo': 'Redo',
'btn-zoom-in': 'Zoom In',
'btn-zoom-out': 'Zoom Out',
'btn-fit-view': 'Fit View',
'btn-export': 'Export Workflow',
'btn-import': 'Import Workflow',
'btn-cancel-task': 'Cancel Task',
'btn-view-full-result': 'View Full Results',
// Tabs
'tab-components': 'Components',
'tab-templates': 'Templates',
// Component categories
'comp-data-source': 'Data Sources',
'comp-data-processing': 'Data Processing',
'comp-model-analysis': 'Model Analysis',
'comp-visualization': 'Visualization',
// Components
'comp-database': 'Database',
'comp-file': 'File',
'comp-crawler': 'Crawler',
'comp-filter': 'Filter',
'comp-sort': 'Sort',
'comp-aggregate': 'Aggregate',
'comp-sentiment': 'Sentiment Analysis',
'comp-topic': 'Topic Classification',
'comp-keywords': 'Keyword Extraction',
'comp-summarize': 'Text Summarization',
'comp-chart': 'Chart',
'comp-table': 'Table',
'comp-wordcloud': 'Word Cloud',
// Template related
'templates-crawler': 'Crawler Templates',
'templates-analysis': 'Analysis Flow Templates',
'modal-save-template': 'Save as Template',
'template-name': 'Template Name',
'template-description': 'Description',
'template-icon': 'Icon',
// Icon names
'icon-chart': 'Chart',
'icon-filter': 'Filter',
'icon-crawler': 'Crawler',
'icon-ai': 'AI Analysis',
'icon-database': 'Database',
'icon-wordcloud': 'Word Cloud',
// Properties panel
'properties-title': 'Component Properties',
// Workflow status
'workflow-status-message': 'Workflow ready. Drag components from the left panel to create nodes.',
'nodes': 'Nodes',
'connections': 'Connections',
// Run workflow
'modal-run-workflow': 'Run Workflow',
'run-workflow-confirm': 'Are you sure you want to run the current workflow?',
'save-before-run': 'Save workflow before running',
// Task status
'modal-task-status': 'Task Execution Status',
'task-progress': 'Progress',
'task-status-info': 'Status Information',
'task-waiting': 'Waiting',
'task-id': 'Task ID:',
'task-status': 'Status:',
'task-start-time': 'Start Time:',
'task-complete-time': 'Complete Time:',
'task-current-step': 'Current Step:',
'waiting-to-start': 'Waiting to start',
'task-elapsed-time': 'Elapsed Time:',
'task-result-preview': 'Result Preview',
'refresh-preview': 'Refresh Preview',
'loading': 'Loading...',
'task-running-preparing': 'Task is running, preparing preview data...',
'preview-after-task': 'Results preview will be displayed after the task is completed...',
'preview-error': 'Error loading preview'
}
}
};
\ No newline at end of file
... ...
let workflowEditorInitialized = false;
// 初始化i18next多语言支持
function initializeI18n() {
// 获取浏览器语言,默认为中文
const browserLang = navigator.language || 'zh-CN';
const defaultLang = browserLang.startsWith('zh') ? 'zh-CN' : 'en-US';
// 初始化i18next
i18next.init({
lng: localStorage.getItem('preferred_language') || defaultLang,
resources: i18nResources,
fallbackLng: 'zh-CN',
}).then(function(t) {
// 更新当前语言显示
updateLanguageDisplay();
// 应用翻译到所有元素
applyTranslations();
});
}
// 更新语言显示
function updateLanguageDisplay() {
const currentLang = i18next.language;
const displayName = currentLang === 'zh-CN' ? '中文' : 'English';
document.getElementById('currentLanguage').textContent = displayName;
}
// 应用翻译到所有元素
function applyTranslations() {
// 翻译data-i18n属性的元素
document.querySelectorAll('[data-i18n]').forEach(element => {
const key = element.getAttribute('data-i18n');
element.textContent = i18next.t(key);
});
// 翻译title属性
document.querySelectorAll('[data-i18n-title]').forEach(element => {
const key = element.getAttribute('data-i18n-title');
element.title = i18next.t(key);
});
// 更新页面标题
document.title = i18next.t('page-title');
}
// 切换语言
function switchLanguage(lang) {
// 保存语言偏好到本地存储
localStorage.setItem('preferred_language', lang);
// 更改i18next语言
i18next.changeLanguage(lang).then(() => {
// 更新语言显示
updateLanguageDisplay();
// 应用翻译
applyTranslations();
});
}
document.addEventListener('DOMContentLoaded', function() {
// 检查是否已初始化,防止多次执行
if (workflowEditorInitialized) {
... ... @@ -8,6 +68,17 @@ document.addEventListener('DOMContentLoaded', function() {
}
workflowEditorInitialized = true;
// 初始化多语言支持
initializeI18n();
// 添加语言切换事件
document.querySelectorAll('.language-option').forEach(option => {
option.addEventListener('click', function() {
const lang = this.getAttribute('data-lang');
switchLanguage(lang);
});
});
// 工作流编辑器的主要元素
const workflowCanvas = document.getElementById('workflowCanvas');
const connectionsSvg = document.getElementById('connectionsSvg');
... ...
... ... @@ -384,6 +384,7 @@
// 初始化页面
window.onload = function() {
updateLanguage(); // 设置初始语言
loadPredefinedTopics();
};
... ... @@ -807,12 +808,12 @@
const data = await response.json();
if (data.success) {
alert('数据库连接测试成功!');
alert(currentLang === 'zh' ? '数据库连接测试成功!' : 'Database connection test successful!');
} else {
alert('数据库连接测试失败:' + data.message);
alert((currentLang === 'zh' ? '数据库连接测试失败:' : 'Database connection test failed: ') + data.message);
}
} catch (error) {
alert('测试连接时发生错误:' + error.message);
alert((currentLang === 'zh' ? '测试连接时发生错误:' : 'Error during connection test: ') + error.message);
}
}
... ... @@ -857,13 +858,13 @@
.then(response => response.json())
.then(data => {
if (data.success) {
alert('配置已保存!');
alert(currentLang === 'zh' ? '配置已保存!' : 'Configuration saved!');
} else {
alert('保存失败:' + data.message);
alert((currentLang === 'zh' ? '保存失败:' : 'Save failed: ') + data.message);
}
})
.catch(error => {
alert('保存出错:' + error.message);
alert((currentLang === 'zh' ? '保存出错:' : 'Error saving: ') + error.message);
});
}
... ... @@ -891,7 +892,7 @@
async function generateConfig() {
const prompt = document.getElementById('aiPrompt').value.trim();
if (!prompt) {
alert('请输入您的爬虫需求描述!');
alert(currentLang === 'zh' ? '请输入您的爬虫需求描述!' : 'Please enter your crawler requirements!');
return;
}
... ... @@ -933,13 +934,13 @@
updateSelectedTopicsList();
// 添加提示
updateCrawlLog('AI配置已自动应用');
updateCrawlLog(currentLang === 'zh' ? 'AI配置已自动应用' : 'AI configuration automatically applied');
}
} else {
throw new Error(data.message);
}
} catch (error) {
aiSuggestion.textContent = '生成配置时出错:' + error.message;
aiSuggestion.textContent = (currentLang === 'zh' ? '生成配置时出错:' : 'Error generating configuration: ') + error.message;
aiResponse.style.display = 'block';
}
}
... ...
... ... @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工作流编辑器 - 微博舆情分析系统</title>
<title data-i18n="page-title">工作流编辑器 - 微博舆情分析系统</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.2.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/jsoneditor@9.5.0/dist/jsoneditor.min.css" rel="stylesheet">
... ... @@ -359,7 +359,7 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<i class="fas fa-project-diagram me-2"></i>工作流编辑器
<i class="fas fa-project-diagram me-2"></i><span data-i18n="navbar-brand">工作流编辑器</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
... ... @@ -367,21 +367,31 @@
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="#">可视化编辑</a>
<a class="nav-link active" href="#"><span data-i18n="nav-visual-editor">可视化编辑</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">模板管理</a>
<a class="nav-link" href="#"><span data-i18n="nav-template-mgmt">模板管理</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">任务列表</a>
<a class="nav-link" href="#"><span data-i18n="nav-task-list">任务列表</span></a>
</li>
</ul>
<div class="d-flex">
<!-- 添加语言切换按钮 -->
<div class="dropdown me-2">
<button class="btn btn-outline-light dropdown-toggle" type="button" id="languageDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-language me-1"></i><span id="currentLanguage">中文</span>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="languageDropdown">
<li><button class="dropdown-item language-option" data-lang="zh-CN">中文</button></li>
<li><button class="dropdown-item language-option" data-lang="en-US">English</button></li>
</ul>
</div>
<button id="saveWorkflowBtn" class="btn btn-success me-2">
<i class="fas fa-save me-1"></i>保存
<i class="fas fa-save me-1"></i><span data-i18n="btn-save">保存</span>
</button>
<button id="runWorkflowBtn" class="btn btn-primary">
<i class="fas fa-play me-1"></i>运行
<i class="fas fa-play me-1"></i><span data-i18n="btn-run">运行</span>
</button>
</div>
</div>
... ... @@ -394,72 +404,72 @@
<div class="col-md-3 col-lg-2 d-md-block sidebar">
<div class="d-flex justify-content-center mb-4">
<div class="btn-group">
<button class="btn btn-outline-primary active" id="componentsTabBtn">组件</button>
<button class="btn btn-outline-primary" id="templatesTabBtn">模板</button>
<button class="btn btn-outline-primary active" id="componentsTabBtn"><span data-i18n="tab-components">组件</span></button>
<button class="btn btn-outline-primary" id="templatesTabBtn"><span data-i18n="tab-templates">模板</span></button>
</div>
</div>
<!-- 组件面板保持不变 -->
<div id="componentsPanel">
<div class="component-container">
<h6><i class="fas fa-database me-2"></i>数据源</h6>
<h6><i class="fas fa-database me-2"></i><span data-i18n="comp-data-source">数据源</span></h6>
<div class="component-list">
<div class="component-item" data-type="data_source" data-subtype="database">
<i class="fas fa-table me-2"></i>数据库
<i class="fas fa-table me-2"></i><span data-i18n="comp-database">数据库</span>
</div>
<div class="component-item" data-type="data_source" data-subtype="file">
<i class="fas fa-file-alt me-2"></i>文件
<i class="fas fa-file-alt me-2"></i><span data-i18n="comp-file">文件</span>
</div>
<div class="component-item" data-type="data_source" data-subtype="crawler">
<i class="fas fa-spider me-2"></i>爬虫
<i class="fas fa-spider me-2"></i><span data-i18n="comp-crawler">爬虫</span>
</div>
</div>
</div>
<div class="component-container">
<h6><i class="fas fa-filter me-2"></i>数据处理</h6>
<h6><i class="fas fa-filter me-2"></i><span data-i18n="comp-data-processing">数据处理</span></h6>
<div class="component-list">
<div class="component-item" data-type="preprocessing" data-subtype="filter">
<i class="fas fa-filter me-2"></i>过滤
<i class="fas fa-filter me-2"></i><span data-i18n="comp-filter">过滤</span>
</div>
<div class="component-item" data-type="preprocessing" data-subtype="sort">
<i class="fas fa-sort me-2"></i>排序
<i class="fas fa-sort me-2"></i><span data-i18n="comp-sort">排序</span>
</div>
<div class="component-item" data-type="preprocessing" data-subtype="aggregate">
<i class="fas fa-layer-group me-2"></i>聚合
<i class="fas fa-layer-group me-2"></i><span data-i18n="comp-aggregate">聚合</span>
</div>
</div>
</div>
<div class="component-container">
<h6><i class="fas fa-brain me-2"></i>模型分析</h6>
<h6><i class="fas fa-brain me-2"></i><span data-i18n="comp-model-analysis">模型分析</span></h6>
<div class="component-list">
<div class="component-item" data-type="model" data-subtype="sentiment">
<i class="fas fa-smile me-2"></i>情感分析
<i class="fas fa-smile me-2"></i><span data-i18n="comp-sentiment">情感分析</span>
</div>
<div class="component-item" data-type="model" data-subtype="topic">
<i class="fas fa-tags me-2"></i>话题分类
<i class="fas fa-tags me-2"></i><span data-i18n="comp-topic">话题分类</span>
</div>
<div class="component-item" data-type="model" data-subtype="keywords">
<i class="fas fa-key me-2"></i>关键词提取
<i class="fas fa-key me-2"></i><span data-i18n="comp-keywords">关键词提取</span>
</div>
<div class="component-item" data-type="model" data-subtype="summarize">
<i class="fas fa-compress-alt me-2"></i>文本摘要
<i class="fas fa-compress-alt me-2"></i><span data-i18n="comp-summarize">文本摘要</span>
</div>
</div>
</div>
<div class="component-container">
<h6><i class="fas fa-chart-bar me-2"></i>可视化</h6>
<h6><i class="fas fa-chart-bar me-2"></i><span data-i18n="comp-visualization">可视化</span></h6>
<div class="component-list">
<div class="component-item" data-type="visualization" data-subtype="chart">
<i class="fas fa-chart-line me-2"></i>图表
<i class="fas fa-chart-line me-2"></i><span data-i18n="comp-chart">图表</span>
</div>
<div class="component-item" data-type="visualization" data-subtype="table">
<i class="fas fa-table me-2"></i>表格
<i class="fas fa-table me-2"></i><span data-i18n="comp-table">表格</span>
</div>
<div class="component-item" data-type="visualization" data-subtype="wordcloud">
<i class="fas fa-cloud me-2"></i>词云
<i class="fas fa-cloud me-2"></i><span data-i18n="comp-wordcloud">词云</span>
</div>
</div>
</div>
... ... @@ -469,9 +479,9 @@
<div id="templatesPanel" style="display: none;">
<div class="mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">爬虫模板</h6>
<h6 class="mb-0"><span data-i18n="templates-crawler">爬虫模板</span></h6>
<button class="btn btn-sm btn-outline-primary">
<i class="fas fa-plus"></i> 新建
<i class="fas fa-plus"></i> <span data-i18n="btn-create-new">新建</span>
</button>
</div>
<div class="templates-wrapper">
... ... @@ -483,9 +493,9 @@
<div class="mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">分析流程模板</h6>
<h6 class="mb-0"><span data-i18n="templates-analysis">分析流程模板</span></h6>
<button class="btn btn-sm btn-outline-primary">
<i class="fas fa-plus"></i> 新建
<i class="fas fa-plus"></i> <span data-i18n="btn-create-new">新建</span>
</button>
</div>
<div class="templates-wrapper">
... ... @@ -502,30 +512,30 @@
<!-- 添加工作流工具栏 -->
<div class="d-flex justify-content-between align-items-center mb-3" id="workflowToolbar">
<div class="btn-group">
<button id="undoBtn" class="btn btn-sm btn-outline-secondary" title="撤销">
<button id="undoBtn" class="btn btn-sm btn-outline-secondary" title="撤销" data-i18n-title="btn-undo">
<i class="fas fa-undo"></i>
</button>
<button id="redoBtn" class="btn btn-sm btn-outline-secondary" title="重做">
<button id="redoBtn" class="btn btn-sm btn-outline-secondary" title="重做" data-i18n-title="btn-redo">
<i class="fas fa-redo"></i>
</button>
<button id="zoomInBtn" class="btn btn-sm btn-outline-secondary" title="放大">
<button id="zoomInBtn" class="btn btn-sm btn-outline-secondary" title="放大" data-i18n-title="btn-zoom-in">
<i class="fas fa-search-plus"></i>
</button>
<button id="zoomOutBtn" class="btn btn-sm btn-outline-secondary" title="缩小">
<button id="zoomOutBtn" class="btn btn-sm btn-outline-secondary" title="缩小" data-i18n-title="btn-zoom-out">
<i class="fas fa-search-minus"></i>
</button>
<button id="fitViewBtn" class="btn btn-sm btn-outline-secondary" title="适应视图">
<button id="fitViewBtn" class="btn btn-sm btn-outline-secondary" title="适应视图" data-i18n-title="btn-fit-view">
<i class="fas fa-expand"></i>
</button>
</div>
<div>
<button id="validateWorkflowBtn" class="btn btn-sm btn-outline-primary" title="验证工作流">
<i class="fas fa-check-circle"></i> 验证
<button id="validateWorkflowBtn" class="btn btn-sm btn-outline-primary" title="验证工作流" data-i18n-title="btn-validate">
<i class="fas fa-check-circle"></i> <span data-i18n="btn-validate">验证</span>
</button>
<button id="exportWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导出工作流">
<button id="exportWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导出工作流" data-i18n-title="btn-export">
<i class="fas fa-file-export"></i>
</button>
<button id="importWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导入工作流">
<button id="importWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导入工作流" data-i18n-title="btn-import">
<i class="fas fa-file-import"></i>
</button>
</div>
... ... @@ -540,12 +550,12 @@
<!-- 添加工作流状态栏 -->
<div class="d-flex justify-content-between align-items-center p-2 bg-light rounded mt-3" id="workflowStatusBar" style="display: none !important;">
<div id="workflowStatusMessage" class="text-muted">
<div id="workflowStatusMessage" class="text-muted" data-i18n="workflow-status-message">
工作流就绪。拖拽左侧组件到画布创建节点。
</div>
<div class="d-flex">
<div class="me-3">节点: <span id="nodeCount">0</span></div>
<div>连接: <span id="connectionCount">0</span></div>
<div class="me-3"><span data-i18n="nodes">节点</span>: <span id="nodeCount">0</span></div>
<div><span data-i18n="connections">连接</span>: <span id="connectionCount">0</span></div>
</div>
</div>
</div>
... ... @@ -555,7 +565,7 @@
<!-- 属性面板 -->
<div class="properties-panel" id="propertiesPanel">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">组件属性</h5>
<h5 class="mb-0"><span data-i18n="properties-title">组件属性</span></h5>
<button class="btn-close" id="closePropertiesBtn"></button>
</div>
<div id="propertiesContent">
... ... @@ -568,35 +578,35 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">保存为模板</h5>
<h5 class="modal-title" data-i18n="modal-save-template">保存为模板</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="saveTemplateForm">
<div class="mb-3">
<label for="templateName" class="form-label">模板名称</label>
<label for="templateName" class="form-label" data-i18n="template-name">模板名称</label>
<input type="text" class="form-control" id="templateName" required>
</div>
<div class="mb-3">
<label for="templateDescription" class="form-label">描述</label>
<label for="templateDescription" class="form-label" data-i18n="template-description">描述</label>
<textarea class="form-control" id="templateDescription" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="templateIcon" class="form-label">图标</label>
<label for="templateIcon" class="form-label" data-i18n="template-icon">图标</label>
<select class="form-select" id="templateIcon">
<option value="chart-line">📊 图表</option>
<option value="filter">🔍 过滤</option>
<option value="spider">🕸️ 爬虫</option>
<option value="brain">🧠 AI分析</option>
<option value="database">💾 数据库</option>
<option value="cloud">☁️ 词云</option>
<option value="chart-line">📊 <span data-i18n="icon-chart">图表</span></option>
<option value="filter">🔍 <span data-i18n="icon-filter">过滤</span></option>
<option value="spider">🕸️ <span data-i18n="icon-crawler">爬虫</span></option>
<option value="brain">🧠 <span data-i18n="icon-ai">AI分析</span></option>
<option value="database">💾 <span data-i18n="icon-database">数据库</span></option>
<option value="cloud">☁️ <span data-i18n="icon-wordcloud">词云</span></option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveTemplateBtn">保存</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="btn-cancel">取消</button>
<button type="button" class="btn btn-primary" id="saveTemplateBtn" data-i18n="btn-save">保存</button>
</div>
</div>
</div>
... ... @@ -606,21 +616,21 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">运行工作流</h5>
<h5 class="modal-title" data-i18n="modal-run-workflow">运行工作流</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>确认要运行当前工作流吗?</p>
<p data-i18n="run-workflow-confirm">确认要运行当前工作流吗?</p>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="saveBeforeRun" checked>
<label class="form-check-label" for="saveBeforeRun">
<label class="form-check-label" for="saveBeforeRun" data-i18n="save-before-run">
运行前保存工作流
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="confirmRunBtn">运行</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="btn-cancel">取消</button>
<button type="button" class="btn btn-primary" id="confirmRunBtn" data-i18n="btn-run">运行</button>
</div>
</div>
</div>
... ... @@ -630,13 +640,13 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">任务执行状态</h5>
<h5 class="modal-title" data-i18n="modal-task-status">任务执行状态</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<h6 class="d-flex align-items-center">
<i class="fas fa-tasks me-2"></i>进度
<i class="fas fa-tasks me-2"></i><span data-i18n="task-progress">进度</span>
<div class="ms-auto" id="taskProgressPercentage">0%</div>
</h6>
<div class="progress" style="height: 10px;">
... ... @@ -645,46 +655,46 @@
</div>
<div class="mb-3">
<h6 class="d-flex align-items-center">
<i class="fas fa-info-circle me-2"></i>状态信息
<span id="taskStatusBadge" class="ms-2 badge bg-info">等待中</span>
<i class="fas fa-info-circle me-2"></i><span data-i18n="task-status-info">状态信息</span>
<span id="taskStatusBadge" class="ms-2 badge bg-info" data-i18n="task-waiting">等待中</span>
</h6>
<div id="taskStatusInfo" class="p-3 bg-light rounded border">
<div class="row g-2">
<div class="col-md-6">
<p class="mb-1"><strong>任务ID:</strong> <span id="taskIdDisplay">-</span></p>
<p class="mb-1"><strong>状态:</strong> <span id="taskStatusDisplay">-</span></p>
<p class="mb-1"><strong data-i18n="task-id">任务ID:</strong> <span id="taskIdDisplay">-</span></p>
<p class="mb-1"><strong data-i18n="task-status">状态:</strong> <span id="taskStatusDisplay">-</span></p>
</div>
<div class="col-md-6">
<p class="mb-1"><strong>开始时间:</strong> <span id="taskStartTimeDisplay">-</span></p>
<p class="mb-1"><strong>完成时间:</strong> <span id="taskCompleteTimeDisplay">-</span></p>
<p class="mb-1"><strong data-i18n="task-start-time">开始时间:</strong> <span id="taskStartTimeDisplay">-</span></p>
<p class="mb-1"><strong data-i18n="task-complete-time">完成时间:</strong> <span id="taskCompleteTimeDisplay">-</span></p>
</div>
</div>
<div class="mt-2" id="taskDetailsContainer">
<p class="mb-1"><strong>当前步骤:</strong> <span id="taskCurrentStepDisplay">等待开始</span></p>
<p class="mb-0"><strong>耗时:</strong> <span id="taskElapsedTimeDisplay">0秒</span></p>
<p class="mb-1"><strong data-i18n="task-current-step">当前步骤:</strong> <span id="taskCurrentStepDisplay" data-i18n="waiting-to-start">等待开始</span></p>
<p class="mb-0"><strong data-i18n="task-elapsed-time">耗时:</strong> <span id="taskElapsedTimeDisplay">0秒</span></p>
</div>
</div>
</div>
<div>
<h6 class="d-flex align-items-center">
<i class="fas fa-chart-bar me-2"></i>结果预览
<button class="btn btn-sm btn-outline-secondary ms-auto" id="refreshPreviewBtn" title="刷新预览">
<i class="fas fa-chart-bar me-2"></i><span data-i18n="task-result-preview">结果预览</span>
<button class="btn btn-sm btn-outline-secondary ms-auto" id="refreshPreviewBtn" title="刷新预览" data-i18n-title="refresh-preview">
<i class="fas fa-sync-alt"></i>
</button>
</h6>
<div id="taskResultPreview" class="p-3 bg-light rounded border" style="max-height: 300px; overflow: auto;">
<div class="text-center py-4" id="previewLoadingIndicator">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
<span class="visually-hidden" data-i18n="loading">加载中...</span>
</div>
<p class="text-muted mt-2">任务运行中,正在准备预览数据...</p>
<p class="text-muted mt-2" data-i18n="task-running-preparing">任务运行中,正在准备预览数据...</p>
</div>
<div id="previewContent" style="display: none;">
<p class="text-muted">任务完成后将显示结果预览...</p>
<p class="text-muted" data-i18n="preview-after-task">任务完成后将显示结果预览...</p>
</div>
<div id="previewError" class="alert alert-danger" style="display: none;">
<i class="fas fa-exclamation-triangle me-2"></i>
<span id="errorMessage">加载预览时发生错误</span>
<span id="errorMessage" data-i18n="preview-error">加载预览时发生错误</span>
</div>
</div>
<div class="mt-2 text-end">
... ... @@ -695,13 +705,13 @@
<div class="modal-footer d-flex justify-content-between">
<div>
<button type="button" class="btn btn-danger" id="cancelTaskBtn">
<i class="fas fa-stop-circle me-1"></i>取消任务
<i class="fas fa-stop-circle me-1"></i><span data-i18n="btn-cancel-task">取消任务</span>
</button>
</div>
<div>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="btn-close">关闭</button>
<button type="button" class="btn btn-primary" id="viewResultBtn">
<i class="fas fa-external-link-alt me-1"></i>查看完整结果
<i class="fas fa-external-link-alt me-1"></i><span data-i18n="btn-view-full-result">查看完整结果</span>
</button>
</div>
</div>
... ... @@ -715,6 +725,10 @@
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.5.0/dist/jsoneditor.min.js"></script>
<!-- 添加i18next库 -->
<script src="https://cdn.jsdelivr.net/npm/i18next@21.8.10/i18next.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-i18next@1.2.1/jquery-i18next.min.js"></script>
<script src="\static\js\i18n\translations.js"></script>
<script src="\static\js\workflow_editor.js"></script>
</body>
</html>
\ No newline at end of file
... ...