mirror of
https://github.com/666ghj/MiroFish.git
synced 2026-04-26 13:42:34 +00:00
Merge pull request #428 from Ghostubborn/feat/i18n
feat(i18n): 添加多语言切换功能,支持中英文
This commit is contained in:
commit
af71244974
39 changed files with 2612 additions and 766 deletions
|
|
@ -15,6 +15,7 @@ from ..services.graph_builder import GraphBuilderService
|
|||
from ..services.text_processor import TextProcessor
|
||||
from ..utils.file_parser import FileParser
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import t, get_locale, set_locale
|
||||
from ..models.task import TaskManager, TaskStatus
|
||||
from ..models.project import ProjectManager, ProjectStatus
|
||||
|
||||
|
|
@ -42,9 +43,9 @@ def get_project(project_id: str):
|
|||
if not project:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"项目不存在: {project_id}"
|
||||
"error": t('api.projectNotFound', id=project_id)
|
||||
}), 404
|
||||
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"data": project.to_dict()
|
||||
|
|
@ -76,12 +77,12 @@ def delete_project(project_id: str):
|
|||
if not success:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"项目不存在或删除失败: {project_id}"
|
||||
"error": t('api.projectDeleteFailed', id=project_id)
|
||||
}), 404
|
||||
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"项目已删除: {project_id}"
|
||||
"message": t('api.projectDeleted', id=project_id)
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -95,9 +96,9 @@ def reset_project(project_id: str):
|
|||
if not project:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"项目不存在: {project_id}"
|
||||
"error": t('api.projectNotFound', id=project_id)
|
||||
}), 404
|
||||
|
||||
|
||||
# 重置到本体已生成状态
|
||||
if project.ontology:
|
||||
project.status = ProjectStatus.ONTOLOGY_GENERATED
|
||||
|
|
@ -111,7 +112,7 @@ def reset_project(project_id: str):
|
|||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"项目已重置: {project_id}",
|
||||
"message": t('api.projectReset', id=project_id),
|
||||
"data": project.to_dict()
|
||||
})
|
||||
|
||||
|
|
@ -160,7 +161,7 @@ def generate_ontology():
|
|||
if not simulation_requirement:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供模拟需求描述 (simulation_requirement)"
|
||||
"error": t('api.requireSimulationRequirement')
|
||||
}), 400
|
||||
|
||||
# 获取上传的文件
|
||||
|
|
@ -168,7 +169,7 @@ def generate_ontology():
|
|||
if not uploaded_files or all(not f.filename for f in uploaded_files):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请至少上传一个文档文件"
|
||||
"error": t('api.requireFileUpload')
|
||||
}), 400
|
||||
|
||||
# 创建项目
|
||||
|
|
@ -203,7 +204,7 @@ def generate_ontology():
|
|||
ProjectManager.delete_project(project.project_id)
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "没有成功处理任何文档,请检查文件格式"
|
||||
"error": t('api.noDocProcessed')
|
||||
}), 400
|
||||
|
||||
# 保存提取的文本
|
||||
|
|
@ -285,12 +286,12 @@ def build_graph():
|
|||
# 检查配置
|
||||
errors = []
|
||||
if not Config.ZEP_API_KEY:
|
||||
errors.append("ZEP_API_KEY未配置")
|
||||
errors.append(t('api.zepApiKeyMissing'))
|
||||
if errors:
|
||||
logger.error(f"配置错误: {errors}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "配置错误: " + "; ".join(errors)
|
||||
"error": t('api.configError', details="; ".join(errors))
|
||||
}), 500
|
||||
|
||||
# 解析请求
|
||||
|
|
@ -301,7 +302,7 @@ def build_graph():
|
|||
if not project_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 project_id"
|
||||
"error": t('api.requireProjectId')
|
||||
}), 400
|
||||
|
||||
# 获取项目
|
||||
|
|
@ -309,22 +310,22 @@ def build_graph():
|
|||
if not project:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"项目不存在: {project_id}"
|
||||
"error": t('api.projectNotFound', id=project_id)
|
||||
}), 404
|
||||
|
||||
|
||||
# 检查项目状态
|
||||
force = data.get('force', False) # 强制重新构建
|
||||
|
||||
if project.status == ProjectStatus.CREATED:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "项目尚未生成本体,请先调用 /ontology/generate"
|
||||
"error": t('api.ontologyNotGenerated')
|
||||
}), 400
|
||||
|
||||
if project.status == ProjectStatus.GRAPH_BUILDING and not force:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "图谱正在构建中,请勿重复提交。如需强制重建,请添加 force: true",
|
||||
"error": t('api.graphBuilding'),
|
||||
"task_id": project.graph_build_task_id
|
||||
}), 400
|
||||
|
||||
|
|
@ -349,7 +350,7 @@ def build_graph():
|
|||
if not text:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "未找到提取的文本内容"
|
||||
"error": t('api.textNotFound')
|
||||
}), 400
|
||||
|
||||
# 获取本体
|
||||
|
|
@ -357,7 +358,7 @@ def build_graph():
|
|||
if not ontology:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "未找到本体定义"
|
||||
"error": t('api.ontologyNotFound')
|
||||
}), 400
|
||||
|
||||
# 创建异步任务
|
||||
|
|
@ -370,15 +371,19 @@ def build_graph():
|
|||
project.graph_build_task_id = task_id
|
||||
ProjectManager.save_project(project)
|
||||
|
||||
# Capture locale before spawning background thread
|
||||
current_locale = get_locale()
|
||||
|
||||
# 启动后台任务
|
||||
def build_task():
|
||||
set_locale(current_locale)
|
||||
build_logger = get_logger('mirofish.build')
|
||||
try:
|
||||
build_logger.info(f"[{task_id}] 开始构建图谱...")
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.PROCESSING,
|
||||
message="初始化图谱构建服务..."
|
||||
message=t('progress.initGraphService')
|
||||
)
|
||||
|
||||
# 创建图谱构建服务
|
||||
|
|
@ -387,7 +392,7 @@ def build_graph():
|
|||
# 分块
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="文本分块中...",
|
||||
message=t('progress.textChunking'),
|
||||
progress=5
|
||||
)
|
||||
chunks = TextProcessor.split_text(
|
||||
|
|
@ -400,7 +405,7 @@ def build_graph():
|
|||
# 创建图谱
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="创建Zep图谱...",
|
||||
message=t('progress.creatingZepGraph'),
|
||||
progress=10
|
||||
)
|
||||
graph_id = builder.create_graph(name=graph_name)
|
||||
|
|
@ -412,7 +417,7 @@ def build_graph():
|
|||
# 设置本体
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="设置本体定义...",
|
||||
message=t('progress.settingOntology'),
|
||||
progress=15
|
||||
)
|
||||
builder.set_ontology(graph_id, ontology)
|
||||
|
|
@ -428,7 +433,7 @@ def build_graph():
|
|||
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message=f"开始添加 {total_chunks} 个文本块...",
|
||||
message=t('progress.addingChunks', count=total_chunks),
|
||||
progress=15
|
||||
)
|
||||
|
||||
|
|
@ -442,7 +447,7 @@ def build_graph():
|
|||
# 等待Zep处理完成(查询每个episode的processed状态)
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="等待Zep处理数据...",
|
||||
message=t('progress.waitingZepProcess'),
|
||||
progress=55
|
||||
)
|
||||
|
||||
|
|
@ -459,7 +464,7 @@ def build_graph():
|
|||
# 获取图谱数据
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="获取图谱数据...",
|
||||
message=t('progress.fetchingGraphData'),
|
||||
progress=95
|
||||
)
|
||||
graph_data = builder.get_graph_data(graph_id)
|
||||
|
|
@ -476,7 +481,7 @@ def build_graph():
|
|||
task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.COMPLETED,
|
||||
message="图谱构建完成",
|
||||
message=t('progress.graphBuildComplete'),
|
||||
progress=100,
|
||||
result={
|
||||
"project_id": project_id,
|
||||
|
|
@ -499,7 +504,7 @@ def build_graph():
|
|||
task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.FAILED,
|
||||
message=f"构建失败: {str(e)}",
|
||||
message=t('progress.buildFailed', error=str(e)),
|
||||
error=traceback.format_exc()
|
||||
)
|
||||
|
||||
|
|
@ -512,7 +517,7 @@ def build_graph():
|
|||
"data": {
|
||||
"project_id": project_id,
|
||||
"task_id": task_id,
|
||||
"message": "图谱构建任务已启动,请通过 /task/{task_id} 查询进度"
|
||||
"message": t('api.graphBuildStarted', taskId=task_id)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -536,7 +541,7 @@ def get_task(task_id: str):
|
|||
if not task:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"任务不存在: {task_id}"
|
||||
"error": t('api.taskNotFound', id=task_id)
|
||||
}), 404
|
||||
|
||||
return jsonify({
|
||||
|
|
@ -570,7 +575,7 @@ def get_graph_data(graph_id: str):
|
|||
if not Config.ZEP_API_KEY:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "ZEP_API_KEY未配置"
|
||||
"error": t('api.zepApiKeyMissing')
|
||||
}), 500
|
||||
|
||||
builder = GraphBuilderService(api_key=Config.ZEP_API_KEY)
|
||||
|
|
@ -598,7 +603,7 @@ def delete_graph(graph_id: str):
|
|||
if not Config.ZEP_API_KEY:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "ZEP_API_KEY未配置"
|
||||
"error": t('api.zepApiKeyMissing')
|
||||
}), 500
|
||||
|
||||
builder = GraphBuilderService(api_key=Config.ZEP_API_KEY)
|
||||
|
|
@ -606,7 +611,7 @@ def delete_graph(graph_id: str):
|
|||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"图谱已删除: {graph_id}"
|
||||
"message": t('api.graphDeleted', id=graph_id)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from ..services.simulation_manager import SimulationManager
|
|||
from ..models.project import ProjectManager
|
||||
from ..models.task import TaskManager, TaskStatus
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import t, get_locale, set_locale
|
||||
|
||||
logger = get_logger('mirofish.api.report')
|
||||
|
||||
|
|
@ -53,9 +54,9 @@ def generate_report():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
|
||||
force_regenerate = data.get('force_regenerate', False)
|
||||
|
||||
# 获取模拟信息
|
||||
|
|
@ -65,9 +66,9 @@ def generate_report():
|
|||
if not state:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟不存在: {simulation_id}"
|
||||
"error": t('api.simulationNotFound', id=simulation_id)
|
||||
}), 404
|
||||
|
||||
|
||||
# 检查是否已有报告
|
||||
if not force_regenerate:
|
||||
existing_report = ReportManager.get_report_by_simulation(simulation_id)
|
||||
|
|
@ -78,7 +79,7 @@ def generate_report():
|
|||
"simulation_id": simulation_id,
|
||||
"report_id": existing_report.report_id,
|
||||
"status": "completed",
|
||||
"message": "报告已存在",
|
||||
"message": t('api.reportAlreadyExists'),
|
||||
"already_generated": True
|
||||
}
|
||||
})
|
||||
|
|
@ -88,21 +89,21 @@ def generate_report():
|
|||
if not project:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"项目不存在: {state.project_id}"
|
||||
"error": t('api.projectNotFound', id=state.project_id)
|
||||
}), 404
|
||||
|
||||
graph_id = state.graph_id or project.graph_id
|
||||
if not graph_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "缺少图谱ID,请确保已构建图谱"
|
||||
"error": t('api.missingGraphIdEnsure')
|
||||
}), 400
|
||||
|
||||
simulation_requirement = project.simulation_requirement
|
||||
if not simulation_requirement:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "缺少模拟需求描述"
|
||||
"error": t('api.missingSimRequirement')
|
||||
}), 400
|
||||
|
||||
# 提前生成 report_id,以便立即返回给前端
|
||||
|
|
@ -120,14 +121,18 @@ def generate_report():
|
|||
}
|
||||
)
|
||||
|
||||
# Capture locale before spawning background thread
|
||||
current_locale = get_locale()
|
||||
|
||||
# 定义后台任务
|
||||
def run_generate():
|
||||
set_locale(current_locale)
|
||||
try:
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.PROCESSING,
|
||||
progress=0,
|
||||
message="初始化Report Agent..."
|
||||
message=t('api.initReportAgent')
|
||||
)
|
||||
|
||||
# 创建Report Agent
|
||||
|
|
@ -164,7 +169,7 @@ def generate_report():
|
|||
}
|
||||
)
|
||||
else:
|
||||
task_manager.fail_task(task_id, report.error or "报告生成失败")
|
||||
task_manager.fail_task(task_id, report.error or t('api.reportGenerateFailed'))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"报告生成失败: {str(e)}")
|
||||
|
|
@ -181,7 +186,7 @@ def generate_report():
|
|||
"report_id": report_id,
|
||||
"task_id": task_id,
|
||||
"status": "generating",
|
||||
"message": "报告生成任务已启动,请通过 /api/report/generate/status 查询进度",
|
||||
"message": t('api.reportGenerateStarted'),
|
||||
"already_generated": False
|
||||
}
|
||||
})
|
||||
|
|
@ -234,7 +239,7 @@ def get_generate_status():
|
|||
"report_id": existing_report.report_id,
|
||||
"status": "completed",
|
||||
"progress": 100,
|
||||
"message": "报告已生成",
|
||||
"message": t('api.reportGenerated'),
|
||||
"already_completed": True
|
||||
}
|
||||
})
|
||||
|
|
@ -242,7 +247,7 @@ def get_generate_status():
|
|||
if not task_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 task_id 或 simulation_id"
|
||||
"error": t('api.requireTaskOrSimId')
|
||||
}), 400
|
||||
|
||||
task_manager = TaskManager()
|
||||
|
|
@ -251,7 +256,7 @@ def get_generate_status():
|
|||
if not task:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"任务不存在: {task_id}"
|
||||
"error": t('api.taskNotFound', id=task_id)
|
||||
}), 404
|
||||
|
||||
return jsonify({
|
||||
|
|
@ -294,7 +299,7 @@ def get_report(report_id: str):
|
|||
if not report:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"报告不存在: {report_id}"
|
||||
"error": t('api.reportNotFound', id=report_id)
|
||||
}), 404
|
||||
|
||||
return jsonify({
|
||||
|
|
@ -331,7 +336,7 @@ def get_report_by_simulation(simulation_id: str):
|
|||
if not report:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"该模拟暂无报告: {simulation_id}",
|
||||
"error": t('api.noReportForSim', id=simulation_id),
|
||||
"has_report": False
|
||||
}), 404
|
||||
|
||||
|
|
@ -403,7 +408,7 @@ def download_report(report_id: str):
|
|||
if not report:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"报告不存在: {report_id}"
|
||||
"error": t('api.reportNotFound', id=report_id)
|
||||
}), 404
|
||||
|
||||
md_path = ReportManager._get_report_markdown_path(report_id)
|
||||
|
|
@ -445,12 +450,12 @@ def delete_report(report_id: str):
|
|||
if not success:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"报告不存在: {report_id}"
|
||||
"error": t('api.reportNotFound', id=report_id)
|
||||
}), 404
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"报告已删除: {report_id}"
|
||||
"message": t('api.reportDeleted', id=report_id)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -501,13 +506,13 @@ def chat_with_report_agent():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
|
||||
if not message:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 message"
|
||||
"error": t('api.requireMessage')
|
||||
}), 400
|
||||
|
||||
# 获取模拟和项目信息
|
||||
|
|
@ -517,21 +522,21 @@ def chat_with_report_agent():
|
|||
if not state:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟不存在: {simulation_id}"
|
||||
"error": t('api.simulationNotFound', id=simulation_id)
|
||||
}), 404
|
||||
|
||||
|
||||
project = ProjectManager.get_project(state.project_id)
|
||||
if not project:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"项目不存在: {state.project_id}"
|
||||
"error": t('api.projectNotFound', id=state.project_id)
|
||||
}), 404
|
||||
|
||||
graph_id = state.graph_id or project.graph_id
|
||||
if not graph_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "缺少图谱ID"
|
||||
"error": t('api.missingGraphId')
|
||||
}), 400
|
||||
|
||||
simulation_requirement = project.simulation_requirement or ""
|
||||
|
|
@ -585,7 +590,7 @@ def get_report_progress(report_id: str):
|
|||
if not progress:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"报告不存在或进度信息不可用: {report_id}"
|
||||
"error": t('api.reportProgressNotAvail', id=report_id)
|
||||
}), 404
|
||||
|
||||
return jsonify({
|
||||
|
|
@ -673,7 +678,7 @@ def get_single_section(report_id: str, section_index: int):
|
|||
if not os.path.exists(section_path):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"章节不存在: section_{section_index:02d}.md"
|
||||
"error": t('api.sectionNotFound', index=f"{section_index:02d}")
|
||||
}), 404
|
||||
|
||||
with open(section_path, 'r', encoding='utf-8') as f:
|
||||
|
|
@ -949,7 +954,7 @@ def search_graph_tool():
|
|||
if not graph_id or not query:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 graph_id 和 query"
|
||||
"error": t('api.requireGraphIdAndQuery')
|
||||
}), 400
|
||||
|
||||
from ..services.zep_tools import ZepToolsService
|
||||
|
|
@ -993,7 +998,7 @@ def get_graph_statistics_tool():
|
|||
if not graph_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 graph_id"
|
||||
"error": t('api.requireGraphId')
|
||||
}), 400
|
||||
|
||||
from ..services.zep_tools import ZepToolsService
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from ..services.oasis_profile_generator import OasisProfileGenerator
|
|||
from ..services.simulation_manager import SimulationManager, SimulationStatus
|
||||
from ..services.simulation_runner import SimulationRunner, RunnerStatus
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import t, get_locale, set_locale
|
||||
from ..models.project import ProjectManager
|
||||
|
||||
logger = get_logger('mirofish.api.simulation')
|
||||
|
|
@ -59,7 +60,7 @@ def get_graph_entities(graph_id: str):
|
|||
if not Config.ZEP_API_KEY:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "ZEP_API_KEY未配置"
|
||||
"error": t('api.zepApiKeyMissing')
|
||||
}), 500
|
||||
|
||||
entity_types_str = request.args.get('entity_types', '')
|
||||
|
|
@ -96,7 +97,7 @@ def get_entity_detail(graph_id: str, entity_uuid: str):
|
|||
if not Config.ZEP_API_KEY:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "ZEP_API_KEY未配置"
|
||||
"error": t('api.zepApiKeyMissing')
|
||||
}), 500
|
||||
|
||||
reader = ZepEntityReader()
|
||||
|
|
@ -105,7 +106,7 @@ def get_entity_detail(graph_id: str, entity_uuid: str):
|
|||
if not entity:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"实体不存在: {entity_uuid}"
|
||||
"error": t('api.entityNotFound', id=entity_uuid)
|
||||
}), 404
|
||||
|
||||
return jsonify({
|
||||
|
|
@ -129,7 +130,7 @@ def get_entities_by_type(graph_id: str, entity_type: str):
|
|||
if not Config.ZEP_API_KEY:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "ZEP_API_KEY未配置"
|
||||
"error": t('api.zepApiKeyMissing')
|
||||
}), 500
|
||||
|
||||
enrich = request.args.get('enrich', 'true').lower() == 'true'
|
||||
|
|
@ -197,21 +198,21 @@ def create_simulation():
|
|||
if not project_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 project_id"
|
||||
"error": t('api.requireProjectId')
|
||||
}), 400
|
||||
|
||||
project = ProjectManager.get_project(project_id)
|
||||
if not project:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"项目不存在: {project_id}"
|
||||
"error": t('api.projectNotFound', id=project_id)
|
||||
}), 404
|
||||
|
||||
graph_id = data.get('graph_id') or project.graph_id
|
||||
if not graph_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "项目尚未构建图谱,请先调用 /api/graph/build"
|
||||
"error": t('api.graphNotBuilt')
|
||||
}), 400
|
||||
|
||||
manager = SimulationManager()
|
||||
|
|
@ -408,7 +409,7 @@ def prepare_simulation():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
manager = SimulationManager()
|
||||
|
|
@ -417,7 +418,7 @@ def prepare_simulation():
|
|||
if not state:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟不存在: {simulation_id}"
|
||||
"error": t('api.simulationNotFound', id=simulation_id)
|
||||
}), 404
|
||||
|
||||
# 检查是否强制重新生成
|
||||
|
|
@ -436,7 +437,7 @@ def prepare_simulation():
|
|||
"data": {
|
||||
"simulation_id": simulation_id,
|
||||
"status": "ready",
|
||||
"message": "已有完成的准备工作,无需重复生成",
|
||||
"message": t('api.alreadyPrepared'),
|
||||
"already_prepared": True,
|
||||
"prepare_info": prepare_info
|
||||
}
|
||||
|
|
@ -449,7 +450,7 @@ def prepare_simulation():
|
|||
if not project:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"项目不存在: {state.project_id}"
|
||||
"error": t('api.projectNotFound', id=state.project_id)
|
||||
}), 404
|
||||
|
||||
# 获取模拟需求
|
||||
|
|
@ -457,7 +458,7 @@ def prepare_simulation():
|
|||
if not simulation_requirement:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "项目缺少模拟需求描述 (simulation_requirement)"
|
||||
"error": t('api.projectMissingRequirement')
|
||||
}), 400
|
||||
|
||||
# 获取文档文本
|
||||
|
|
@ -500,14 +501,18 @@ def prepare_simulation():
|
|||
state.status = SimulationStatus.PREPARING
|
||||
manager._save_simulation_state(state)
|
||||
|
||||
# Capture locale before spawning background thread
|
||||
current_locale = get_locale()
|
||||
|
||||
# 定义后台任务
|
||||
def run_prepare():
|
||||
set_locale(current_locale)
|
||||
try:
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.PROCESSING,
|
||||
progress=0,
|
||||
message="开始准备模拟环境..."
|
||||
message=t('progress.startPreparingEnv')
|
||||
)
|
||||
|
||||
# 准备模拟(带进度回调)
|
||||
|
|
@ -528,10 +533,10 @@ def prepare_simulation():
|
|||
|
||||
# 构建详细进度信息
|
||||
stage_names = {
|
||||
"reading": "读取图谱实体",
|
||||
"generating_profiles": "生成Agent人设",
|
||||
"generating_config": "生成模拟配置",
|
||||
"copying_scripts": "准备模拟脚本"
|
||||
"reading": t('progress.readingGraphEntities'),
|
||||
"generating_profiles": t('progress.generatingProfiles'),
|
||||
"generating_config": t('progress.generatingSimConfig'),
|
||||
"copying_scripts": t('progress.preparingScripts')
|
||||
}
|
||||
|
||||
stage_index = list(stage_weights.keys()).index(stage) + 1 if stage in stage_weights else 1
|
||||
|
|
@ -612,7 +617,7 @@ def prepare_simulation():
|
|||
"simulation_id": simulation_id,
|
||||
"task_id": task_id,
|
||||
"status": "preparing",
|
||||
"message": "准备任务已启动,请通过 /api/simulation/prepare/status 查询进度",
|
||||
"message": t('api.prepareStarted'),
|
||||
"already_prepared": False,
|
||||
"expected_entities_count": state.entities_count, # 预期的Agent总数
|
||||
"entity_types": state.entity_types # 实体类型列表
|
||||
|
|
@ -680,7 +685,7 @@ def get_prepare_status():
|
|||
"simulation_id": simulation_id,
|
||||
"status": "ready",
|
||||
"progress": 100,
|
||||
"message": "已有完成的准备工作",
|
||||
"message": t('api.alreadyPrepared'),
|
||||
"already_prepared": True,
|
||||
"prepare_info": prepare_info
|
||||
}
|
||||
|
|
@ -696,13 +701,13 @@ def get_prepare_status():
|
|||
"simulation_id": simulation_id,
|
||||
"status": "not_started",
|
||||
"progress": 0,
|
||||
"message": "尚未开始准备,请调用 /api/simulation/prepare 开始",
|
||||
"message": t('api.notStartedPrepare'),
|
||||
"already_prepared": False
|
||||
}
|
||||
})
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 task_id 或 simulation_id"
|
||||
"error": t('api.requireTaskOrSimId')
|
||||
}), 400
|
||||
|
||||
task_manager = TaskManager()
|
||||
|
|
@ -720,7 +725,7 @@ def get_prepare_status():
|
|||
"task_id": task_id,
|
||||
"status": "ready",
|
||||
"progress": 100,
|
||||
"message": "任务已完成(准备工作已存在)",
|
||||
"message": t('api.taskCompletedPrepared'),
|
||||
"already_prepared": True,
|
||||
"prepare_info": prepare_info
|
||||
}
|
||||
|
|
@ -728,7 +733,7 @@ def get_prepare_status():
|
|||
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"任务不存在: {task_id}"
|
||||
"error": t('api.taskNotFound', id=task_id)
|
||||
}), 404
|
||||
|
||||
task_dict = task.to_dict()
|
||||
|
|
@ -757,7 +762,7 @@ def get_simulation(simulation_id: str):
|
|||
if not state:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟不存在: {simulation_id}"
|
||||
"error": t('api.simulationNotFound', id=simulation_id)
|
||||
}), 404
|
||||
|
||||
result = state.to_dict()
|
||||
|
|
@ -1061,7 +1066,7 @@ def get_simulation_profiles_realtime(simulation_id: str):
|
|||
if not os.path.exists(sim_dir):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟不存在: {simulation_id}"
|
||||
"error": t('api.simulationNotFound', id=simulation_id)
|
||||
}), 404
|
||||
|
||||
# 确定文件路径
|
||||
|
|
@ -1164,7 +1169,7 @@ def get_simulation_config_realtime(simulation_id: str):
|
|||
if not os.path.exists(sim_dir):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟不存在: {simulation_id}"
|
||||
"error": t('api.simulationNotFound', id=simulation_id)
|
||||
}), 404
|
||||
|
||||
# 配置文件路径
|
||||
|
|
@ -1269,7 +1274,7 @@ def get_simulation_config(simulation_id: str):
|
|||
if not config:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟配置不存在,请先调用 /prepare 接口"
|
||||
"error": t('api.configNotFound')
|
||||
}), 404
|
||||
|
||||
return jsonify({
|
||||
|
|
@ -1297,7 +1302,7 @@ def download_simulation_config(simulation_id: str):
|
|||
if not os.path.exists(config_path):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "配置文件不存在,请先调用 /prepare 接口"
|
||||
"error": t('api.configFileNotFound')
|
||||
}), 404
|
||||
|
||||
return send_file(
|
||||
|
|
@ -1341,7 +1346,7 @@ def download_simulation_script(script_name: str):
|
|||
if script_name not in allowed_scripts:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"未知脚本: {script_name},可选: {allowed_scripts}"
|
||||
"error": t('api.unknownScript', name=script_name, allowed=allowed_scripts)
|
||||
}), 400
|
||||
|
||||
script_path = os.path.join(scripts_dir, script_name)
|
||||
|
|
@ -1349,7 +1354,7 @@ def download_simulation_script(script_name: str):
|
|||
if not os.path.exists(script_path):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"脚本文件不存在: {script_name}"
|
||||
"error": t('api.scriptFileNotFound', name=script_name)
|
||||
}), 404
|
||||
|
||||
return send_file(
|
||||
|
|
@ -1389,7 +1394,7 @@ def generate_profiles():
|
|||
if not graph_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 graph_id"
|
||||
"error": t('api.requireGraphId')
|
||||
}), 400
|
||||
|
||||
entity_types = data.get('entity_types')
|
||||
|
|
@ -1406,7 +1411,7 @@ def generate_profiles():
|
|||
if filtered.filtered_count == 0:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "没有找到符合条件的实体"
|
||||
"error": t('api.noMatchingEntities')
|
||||
}), 400
|
||||
|
||||
generator = OasisProfileGenerator()
|
||||
|
|
@ -1491,7 +1496,7 @@ def start_simulation():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
platform = data.get('platform', 'parallel')
|
||||
|
|
@ -1506,18 +1511,18 @@ def start_simulation():
|
|||
if max_rounds <= 0:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "max_rounds 必须是正整数"
|
||||
"error": t('api.maxRoundsPositive')
|
||||
}), 400
|
||||
except (ValueError, TypeError):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "max_rounds 必须是有效的整数"
|
||||
"error": t('api.maxRoundsInvalid')
|
||||
}), 400
|
||||
|
||||
if platform not in ['twitter', 'reddit', 'parallel']:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"无效的平台类型: {platform},可选: twitter/reddit/parallel"
|
||||
"error": t('api.invalidPlatform', platform=platform)
|
||||
}), 400
|
||||
|
||||
# 检查模拟是否已准备好
|
||||
|
|
@ -1527,7 +1532,7 @@ def start_simulation():
|
|||
if not state:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟不存在: {simulation_id}"
|
||||
"error": t('api.simulationNotFound', id=simulation_id)
|
||||
}), 404
|
||||
|
||||
force_restarted = False
|
||||
|
|
@ -1554,7 +1559,7 @@ def start_simulation():
|
|||
else:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟正在运行中,请先调用 /stop 接口停止,或使用 force=true 强制重新开始"
|
||||
"error": t('api.simRunningForceHint')
|
||||
}), 400
|
||||
|
||||
# 如果是强制模式,清理运行日志
|
||||
|
|
@ -1573,7 +1578,7 @@ def start_simulation():
|
|||
# 准备工作未完成
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"模拟未准备好,当前状态: {state.status.value},请先调用 /prepare 接口"
|
||||
"error": t('api.simNotReady', status=state.status.value)
|
||||
}), 400
|
||||
|
||||
# 获取图谱ID(用于图谱记忆更新)
|
||||
|
|
@ -1590,7 +1595,7 @@ def start_simulation():
|
|||
if not graph_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "启用图谱记忆更新需要有效的 graph_id,请确保项目已构建图谱"
|
||||
"error": t('api.graphIdRequiredForMemory')
|
||||
}), 400
|
||||
|
||||
logger.info(f"启用图谱记忆更新: simulation_id={simulation_id}, graph_id={graph_id}")
|
||||
|
|
@ -1663,7 +1668,7 @@ def stop_simulation():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
run_state = SimulationRunner.stop_simulation(simulation_id)
|
||||
|
|
@ -2011,7 +2016,7 @@ def get_simulation_posts(simulation_id: str):
|
|||
"platform": platform,
|
||||
"count": 0,
|
||||
"posts": [],
|
||||
"message": "数据库不存在,模拟可能尚未运行"
|
||||
"message": t('api.dbNotExist')
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -2197,33 +2202,33 @@ def interview_agent():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
if agent_id is None:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 agent_id"
|
||||
"error": t('api.requireAgentId')
|
||||
}), 400
|
||||
|
||||
if not prompt:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 prompt(采访问题)"
|
||||
"error": t('api.requirePrompt')
|
||||
}), 400
|
||||
|
||||
# 验证platform参数
|
||||
if platform and platform not in ("twitter", "reddit"):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "platform 参数只能是 'twitter' 或 'reddit'"
|
||||
"error": t('api.invalidInterviewPlatform')
|
||||
}), 400
|
||||
|
||||
# 检查环境状态
|
||||
if not SimulationRunner.check_env_alive(simulation_id):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "模拟环境未运行或已关闭。请确保模拟已完成并进入等待命令模式。"
|
||||
"error": t('api.envNotRunning')
|
||||
}), 400
|
||||
|
||||
# 优化prompt,添加前缀避免Agent调用工具
|
||||
|
|
@ -2251,7 +2256,7 @@ def interview_agent():
|
|||
except TimeoutError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"等待Interview响应超时: {str(e)}"
|
||||
"error": t('api.interviewTimeout', error=str(e))
|
||||
}), 504
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -2318,20 +2323,20 @@ def interview_agents_batch():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
if not interviews or not isinstance(interviews, list):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 interviews(采访列表)"
|
||||
"error": t('api.requireInterviews')
|
||||
}), 400
|
||||
|
||||
# 验证platform参数
|
||||
if platform and platform not in ("twitter", "reddit"):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "platform 参数只能是 'twitter' 或 'reddit'"
|
||||
"error": t('api.invalidInterviewPlatform')
|
||||
}), 400
|
||||
|
||||
# 验证每个采访项
|
||||
|
|
@ -2339,26 +2344,26 @@ def interview_agents_batch():
|
|||
if 'agent_id' not in interview:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"采访列表第{i+1}项缺少 agent_id"
|
||||
"error": t('api.interviewListMissingAgentId', index=i+1)
|
||||
}), 400
|
||||
if 'prompt' not in interview:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"采访列表第{i+1}项缺少 prompt"
|
||||
"error": t('api.interviewListMissingPrompt', index=i+1)
|
||||
}), 400
|
||||
# 验证每项的platform(如果有)
|
||||
item_platform = interview.get('platform')
|
||||
if item_platform and item_platform not in ("twitter", "reddit"):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"采访列表第{i+1}项的platform只能是 'twitter' 或 'reddit'"
|
||||
"error": t('api.interviewListInvalidPlatform', index=i+1)
|
||||
}), 400
|
||||
|
||||
# 检查环境状态
|
||||
if not SimulationRunner.check_env_alive(simulation_id):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "模拟环境未运行或已关闭。请确保模拟已完成并进入等待命令模式。"
|
||||
"error": t('api.envNotRunning')
|
||||
}), 400
|
||||
|
||||
# 优化每个采访项的prompt,添加前缀避免Agent调用工具
|
||||
|
|
@ -2389,7 +2394,7 @@ def interview_agents_batch():
|
|||
except TimeoutError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"等待批量Interview响应超时: {str(e)}"
|
||||
"error": t('api.batchInterviewTimeout', error=str(e))
|
||||
}), 504
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -2445,27 +2450,27 @@ def interview_all_agents():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
if not prompt:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 prompt(采访问题)"
|
||||
"error": t('api.requirePrompt')
|
||||
}), 400
|
||||
|
||||
# 验证platform参数
|
||||
if platform and platform not in ("twitter", "reddit"):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "platform 参数只能是 'twitter' 或 'reddit'"
|
||||
"error": t('api.invalidInterviewPlatform')
|
||||
}), 400
|
||||
|
||||
# 检查环境状态
|
||||
if not SimulationRunner.check_env_alive(simulation_id):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "模拟环境未运行或已关闭。请确保模拟已完成并进入等待命令模式。"
|
||||
"error": t('api.envNotRunning')
|
||||
}), 400
|
||||
|
||||
# 优化prompt,添加前缀避免Agent调用工具
|
||||
|
|
@ -2492,7 +2497,7 @@ def interview_all_agents():
|
|||
except TimeoutError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"等待全局Interview响应超时: {str(e)}"
|
||||
"error": t('api.globalInterviewTimeout', error=str(e))
|
||||
}), 504
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -2549,7 +2554,7 @@ def get_interview_history():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
history = SimulationRunner.get_interview_history(
|
||||
|
|
@ -2608,7 +2613,7 @@ def get_env_status():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
env_alive = SimulationRunner.check_env_alive(simulation_id)
|
||||
|
|
@ -2617,9 +2622,9 @@ def get_env_status():
|
|||
env_status = SimulationRunner.get_env_status_detail(simulation_id)
|
||||
|
||||
if env_alive:
|
||||
message = "环境正在运行,可以接收Interview命令"
|
||||
message = t('api.envRunning')
|
||||
else:
|
||||
message = "环境未运行或已关闭"
|
||||
message = t('api.envNotRunningShort')
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
|
|
@ -2676,7 +2681,7 @@ def close_simulation_env():
|
|||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
"error": t('api.requireSimulationId')
|
||||
}), 400
|
||||
|
||||
result = SimulationRunner.close_simulation_env(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ from enum import Enum
|
|||
from typing import Dict, Any, Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from ..utils.locale import t
|
||||
|
||||
|
||||
class TaskStatus(str, Enum):
|
||||
"""任务状态枚举"""
|
||||
|
|
@ -148,7 +150,7 @@ class TaskManager:
|
|||
task_id,
|
||||
status=TaskStatus.COMPLETED,
|
||||
progress=100,
|
||||
message="任务完成",
|
||||
message=t('progress.taskComplete'),
|
||||
result=result
|
||||
)
|
||||
|
||||
|
|
@ -157,7 +159,7 @@ class TaskManager:
|
|||
self.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.FAILED,
|
||||
message="任务失败",
|
||||
message=t('progress.taskFailed'),
|
||||
error=error
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from ..config import Config
|
|||
from ..models.task import TaskManager, TaskStatus
|
||||
from ..utils.zep_paging import fetch_all_nodes, fetch_all_edges
|
||||
from .text_processor import TextProcessor
|
||||
from ..utils.locale import t, get_locale, set_locale
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -83,10 +84,13 @@ class GraphBuilderService:
|
|||
}
|
||||
)
|
||||
|
||||
# Capture locale before spawning background thread
|
||||
current_locale = get_locale()
|
||||
|
||||
# 在后台线程中执行构建
|
||||
thread = threading.Thread(
|
||||
target=self._build_graph_worker,
|
||||
args=(task_id, text, ontology, graph_name, chunk_size, chunk_overlap, batch_size)
|
||||
args=(task_id, text, ontology, graph_name, chunk_size, chunk_overlap, batch_size, current_locale)
|
||||
)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
|
@ -101,15 +105,17 @@ class GraphBuilderService:
|
|||
graph_name: str,
|
||||
chunk_size: int,
|
||||
chunk_overlap: int,
|
||||
batch_size: int
|
||||
batch_size: int,
|
||||
locale: str = 'zh'
|
||||
):
|
||||
"""图谱构建工作线程"""
|
||||
set_locale(locale)
|
||||
try:
|
||||
self.task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.PROCESSING,
|
||||
progress=5,
|
||||
message="开始构建图谱..."
|
||||
message=t('progress.startBuildingGraph')
|
||||
)
|
||||
|
||||
# 1. 创建图谱
|
||||
|
|
@ -117,7 +123,7 @@ class GraphBuilderService:
|
|||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=10,
|
||||
message=f"图谱已创建: {graph_id}"
|
||||
message=t('progress.graphCreated', graphId=graph_id)
|
||||
)
|
||||
|
||||
# 2. 设置本体
|
||||
|
|
@ -125,7 +131,7 @@ class GraphBuilderService:
|
|||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=15,
|
||||
message="本体已设置"
|
||||
message=t('progress.ontologySet')
|
||||
)
|
||||
|
||||
# 3. 文本分块
|
||||
|
|
@ -134,7 +140,7 @@ class GraphBuilderService:
|
|||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=20,
|
||||
message=f"文本已分割为 {total_chunks} 个块"
|
||||
message=t('progress.textSplit', count=total_chunks)
|
||||
)
|
||||
|
||||
# 4. 分批发送数据
|
||||
|
|
@ -151,7 +157,7 @@ class GraphBuilderService:
|
|||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=60,
|
||||
message="等待Zep处理数据..."
|
||||
message=t('progress.waitingZepProcess')
|
||||
)
|
||||
|
||||
self._wait_for_episodes(
|
||||
|
|
@ -167,7 +173,7 @@ class GraphBuilderService:
|
|||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=90,
|
||||
message="获取图谱信息..."
|
||||
message=t('progress.fetchingGraphInfo')
|
||||
)
|
||||
|
||||
graph_info = self._get_graph_info(graph_id)
|
||||
|
|
@ -304,7 +310,7 @@ class GraphBuilderService:
|
|||
if progress_callback:
|
||||
progress = (i + len(batch_chunks)) / total_chunks
|
||||
progress_callback(
|
||||
f"发送第 {batch_num}/{total_batches} 批数据 ({len(batch_chunks)} 块)...",
|
||||
t('progress.sendingBatch', current=batch_num, total=total_batches, chunks=len(batch_chunks)),
|
||||
progress
|
||||
)
|
||||
|
||||
|
|
@ -333,7 +339,7 @@ class GraphBuilderService:
|
|||
|
||||
except Exception as e:
|
||||
if progress_callback:
|
||||
progress_callback(f"批次 {batch_num} 发送失败: {str(e)}", 0)
|
||||
progress_callback(t('progress.batchFailed', batch=batch_num, error=str(e)), 0)
|
||||
raise
|
||||
|
||||
return episode_uuids
|
||||
|
|
@ -347,7 +353,7 @@ class GraphBuilderService:
|
|||
"""等待所有 episode 处理完成(通过查询每个 episode 的 processed 状态)"""
|
||||
if not episode_uuids:
|
||||
if progress_callback:
|
||||
progress_callback("无需等待(没有 episode)", 1.0)
|
||||
progress_callback(t('progress.noEpisodesWait'), 1.0)
|
||||
return
|
||||
|
||||
start_time = time.time()
|
||||
|
|
@ -356,13 +362,13 @@ class GraphBuilderService:
|
|||
total_episodes = len(episode_uuids)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(f"开始等待 {total_episodes} 个文本块处理...", 0)
|
||||
progress_callback(t('progress.waitingEpisodes', count=total_episodes), 0)
|
||||
|
||||
while pending_episodes:
|
||||
if time.time() - start_time > timeout:
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
f"部分文本块超时,已完成 {completed_count}/{total_episodes}",
|
||||
t('progress.episodesTimeout', completed=completed_count, total=total_episodes),
|
||||
completed_count / total_episodes
|
||||
)
|
||||
break
|
||||
|
|
@ -384,7 +390,7 @@ class GraphBuilderService:
|
|||
elapsed = int(time.time() - start_time)
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
f"Zep处理中... {completed_count}/{total_episodes} 完成, {len(pending_episodes)} 待处理 ({elapsed}秒)",
|
||||
t('progress.zepProcessing', completed=completed_count, total=total_episodes, pending=len(pending_episodes), elapsed=elapsed),
|
||||
completed_count / total_episodes if total_episodes > 0 else 0
|
||||
)
|
||||
|
||||
|
|
@ -392,7 +398,7 @@ class GraphBuilderService:
|
|||
time.sleep(3) # 每3秒检查一次
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(f"处理完成: {completed_count}/{total_episodes}", 1.0)
|
||||
progress_callback(t('progress.processingComplete', completed=completed_count, total=total_episodes), 1.0)
|
||||
|
||||
def _get_graph_info(self, graph_id: str) -> GraphInfo:
|
||||
"""获取图谱信息"""
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from zep_cloud.client import Zep
|
|||
|
||||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_language_instruction, get_locale, set_locale, t
|
||||
from .zep_entity_reader import EntityNode, ZepEntityReader
|
||||
|
||||
logger = get_logger('mirofish.oasis_profile')
|
||||
|
|
@ -313,7 +314,7 @@ class OasisProfileGenerator:
|
|||
logger.debug(f"跳过Zep检索:未设置graph_id")
|
||||
return results
|
||||
|
||||
comprehensive_query = f"关于{entity_name}的所有信息、活动、事件、关系和背景"
|
||||
comprehensive_query = t('progress.zepSearchQuery', name=entity_name)
|
||||
|
||||
def search_edges():
|
||||
"""搜索边(事实/关系)- 带重试机制"""
|
||||
|
|
@ -670,8 +671,8 @@ class OasisProfileGenerator:
|
|||
|
||||
def _get_system_prompt(self, is_individual: bool) -> str:
|
||||
"""获取系统提示词"""
|
||||
base_prompt = "你是社交媒体用户画像生成专家。生成详细、真实的人设用于舆论模拟,最大程度还原已有现实情况。必须返回有效的JSON格式,所有字符串值不能包含未转义的换行符。使用中文。"
|
||||
return base_prompt
|
||||
base_prompt = "你是社交媒体用户画像生成专家。生成详细、真实的人设用于舆论模拟,最大程度还原已有现实情况。必须返回有效的JSON格式,所有字符串值不能包含未转义的换行符。"
|
||||
return f"{base_prompt}\n\n{get_language_instruction()}"
|
||||
|
||||
def _build_individual_persona_prompt(
|
||||
self,
|
||||
|
|
@ -717,7 +718,7 @@ class OasisProfileGenerator:
|
|||
重要:
|
||||
- 所有字段值必须是字符串或数字,不要使用换行符
|
||||
- persona必须是一段连贯的文字描述
|
||||
- 使用中文(除了gender字段必须用英文male/female)
|
||||
- {get_language_instruction()} (gender字段必须用英文male/female)
|
||||
- 内容要与实体信息保持一致
|
||||
- age必须是有效的整数,gender必须是"male"或"female"
|
||||
"""
|
||||
|
|
@ -766,7 +767,7 @@ class OasisProfileGenerator:
|
|||
重要:
|
||||
- 所有字段值必须是字符串或数字,不允许null值
|
||||
- persona必须是一段连贯的文字描述,不要使用换行符
|
||||
- 使用中文(除了gender字段必须用英文"other")
|
||||
- {get_language_instruction()} (gender字段必须用英文"other")
|
||||
- age必须是整数30,gender必须是字符串"other"
|
||||
- 机构账号发言要符合其身份定位"""
|
||||
|
||||
|
|
@ -915,8 +916,12 @@ class OasisProfileGenerator:
|
|||
except Exception as e:
|
||||
logger.warning(f"实时保存 profiles 失败: {e}")
|
||||
|
||||
# Capture locale before spawning thread pool workers
|
||||
current_locale = get_locale()
|
||||
|
||||
def generate_single_profile(idx: int, entity: EntityNode) -> tuple:
|
||||
"""生成单个profile的工作函数"""
|
||||
set_locale(current_locale)
|
||||
entity_type = entity.get_entity_type() or "Entity"
|
||||
|
||||
try:
|
||||
|
|
@ -1017,7 +1022,7 @@ class OasisProfileGenerator:
|
|||
|
||||
output_lines = [
|
||||
f"\n{separator}",
|
||||
f"[已生成] {entity_name} ({entity_type})",
|
||||
t('progress.profileGenerated', name=entity_name, type=entity_type),
|
||||
f"{separator}",
|
||||
f"用户名: {profile.user_name}",
|
||||
f"",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import logging
|
|||
import re
|
||||
from typing import Dict, Any, List, Optional
|
||||
from ..utils.llm_client import LLMClient
|
||||
from ..utils.locale import get_language_instruction
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -83,7 +84,7 @@ ONTOLOGY_SYSTEM_PROMPT = """你是一个专业的知识图谱本体设计专家
|
|||
"attributes": []
|
||||
}
|
||||
],
|
||||
"analysis_summary": "对文本内容的简要分析说明(中文)"
|
||||
"analysis_summary": "对文本内容的简要分析说明"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -205,8 +206,10 @@ class OntologyGenerator:
|
|||
additional_context
|
||||
)
|
||||
|
||||
lang_instruction = get_language_instruction()
|
||||
system_prompt = f"{ONTOLOGY_SYSTEM_PROMPT}\n\n{lang_instruction}\nIMPORTANT: Entity type names MUST be in English PascalCase (e.g., 'PersonEntity', 'MediaOrganization'). Relationship type names MUST be in English UPPER_SNAKE_CASE (e.g., 'WORKS_FOR'). Attribute names MUST be in English snake_case. Only description fields and analysis_summary should use the specified language above."
|
||||
messages = [
|
||||
{"role": "system", "content": ONTOLOGY_SYSTEM_PROMPT},
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_message}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from enum import Enum
|
|||
from ..config import Config
|
||||
from ..utils.llm_client import LLMClient
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_language_instruction, t
|
||||
from .zep_tools import (
|
||||
ZepToolsService,
|
||||
SearchResult,
|
||||
|
|
@ -105,7 +106,7 @@ class ReportLogger:
|
|||
"simulation_id": simulation_id,
|
||||
"graph_id": graph_id,
|
||||
"simulation_requirement": simulation_requirement,
|
||||
"message": "报告生成任务开始"
|
||||
"message": t('report.taskStarted')
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -114,7 +115,7 @@ class ReportLogger:
|
|||
self.log(
|
||||
action="planning_start",
|
||||
stage="planning",
|
||||
details={"message": "开始规划报告大纲"}
|
||||
details={"message": t('report.planningStart')}
|
||||
)
|
||||
|
||||
def log_planning_context(self, context: Dict[str, Any]):
|
||||
|
|
@ -123,7 +124,7 @@ class ReportLogger:
|
|||
action="planning_context",
|
||||
stage="planning",
|
||||
details={
|
||||
"message": "获取模拟上下文信息",
|
||||
"message": t('report.fetchSimContext'),
|
||||
"context": context
|
||||
}
|
||||
)
|
||||
|
|
@ -134,7 +135,7 @@ class ReportLogger:
|
|||
action="planning_complete",
|
||||
stage="planning",
|
||||
details={
|
||||
"message": "大纲规划完成",
|
||||
"message": t('report.planningComplete'),
|
||||
"outline": outline_dict
|
||||
}
|
||||
)
|
||||
|
|
@ -146,7 +147,7 @@ class ReportLogger:
|
|||
stage="generating",
|
||||
section_title=section_title,
|
||||
section_index=section_index,
|
||||
details={"message": f"开始生成章节: {section_title}"}
|
||||
details={"message": t('report.sectionStart', title=section_title)}
|
||||
)
|
||||
|
||||
def log_react_thought(self, section_title: str, section_index: int, iteration: int, thought: str):
|
||||
|
|
@ -159,7 +160,7 @@ class ReportLogger:
|
|||
details={
|
||||
"iteration": iteration,
|
||||
"thought": thought,
|
||||
"message": f"ReACT 第{iteration}轮思考"
|
||||
"message": t('report.reactThought', iteration=iteration)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -181,7 +182,7 @@ class ReportLogger:
|
|||
"iteration": iteration,
|
||||
"tool_name": tool_name,
|
||||
"parameters": parameters,
|
||||
"message": f"调用工具: {tool_name}"
|
||||
"message": t('report.toolCall', toolName=tool_name)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -204,7 +205,7 @@ class ReportLogger:
|
|||
"tool_name": tool_name,
|
||||
"result": result, # 完整结果,不截断
|
||||
"result_length": len(result),
|
||||
"message": f"工具 {tool_name} 返回结果"
|
||||
"message": t('report.toolResult', toolName=tool_name)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -229,7 +230,7 @@ class ReportLogger:
|
|||
"response_length": len(response),
|
||||
"has_tool_calls": has_tool_calls,
|
||||
"has_final_answer": has_final_answer,
|
||||
"message": f"LLM 响应 (工具调用: {has_tool_calls}, 最终答案: {has_final_answer})"
|
||||
"message": t('report.llmResponse', hasToolCalls=has_tool_calls, hasFinalAnswer=has_final_answer)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -250,7 +251,7 @@ class ReportLogger:
|
|||
"content": content, # 完整内容,不截断
|
||||
"content_length": len(content),
|
||||
"tool_calls_count": tool_calls_count,
|
||||
"message": f"章节 {section_title} 内容生成完成"
|
||||
"message": t('report.sectionContentDone', title=section_title)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -273,7 +274,7 @@ class ReportLogger:
|
|||
details={
|
||||
"content": full_content,
|
||||
"content_length": len(full_content),
|
||||
"message": f"章节 {section_title} 生成完成"
|
||||
"message": t('report.sectionComplete', title=section_title)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -285,7 +286,7 @@ class ReportLogger:
|
|||
details={
|
||||
"total_sections": total_sections,
|
||||
"total_time_seconds": round(total_time_seconds, 2),
|
||||
"message": "报告生成完成"
|
||||
"message": t('report.reportComplete')
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -298,7 +299,7 @@ class ReportLogger:
|
|||
section_index=None,
|
||||
details={
|
||||
"error": error_message,
|
||||
"message": f"发生错误: {error_message}"
|
||||
"message": t('report.errorOccurred', error=error_message)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -652,9 +653,9 @@ SECTION_SYSTEM_PROMPT_TEMPLATE = """\
|
|||
- 这些引用是模拟预测的核心证据
|
||||
|
||||
3. 【语言一致性 - 引用内容必须翻译为报告语言】
|
||||
- 工具返回的内容可能包含英文或中英文混杂的表述
|
||||
- 如果模拟需求和材料原文是中文的,报告必须全部使用中文撰写
|
||||
- 当你引用工具返回的英文或中英混杂内容时,必须将其翻译为流畅的中文后再写入报告
|
||||
- 工具返回的内容可能包含与报告语言不同的表述
|
||||
- 报告必须全部使用与用户指定语言一致的语言撰写
|
||||
- 当你引用工具返回的其他语言内容时,必须将其翻译为报告语言后再写入
|
||||
- 翻译时保持原意不变,确保表述自然通顺
|
||||
- 这一规则同时适用于正文和引用块(> 格式)中的内容
|
||||
|
||||
|
|
@ -913,7 +914,7 @@ class ReportAgent:
|
|||
# 控制台日志记录器(在 generate_report 中初始化)
|
||||
self.console_logger: Optional[ReportConsoleLogger] = None
|
||||
|
||||
logger.info(f"ReportAgent 初始化完成: graph_id={graph_id}, simulation_id={simulation_id}")
|
||||
logger.info(t('report.agentInitDone', graphId=graph_id, simulationId=simulation_id))
|
||||
|
||||
def _define_tools(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""定义可用工具"""
|
||||
|
|
@ -964,7 +965,7 @@ class ReportAgent:
|
|||
Returns:
|
||||
工具执行结果(文本格式)
|
||||
"""
|
||||
logger.info(f"执行工具: {tool_name}, 参数: {parameters}")
|
||||
logger.info(t('report.executingTool', toolName=tool_name, params=parameters))
|
||||
|
||||
try:
|
||||
if tool_name == "insight_forge":
|
||||
|
|
@ -1023,7 +1024,7 @@ class ReportAgent:
|
|||
|
||||
elif tool_name == "search_graph":
|
||||
# 重定向到 quick_search
|
||||
logger.info("search_graph 已重定向到 quick_search")
|
||||
logger.info(t('report.redirectToQuickSearch'))
|
||||
return self._execute_tool("quick_search", parameters, report_context)
|
||||
|
||||
elif tool_name == "get_graph_statistics":
|
||||
|
|
@ -1040,7 +1041,7 @@ class ReportAgent:
|
|||
|
||||
elif tool_name == "get_simulation_context":
|
||||
# 重定向到 insight_forge,因为它更强大
|
||||
logger.info("get_simulation_context 已重定向到 insight_forge")
|
||||
logger.info(t('report.redirectToInsightForge'))
|
||||
query = parameters.get("query", self.simulation_requirement)
|
||||
return self._execute_tool("insight_forge", {"query": query}, report_context)
|
||||
|
||||
|
|
@ -1057,7 +1058,7 @@ class ReportAgent:
|
|||
return f"未知工具: {tool_name}。请使用以下工具之一: insight_forge, panorama_search, quick_search"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"工具执行失败: {tool_name}, 错误: {str(e)}")
|
||||
logger.error(t('report.toolExecFailed', toolName=tool_name, error=str(e)))
|
||||
return f"工具执行失败: {str(e)}"
|
||||
|
||||
# 合法的工具名称集合,用于裸 JSON 兜底解析时校验
|
||||
|
|
@ -1148,10 +1149,10 @@ class ReportAgent:
|
|||
Returns:
|
||||
ReportOutline: 报告大纲
|
||||
"""
|
||||
logger.info("开始规划报告大纲...")
|
||||
logger.info(t('report.startPlanningOutline'))
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 0, "正在分析模拟需求...")
|
||||
progress_callback("planning", 0, t('progress.analyzingRequirements'))
|
||||
|
||||
# 首先获取模拟上下文
|
||||
context = self.zep_tools.get_simulation_context(
|
||||
|
|
@ -1160,9 +1161,9 @@ class ReportAgent:
|
|||
)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 30, "正在生成报告大纲...")
|
||||
progress_callback("planning", 30, t('progress.generatingOutline'))
|
||||
|
||||
system_prompt = PLAN_SYSTEM_PROMPT
|
||||
system_prompt = f"{PLAN_SYSTEM_PROMPT}\n\n{get_language_instruction()}"
|
||||
user_prompt = PLAN_USER_PROMPT_TEMPLATE.format(
|
||||
simulation_requirement=self.simulation_requirement,
|
||||
total_nodes=context.get('graph_statistics', {}).get('total_nodes', 0),
|
||||
|
|
@ -1182,7 +1183,7 @@ class ReportAgent:
|
|||
)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 80, "正在解析大纲结构...")
|
||||
progress_callback("planning", 80, t('progress.parsingOutline'))
|
||||
|
||||
# 解析大纲
|
||||
sections = []
|
||||
|
|
@ -1199,13 +1200,13 @@ class ReportAgent:
|
|||
)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 100, "大纲规划完成")
|
||||
progress_callback("planning", 100, t('progress.outlinePlanComplete'))
|
||||
|
||||
logger.info(f"大纲规划完成: {len(sections)} 个章节")
|
||||
logger.info(t('report.outlinePlanDone', count=len(sections)))
|
||||
return outline
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"大纲规划失败: {str(e)}")
|
||||
logger.error(t('report.outlinePlanFailed', error=str(e)))
|
||||
# 返回默认大纲(3个章节,作为fallback)
|
||||
return ReportOutline(
|
||||
title="未来预测报告",
|
||||
|
|
@ -1245,7 +1246,7 @@ class ReportAgent:
|
|||
Returns:
|
||||
章节内容(Markdown格式)
|
||||
"""
|
||||
logger.info(f"ReACT生成章节: {section.title}")
|
||||
logger.info(t('report.reactGenerateSection', title=section.title))
|
||||
|
||||
# 记录章节开始日志
|
||||
if self.report_logger:
|
||||
|
|
@ -1258,6 +1259,7 @@ class ReportAgent:
|
|||
section_title=section.title,
|
||||
tools_description=self._get_tools_description(),
|
||||
)
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"
|
||||
|
||||
# 构建用户prompt - 每个已完成章节各传入最大4000字
|
||||
if previous_sections:
|
||||
|
|
@ -1296,7 +1298,7 @@ class ReportAgent:
|
|||
progress_callback(
|
||||
"generating",
|
||||
int((iteration / max_iterations) * 100),
|
||||
f"深度检索与撰写中 ({tool_calls_count}/{self.MAX_TOOL_CALLS_PER_SECTION})"
|
||||
t('progress.deepSearchAndWrite', current=tool_calls_count, max=self.MAX_TOOL_CALLS_PER_SECTION)
|
||||
)
|
||||
|
||||
# 调用LLM
|
||||
|
|
@ -1308,7 +1310,7 @@ class ReportAgent:
|
|||
|
||||
# 检查 LLM 返回是否为 None(API 异常或内容为空)
|
||||
if response is None:
|
||||
logger.warning(f"章节 {section.title} 第 {iteration + 1} 次迭代: LLM 返回 None")
|
||||
logger.warning(t('report.sectionIterNone', title=section.title, iteration=iteration + 1))
|
||||
# 如果还有迭代次数,添加消息并重试
|
||||
if iteration < max_iterations - 1:
|
||||
messages.append({"role": "assistant", "content": "(响应为空)"})
|
||||
|
|
@ -1328,8 +1330,7 @@ class ReportAgent:
|
|||
if has_tool_calls and has_final_answer:
|
||||
conflict_retries += 1
|
||||
logger.warning(
|
||||
f"章节 {section.title} 第 {iteration+1} 轮: "
|
||||
f"LLM 同时输出工具调用和 Final Answer(第 {conflict_retries} 次冲突)"
|
||||
t('report.sectionConflict', title=section.title, iteration=iteration+1, conflictCount=conflict_retries)
|
||||
)
|
||||
|
||||
if conflict_retries <= 2:
|
||||
|
|
@ -1349,8 +1350,7 @@ class ReportAgent:
|
|||
else:
|
||||
# 第三次:降级处理,截断到第一个工具调用,强制执行
|
||||
logger.warning(
|
||||
f"章节 {section.title}: 连续 {conflict_retries} 次冲突,"
|
||||
"降级为截断执行第一个工具调用"
|
||||
t('report.sectionConflictDowngrade', title=section.title, conflictCount=conflict_retries)
|
||||
)
|
||||
first_tool_end = response.find('</tool_call>')
|
||||
if first_tool_end != -1:
|
||||
|
|
@ -1390,7 +1390,7 @@ class ReportAgent:
|
|||
|
||||
# 正常结束
|
||||
final_answer = response.split("Final Answer:")[-1].strip()
|
||||
logger.info(f"章节 {section.title} 生成完成(工具调用: {tool_calls_count}次)")
|
||||
logger.info(t('report.sectionGenDone', title=section.title, count=tool_calls_count))
|
||||
|
||||
if self.report_logger:
|
||||
self.report_logger.log_section_content(
|
||||
|
|
@ -1418,7 +1418,7 @@ class ReportAgent:
|
|||
# 只执行第一个工具调用
|
||||
call = tool_calls[0]
|
||||
if len(tool_calls) > 1:
|
||||
logger.info(f"LLM 尝试调用 {len(tool_calls)} 个工具,只执行第一个: {call['name']}")
|
||||
logger.info(t('report.multiToolOnlyFirst', total=len(tool_calls), toolName=call['name']))
|
||||
|
||||
if self.report_logger:
|
||||
self.report_logger.log_tool_call(
|
||||
|
|
@ -1487,7 +1487,7 @@ class ReportAgent:
|
|||
|
||||
# 工具调用已足够,LLM 输出了内容但没带 "Final Answer:" 前缀
|
||||
# 直接将这段内容作为最终答案,不再空转
|
||||
logger.info(f"章节 {section.title} 未检测到 'Final Answer:' 前缀,直接采纳LLM输出作为最终内容(工具调用: {tool_calls_count}次)")
|
||||
logger.info(t('report.sectionNoPrefix', title=section.title, count=tool_calls_count))
|
||||
final_answer = response.strip()
|
||||
|
||||
if self.report_logger:
|
||||
|
|
@ -1500,7 +1500,7 @@ class ReportAgent:
|
|||
return final_answer
|
||||
|
||||
# 达到最大迭代次数,强制生成内容
|
||||
logger.warning(f"章节 {section.title} 达到最大迭代次数,强制生成")
|
||||
logger.warning(t('report.sectionMaxIter', title=section.title))
|
||||
messages.append({"role": "user", "content": REACT_FORCE_FINAL_MSG})
|
||||
|
||||
response = self.llm.chat(
|
||||
|
|
@ -1511,8 +1511,8 @@ class ReportAgent:
|
|||
|
||||
# 检查强制收尾时 LLM 返回是否为 None
|
||||
if response is None:
|
||||
logger.error(f"章节 {section.title} 强制收尾时 LLM 返回 None,使用默认错误提示")
|
||||
final_answer = f"(本章节生成失败:LLM 返回空响应,请稍后重试)"
|
||||
logger.error(t('report.sectionForceFailed', title=section.title))
|
||||
final_answer = t('report.sectionGenFailedContent')
|
||||
elif "Final Answer:" in response:
|
||||
final_answer = response.split("Final Answer:")[-1].strip()
|
||||
else:
|
||||
|
|
@ -1590,7 +1590,7 @@ class ReportAgent:
|
|||
self.console_logger = ReportConsoleLogger(report_id)
|
||||
|
||||
ReportManager.update_progress(
|
||||
report_id, "pending", 0, "初始化报告...",
|
||||
report_id, "pending", 0, t('progress.initReport'),
|
||||
completed_sections=[]
|
||||
)
|
||||
ReportManager.save_report(report)
|
||||
|
|
@ -1598,7 +1598,7 @@ class ReportAgent:
|
|||
# 阶段1: 规划大纲
|
||||
report.status = ReportStatus.PLANNING
|
||||
ReportManager.update_progress(
|
||||
report_id, "planning", 5, "开始规划报告大纲...",
|
||||
report_id, "planning", 5, t('progress.startPlanningOutline'),
|
||||
completed_sections=[]
|
||||
)
|
||||
|
||||
|
|
@ -1606,7 +1606,7 @@ class ReportAgent:
|
|||
self.report_logger.log_planning_start()
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 0, "开始规划报告大纲...")
|
||||
progress_callback("planning", 0, t('progress.startPlanningOutline'))
|
||||
|
||||
outline = self.plan_outline(
|
||||
progress_callback=lambda stage, prog, msg:
|
||||
|
|
@ -1620,12 +1620,12 @@ class ReportAgent:
|
|||
# 保存大纲到文件
|
||||
ReportManager.save_outline(report_id, outline)
|
||||
ReportManager.update_progress(
|
||||
report_id, "planning", 15, f"大纲规划完成,共{len(outline.sections)}个章节",
|
||||
report_id, "planning", 15, t('progress.outlineDone', count=len(outline.sections)),
|
||||
completed_sections=[]
|
||||
)
|
||||
ReportManager.save_report(report)
|
||||
|
||||
logger.info(f"大纲已保存到文件: {report_id}/outline.json")
|
||||
logger.info(t('report.outlineSavedToFile', reportId=report_id))
|
||||
|
||||
# 阶段2: 逐章节生成(分章节保存)
|
||||
report.status = ReportStatus.GENERATING
|
||||
|
|
@ -1640,16 +1640,16 @@ class ReportAgent:
|
|||
# 更新进度
|
||||
ReportManager.update_progress(
|
||||
report_id, "generating", base_progress,
|
||||
f"正在生成章节: {section.title} ({section_num}/{total_sections})",
|
||||
t('progress.generatingSection', title=section.title, current=section_num, total=total_sections),
|
||||
current_section=section.title,
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating",
|
||||
base_progress,
|
||||
f"正在生成章节: {section.title} ({section_num}/{total_sections})"
|
||||
"generating",
|
||||
base_progress,
|
||||
t('progress.generatingSection', title=section.title, current=section_num, total=total_sections)
|
||||
)
|
||||
|
||||
# 生成主章节内容
|
||||
|
|
@ -1683,23 +1683,23 @@ class ReportAgent:
|
|||
full_content=full_section_content.strip()
|
||||
)
|
||||
|
||||
logger.info(f"章节已保存: {report_id}/section_{section_num:02d}.md")
|
||||
logger.info(t('report.sectionSaved', reportId=report_id, sectionNum=f"{section_num:02d}"))
|
||||
|
||||
# 更新进度
|
||||
ReportManager.update_progress(
|
||||
report_id, "generating",
|
||||
base_progress + int(70 / total_sections),
|
||||
f"章节 {section.title} 已完成",
|
||||
t('progress.sectionDone', title=section.title),
|
||||
current_section=None,
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
|
||||
# 阶段3: 组装完整报告
|
||||
if progress_callback:
|
||||
progress_callback("generating", 95, "正在组装完整报告...")
|
||||
progress_callback("generating", 95, t('progress.assemblingReport'))
|
||||
|
||||
ReportManager.update_progress(
|
||||
report_id, "generating", 95, "正在组装完整报告...",
|
||||
report_id, "generating", 95, t('progress.assemblingReport'),
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
|
||||
|
|
@ -1721,14 +1721,14 @@ class ReportAgent:
|
|||
# 保存最终报告
|
||||
ReportManager.save_report(report)
|
||||
ReportManager.update_progress(
|
||||
report_id, "completed", 100, "报告生成完成",
|
||||
report_id, "completed", 100, t('progress.reportComplete'),
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("completed", 100, "报告生成完成")
|
||||
progress_callback("completed", 100, t('progress.reportComplete'))
|
||||
|
||||
logger.info(f"报告生成完成: {report_id}")
|
||||
logger.info(t('report.reportGenDone', reportId=report_id))
|
||||
|
||||
# 关闭控制台日志记录器
|
||||
if self.console_logger:
|
||||
|
|
@ -1738,7 +1738,7 @@ class ReportAgent:
|
|||
return report
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"报告生成失败: {str(e)}")
|
||||
logger.error(t('report.reportGenFailed', error=str(e)))
|
||||
report.status = ReportStatus.FAILED
|
||||
report.error = str(e)
|
||||
|
||||
|
|
@ -1750,7 +1750,7 @@ class ReportAgent:
|
|||
try:
|
||||
ReportManager.save_report(report)
|
||||
ReportManager.update_progress(
|
||||
report_id, "failed", -1, f"报告生成失败: {str(e)}",
|
||||
report_id, "failed", -1, t('progress.reportFailed', error=str(e)),
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
except Exception:
|
||||
|
|
@ -1784,7 +1784,7 @@ class ReportAgent:
|
|||
"sources": [信息来源]
|
||||
}
|
||||
"""
|
||||
logger.info(f"Report Agent对话: {message[:50]}...")
|
||||
logger.info(t('report.agentChat', message=message[:50]))
|
||||
|
||||
chat_history = chat_history or []
|
||||
|
||||
|
|
@ -1798,13 +1798,14 @@ class ReportAgent:
|
|||
if len(report.markdown_content) > 15000:
|
||||
report_content += "\n\n... [报告内容已截断] ..."
|
||||
except Exception as e:
|
||||
logger.warning(f"获取报告内容失败: {e}")
|
||||
logger.warning(t('report.fetchReportFailed', error=e))
|
||||
|
||||
system_prompt = CHAT_SYSTEM_PROMPT_TEMPLATE.format(
|
||||
simulation_requirement=self.simulation_requirement,
|
||||
report_content=report_content if report_content else "(暂无报告)",
|
||||
tools_description=self._get_tools_description(),
|
||||
)
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"
|
||||
|
||||
# 构建消息
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
|
|
@ -2088,7 +2089,7 @@ class ReportManager:
|
|||
with open(cls._get_outline_path(report_id), 'w', encoding='utf-8') as f:
|
||||
json.dump(outline.to_dict(), f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"大纲已保存: {report_id}")
|
||||
logger.info(t('report.outlineSaved', reportId=report_id))
|
||||
|
||||
@classmethod
|
||||
def save_section(
|
||||
|
|
@ -2124,7 +2125,7 @@ class ReportManager:
|
|||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(md_content)
|
||||
|
||||
logger.info(f"章节已保存: {report_id}/{file_suffix}")
|
||||
logger.info(t('report.sectionFileSaved', reportId=report_id, fileSuffix=file_suffix))
|
||||
return file_path
|
||||
|
||||
@classmethod
|
||||
|
|
@ -2293,7 +2294,7 @@ class ReportManager:
|
|||
with open(full_path, 'w', encoding='utf-8') as f:
|
||||
f.write(md_content)
|
||||
|
||||
logger.info(f"完整报告已组装: {report_id}")
|
||||
logger.info(t('report.fullReportAssembled', reportId=report_id))
|
||||
return md_content
|
||||
|
||||
@classmethod
|
||||
|
|
@ -2440,7 +2441,7 @@ class ReportManager:
|
|||
with open(cls._get_report_markdown_path(report.report_id), 'w', encoding='utf-8') as f:
|
||||
f.write(report.markdown_content)
|
||||
|
||||
logger.info(f"报告已保存: {report.report_id}")
|
||||
logger.info(t('report.reportSaved', reportId=report.report_id))
|
||||
|
||||
@classmethod
|
||||
def get_report(cls, report_id: str) -> Optional[Report]:
|
||||
|
|
@ -2553,7 +2554,7 @@ class ReportManager:
|
|||
# 新格式:删除整个文件夹
|
||||
if os.path.exists(folder_path) and os.path.isdir(folder_path):
|
||||
shutil.rmtree(folder_path)
|
||||
logger.info(f"报告文件夹已删除: {report_id}")
|
||||
logger.info(t('report.reportFolderDeleted', reportId=report_id))
|
||||
return True
|
||||
|
||||
# 兼容旧格式:删除单独的文件
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from openai import OpenAI
|
|||
|
||||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_language_instruction, t
|
||||
from .zep_entity_reader import EntityNode, ZepEntityReader
|
||||
|
||||
logger = get_logger('mirofish.simulation_config')
|
||||
|
|
@ -292,17 +293,17 @@ class SimulationConfigGenerator:
|
|||
reasoning_parts = []
|
||||
|
||||
# ========== 步骤1: 生成时间配置 ==========
|
||||
report_progress(1, "生成时间配置...")
|
||||
report_progress(1, t('progress.generatingTimeConfig'))
|
||||
num_entities = len(entities)
|
||||
time_config_result = self._generate_time_config(context, num_entities)
|
||||
time_config = self._parse_time_config(time_config_result, num_entities)
|
||||
reasoning_parts.append(f"时间配置: {time_config_result.get('reasoning', '成功')}")
|
||||
reasoning_parts.append(f"{t('progress.timeConfigLabel')}: {time_config_result.get('reasoning', t('common.success'))}")
|
||||
|
||||
# ========== 步骤2: 生成事件配置 ==========
|
||||
report_progress(2, "生成事件配置和热点话题...")
|
||||
report_progress(2, t('progress.generatingEventConfig'))
|
||||
event_config_result = self._generate_event_config(context, simulation_requirement, entities)
|
||||
event_config = self._parse_event_config(event_config_result)
|
||||
reasoning_parts.append(f"事件配置: {event_config_result.get('reasoning', '成功')}")
|
||||
reasoning_parts.append(f"{t('progress.eventConfigLabel')}: {event_config_result.get('reasoning', t('common.success'))}")
|
||||
|
||||
# ========== 步骤3-N: 分批生成Agent配置 ==========
|
||||
all_agent_configs = []
|
||||
|
|
@ -313,7 +314,7 @@ class SimulationConfigGenerator:
|
|||
|
||||
report_progress(
|
||||
3 + batch_idx,
|
||||
f"生成Agent配置 ({start_idx + 1}-{end_idx}/{len(entities)})..."
|
||||
t('progress.generatingAgentConfig', start=start_idx + 1, end=end_idx, total=len(entities))
|
||||
)
|
||||
|
||||
batch_configs = self._generate_agent_configs_batch(
|
||||
|
|
@ -324,16 +325,16 @@ class SimulationConfigGenerator:
|
|||
)
|
||||
all_agent_configs.extend(batch_configs)
|
||||
|
||||
reasoning_parts.append(f"Agent配置: 成功生成 {len(all_agent_configs)} 个")
|
||||
reasoning_parts.append(t('progress.agentConfigResult', count=len(all_agent_configs)))
|
||||
|
||||
# ========== 为初始帖子分配发布者 Agent ==========
|
||||
logger.info("为初始帖子分配合适的发布者 Agent...")
|
||||
event_config = self._assign_initial_post_agents(event_config, all_agent_configs)
|
||||
assigned_count = len([p for p in event_config.initial_posts if p.get("poster_agent_id") is not None])
|
||||
reasoning_parts.append(f"初始帖子分配: {assigned_count} 个帖子已分配发布者")
|
||||
reasoning_parts.append(t('progress.postAssignResult', count=assigned_count))
|
||||
|
||||
# ========== 最后一步: 生成平台配置 ==========
|
||||
report_progress(total_steps, "生成平台配置...")
|
||||
report_progress(total_steps, t('progress.generatingPlatformConfig'))
|
||||
twitter_config = None
|
||||
reddit_config = None
|
||||
|
||||
|
|
@ -547,7 +548,7 @@ class SimulationConfigGenerator:
|
|||
请生成时间配置JSON。
|
||||
|
||||
### 基本原则(仅供参考,需根据具体事件和参与群体灵活调整):
|
||||
- 用户群体为中国人,需符合北京时间作息习惯
|
||||
- 请根据模拟场景推断目标用户群体所在时区和作息习惯,以下为东八区(UTC+8)的参考示例
|
||||
- 凌晨0-5点几乎无人活动(活跃度系数0.05)
|
||||
- 早上6-8点逐渐活跃(活跃度系数0.4)
|
||||
- 工作时间9-18点中等活跃(活跃度系数0.7)
|
||||
|
|
@ -584,8 +585,9 @@ class SimulationConfigGenerator:
|
|||
- work_hours (int数组): 工作时段
|
||||
- reasoning (string): 简要说明为什么这样配置"""
|
||||
|
||||
system_prompt = "你是社交媒体模拟专家。返回纯JSON格式,时间配置需符合中国人作息习惯。"
|
||||
|
||||
system_prompt = "你是社交媒体模拟专家。返回纯JSON格式,时间配置需符合模拟场景中目标用户群体的作息习惯。"
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"
|
||||
|
||||
try:
|
||||
return self._call_llm_with_retry(prompt, system_prompt)
|
||||
except Exception as e:
|
||||
|
|
@ -701,7 +703,8 @@ class SimulationConfigGenerator:
|
|||
}}"""
|
||||
|
||||
system_prompt = "你是舆论分析专家。返回纯JSON格式。注意 poster_type 必须精确匹配可用实体类型。"
|
||||
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}\nIMPORTANT: The 'poster_type' field value MUST be in English PascalCase exactly matching the available entity types. Only 'content', 'narrative_direction', 'hot_topics' and 'reasoning' fields should use the specified language."
|
||||
|
||||
try:
|
||||
return self._call_llm_with_retry(prompt, system_prompt)
|
||||
except Exception as e:
|
||||
|
|
@ -838,7 +841,7 @@ class SimulationConfigGenerator:
|
|||
|
||||
## 任务
|
||||
为每个实体生成活动配置,注意:
|
||||
- **时间符合中国人作息**:凌晨0-5点几乎不活动,晚间19-22点最活跃
|
||||
- **时间符合目标用户群体作息**:以下为参考(东八区),请根据模拟场景调整
|
||||
- **官方机构**(University/GovernmentAgency):活跃度低(0.1-0.3),工作时间(9-17)活动,响应慢(60-240分钟),影响力高(2.5-3.0)
|
||||
- **媒体**(MediaOutlet):活跃度中(0.4-0.6),全天活动(8-23),响应快(5-30分钟),影响力高(2.0-2.5)
|
||||
- **个人**(Student/Person/Alumni):活跃度高(0.6-0.9),主要晚间活动(18-23),响应快(1-15分钟),影响力低(0.8-1.2)
|
||||
|
|
@ -863,8 +866,9 @@ class SimulationConfigGenerator:
|
|||
]
|
||||
}}"""
|
||||
|
||||
system_prompt = "你是社交媒体行为分析专家。返回纯JSON,配置需符合中国人作息习惯。"
|
||||
|
||||
system_prompt = "你是社交媒体行为分析专家。返回纯JSON,配置需符合模拟场景中目标用户群体的作息习惯。"
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}\nIMPORTANT: The 'stance' field value MUST be one of the English strings: 'supportive', 'opposing', 'neutral', 'observer'. All JSON field names and numeric values must remain unchanged. Only natural language text fields should use the specified language."
|
||||
|
||||
try:
|
||||
result = self._call_llm_with_retry(prompt, system_prompt)
|
||||
llm_configs = {cfg["agent_id"]: cfg for cfg in result.get("agent_configs", [])}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from ..utils.logger import get_logger
|
|||
from .zep_entity_reader import ZepEntityReader, FilteredEntities
|
||||
from .oasis_profile_generator import OasisProfileGenerator, OasisAgentProfile
|
||||
from .simulation_config_generator import SimulationConfigGenerator, SimulationParameters
|
||||
from ..utils.locale import t
|
||||
|
||||
logger = get_logger('mirofish.simulation')
|
||||
|
||||
|
|
@ -270,12 +271,12 @@ class SimulationManager:
|
|||
|
||||
# ========== 阶段1: 读取并过滤实体 ==========
|
||||
if progress_callback:
|
||||
progress_callback("reading", 0, "正在连接Zep图谱...")
|
||||
progress_callback("reading", 0, t('progress.connectingZepGraph'))
|
||||
|
||||
reader = ZepEntityReader()
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("reading", 30, "正在读取节点数据...")
|
||||
progress_callback("reading", 30, t('progress.readingNodeData'))
|
||||
|
||||
filtered = reader.filter_defined_entities(
|
||||
graph_id=state.graph_id,
|
||||
|
|
@ -288,8 +289,8 @@ class SimulationManager:
|
|||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"reading", 100,
|
||||
f"完成,共 {filtered.filtered_count} 个实体",
|
||||
"reading", 100,
|
||||
t('progress.readingComplete', count=filtered.filtered_count),
|
||||
current=filtered.filtered_count,
|
||||
total=filtered.filtered_count
|
||||
)
|
||||
|
|
@ -305,8 +306,8 @@ class SimulationManager:
|
|||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_profiles", 0,
|
||||
"开始生成...",
|
||||
"generating_profiles", 0,
|
||||
t('progress.startGenerating'),
|
||||
current=0,
|
||||
total=total_entities
|
||||
)
|
||||
|
|
@ -351,8 +352,8 @@ class SimulationManager:
|
|||
# Reddit 已经在生成过程中实时保存了,这里再保存一次确保完整性
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_profiles", 95,
|
||||
"保存Profile文件...",
|
||||
"generating_profiles", 95,
|
||||
t('progress.savingProfiles'),
|
||||
current=total_entities,
|
||||
total=total_entities
|
||||
)
|
||||
|
|
@ -374,8 +375,8 @@ class SimulationManager:
|
|||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_profiles", 100,
|
||||
f"完成,共 {len(profiles)} 个Profile",
|
||||
"generating_profiles", 100,
|
||||
t('progress.profilesComplete', count=len(profiles)),
|
||||
current=len(profiles),
|
||||
total=len(profiles)
|
||||
)
|
||||
|
|
@ -383,8 +384,8 @@ class SimulationManager:
|
|||
# ========== 阶段3: LLM智能生成模拟配置 ==========
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_config", 0,
|
||||
"正在分析模拟需求...",
|
||||
"generating_config", 0,
|
||||
t('progress.analyzingRequirements'),
|
||||
current=0,
|
||||
total=3
|
||||
)
|
||||
|
|
@ -393,8 +394,8 @@ class SimulationManager:
|
|||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_config", 30,
|
||||
"正在调用LLM生成配置...",
|
||||
"generating_config", 30,
|
||||
t('progress.callingLLMConfig'),
|
||||
current=1,
|
||||
total=3
|
||||
)
|
||||
|
|
@ -412,8 +413,8 @@ class SimulationManager:
|
|||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_config", 70,
|
||||
"正在保存配置文件...",
|
||||
"generating_config", 70,
|
||||
t('progress.savingConfigFiles'),
|
||||
current=2,
|
||||
total=3
|
||||
)
|
||||
|
|
@ -428,8 +429,8 @@ class SimulationManager:
|
|||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_config", 100,
|
||||
"配置生成完成",
|
||||
"generating_config", 100,
|
||||
t('progress.configComplete'),
|
||||
current=3,
|
||||
total=3
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from queue import Queue
|
|||
|
||||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_locale, set_locale
|
||||
from .zep_graph_memory_updater import ZepGraphMemoryManager
|
||||
from .simulation_ipc import SimulationIPCClient, CommandType, IPCResponse
|
||||
|
||||
|
|
@ -455,10 +456,13 @@ class SimulationRunner:
|
|||
cls._processes[simulation_id] = process
|
||||
cls._save_run_state(state)
|
||||
|
||||
# Capture locale before spawning monitor thread
|
||||
current_locale = get_locale()
|
||||
|
||||
# 启动监控线程
|
||||
monitor_thread = threading.Thread(
|
||||
target=cls._monitor_simulation,
|
||||
args=(simulation_id,),
|
||||
args=(simulation_id, current_locale),
|
||||
daemon=True
|
||||
)
|
||||
monitor_thread.start()
|
||||
|
|
@ -475,8 +479,9 @@ class SimulationRunner:
|
|||
return state
|
||||
|
||||
@classmethod
|
||||
def _monitor_simulation(cls, simulation_id: str):
|
||||
def _monitor_simulation(cls, simulation_id: str, locale: str = 'zh'):
|
||||
"""监控模拟进程,解析动作日志"""
|
||||
set_locale(locale)
|
||||
sim_dir = os.path.join(cls.RUN_STATE_DIR, simulation_id)
|
||||
|
||||
# 新的日志结构:分平台的动作日志
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from zep_cloud.client import Zep
|
|||
|
||||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_locale, set_locale
|
||||
|
||||
logger = get_logger('mirofish.zep_graph_memory_updater')
|
||||
|
||||
|
|
@ -275,10 +276,14 @@ class ZepGraphMemoryUpdater:
|
|||
"""启动后台工作线程"""
|
||||
if self._running:
|
||||
return
|
||||
|
||||
|
||||
# Capture locale before spawning background thread
|
||||
current_locale = get_locale()
|
||||
|
||||
self._running = True
|
||||
self._worker_thread = threading.Thread(
|
||||
target=self._worker_loop,
|
||||
args=(current_locale,),
|
||||
daemon=True,
|
||||
name=f"ZepMemoryUpdater-{self.graph_id[:8]}"
|
||||
)
|
||||
|
|
@ -356,8 +361,9 @@ class ZepGraphMemoryUpdater:
|
|||
|
||||
self.add_activity(activity)
|
||||
|
||||
def _worker_loop(self):
|
||||
def _worker_loop(self, locale: str = 'zh'):
|
||||
"""后台工作循环 - 按平台批量发送活动到Zep"""
|
||||
set_locale(locale)
|
||||
while self._running or not self._activity_queue.empty():
|
||||
try:
|
||||
# 尝试从队列获取活动(超时1秒)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from zep_cloud.client import Zep
|
|||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.llm_client import LLMClient
|
||||
from ..utils.locale import get_locale, t
|
||||
from ..utils.zep_paging import fetch_all_nodes, fetch_all_edges
|
||||
|
||||
logger = get_logger('mirofish.zep_tools')
|
||||
|
|
@ -429,7 +430,7 @@ class ZepToolsService:
|
|||
self.client = Zep(api_key=self.api_key)
|
||||
# LLM客户端用于InsightForge生成子问题
|
||||
self._llm_client = llm_client
|
||||
logger.info("ZepToolsService 初始化完成")
|
||||
logger.info(t("console.zepToolsInitialized"))
|
||||
|
||||
@property
|
||||
def llm(self) -> LLMClient:
|
||||
|
|
@ -451,13 +452,12 @@ class ZepToolsService:
|
|||
last_exception = e
|
||||
if attempt < max_retries - 1:
|
||||
logger.warning(
|
||||
f"Zep {operation_name} 第 {attempt + 1} 次尝试失败: {str(e)[:100]}, "
|
||||
f"{delay:.1f}秒后重试..."
|
||||
t("console.zepRetryAttempt", operation=operation_name, attempt=attempt + 1, error=str(e)[:100], delay=f"{delay:.1f}")
|
||||
)
|
||||
time.sleep(delay)
|
||||
delay *= 2
|
||||
else:
|
||||
logger.error(f"Zep {operation_name} 在 {max_retries} 次尝试后仍失败: {str(e)}")
|
||||
logger.error(t("console.zepAllRetriesFailed", operation=operation_name, retries=max_retries, error=str(e)))
|
||||
|
||||
raise last_exception
|
||||
|
||||
|
|
@ -483,7 +483,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
SearchResult: 搜索结果
|
||||
"""
|
||||
logger.info(f"图谱搜索: graph_id={graph_id}, query={query[:50]}...")
|
||||
logger.info(t("console.graphSearch", graphId=graph_id, query=query[:50]))
|
||||
|
||||
# 尝试使用Zep Cloud Search API
|
||||
try:
|
||||
|
|
@ -495,7 +495,7 @@ class ZepToolsService:
|
|||
scope=scope,
|
||||
reranker="cross_encoder"
|
||||
),
|
||||
operation_name=f"图谱搜索(graph={graph_id})"
|
||||
operation_name=t("console.graphSearchOp", graphId=graph_id)
|
||||
)
|
||||
|
||||
facts = []
|
||||
|
|
@ -528,7 +528,7 @@ class ZepToolsService:
|
|||
if hasattr(node, 'summary') and node.summary:
|
||||
facts.append(f"[{node.name}]: {node.summary}")
|
||||
|
||||
logger.info(f"搜索完成: 找到 {len(facts)} 条相关事实")
|
||||
logger.info(t("console.searchComplete", count=len(facts)))
|
||||
|
||||
return SearchResult(
|
||||
facts=facts,
|
||||
|
|
@ -539,7 +539,7 @@ class ZepToolsService:
|
|||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Zep Search API失败,降级为本地搜索: {str(e)}")
|
||||
logger.warning(t("console.zepSearchApiFallback", error=str(e)))
|
||||
# 降级:使用本地关键词匹配搜索
|
||||
return self._local_search(graph_id, query, limit, scope)
|
||||
|
||||
|
|
@ -564,7 +564,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
SearchResult: 搜索结果
|
||||
"""
|
||||
logger.info(f"使用本地搜索: query={query[:30]}...")
|
||||
logger.info(t("console.usingLocalSearch", query=query[:30]))
|
||||
|
||||
facts = []
|
||||
edges_result = []
|
||||
|
|
@ -634,10 +634,10 @@ class ZepToolsService:
|
|||
if node.summary:
|
||||
facts.append(f"[{node.name}]: {node.summary}")
|
||||
|
||||
logger.info(f"本地搜索完成: 找到 {len(facts)} 条相关事实")
|
||||
logger.info(t("console.localSearchComplete", count=len(facts)))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"本地搜索失败: {str(e)}")
|
||||
logger.error(t("console.localSearchFailed", error=str(e)))
|
||||
|
||||
return SearchResult(
|
||||
facts=facts,
|
||||
|
|
@ -657,7 +657,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
节点列表
|
||||
"""
|
||||
logger.info(f"获取图谱 {graph_id} 的所有节点...")
|
||||
logger.info(t("console.fetchingAllNodes", graphId=graph_id))
|
||||
|
||||
nodes = fetch_all_nodes(self.client, graph_id)
|
||||
|
||||
|
|
@ -672,7 +672,7 @@ class ZepToolsService:
|
|||
attributes=node.attributes or {}
|
||||
))
|
||||
|
||||
logger.info(f"获取到 {len(result)} 个节点")
|
||||
logger.info(t("console.fetchedNodes", count=len(result)))
|
||||
return result
|
||||
|
||||
def get_all_edges(self, graph_id: str, include_temporal: bool = True) -> List[EdgeInfo]:
|
||||
|
|
@ -686,7 +686,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
边列表(包含created_at, valid_at, invalid_at, expired_at)
|
||||
"""
|
||||
logger.info(f"获取图谱 {graph_id} 的所有边...")
|
||||
logger.info(t("console.fetchingAllEdges", graphId=graph_id))
|
||||
|
||||
edges = fetch_all_edges(self.client, graph_id)
|
||||
|
||||
|
|
@ -710,7 +710,7 @@ class ZepToolsService:
|
|||
|
||||
result.append(edge_info)
|
||||
|
||||
logger.info(f"获取到 {len(result)} 条边")
|
||||
logger.info(t("console.fetchedEdges", count=len(result)))
|
||||
return result
|
||||
|
||||
def get_node_detail(self, node_uuid: str) -> Optional[NodeInfo]:
|
||||
|
|
@ -723,12 +723,12 @@ class ZepToolsService:
|
|||
Returns:
|
||||
节点信息或None
|
||||
"""
|
||||
logger.info(f"获取节点详情: {node_uuid[:8]}...")
|
||||
logger.info(t("console.fetchingNodeDetail", uuid=node_uuid[:8]))
|
||||
|
||||
try:
|
||||
node = self._call_with_retry(
|
||||
func=lambda: self.client.graph.node.get(uuid_=node_uuid),
|
||||
operation_name=f"获取节点详情(uuid={node_uuid[:8]}...)"
|
||||
operation_name=t("console.fetchNodeDetailOp", uuid=node_uuid[:8])
|
||||
)
|
||||
|
||||
if not node:
|
||||
|
|
@ -742,7 +742,7 @@ class ZepToolsService:
|
|||
attributes=node.attributes or {}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"获取节点详情失败: {str(e)}")
|
||||
logger.error(t("console.fetchNodeDetailFailed", error=str(e)))
|
||||
return None
|
||||
|
||||
def get_node_edges(self, graph_id: str, node_uuid: str) -> List[EdgeInfo]:
|
||||
|
|
@ -758,7 +758,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
边列表
|
||||
"""
|
||||
logger.info(f"获取节点 {node_uuid[:8]}... 的相关边")
|
||||
logger.info(t("console.fetchingNodeEdges", uuid=node_uuid[:8]))
|
||||
|
||||
try:
|
||||
# 获取图谱所有边,然后过滤
|
||||
|
|
@ -770,11 +770,11 @@ class ZepToolsService:
|
|||
if edge.source_node_uuid == node_uuid or edge.target_node_uuid == node_uuid:
|
||||
result.append(edge)
|
||||
|
||||
logger.info(f"找到 {len(result)} 条与节点相关的边")
|
||||
logger.info(t("console.foundNodeEdges", count=len(result)))
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"获取节点边失败: {str(e)}")
|
||||
logger.warning(t("console.fetchNodeEdgesFailed", error=str(e)))
|
||||
return []
|
||||
|
||||
def get_entities_by_type(
|
||||
|
|
@ -792,7 +792,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
符合类型的实体列表
|
||||
"""
|
||||
logger.info(f"获取类型为 {entity_type} 的实体...")
|
||||
logger.info(t("console.fetchingEntitiesByType", type=entity_type))
|
||||
|
||||
all_nodes = self.get_all_nodes(graph_id)
|
||||
|
||||
|
|
@ -802,7 +802,7 @@ class ZepToolsService:
|
|||
if entity_type in node.labels:
|
||||
filtered.append(node)
|
||||
|
||||
logger.info(f"找到 {len(filtered)} 个 {entity_type} 类型的实体")
|
||||
logger.info(t("console.foundEntitiesByType", count=len(filtered), type=entity_type))
|
||||
return filtered
|
||||
|
||||
def get_entity_summary(
|
||||
|
|
@ -822,7 +822,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
实体摘要信息
|
||||
"""
|
||||
logger.info(f"获取实体 {entity_name} 的关系摘要...")
|
||||
logger.info(t("console.fetchingEntitySummary", name=entity_name))
|
||||
|
||||
# 先搜索该实体相关的信息
|
||||
search_result = self.search_graph(
|
||||
|
|
@ -862,7 +862,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
统计信息
|
||||
"""
|
||||
logger.info(f"获取图谱 {graph_id} 的统计信息...")
|
||||
logger.info(t("console.fetchingGraphStats", graphId=graph_id))
|
||||
|
||||
nodes = self.get_all_nodes(graph_id)
|
||||
edges = self.get_all_edges(graph_id)
|
||||
|
|
@ -906,7 +906,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
模拟上下文信息
|
||||
"""
|
||||
logger.info(f"获取模拟上下文: {simulation_requirement[:50]}...")
|
||||
logger.info(t("console.fetchingSimContext", requirement=simulation_requirement[:50]))
|
||||
|
||||
# 搜索与模拟需求相关的信息
|
||||
search_result = self.search_graph(
|
||||
|
|
@ -970,7 +970,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
InsightForgeResult: 深度洞察检索结果
|
||||
"""
|
||||
logger.info(f"InsightForge 深度洞察检索: {query[:50]}...")
|
||||
logger.info(t("console.insightForgeStart", query=query[:50]))
|
||||
|
||||
result = InsightForgeResult(
|
||||
query=query,
|
||||
|
|
@ -986,7 +986,7 @@ class ZepToolsService:
|
|||
max_queries=max_sub_queries
|
||||
)
|
||||
result.sub_queries = sub_queries
|
||||
logger.info(f"生成 {len(sub_queries)} 个子问题")
|
||||
logger.info(t("console.generatedSubQueries", count=len(sub_queries)))
|
||||
|
||||
# Step 2: 对每个子问题进行语义搜索
|
||||
all_facts = []
|
||||
|
|
@ -1086,7 +1086,7 @@ class ZepToolsService:
|
|||
result.relationship_chains = relationship_chains
|
||||
result.total_relationships = len(relationship_chains)
|
||||
|
||||
logger.info(f"InsightForge完成: {result.total_facts}条事实, {result.total_entities}个实体, {result.total_relationships}条关系")
|
||||
logger.info(t("console.insightForgeComplete", facts=result.total_facts, entities=result.total_entities, relationships=result.total_relationships))
|
||||
return result
|
||||
|
||||
def _generate_sub_queries(
|
||||
|
|
@ -1133,7 +1133,7 @@ class ZepToolsService:
|
|||
return [str(sq) for sq in sub_queries[:max_queries]]
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"生成子问题失败: {str(e)},使用默认子问题")
|
||||
logger.warning(t("console.generateSubQueriesFailed", error=str(e)))
|
||||
# 降级:返回基于原问题的变体
|
||||
return [
|
||||
query,
|
||||
|
|
@ -1168,7 +1168,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
PanoramaResult: 广度搜索结果
|
||||
"""
|
||||
logger.info(f"PanoramaSearch 广度搜索: {query[:50]}...")
|
||||
logger.info(t("console.panoramaSearchStart", query=query[:50]))
|
||||
|
||||
result = PanoramaResult(query=query)
|
||||
|
||||
|
|
@ -1231,7 +1231,7 @@ class ZepToolsService:
|
|||
result.active_count = len(active_facts)
|
||||
result.historical_count = len(historical_facts)
|
||||
|
||||
logger.info(f"PanoramaSearch完成: {result.active_count}条有效, {result.historical_count}条历史")
|
||||
logger.info(t("console.panoramaSearchComplete", active=result.active_count, historical=result.historical_count))
|
||||
return result
|
||||
|
||||
def quick_search(
|
||||
|
|
@ -1256,7 +1256,7 @@ class ZepToolsService:
|
|||
Returns:
|
||||
SearchResult: 搜索结果
|
||||
"""
|
||||
logger.info(f"QuickSearch 简单搜索: {query[:50]}...")
|
||||
logger.info(t("console.quickSearchStart", query=query[:50]))
|
||||
|
||||
# 直接调用现有的search_graph方法
|
||||
result = self.search_graph(
|
||||
|
|
@ -1266,7 +1266,7 @@ class ZepToolsService:
|
|||
scope="edges"
|
||||
)
|
||||
|
||||
logger.info(f"QuickSearch完成: {result.total_count}条结果")
|
||||
logger.info(t("console.quickSearchComplete", count=result.total_count))
|
||||
return result
|
||||
|
||||
def interview_agents(
|
||||
|
|
@ -1306,7 +1306,7 @@ class ZepToolsService:
|
|||
"""
|
||||
from .simulation_runner import SimulationRunner
|
||||
|
||||
logger.info(f"InterviewAgents 深度采访(真实API): {interview_requirement[:50]}...")
|
||||
logger.info(t("console.interviewAgentsStart", requirement=interview_requirement[:50]))
|
||||
|
||||
result = InterviewResult(
|
||||
interview_topic=interview_requirement,
|
||||
|
|
@ -1317,12 +1317,12 @@ class ZepToolsService:
|
|||
profiles = self._load_agent_profiles(simulation_id)
|
||||
|
||||
if not profiles:
|
||||
logger.warning(f"未找到模拟 {simulation_id} 的人设文件")
|
||||
logger.warning(t("console.profilesNotFound", simId=simulation_id))
|
||||
result.summary = "未找到可采访的Agent人设文件"
|
||||
return result
|
||||
|
||||
result.total_agents = len(profiles)
|
||||
logger.info(f"加载到 {len(profiles)} 个Agent人设")
|
||||
logger.info(t("console.loadedProfiles", count=len(profiles)))
|
||||
|
||||
# Step 2: 使用LLM选择要采访的Agent(返回agent_id列表)
|
||||
selected_agents, selected_indices, selection_reasoning = self._select_agents_for_interview(
|
||||
|
|
@ -1334,7 +1334,7 @@ class ZepToolsService:
|
|||
|
||||
result.selected_agents = selected_agents
|
||||
result.selection_reasoning = selection_reasoning
|
||||
logger.info(f"选择了 {len(selected_agents)} 个Agent进行采访: {selected_indices}")
|
||||
logger.info(t("console.selectedAgentsForInterview", count=len(selected_agents), indices=selected_indices))
|
||||
|
||||
# Step 3: 生成采访问题(如果没有提供)
|
||||
if not result.interview_questions:
|
||||
|
|
@ -1343,7 +1343,7 @@ class ZepToolsService:
|
|||
simulation_requirement=simulation_requirement,
|
||||
selected_agents=selected_agents
|
||||
)
|
||||
logger.info(f"生成了 {len(result.interview_questions)} 个采访问题")
|
||||
logger.info(t("console.generatedInterviewQuestions", count=len(result.interview_questions)))
|
||||
|
||||
# 将问题合并为一个采访prompt
|
||||
combined_prompt = "\n".join([f"{i+1}. {q}" for i, q in enumerate(result.interview_questions)])
|
||||
|
|
@ -1373,7 +1373,7 @@ class ZepToolsService:
|
|||
# 不指定platform,API会在twitter和reddit两个平台都采访
|
||||
})
|
||||
|
||||
logger.info(f"调用批量采访API(双平台): {len(interviews_request)} 个Agent")
|
||||
logger.info(t("console.callingBatchInterviewApi", count=len(interviews_request)))
|
||||
|
||||
# 调用 SimulationRunner 的批量采访方法(不传platform,双平台采访)
|
||||
api_result = SimulationRunner.interview_agents_batch(
|
||||
|
|
@ -1383,12 +1383,12 @@ class ZepToolsService:
|
|||
timeout=180.0 # 双平台需要更长超时
|
||||
)
|
||||
|
||||
logger.info(f"采访API返回: {api_result.get('interviews_count', 0)} 个结果, success={api_result.get('success')}")
|
||||
logger.info(t("console.interviewApiReturned", count=api_result.get('interviews_count', 0), success=api_result.get('success')))
|
||||
|
||||
# 检查API调用是否成功
|
||||
if not api_result.get("success", False):
|
||||
error_msg = api_result.get("error", "未知错误")
|
||||
logger.warning(f"采访API返回失败: {error_msg}")
|
||||
logger.warning(t("console.interviewApiReturnedFailure", error=error_msg))
|
||||
result.summary = f"采访API调用失败:{error_msg}。请检查OASIS模拟环境状态。"
|
||||
return result
|
||||
|
||||
|
|
@ -1461,11 +1461,11 @@ class ZepToolsService:
|
|||
|
||||
except ValueError as e:
|
||||
# 模拟环境未运行
|
||||
logger.warning(f"采访API调用失败(环境未运行?): {e}")
|
||||
logger.warning(t("console.interviewApiCallFailed", error=e))
|
||||
result.summary = f"采访失败:{str(e)}。模拟环境可能已关闭,请确保OASIS环境正在运行。"
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"采访API调用异常: {e}")
|
||||
logger.error(t("console.interviewApiCallException", error=e))
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
result.summary = f"采访过程发生错误:{str(e)}"
|
||||
|
|
@ -1478,7 +1478,7 @@ class ZepToolsService:
|
|||
interview_requirement=interview_requirement
|
||||
)
|
||||
|
||||
logger.info(f"InterviewAgents完成: 采访了 {result.interviewed_count} 个Agent(双平台)")
|
||||
logger.info(t("console.interviewAgentsComplete", count=result.interviewed_count))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -1521,10 +1521,10 @@ class ZepToolsService:
|
|||
try:
|
||||
with open(reddit_profile_path, 'r', encoding='utf-8') as f:
|
||||
profiles = json.load(f)
|
||||
logger.info(f"从 reddit_profiles.json 加载了 {len(profiles)} 个人设")
|
||||
logger.info(t("console.loadedRedditProfiles", count=len(profiles)))
|
||||
return profiles
|
||||
except Exception as e:
|
||||
logger.warning(f"读取 reddit_profiles.json 失败: {e}")
|
||||
logger.warning(t("console.readRedditProfilesFailed", error=e))
|
||||
|
||||
# 尝试读取Twitter CSV格式
|
||||
twitter_profile_path = os.path.join(sim_dir, "twitter_profiles.csv")
|
||||
|
|
@ -1541,10 +1541,10 @@ class ZepToolsService:
|
|||
"persona": row.get("user_char", ""),
|
||||
"profession": "未知"
|
||||
})
|
||||
logger.info(f"从 twitter_profiles.csv 加载了 {len(profiles)} 个人设")
|
||||
logger.info(t("console.loadedTwitterProfiles", count=len(profiles)))
|
||||
return profiles
|
||||
except Exception as e:
|
||||
logger.warning(f"读取 twitter_profiles.csv 失败: {e}")
|
||||
logger.warning(t("console.readTwitterProfilesFailed", error=e))
|
||||
|
||||
return profiles
|
||||
|
||||
|
|
@ -1625,7 +1625,7 @@ class ZepToolsService:
|
|||
return selected_agents, valid_indices, reasoning
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM选择Agent失败,使用默认选择: {e}")
|
||||
logger.warning(t("console.llmSelectAgentFailed", error=e))
|
||||
# 降级:选择前N个
|
||||
selected = profiles[:max_agents]
|
||||
indices = list(range(min(max_agents, len(profiles))))
|
||||
|
|
@ -1673,7 +1673,7 @@ class ZepToolsService:
|
|||
return response.get("questions", [f"关于{interview_requirement},您有什么看法?"])
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"生成采访问题失败: {e}")
|
||||
logger.warning(t("console.generateInterviewQuestionsFailed", error=e))
|
||||
return [
|
||||
f"关于{interview_requirement},您的观点是什么?",
|
||||
"这件事对您或您所代表的群体有什么影响?",
|
||||
|
|
@ -1695,7 +1695,8 @@ class ZepToolsService:
|
|||
for interview in interviews:
|
||||
interview_texts.append(f"【{interview.agent_name}({interview.agent_role})】\n{interview.response[:500]}")
|
||||
|
||||
system_prompt = """你是一个专业的新闻编辑。请根据多位受访者的回答,生成一份采访摘要。
|
||||
quote_instruction = "引用受访者原话时使用中文引号「」" if get_locale() == 'zh' else 'Use quotation marks "" when quoting interviewees'
|
||||
system_prompt = f"""你是一个专业的新闻编辑。请根据多位受访者的回答,生成一份采访摘要。
|
||||
|
||||
摘要要求:
|
||||
1. 提炼各方主要观点
|
||||
|
|
@ -1708,7 +1709,7 @@ class ZepToolsService:
|
|||
- 使用纯文本段落,用空行分隔不同部分
|
||||
- 不要使用Markdown标题(如#、##、###)
|
||||
- 不要使用分割线(如---、***)
|
||||
- 引用受访者原话时使用中文引号「」
|
||||
- {quote_instruction}
|
||||
- 可以使用**加粗**标记关键词,但不要使用其他Markdown语法"""
|
||||
|
||||
user_prompt = f"""采访主题:{interview_requirement}
|
||||
|
|
@ -1730,6 +1731,6 @@ class ZepToolsService:
|
|||
return summary
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"生成采访摘要失败: {e}")
|
||||
logger.warning(t("console.generateInterviewSummaryFailed", error=e))
|
||||
# 降级:简单拼接
|
||||
return f"共采访了{len(interviews)}位受访者,包括:" + "、".join([i.agent_name for i in interviews])
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
from .file_parser import FileParser
|
||||
from .llm_client import LLMClient
|
||||
from .locale import t, get_locale, set_locale, get_language_instruction
|
||||
|
||||
__all__ = ['FileParser', 'LLMClient']
|
||||
__all__ = ['FileParser', 'LLMClient', 't', 'get_locale', 'set_locale', 'get_language_instruction']
|
||||
|
||||
|
|
|
|||
69
backend/app/utils/locale.py
Normal file
69
backend/app/utils/locale.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import json
|
||||
import os
|
||||
import threading
|
||||
from flask import request, has_request_context
|
||||
|
||||
_thread_local = threading.local()
|
||||
|
||||
_locales_dir = os.path.join(os.path.dirname(__file__), '..', '..', '..', 'locales')
|
||||
|
||||
# Load language registry
|
||||
with open(os.path.join(_locales_dir, 'languages.json'), 'r', encoding='utf-8') as f:
|
||||
_languages = json.load(f)
|
||||
|
||||
# Load translation files
|
||||
_translations = {}
|
||||
for filename in os.listdir(_locales_dir):
|
||||
if filename.endswith('.json') and filename != 'languages.json':
|
||||
locale_name = filename[:-5]
|
||||
with open(os.path.join(_locales_dir, filename), 'r', encoding='utf-8') as f:
|
||||
_translations[locale_name] = json.load(f)
|
||||
|
||||
|
||||
def set_locale(locale: str):
|
||||
"""Set locale for current thread. Call at the start of background threads."""
|
||||
_thread_local.locale = locale
|
||||
|
||||
|
||||
def get_locale() -> str:
|
||||
if has_request_context():
|
||||
raw = request.headers.get('Accept-Language', 'zh')
|
||||
return raw if raw in _translations else 'zh'
|
||||
return getattr(_thread_local, 'locale', 'zh')
|
||||
|
||||
|
||||
def t(key: str, **kwargs) -> str:
|
||||
locale = get_locale()
|
||||
messages = _translations.get(locale, _translations.get('zh', {}))
|
||||
|
||||
value = messages
|
||||
for part in key.split('.'):
|
||||
if isinstance(value, dict):
|
||||
value = value.get(part)
|
||||
else:
|
||||
value = None
|
||||
break
|
||||
|
||||
if value is None:
|
||||
value = _translations.get('zh', {})
|
||||
for part in key.split('.'):
|
||||
if isinstance(value, dict):
|
||||
value = value.get(part)
|
||||
else:
|
||||
value = None
|
||||
break
|
||||
|
||||
if value is None:
|
||||
return key
|
||||
|
||||
if kwargs:
|
||||
for k, v in kwargs.items():
|
||||
value = value.replace(f'{{{k}}}', str(v))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def get_language_instruction() -> str:
|
||||
locale = get_locale()
|
||||
lang_config = _languages.get(locale, _languages.get('zh', {}))
|
||||
return lang_config.get('llmInstruction', '请使用中文回答。')
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<script>document.documentElement.lang = localStorage.getItem('locale') || 'zh'</script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@100..800&family=Noto+Sans+SC:wght@300;400;500;700;800;900&family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">
|
||||
|
|
|
|||
83
frontend/package-lock.json
generated
83
frontend/package-lock.json
generated
|
|
@ -11,6 +11,7 @@
|
|||
"axios": "^1.13.2",
|
||||
"d3": "^7.9.0",
|
||||
"vue": "^3.5.24",
|
||||
"vue-i18n": "^11.3.0",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -506,6 +507,67 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "11.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.3.0.tgz",
|
||||
"integrity": "sha512-NNX5jIwF4TJBe7RtSKDMOA6JD9mp2mRcBHAwt2X+Q8PvnZub0yj5YYXlFu2AcESdgQpEv/5Yx2uOCV/yh7YkZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/devtools-types": "11.3.0",
|
||||
"@intlify/message-compiler": "11.3.0",
|
||||
"@intlify/shared": "11.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/devtools-types": {
|
||||
"version": "11.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.3.0.tgz",
|
||||
"integrity": "sha512-G9CNL4WpANWVdUjubOIIS7/D2j/0j+1KJmhBJxHilWNKr9mmt3IjFV3Hq4JoBP23uOoC5ynxz/FHZ42M+YxfGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "11.3.0",
|
||||
"@intlify/shared": "11.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "11.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.3.0.tgz",
|
||||
"integrity": "sha512-RAJp3TMsqohg/Wa7bVF3cChRhecSYBLrTCQSj7j0UtWVFLP+6iEJoE2zb7GU5fp+fmG5kCbUdzhmlAUCWXiUJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "11.3.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "11.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.3.0.tgz",
|
||||
"integrity": "sha512-LC6P/uay7rXL5zZ5+5iRJfLs/iUN8apu9tm8YqQVmW3Uq3X4A0dOFUIDuAmB7gAC29wTHOS3EiN/IosNSz0eNQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
|
|
@ -2035,6 +2097,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "11.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.3.0.tgz",
|
||||
"integrity": "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "11.3.0",
|
||||
"@intlify/devtools-types": "11.3.0",
|
||||
"@intlify/shared": "11.3.0",
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"axios": "1.13.2",
|
||||
"d3": "^7.9.0",
|
||||
"vue": "^3.5.24",
|
||||
"vue-i18n": "^11.3.0",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import axios from 'axios'
|
||||
import i18n from '../i18n'
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
|
|
@ -12,6 +13,7 @@ const service = axios.create({
|
|||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
config.headers['Accept-Language'] = i18n.global.locale.value
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<div class="graph-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">Graph Relationship Visualization</span>
|
||||
<span class="panel-title">{{ $t('graph.panelTitle') }}</span>
|
||||
<!-- 顶部工具栏 (Internal Top Right) -->
|
||||
<div class="header-tools">
|
||||
<button class="tool-btn" @click="$emit('refresh')" :disabled="loading" title="刷新图谱">
|
||||
<button class="tool-btn" @click="$emit('refresh')" :disabled="loading" :title="$t('graph.refreshGraph')">
|
||||
<span class="icon-refresh" :class="{ 'spinning': loading }">↻</span>
|
||||
<span class="btn-text">Refresh</span>
|
||||
</button>
|
||||
<button class="tool-btn" @click="$emit('toggle-maximize')" title="最大化/还原">
|
||||
<button class="tool-btn" @click="$emit('toggle-maximize')" :title="$t('graph.toggleMaximize')">
|
||||
<span class="icon-maximize">⛶</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
<path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-4.44-4.04z" />
|
||||
</svg>
|
||||
</div>
|
||||
{{ isSimulating ? 'GraphRAG长短期记忆实时更新中' : '实时更新中...' }}
|
||||
{{ isSimulating ? $t('graph.graphMemoryRealtime') : $t('graph.realtimeUpdating') }}
|
||||
</div>
|
||||
|
||||
<!-- 模拟结束后的提示 -->
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
<line x1="12" y1="8" x2="12.01" y2="8"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="hint-text">还有少量内容处理中,建议稍后手动刷新图谱</span>
|
||||
<button class="hint-close-btn" @click="dismissFinishedHint" title="关闭提示">
|
||||
<span class="hint-text">{{ $t('graph.pendingContentHint') }}</span>
|
||||
<button class="hint-close-btn" @click="dismissFinishedHint" :title="$t('graph.closeHint')">
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
<!-- 节点/边详情面板 -->
|
||||
<div v-if="selectedItem" class="detail-panel">
|
||||
<div class="detail-panel-header">
|
||||
<span class="detail-title">{{ selectedItem.type === 'node' ? 'Node Details' : 'Relationship' }}</span>
|
||||
<span class="detail-title">{{ selectedItem.type === 'node' ? $t('graph.nodeDetails') : $t('graph.relationship') }}</span>
|
||||
<span v-if="selectedItem.type === 'node'" class="detail-type-badge" :style="{ background: selectedItem.color, color: '#fff' }">
|
||||
{{ selectedItem.entityType }}
|
||||
</span>
|
||||
|
|
@ -203,13 +203,13 @@
|
|||
<!-- 加载状态 -->
|
||||
<div v-else-if="loading" class="graph-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>图谱数据加载中...</p>
|
||||
<p>{{ $t('graph.graphDataLoading') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 等待/空状态 -->
|
||||
<div v-else class="graph-state">
|
||||
<div class="empty-icon">❖</div>
|
||||
<p class="empty-text">等待本体生成...</p>
|
||||
<p class="empty-text">{{ $t('graph.waitingOntology') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<!-- 标题区域 -->
|
||||
<div class="section-header">
|
||||
<div class="section-line"></div>
|
||||
<span class="section-title">推演记录</span>
|
||||
<span class="section-title">{{ $t('history.title') }}</span>
|
||||
<div class="section-line"></div>
|
||||
</div>
|
||||
|
||||
|
|
@ -36,16 +36,16 @@
|
|||
<span
|
||||
class="status-icon"
|
||||
:class="{ available: project.project_id, unavailable: !project.project_id }"
|
||||
title="图谱构建"
|
||||
:title="$t('history.graphBuild')"
|
||||
>◇</span>
|
||||
<span
|
||||
class="status-icon available"
|
||||
title="环境搭建"
|
||||
:title="$t('history.envSetup')"
|
||||
>◈</span>
|
||||
<span
|
||||
class="status-icon"
|
||||
:class="{ available: project.report_id, unavailable: !project.report_id }"
|
||||
title="分析报告"
|
||||
:title="$t('history.analysisReport')"
|
||||
>◆</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -67,13 +67,13 @@
|
|||
</div>
|
||||
<!-- 如果有更多文件,显示提示 -->
|
||||
<div v-if="project.files.length > 3" class="files-more">
|
||||
+{{ project.files.length - 3 }} 个文件
|
||||
{{ $t('history.moreFiles', { count: project.files.length - 3 }) }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 无文件时的占位 -->
|
||||
<div class="files-empty" v-else>
|
||||
<span class="empty-file-icon">◇</span>
|
||||
<span class="empty-file-text">暂无文件</span>
|
||||
<span class="empty-file-text">{{ $t('history.noFiles') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -102,7 +102,7 @@
|
|||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-state">
|
||||
<span class="loading-spinner"></span>
|
||||
<span class="loading-text">加载中...</span>
|
||||
<span class="loading-text">{{ $t('history.loadingText') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 历史回放详情弹窗 -->
|
||||
|
|
@ -126,27 +126,27 @@
|
|||
<div class="modal-body">
|
||||
<!-- 模拟需求 -->
|
||||
<div class="modal-section">
|
||||
<div class="modal-label">模拟需求</div>
|
||||
<div class="modal-requirement">{{ selectedProject.simulation_requirement || '无' }}</div>
|
||||
<div class="modal-label">{{ $t('history.simRequirement') }}</div>
|
||||
<div class="modal-requirement">{{ selectedProject.simulation_requirement || $t('common.none') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<div class="modal-section">
|
||||
<div class="modal-label">关联文件</div>
|
||||
<div class="modal-label">{{ $t('history.relatedFiles') }}</div>
|
||||
<div class="modal-files" v-if="selectedProject.files && selectedProject.files.length > 0">
|
||||
<div v-for="(file, index) in selectedProject.files" :key="index" class="modal-file-item">
|
||||
<span class="file-tag" :class="getFileType(file.filename)">{{ getFileTypeLabel(file.filename) }}</span>
|
||||
<span class="modal-file-name">{{ file.filename }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-empty" v-else>暂无关联文件</div>
|
||||
<div class="modal-empty" v-else>{{ $t('history.noRelatedFiles') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 推演回放分割线 -->
|
||||
<div class="modal-divider">
|
||||
<span class="divider-line"></span>
|
||||
<span class="divider-text">推演回放</span>
|
||||
<span class="divider-text">{{ $t('history.replayTitle') }}</span>
|
||||
<span class="divider-line"></span>
|
||||
</div>
|
||||
|
||||
|
|
@ -159,7 +159,7 @@
|
|||
>
|
||||
<span class="btn-step">Step1</span>
|
||||
<span class="btn-icon">◇</span>
|
||||
<span class="btn-text">图谱构建</span>
|
||||
<span class="btn-text">{{ $t('history.step1Button') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="modal-btn btn-simulation"
|
||||
|
|
@ -167,7 +167,7 @@
|
|||
>
|
||||
<span class="btn-step">Step2</span>
|
||||
<span class="btn-icon">◈</span>
|
||||
<span class="btn-text">环境搭建</span>
|
||||
<span class="btn-text">{{ $t('history.step2Button') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="modal-btn btn-report"
|
||||
|
|
@ -176,12 +176,12 @@
|
|||
>
|
||||
<span class="btn-step">Step4</span>
|
||||
<span class="btn-icon">◆</span>
|
||||
<span class="btn-text">分析报告</span>
|
||||
<span class="btn-text">{{ $t('history.step4Button') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 不可回放提示 -->
|
||||
<div class="modal-playback-hint">
|
||||
<span class="hint-text">Step3「开始模拟」与 Step5「深度互动」需在运行中启动,不支持历史回放</span>
|
||||
<span class="hint-text">{{ $t('history.replayHint') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -193,10 +193,12 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, onActivated, watch, nextTick } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getSimulationHistory } from '../api/simulation'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
// 状态
|
||||
const projects = ref([])
|
||||
|
|
@ -337,7 +339,7 @@ const truncateText = (text, maxLength) => {
|
|||
|
||||
// 从模拟需求生成标题(取前20字)
|
||||
const getSimulationTitle = (requirement) => {
|
||||
if (!requirement) return '未命名模拟'
|
||||
if (!requirement) return t('history.untitledSimulation')
|
||||
const title = requirement.slice(0, 20)
|
||||
return requirement.length > 20 ? title + '...' : title
|
||||
}
|
||||
|
|
@ -353,8 +355,8 @@ const formatSimulationId = (simulationId) => {
|
|||
const formatRounds = (simulation) => {
|
||||
const current = simulation.current_round || 0
|
||||
const total = simulation.total_rounds || 0
|
||||
if (total === 0) return '未开始'
|
||||
return `${current}/${total} 轮`
|
||||
if (total === 0) return t('history.notStarted')
|
||||
return t('history.roundsProgress', { current, total })
|
||||
}
|
||||
|
||||
// 获取文件类型(用于样式)
|
||||
|
|
@ -382,7 +384,7 @@ const getFileTypeLabel = (filename) => {
|
|||
|
||||
// 截断文件名(保留扩展名)
|
||||
const truncateFilename = (filename, maxLength) => {
|
||||
if (!filename) return '未知文件'
|
||||
if (!filename) return t('history.unknownFile')
|
||||
if (filename.length <= maxLength) return filename
|
||||
|
||||
const ext = filename.includes('.') ? '.' + filename.split('.').pop() : ''
|
||||
|
|
|
|||
124
frontend/src/components/LanguageSwitcher.vue
Normal file
124
frontend/src/components/LanguageSwitcher.vue
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<template>
|
||||
<div class="language-switcher" ref="switcherRef">
|
||||
<button class="switcher-trigger" @click="toggleDropdown">
|
||||
{{ currentLabel }}
|
||||
<span class="caret">{{ open ? '▲' : '▼' }}</span>
|
||||
</button>
|
||||
<ul v-if="open" class="switcher-dropdown">
|
||||
<li
|
||||
v-for="loc in availableLocales"
|
||||
:key="loc.key"
|
||||
class="switcher-option"
|
||||
:class="{ active: loc.key === locale }"
|
||||
@click="switchLocale(loc.key)"
|
||||
>
|
||||
{{ loc.label }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { availableLocales } from '@/i18n/index.js'
|
||||
|
||||
const { locale } = useI18n()
|
||||
const open = ref(false)
|
||||
const switcherRef = ref(null)
|
||||
|
||||
const currentLabel = computed(() => {
|
||||
const found = availableLocales.find(l => l.key === locale.value)
|
||||
return found ? found.label : locale.value
|
||||
})
|
||||
|
||||
const toggleDropdown = () => {
|
||||
open.value = !open.value
|
||||
}
|
||||
|
||||
const switchLocale = (key) => {
|
||||
locale.value = key
|
||||
localStorage.setItem('locale', key)
|
||||
document.documentElement.lang = key
|
||||
open.value = false
|
||||
}
|
||||
|
||||
const onClickOutside = (e) => {
|
||||
if (switcherRef.value && !switcherRef.value.contains(e.target)) {
|
||||
open.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', onClickOutside)
|
||||
document.documentElement.lang = locale.value
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', onClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.language-switcher {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
/* Light theme (default - for white header backgrounds) */
|
||||
.switcher-trigger {
|
||||
background: transparent;
|
||||
color: #333;
|
||||
border: 1px solid #CCC;
|
||||
padding: 4px 12px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: border-color 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.switcher-trigger:hover {
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
.caret {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.switcher-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 4px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #DDD;
|
||||
list-style: none;
|
||||
padding: 4px 0;
|
||||
min-width: 100%;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.switcher-option {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8rem;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.switcher-option:hover {
|
||||
background: #F0F0F0;
|
||||
}
|
||||
|
||||
.switcher-option.active {
|
||||
color: var(--orange, #FF4500);
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
|
@ -6,25 +6,25 @@
|
|||
<div class="card-header">
|
||||
<div class="step-info">
|
||||
<span class="step-num">01</span>
|
||||
<span class="step-title">本体生成</span>
|
||||
<span class="step-title">{{ $t('step1.ontologyGeneration') }}</span>
|
||||
</div>
|
||||
<div class="step-status">
|
||||
<span v-if="currentPhase > 0" class="badge success">已完成</span>
|
||||
<span v-else-if="currentPhase === 0" class="badge processing">生成中</span>
|
||||
<span v-else class="badge pending">等待</span>
|
||||
<span v-if="currentPhase > 0" class="badge success">{{ $t('step1.ontologyCompleted') }}</span>
|
||||
<span v-else-if="currentPhase === 0" class="badge processing">{{ $t('step1.ontologyGenerating') }}</span>
|
||||
<span v-else class="badge pending">{{ $t('step1.ontologyPending') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="api-note">POST /api/graph/ontology/generate</p>
|
||||
<p class="description">
|
||||
LLM分析文档内容与模拟需求,提取出现实种子,自动生成合适的本体结构
|
||||
{{ $t('step1.ontologyDesc') }}
|
||||
</p>
|
||||
|
||||
<!-- Loading / Progress -->
|
||||
<div v-if="currentPhase === 0 && ontologyProgress" class="progress-section">
|
||||
<div class="spinner-sm"></div>
|
||||
<span>{{ ontologyProgress.message || '正在分析文档...' }}</span>
|
||||
<span>{{ ontologyProgress.message || $t('step1.analyzingDocs') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Detail Overlay -->
|
||||
|
|
@ -110,34 +110,34 @@
|
|||
<div class="card-header">
|
||||
<div class="step-info">
|
||||
<span class="step-num">02</span>
|
||||
<span class="step-title">GraphRAG构建</span>
|
||||
<span class="step-title">{{ $t('step1.graphRagBuild') }}</span>
|
||||
</div>
|
||||
<div class="step-status">
|
||||
<span v-if="currentPhase > 1" class="badge success">已完成</span>
|
||||
<span v-if="currentPhase > 1" class="badge success">{{ $t('step1.ontologyCompleted') }}</span>
|
||||
<span v-else-if="currentPhase === 1" class="badge processing">{{ buildProgress?.progress || 0 }}%</span>
|
||||
<span v-else class="badge pending">等待</span>
|
||||
<span v-else class="badge pending">{{ $t('step1.ontologyPending') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="api-note">POST /api/graph/build</p>
|
||||
<p class="description">
|
||||
基于生成的本体,将文档自动分块后调用 Zep 构建知识图谱,提取实体和关系,并形成时序记忆与社区摘要
|
||||
{{ $t('step1.graphRagDesc') }}
|
||||
</p>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ graphStats.nodes }}</span>
|
||||
<span class="stat-label">实体节点</span>
|
||||
<span class="stat-label">{{ $t('step1.entityNodes') }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ graphStats.edges }}</span>
|
||||
<span class="stat-label">关系边</span>
|
||||
<span class="stat-label">{{ $t('step1.relationEdges') }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ graphStats.types }}</span>
|
||||
<span class="stat-label">SCHEMA类型</span>
|
||||
<span class="stat-label">{{ $t('step1.schemaTypes') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -148,23 +148,23 @@
|
|||
<div class="card-header">
|
||||
<div class="step-info">
|
||||
<span class="step-num">03</span>
|
||||
<span class="step-title">构建完成</span>
|
||||
<span class="step-title">{{ $t('step1.buildComplete') }}</span>
|
||||
</div>
|
||||
<div class="step-status">
|
||||
<span v-if="currentPhase >= 2" class="badge accent">进行中</span>
|
||||
<span v-if="currentPhase >= 2" class="badge accent">{{ $t('step1.inProgress') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="api-note">POST /api/simulation/create</p>
|
||||
<p class="description">图谱构建已完成,请进入下一步进行模拟环境搭建</p>
|
||||
<p class="description">{{ $t('step1.buildCompleteDesc') }}</p>
|
||||
<button
|
||||
class="action-btn"
|
||||
:disabled="currentPhase < 2 || creatingSimulation"
|
||||
@click="handleEnterEnvSetup"
|
||||
>
|
||||
<span v-if="creatingSimulation" class="spinner-sm"></span>
|
||||
{{ creatingSimulation ? '创建中...' : '进入环境搭建 ➝' }}
|
||||
{{ creatingSimulation ? $t('step1.creating') : $t('step1.enterEnvSetup') + ' ➝' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -189,9 +189,11 @@
|
|||
<script setup>
|
||||
import { computed, ref, watch, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { createSimulation } from '../api/simulation'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
currentPhase: { type: Number, default: 0 },
|
||||
|
|
@ -233,11 +235,11 @@ const handleEnterEnvSetup = async () => {
|
|||
})
|
||||
} else {
|
||||
console.error('创建模拟失败:', res.error)
|
||||
alert('创建模拟失败: ' + (res.error || '未知错误'))
|
||||
alert(t('step1.createSimulationFailed', { error: res.error || t('common.unknownError') }))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('创建模拟异常:', err)
|
||||
alert('创建模拟异常: ' + err.message)
|
||||
alert(t('step1.createSimulationException', { error: err.message }))
|
||||
} finally {
|
||||
creatingSimulation.value = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@
|
|||
<div class="card-header">
|
||||
<div class="step-info">
|
||||
<span class="step-num">01</span>
|
||||
<span class="step-title">模拟实例初始化</span>
|
||||
<span class="step-title">{{ $t('step2.simInstanceInit') }}</span>
|
||||
</div>
|
||||
<div class="step-status">
|
||||
<span v-if="phase > 0" class="badge success">已完成</span>
|
||||
<span v-else class="badge processing">初始化</span>
|
||||
<span v-if="phase > 0" class="badge success">{{ $t('common.completed') }}</span>
|
||||
<span v-else class="badge processing">{{ $t('step2.initializing') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="api-note">POST /api/simulation/create</p>
|
||||
<p class="description">
|
||||
新建simulation实例,拉取模拟世界参数模版
|
||||
{{ $t('step2.simInstanceDesc') }}
|
||||
</p>
|
||||
|
||||
<div v-if="simulationId" class="info-card">
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Task ID</span>
|
||||
<span class="info-value mono">{{ taskId || '异步任务已完成' }}</span>
|
||||
<span class="info-value mono">{{ taskId || $t('step2.asyncTaskDone') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -46,41 +46,41 @@
|
|||
<div class="card-header">
|
||||
<div class="step-info">
|
||||
<span class="step-num">02</span>
|
||||
<span class="step-title">生成 Agent 人设</span>
|
||||
<span class="step-title">{{ $t('step2.generateAgentPersona') }}</span>
|
||||
</div>
|
||||
<div class="step-status">
|
||||
<span v-if="phase > 1" class="badge success">已完成</span>
|
||||
<span v-if="phase > 1" class="badge success">{{ $t('common.completed') }}</span>
|
||||
<span v-else-if="phase === 1" class="badge processing">{{ prepareProgress }}%</span>
|
||||
<span v-else class="badge pending">等待</span>
|
||||
<span v-else class="badge pending">{{ $t('common.pending') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="api-note">POST /api/simulation/prepare</p>
|
||||
<p class="description">
|
||||
结合上下文,自动调用工具从知识图谱梳理实体与关系,初始化模拟个体,并基于现实种子赋予他们独特的行为与记忆
|
||||
{{ $t('step2.generateAgentPersonaDesc') }}
|
||||
</p>
|
||||
|
||||
<!-- Profiles Stats -->
|
||||
<div v-if="profiles.length > 0" class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ profiles.length }}</span>
|
||||
<span class="stat-label">当前Agent数</span>
|
||||
<span class="stat-label">{{ $t('step2.currentAgentCount') }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ expectedTotal || '-' }}</span>
|
||||
<span class="stat-label">预期Agent总数</span>
|
||||
<span class="stat-label">{{ $t('step2.expectedAgentTotal') }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ totalTopicsCount }}</span>
|
||||
<span class="stat-label">现实种子当前关联话题数</span>
|
||||
<span class="stat-label">{{ $t('step2.relatedTopicsCount') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profiles List Preview -->
|
||||
<div v-if="profiles.length > 0" class="profiles-preview">
|
||||
<div class="preview-header">
|
||||
<span class="preview-title">已生成的 Agent 人设</span>
|
||||
<span class="preview-title">{{ $t('step2.generatedAgentPersonas') }}</span>
|
||||
</div>
|
||||
<div class="profiles-list">
|
||||
<div
|
||||
|
|
@ -94,9 +94,9 @@
|
|||
<span class="profile-username">@{{ profile.name || `agent_${idx}` }}</span>
|
||||
</div>
|
||||
<div class="profile-meta">
|
||||
<span class="profile-profession">{{ profile.profession || '未知职业' }}</span>
|
||||
<span class="profile-profession">{{ profile.profession || $t('step2.unknownProfession') }}</span>
|
||||
</div>
|
||||
<p class="profile-bio">{{ profile.bio || '暂无简介' }}</p>
|
||||
<p class="profile-bio">{{ profile.bio || $t('step2.noBio') }}</p>
|
||||
<div v-if="profile.interested_topics?.length" class="profile-topics">
|
||||
<span
|
||||
v-for="topic in profile.interested_topics.slice(0, 3)"
|
||||
|
|
@ -118,19 +118,19 @@
|
|||
<div class="card-header">
|
||||
<div class="step-info">
|
||||
<span class="step-num">03</span>
|
||||
<span class="step-title">生成双平台模拟配置</span>
|
||||
<span class="step-title">{{ $t('step2.dualPlatformConfig') }}</span>
|
||||
</div>
|
||||
<div class="step-status">
|
||||
<span v-if="phase > 2" class="badge success">已完成</span>
|
||||
<span v-else-if="phase === 2" class="badge processing">生成中</span>
|
||||
<span v-else class="badge pending">等待</span>
|
||||
<span v-if="phase > 2" class="badge success">{{ $t('common.completed') }}</span>
|
||||
<span v-else-if="phase === 2" class="badge processing">{{ $t('step2.generating') }}</span>
|
||||
<span v-else class="badge pending">{{ $t('common.pending') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="api-note">POST /api/simulation/prepare</p>
|
||||
<p class="description">
|
||||
LLM 根据模拟需求与现实种子,智能设置世界时间流速、推荐算法、每个个体的活跃时间段、发言频率、事件触发等参数
|
||||
{{ $t('step2.dualPlatformConfigDesc') }}
|
||||
</p>
|
||||
|
||||
<!-- Config Preview -->
|
||||
|
|
@ -139,40 +139,40 @@
|
|||
<div class="config-block">
|
||||
<div class="config-grid">
|
||||
<div class="config-item">
|
||||
<span class="config-item-label">模拟时长</span>
|
||||
<span class="config-item-value">{{ simulationConfig.time_config?.total_simulation_hours || '-' }} 小时</span>
|
||||
<span class="config-item-label">{{ $t('step2.simulationDuration') }}</span>
|
||||
<span class="config-item-value">{{ simulationConfig.time_config?.total_simulation_hours || '-' }} {{ $t('common.hours') }}</span>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<span class="config-item-label">每轮时长</span>
|
||||
<span class="config-item-value">{{ simulationConfig.time_config?.minutes_per_round || '-' }} 分钟</span>
|
||||
<span class="config-item-label">{{ $t('step2.roundDuration') }}</span>
|
||||
<span class="config-item-value">{{ simulationConfig.time_config?.minutes_per_round || '-' }} {{ $t('common.minutes') }}</span>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<span class="config-item-label">总轮次</span>
|
||||
<span class="config-item-value">{{ Math.floor((simulationConfig.time_config?.total_simulation_hours * 60 / simulationConfig.time_config?.minutes_per_round)) || '-' }} 轮</span>
|
||||
<span class="config-item-label">{{ $t('step2.totalRounds') }}</span>
|
||||
<span class="config-item-value">{{ Math.floor((simulationConfig.time_config?.total_simulation_hours * 60 / simulationConfig.time_config?.minutes_per_round)) || '-' }} {{ $t('common.rounds') }}</span>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<span class="config-item-label">每小时活跃</span>
|
||||
<span class="config-item-label">{{ $t('step2.activePerHour') }}</span>
|
||||
<span class="config-item-value">{{ simulationConfig.time_config?.agents_per_hour_min }}-{{ simulationConfig.time_config?.agents_per_hour_max }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-periods">
|
||||
<div class="period-item">
|
||||
<span class="period-label">高峰时段</span>
|
||||
<span class="period-label">{{ $t('step2.peakHours') }}</span>
|
||||
<span class="period-hours">{{ simulationConfig.time_config?.peak_hours?.join(':00, ') }}:00</span>
|
||||
<span class="period-multiplier">×{{ simulationConfig.time_config?.peak_activity_multiplier }}</span>
|
||||
</div>
|
||||
<div class="period-item">
|
||||
<span class="period-label">工作时段</span>
|
||||
<span class="period-label">{{ $t('step2.workHours') }}</span>
|
||||
<span class="period-hours">{{ simulationConfig.time_config?.work_hours?.[0] }}:00-{{ simulationConfig.time_config?.work_hours?.slice(-1)[0] }}:00</span>
|
||||
<span class="period-multiplier">×{{ simulationConfig.time_config?.work_activity_multiplier }}</span>
|
||||
</div>
|
||||
<div class="period-item">
|
||||
<span class="period-label">早间时段</span>
|
||||
<span class="period-label">{{ $t('step2.morningHours') }}</span>
|
||||
<span class="period-hours">{{ simulationConfig.time_config?.morning_hours?.[0] }}:00-{{ simulationConfig.time_config?.morning_hours?.slice(-1)[0] }}:00</span>
|
||||
<span class="period-multiplier">×{{ simulationConfig.time_config?.morning_activity_multiplier }}</span>
|
||||
</div>
|
||||
<div class="period-item">
|
||||
<span class="period-label">低谷时段</span>
|
||||
<span class="period-label">{{ $t('step2.offPeakHours') }}</span>
|
||||
<span class="period-hours">{{ simulationConfig.time_config?.off_peak_hours?.[0] }}:00-{{ simulationConfig.time_config?.off_peak_hours?.slice(-1)[0] }}:00</span>
|
||||
<span class="period-multiplier">×{{ simulationConfig.time_config?.off_peak_activity_multiplier }}</span>
|
||||
</div>
|
||||
|
|
@ -182,8 +182,8 @@
|
|||
<!-- Agent 配置 -->
|
||||
<div class="config-block">
|
||||
<div class="config-block-header">
|
||||
<span class="config-block-title">Agent 配置</span>
|
||||
<span class="config-block-badge">{{ simulationConfig.agent_configs?.length || 0 }} 个</span>
|
||||
<span class="config-block-title">{{ $t('step2.agentConfig') }}</span>
|
||||
<span class="config-block-badge">{{ simulationConfig.agent_configs?.length || 0 }} {{ $t('common.items') }}</span>
|
||||
</div>
|
||||
<div class="agents-cards">
|
||||
<div
|
||||
|
|
@ -205,7 +205,7 @@
|
|||
|
||||
<!-- 活跃时间轴 -->
|
||||
<div class="agent-timeline">
|
||||
<span class="timeline-label">活跃时段</span>
|
||||
<span class="timeline-label">{{ $t('step2.activeTimePeriod') }}</span>
|
||||
<div class="mini-timeline">
|
||||
<div
|
||||
v-for="hour in 24"
|
||||
|
|
@ -228,34 +228,34 @@
|
|||
<div class="agent-params">
|
||||
<div class="param-group">
|
||||
<div class="param-item">
|
||||
<span class="param-label">发帖/时</span>
|
||||
<span class="param-label">{{ $t('step2.postsPerHour') }}</span>
|
||||
<span class="param-value">{{ agent.posts_per_hour }}</span>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<span class="param-label">评论/时</span>
|
||||
<span class="param-label">{{ $t('step2.commentsPerHour') }}</span>
|
||||
<span class="param-value">{{ agent.comments_per_hour }}</span>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<span class="param-label">响应延迟</span>
|
||||
<span class="param-label">{{ $t('step2.responseDelay') }}</span>
|
||||
<span class="param-value">{{ agent.response_delay_min }}-{{ agent.response_delay_max }}min</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="param-group">
|
||||
<div class="param-item">
|
||||
<span class="param-label">活跃度</span>
|
||||
<span class="param-label">{{ $t('step2.activityLevel') }}</span>
|
||||
<span class="param-value with-bar">
|
||||
<span class="mini-bar" :style="{ width: (agent.activity_level * 100) + '%' }"></span>
|
||||
{{ (agent.activity_level * 100).toFixed(0) }}%
|
||||
</span>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<span class="param-label">情感倾向</span>
|
||||
<span class="param-label">{{ $t('step2.sentimentBias') }}</span>
|
||||
<span class="param-value" :class="agent.sentiment_bias > 0 ? 'positive' : agent.sentiment_bias < 0 ? 'negative' : 'neutral'">
|
||||
{{ agent.sentiment_bias > 0 ? '+' : '' }}{{ agent.sentiment_bias?.toFixed(1) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<span class="param-label">影响力</span>
|
||||
<span class="param-label">{{ $t('step2.influenceWeight') }}</span>
|
||||
<span class="param-value highlight">{{ agent.influence_weight?.toFixed(1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -267,59 +267,59 @@
|
|||
<!-- 平台配置 -->
|
||||
<div class="config-block">
|
||||
<div class="config-block-header">
|
||||
<span class="config-block-title">推荐算法配置</span>
|
||||
<span class="config-block-title">{{ $t('step2.recommendAlgoConfig') }}</span>
|
||||
</div>
|
||||
<div class="platforms-grid">
|
||||
<div v-if="simulationConfig.twitter_config" class="platform-card">
|
||||
<div class="platform-card-header">
|
||||
<span class="platform-name">平台 1:广场 / 信息流</span>
|
||||
<span class="platform-name">{{ $t('step2.platform1Name') }}</span>
|
||||
</div>
|
||||
<div class="platform-params">
|
||||
<div class="param-row">
|
||||
<span class="param-label">时效权重</span>
|
||||
<span class="param-label">{{ $t('step2.recencyWeight') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.twitter_config.recency_weight }}</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">热度权重</span>
|
||||
<span class="param-label">{{ $t('step2.popularityWeight') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.twitter_config.popularity_weight }}</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">相关性权重</span>
|
||||
<span class="param-label">{{ $t('step2.relevanceWeight') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.twitter_config.relevance_weight }}</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">病毒阈值</span>
|
||||
<span class="param-label">{{ $t('step2.viralThreshold') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.twitter_config.viral_threshold }}</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">回音室强度</span>
|
||||
<span class="param-label">{{ $t('step2.echoChamberStrength') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.twitter_config.echo_chamber_strength }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="simulationConfig.reddit_config" class="platform-card">
|
||||
<div class="platform-card-header">
|
||||
<span class="platform-name">平台 2:话题 / 社区</span>
|
||||
<span class="platform-name">{{ $t('step2.platform2Name') }}</span>
|
||||
</div>
|
||||
<div class="platform-params">
|
||||
<div class="param-row">
|
||||
<span class="param-label">时效权重</span>
|
||||
<span class="param-label">{{ $t('step2.recencyWeight') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.reddit_config.recency_weight }}</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">热度权重</span>
|
||||
<span class="param-label">{{ $t('step2.popularityWeight') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.reddit_config.popularity_weight }}</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">相关性权重</span>
|
||||
<span class="param-label">{{ $t('step2.relevanceWeight') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.reddit_config.relevance_weight }}</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">病毒阈值</span>
|
||||
<span class="param-label">{{ $t('step2.viralThreshold') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.reddit_config.viral_threshold }}</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">回音室强度</span>
|
||||
<span class="param-label">{{ $t('step2.echoChamberStrength') }}</span>
|
||||
<span class="param-value">{{ simulationConfig.reddit_config.echo_chamber_strength }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -330,7 +330,7 @@
|
|||
<!-- LLM 配置推理 -->
|
||||
<div v-if="simulationConfig.generation_reasoning" class="config-block">
|
||||
<div class="config-block-header">
|
||||
<span class="config-block-title">LLM 配置推理</span>
|
||||
<span class="config-block-title">{{ $t('step2.llmConfigReasoning') }}</span>
|
||||
</div>
|
||||
<div class="reasoning-content">
|
||||
<div
|
||||
|
|
@ -351,19 +351,19 @@
|
|||
<div class="card-header">
|
||||
<div class="step-info">
|
||||
<span class="step-num">04</span>
|
||||
<span class="step-title">初始激活编排</span>
|
||||
<span class="step-title">{{ $t('step2.initialActivation') }}</span>
|
||||
</div>
|
||||
<div class="step-status">
|
||||
<span v-if="phase > 3" class="badge success">已完成</span>
|
||||
<span v-else-if="phase === 3" class="badge processing">编排中</span>
|
||||
<span v-else class="badge pending">等待</span>
|
||||
<span v-if="phase > 3" class="badge success">{{ $t('common.completed') }}</span>
|
||||
<span v-else-if="phase === 3" class="badge processing">{{ $t('step2.orchestrating') }}</span>
|
||||
<span v-else class="badge pending">{{ $t('common.pending') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="api-note">POST /api/simulation/prepare</p>
|
||||
<p class="description">
|
||||
基于叙事方向,自动生成初始激活事件与热点话题,引导模拟世界的初始状态
|
||||
{{ $t('step2.initialActivationDesc') }}
|
||||
</p>
|
||||
|
||||
<div v-if="simulationConfig?.event_config" class="orchestration-content">
|
||||
|
|
@ -380,14 +380,14 @@
|
|||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
叙事引导方向
|
||||
{{ $t('step2.narrativeDirection') }}
|
||||
</span>
|
||||
<p class="narrative-text">{{ simulationConfig.event_config.narrative_direction }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 热点话题 -->
|
||||
<div class="topics-section">
|
||||
<span class="box-label">初始热点话题</span>
|
||||
<span class="box-label">{{ $t('step2.initialHotTopics') }}</span>
|
||||
<div class="hot-topics-grid">
|
||||
<span v-for="topic in simulationConfig.event_config.hot_topics" :key="topic" class="hot-topic-tag">
|
||||
# {{ topic }}
|
||||
|
|
@ -397,7 +397,7 @@
|
|||
|
||||
<!-- 初始帖子流 -->
|
||||
<div class="initial-posts-section">
|
||||
<span class="box-label">初始激活序列 ({{ simulationConfig.event_config.initial_posts.length }})</span>
|
||||
<span class="box-label">{{ $t('step2.initialActivationSeq', { count: simulationConfig.event_config.initial_posts.length }) }}</span>
|
||||
<div class="posts-timeline">
|
||||
<div v-for="(post, idx) in simulationConfig.event_config.initial_posts" :key="idx" class="timeline-item">
|
||||
<div class="timeline-marker"></div>
|
||||
|
|
@ -423,29 +423,29 @@
|
|||
<div class="card-header">
|
||||
<div class="step-info">
|
||||
<span class="step-num">05</span>
|
||||
<span class="step-title">准备完成</span>
|
||||
<span class="step-title">{{ $t('step2.setupComplete') }}</span>
|
||||
</div>
|
||||
<div class="step-status">
|
||||
<span v-if="phase >= 4" class="badge processing">进行中</span>
|
||||
<span v-else class="badge pending">等待</span>
|
||||
<span v-if="phase >= 4" class="badge processing">{{ $t('step1.inProgress') }}</span>
|
||||
<span v-else class="badge pending">{{ $t('common.pending') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="api-note">POST /api/simulation/start</p>
|
||||
<p class="description">模拟环境已准备完成,可以开始运行模拟</p>
|
||||
<p class="description">{{ $t('step2.setupCompleteDesc') }}</p>
|
||||
|
||||
<!-- 模拟轮数配置 - 只有在配置生成完成且轮数计算出来后才显示 -->
|
||||
<div v-if="simulationConfig && autoGeneratedRounds" class="rounds-config-section">
|
||||
<div class="rounds-header">
|
||||
<div class="header-left">
|
||||
<span class="section-title">模拟轮数设定</span>
|
||||
<span class="section-desc">MiroFish 自动规划推演现实 <span class="desc-highlight">{{ simulationConfig?.time_config?.total_simulation_hours || '-' }}</span> 小时,每轮代表现实 <span class="desc-highlight">{{ simulationConfig?.time_config?.minutes_per_round || '-' }}</span> 分钟时间流逝</span>
|
||||
<span class="section-title">{{ $t('step2.roundsConfig') }}</span>
|
||||
<span class="section-desc">{{ $t('step2.roundsConfigDesc', { hours: simulationConfig?.time_config?.total_simulation_hours || '-', minutesPerRound: simulationConfig?.time_config?.minutes_per_round || '-' }) }}</span>
|
||||
</div>
|
||||
<label class="switch-control">
|
||||
<input type="checkbox" v-model="useCustomRounds">
|
||||
<span class="switch-track"></span>
|
||||
<span class="switch-label">自定义</span>
|
||||
<span class="switch-label">{{ $t('step2.customToggle') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
|
@ -454,10 +454,10 @@
|
|||
<div class="slider-display">
|
||||
<div class="slider-main-value">
|
||||
<span class="val-num">{{ customMaxRounds }}</span>
|
||||
<span class="val-unit">轮</span>
|
||||
<span class="val-unit">{{ $t('step2.roundsUnit') }}</span>
|
||||
</div>
|
||||
<div class="slider-meta-info">
|
||||
<span>若Agent规模为100:预计耗时约 {{ Math.round(customMaxRounds * 0.6) }} 分钟</span>
|
||||
<span>{{ $t('step2.estimatedDuration', { minutes: Math.round(customMaxRounds * 0.6) }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -478,7 +478,7 @@
|
|||
:class="{ active: customMaxRounds === 40 }"
|
||||
@click="customMaxRounds = 40"
|
||||
:style="{ position: 'absolute', left: `calc(${(40 - 10) / (autoGeneratedRounds - 10) * 100}% - 30px)` }"
|
||||
>40 (推荐)</span>
|
||||
>{{ $t('step2.recommendedRounds', { rounds: 40 }) }}</span>
|
||||
<span>{{ autoGeneratedRounds }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -488,7 +488,7 @@
|
|||
<div class="auto-info-card">
|
||||
<div class="auto-value">
|
||||
<span class="val-num">{{ autoGeneratedRounds }}</span>
|
||||
<span class="val-unit">轮</span>
|
||||
<span class="val-unit">{{ $t('step2.roundsUnit') }}</span>
|
||||
</div>
|
||||
<div class="auto-content">
|
||||
<div class="auto-meta-row">
|
||||
|
|
@ -497,11 +497,11 @@
|
|||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</svg>
|
||||
若Agent规模为100:预计耗时 {{ Math.round(autoGeneratedRounds * 0.6) }} 分钟
|
||||
{{ $t('step2.estimatedDurationFull', { minutes: Math.round(autoGeneratedRounds * 0.6) }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="auto-desc">
|
||||
<p class="highlight-tip" @click="useCustomRounds = true">若首次运行,强烈建议切换至‘自定义模式’减少模拟轮数,以便快速预览效果并降低报错风险 ➝</p>
|
||||
<p class="highlight-tip" @click="useCustomRounds = true">{{ $t('step2.customTip') }} ➝</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -514,14 +514,14 @@
|
|||
class="action-btn secondary"
|
||||
@click="$emit('go-back')"
|
||||
>
|
||||
← 返回图谱构建
|
||||
← {{ $t('step2.backToGraphBuild') }}
|
||||
</button>
|
||||
<button
|
||||
class="action-btn primary"
|
||||
:disabled="phase < 4"
|
||||
@click="handleStartSimulation"
|
||||
>
|
||||
开始双世界并行模拟 ➝
|
||||
{{ $t('step2.startDualWorldSim') }} ➝
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -547,32 +547,32 @@
|
|||
<!-- 基本信息 -->
|
||||
<div class="modal-info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">事件外显年龄</span>
|
||||
<span class="info-value">{{ selectedProfile.age || '-' }} 岁</span>
|
||||
<span class="info-label">{{ $t('step2.profileModalAge') }}</span>
|
||||
<span class="info-value">{{ selectedProfile.age || '-' }} {{ $t('step2.yearsOld') }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">事件外显性别</span>
|
||||
<span class="info-value">{{ { male: '男', female: '女', other: '其他' }[selectedProfile.gender] || selectedProfile.gender }}</span>
|
||||
<span class="info-label">{{ $t('step2.profileModalGender') }}</span>
|
||||
<span class="info-value">{{ { male: $t('step2.genderMale'), female: $t('step2.genderFemale'), other: $t('step2.genderOther') }[selectedProfile.gender] || selectedProfile.gender }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">国家/地区</span>
|
||||
<span class="info-label">{{ $t('step2.profileModalCountry') }}</span>
|
||||
<span class="info-value">{{ selectedProfile.country || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">事件外显MBTI</span>
|
||||
<span class="info-label">{{ $t('step2.profileModalMbti') }}</span>
|
||||
<span class="info-value mbti">{{ selectedProfile.mbti || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 简介 -->
|
||||
<div class="modal-section">
|
||||
<span class="section-label">人设简介</span>
|
||||
<p class="section-bio">{{ selectedProfile.bio || '暂无简介' }}</p>
|
||||
<span class="section-label">{{ $t('step2.profileModalBio') }}</span>
|
||||
<p class="section-bio">{{ selectedProfile.bio || $t('step2.noBio') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 关注话题 -->
|
||||
<div class="modal-section" v-if="selectedProfile.interested_topics?.length">
|
||||
<span class="section-label">现实种子关联话题</span>
|
||||
<span class="section-label">{{ $t('step2.profileModalTopics') }}</span>
|
||||
<div class="topics-grid">
|
||||
<span
|
||||
v-for="topic in selectedProfile.interested_topics"
|
||||
|
|
@ -584,25 +584,25 @@
|
|||
|
||||
<!-- 详细人设 -->
|
||||
<div class="modal-section" v-if="selectedProfile.persona">
|
||||
<span class="section-label">详细人设背景</span>
|
||||
<span class="section-label">{{ $t('step2.profileModalPersona') }}</span>
|
||||
|
||||
<!-- 人设维度概览 -->
|
||||
<div class="persona-dimensions">
|
||||
<div class="dimension-card">
|
||||
<span class="dim-title">事件全景经历</span>
|
||||
<span class="dim-desc">在此事件中的完整行为轨迹</span>
|
||||
<span class="dim-title">{{ $t('step2.personaDimExperience') }}</span>
|
||||
<span class="dim-desc">{{ $t('step2.personaDimExperienceDesc') }}</span>
|
||||
</div>
|
||||
<div class="dimension-card">
|
||||
<span class="dim-title">行为模式侧写</span>
|
||||
<span class="dim-desc">经验总结与行事风格偏好</span>
|
||||
<span class="dim-title">{{ $t('step2.personaDimBehavior') }}</span>
|
||||
<span class="dim-desc">{{ $t('step2.personaDimBehaviorDesc') }}</span>
|
||||
</div>
|
||||
<div class="dimension-card">
|
||||
<span class="dim-title">独特记忆印记</span>
|
||||
<span class="dim-desc">基于现实种子形成的记忆</span>
|
||||
<span class="dim-title">{{ $t('step2.personaDimMemory') }}</span>
|
||||
<span class="dim-desc">{{ $t('step2.personaDimMemoryDesc') }}</span>
|
||||
</div>
|
||||
<div class="dimension-card">
|
||||
<span class="dim-title">社会关系网络</span>
|
||||
<span class="dim-desc">个体链接与交互图谱</span>
|
||||
<span class="dim-title">{{ $t('step2.personaDimSocial') }}</span>
|
||||
<span class="dim-desc">{{ $t('step2.personaDimSocialDesc') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -633,14 +633,17 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import {
|
||||
prepareSimulation,
|
||||
getPrepareStatus,
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
prepareSimulation,
|
||||
getPrepareStatus,
|
||||
getSimulationProfilesRealtime,
|
||||
getSimulationConfig,
|
||||
getSimulationConfigRealtime
|
||||
getSimulationConfigRealtime
|
||||
} from '../api/simulation'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
simulationId: String, // 从父组件传入
|
||||
projectData: Object,
|
||||
|
|
@ -680,7 +683,7 @@ watch(currentStage, (newStage) => {
|
|||
phase.value = 2
|
||||
// 进入配置生成阶段,开始轮询配置
|
||||
if (!configTimer) {
|
||||
addLog('开始生成双平台模拟配置...')
|
||||
addLog(t('log.startGeneratingConfig'))
|
||||
startConfigPolling()
|
||||
}
|
||||
} else if (newStage === '准备模拟脚本' || newStage === 'copying_scripts') {
|
||||
|
|
@ -745,10 +748,10 @@ const handleStartSimulation = () => {
|
|||
if (useCustomRounds.value) {
|
||||
// 用户自定义轮数,传递 max_rounds 参数
|
||||
params.maxRounds = customMaxRounds.value
|
||||
addLog(`开始模拟,自定义轮数: ${customMaxRounds.value} 轮`)
|
||||
addLog(t('log.startSimCustomRounds', { rounds: customMaxRounds.value }))
|
||||
} else {
|
||||
// 用户选择保持自动生成的轮数,不传递 max_rounds 参数
|
||||
addLog(`开始模拟,使用自动配置轮数: ${autoGeneratedRounds.value} 轮`)
|
||||
addLog(t('log.startSimAutoRounds', { rounds: autoGeneratedRounds.value }))
|
||||
}
|
||||
|
||||
emit('next-step', params)
|
||||
|
|
@ -768,15 +771,15 @@ const selectProfile = (profile) => {
|
|||
// 自动开始准备模拟
|
||||
const startPrepareSimulation = async () => {
|
||||
if (!props.simulationId) {
|
||||
addLog('错误:缺少 simulationId')
|
||||
addLog(t('log.errorMissingSimId'))
|
||||
emit('update-status', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
// 标记第一步完成,开始第二步
|
||||
phase.value = 1
|
||||
addLog(`模拟实例已创建: ${props.simulationId}`)
|
||||
addLog('正在准备模拟环境...')
|
||||
addLog(t('log.simInstanceCreated', { id: props.simulationId }))
|
||||
addLog(t('log.preparingSimEnv'))
|
||||
emit('update-status', 'processing')
|
||||
|
||||
try {
|
||||
|
|
@ -788,35 +791,35 @@ const startPrepareSimulation = async () => {
|
|||
|
||||
if (res.success && res.data) {
|
||||
if (res.data.already_prepared) {
|
||||
addLog('检测到已有完成的准备工作,直接使用')
|
||||
addLog(t('log.detectedExistingPrep'))
|
||||
await loadPreparedData()
|
||||
return
|
||||
}
|
||||
|
||||
taskId.value = res.data.task_id
|
||||
addLog(`准备任务已启动`)
|
||||
addLog(` └─ Task ID: ${res.data.task_id}`)
|
||||
addLog(t('log.prepareTaskStarted'))
|
||||
addLog(t('log.prepareTaskId', { taskId: res.data.task_id }))
|
||||
|
||||
// 立即设置预期Agent总数(从prepare接口返回值获取)
|
||||
if (res.data.expected_entities_count) {
|
||||
expectedTotal.value = res.data.expected_entities_count
|
||||
addLog(`从Zep图谱读取到 ${res.data.expected_entities_count} 个实体`)
|
||||
addLog(t('log.zepEntitiesFound', { count: res.data.expected_entities_count }))
|
||||
if (res.data.entity_types && res.data.entity_types.length > 0) {
|
||||
addLog(` └─ 实体类型: ${res.data.entity_types.join(', ')}`)
|
||||
addLog(t('log.entityTypes', { types: res.data.entity_types.join(', ') }))
|
||||
}
|
||||
}
|
||||
|
||||
addLog('开始轮询准备进度...')
|
||||
addLog(t('log.startPollingProgress'))
|
||||
// 开始轮询进度
|
||||
startPolling()
|
||||
// 开始实时获取 Profiles
|
||||
startProfilesPolling()
|
||||
} else {
|
||||
addLog(`准备失败: ${res.error || '未知错误'}`)
|
||||
addLog(t('log.prepareFailed', { error: res.error || t('common.unknownError') }))
|
||||
emit('update-status', 'error')
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`准备异常: ${err.message}`)
|
||||
addLog(t('log.prepareException', { error: err.message }))
|
||||
emit('update-status', 'error')
|
||||
}
|
||||
}
|
||||
|
|
@ -890,12 +893,12 @@ const pollPrepareStatus = async () => {
|
|||
|
||||
// 检查是否完成
|
||||
if (data.status === 'completed' || data.status === 'ready' || data.already_prepared) {
|
||||
addLog('✓ 准备工作已完成')
|
||||
addLog(t('log.prepareComplete'))
|
||||
stopPolling()
|
||||
stopProfilesPolling()
|
||||
await loadPreparedData()
|
||||
} else if (data.status === 'failed') {
|
||||
addLog(`✗ 准备失败: ${data.error || '未知错误'}`)
|
||||
addLog(t('log.prepareFailedWithError', { error: data.error || t('common.unknownError') }))
|
||||
stopPolling()
|
||||
stopProfilesPolling()
|
||||
}
|
||||
|
|
@ -934,13 +937,13 @@ const fetchProfilesRealtime = async () => {
|
|||
const latestProfile = profiles.value[currentCount - 1]
|
||||
const profileName = latestProfile?.name || latestProfile?.username || `Agent_${currentCount}`
|
||||
if (currentCount === 1) {
|
||||
addLog(`开始生成Agent人设...`)
|
||||
addLog(t('log.startGeneratingAgentProfiles'))
|
||||
}
|
||||
addLog(`→ Agent人设 ${currentCount}/${total}: ${profileName} (${latestProfile?.profession || '未知职业'})`)
|
||||
|
||||
addLog(t('log.agentProfile', { current: currentCount, total: total, name: profileName, profession: latestProfile?.profession || t('step2.unknownProfession') }))
|
||||
|
||||
// 如果全部生成完成
|
||||
if (expectedTotal.value && currentCount >= expectedTotal.value) {
|
||||
addLog(`✓ 全部 ${currentCount} 个Agent人设生成完成`)
|
||||
addLog(t('log.allProfilesComplete', { count: currentCount }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -974,41 +977,41 @@ const fetchConfigRealtime = async () => {
|
|||
if (data.generation_stage && data.generation_stage !== lastLoggedConfigStage) {
|
||||
lastLoggedConfigStage = data.generation_stage
|
||||
if (data.generation_stage === 'generating_profiles') {
|
||||
addLog('正在生成Agent人设配置...')
|
||||
addLog(t('log.generatingAgentProfileConfig'))
|
||||
} else if (data.generation_stage === 'generating_config') {
|
||||
addLog('正在调用LLM生成模拟配置参数...')
|
||||
addLog(t('log.generatingLLMConfig'))
|
||||
}
|
||||
}
|
||||
|
||||
// 如果配置已生成
|
||||
if (data.config_generated && data.config) {
|
||||
simulationConfig.value = data.config
|
||||
addLog('✓ 模拟配置生成完成')
|
||||
|
||||
addLog(t('log.configComplete'))
|
||||
|
||||
// 显示详细配置摘要
|
||||
if (data.summary) {
|
||||
addLog(` ├─ Agent数量: ${data.summary.total_agents}个`)
|
||||
addLog(` ├─ 模拟时长: ${data.summary.simulation_hours}小时`)
|
||||
addLog(` ├─ 初始帖子: ${data.summary.initial_posts_count}条`)
|
||||
addLog(` ├─ 热点话题: ${data.summary.hot_topics_count}个`)
|
||||
addLog(` └─ 平台配置: Twitter ${data.summary.has_twitter_config ? '✓' : '✗'}, Reddit ${data.summary.has_reddit_config ? '✓' : '✗'}`)
|
||||
addLog(t('log.configSummaryAgents', { count: data.summary.total_agents }))
|
||||
addLog(t('log.configSummaryHours', { hours: data.summary.simulation_hours }))
|
||||
addLog(t('log.configSummaryPosts', { count: data.summary.initial_posts_count }))
|
||||
addLog(t('log.configSummaryTopics', { count: data.summary.hot_topics_count }))
|
||||
addLog(t('log.configSummaryPlatforms', { twitter: data.summary.has_twitter_config ? '✓' : '✗', reddit: data.summary.has_reddit_config ? '✓' : '✗' }))
|
||||
}
|
||||
|
||||
// 显示时间配置详情
|
||||
if (data.config.time_config) {
|
||||
const tc = data.config.time_config
|
||||
addLog(`时间配置: 每轮${tc.minutes_per_round}分钟, 共${Math.floor((tc.total_simulation_hours * 60) / tc.minutes_per_round)}轮`)
|
||||
addLog(t('log.timeConfigDetail', { minutes: tc.minutes_per_round, rounds: Math.floor((tc.total_simulation_hours * 60) / tc.minutes_per_round) }))
|
||||
}
|
||||
|
||||
// 显示事件配置
|
||||
if (data.config.event_config?.narrative_direction) {
|
||||
const narrative = data.config.event_config.narrative_direction
|
||||
addLog(`叙事方向: ${narrative.length > 50 ? narrative.substring(0, 50) + '...' : narrative}`)
|
||||
addLog(t('log.narrativeDirection', { direction: narrative.length > 50 ? narrative.substring(0, 50) + '...' : narrative }))
|
||||
}
|
||||
|
||||
stopConfigPolling()
|
||||
phase.value = 4
|
||||
addLog('✓ 环境搭建完成,可以开始模拟')
|
||||
addLog(t('log.envSetupComplete'))
|
||||
emit('update-status', 'completed')
|
||||
}
|
||||
}
|
||||
|
|
@ -1019,11 +1022,11 @@ const fetchConfigRealtime = async () => {
|
|||
|
||||
const loadPreparedData = async () => {
|
||||
phase.value = 2
|
||||
addLog('正在加载已有配置数据...')
|
||||
addLog(t('log.loadingExistingConfig'))
|
||||
|
||||
// 最后获取一次 Profiles
|
||||
await fetchProfilesRealtime()
|
||||
addLog(`已加载 ${profiles.value.length} 个Agent人设`)
|
||||
addLog(t('log.loadedAgentProfiles', { count: profiles.value.length }))
|
||||
|
||||
// 获取配置(使用实时接口)
|
||||
try {
|
||||
|
|
@ -1031,26 +1034,26 @@ const loadPreparedData = async () => {
|
|||
if (res.success && res.data) {
|
||||
if (res.data.config_generated && res.data.config) {
|
||||
simulationConfig.value = res.data.config
|
||||
addLog('✓ 模拟配置加载成功')
|
||||
|
||||
addLog(t('log.configLoadSuccess'))
|
||||
|
||||
// 显示详细配置摘要
|
||||
if (res.data.summary) {
|
||||
addLog(` ├─ Agent数量: ${res.data.summary.total_agents}个`)
|
||||
addLog(` ├─ 模拟时长: ${res.data.summary.simulation_hours}小时`)
|
||||
addLog(` └─ 初始帖子: ${res.data.summary.initial_posts_count}条`)
|
||||
addLog(t('log.configSummaryAgents', { count: res.data.summary.total_agents }))
|
||||
addLog(t('log.configSummaryHours', { hours: res.data.summary.simulation_hours }))
|
||||
addLog(t('log.configSummaryPostsAlt', { count: res.data.summary.initial_posts_count }))
|
||||
}
|
||||
|
||||
addLog('✓ 环境搭建完成,可以开始模拟')
|
||||
|
||||
addLog(t('log.envSetupComplete'))
|
||||
phase.value = 4
|
||||
emit('update-status', 'completed')
|
||||
} else {
|
||||
// 配置尚未生成,开始轮询
|
||||
addLog('配置生成中,开始轮询等待...')
|
||||
addLog(t('log.configGenerating'))
|
||||
startConfigPolling()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`加载配置失败: ${err.message}`)
|
||||
addLog(t('log.loadConfigFailed', { error: err.message }))
|
||||
emit('update-status', 'error')
|
||||
}
|
||||
}
|
||||
|
|
@ -1068,7 +1071,7 @@ watch(() => props.systemLogs?.length, () => {
|
|||
onMounted(() => {
|
||||
// 自动开始准备流程
|
||||
if (props.simulationId) {
|
||||
addLog('Step2 环境搭建初始化')
|
||||
addLog(t('log.step2Init'))
|
||||
startPrepareSimulation()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<span class="stat-value mono">{{ runStatus.twitter_current_round || 0 }}<span class="stat-total">/{{ runStatus.total_rounds || maxRounds || '-' }}</span></span>
|
||||
</span>
|
||||
<span class="stat">
|
||||
<span class="stat-label">Elapsed Time</span>
|
||||
<span class="stat-label">TIME</span>
|
||||
<span class="stat-value mono">{{ twitterElapsedTime }}</span>
|
||||
</span>
|
||||
<span class="stat">
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
<span class="stat-value mono">{{ runStatus.reddit_current_round || 0 }}<span class="stat-total">/{{ runStatus.total_rounds || maxRounds || '-' }}</span></span>
|
||||
</span>
|
||||
<span class="stat">
|
||||
<span class="stat-label">Elapsed Time</span>
|
||||
<span class="stat-label">TIME</span>
|
||||
<span class="stat-value mono">{{ redditElapsedTime }}</span>
|
||||
</span>
|
||||
<span class="stat">
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
@click="handleNextStep"
|
||||
>
|
||||
<span v-if="isGeneratingReport" class="loading-spinner-small"></span>
|
||||
{{ isGeneratingReport ? '启动中...' : '开始生成结果报告' }}
|
||||
{{ isGeneratingReport ? $t('step3.generatingReportBtn') : $t('step3.startGenerateReportBtn') }}
|
||||
<span v-if="!isGeneratingReport" class="arrow-icon">→</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -288,14 +288,17 @@
|
|||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
startSimulation,
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
startSimulation,
|
||||
stopSimulation,
|
||||
getRunStatus,
|
||||
getRunStatus,
|
||||
getRunStatusDetail
|
||||
} from '../api/simulation'
|
||||
import { generateReport } from '../api/report'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
simulationId: String,
|
||||
maxRounds: Number, // 从Step2传入的最大轮数
|
||||
|
|
@ -379,16 +382,16 @@ const resetAllState = () => {
|
|||
// 启动模拟
|
||||
const doStartSimulation = async () => {
|
||||
if (!props.simulationId) {
|
||||
addLog('错误:缺少 simulationId')
|
||||
addLog(t('log.errorMissingSimId'))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 先重置所有状态,确保不会受到上一次模拟的影响
|
||||
resetAllState()
|
||||
|
||||
isStarting.value = true
|
||||
startError.value = null
|
||||
addLog('正在启动双平台并行模拟...')
|
||||
addLog(t('log.startingDualSim'))
|
||||
emit('update-status', 'processing')
|
||||
|
||||
try {
|
||||
|
|
@ -401,18 +404,18 @@ const doStartSimulation = async () => {
|
|||
|
||||
if (props.maxRounds) {
|
||||
params.max_rounds = props.maxRounds
|
||||
addLog(`设置最大模拟轮数: ${props.maxRounds}`)
|
||||
addLog(t('log.setMaxRounds', { rounds: props.maxRounds }))
|
||||
}
|
||||
|
||||
addLog('已开启动态图谱更新模式')
|
||||
addLog(t('log.graphMemoryUpdateEnabled'))
|
||||
|
||||
const res = await startSimulation(params)
|
||||
|
||||
if (res.success && res.data) {
|
||||
if (res.data.force_restarted) {
|
||||
addLog('✓ 已清理旧的模拟日志,重新开始模拟')
|
||||
addLog(t('log.oldSimCleared'))
|
||||
}
|
||||
addLog('✓ 模拟引擎启动成功')
|
||||
addLog(t('log.engineStarted'))
|
||||
addLog(` ├─ PID: ${res.data.process_pid || '-'}`)
|
||||
|
||||
phase.value = 1
|
||||
|
|
@ -422,12 +425,12 @@ const doStartSimulation = async () => {
|
|||
startDetailPolling()
|
||||
} else {
|
||||
startError.value = res.error || '启动失败'
|
||||
addLog(`✗ 启动失败: ${res.error || '未知错误'}`)
|
||||
addLog(t('log.startFailed', { error: res.error || t('common.unknownError') }))
|
||||
emit('update-status', 'error')
|
||||
}
|
||||
} catch (err) {
|
||||
startError.value = err.message
|
||||
addLog(`✗ 启动异常: ${err.message}`)
|
||||
addLog(t('log.startException', { error: err.message }))
|
||||
emit('update-status', 'error')
|
||||
} finally {
|
||||
isStarting.value = false
|
||||
|
|
@ -439,21 +442,21 @@ const handleStopSimulation = async () => {
|
|||
if (!props.simulationId) return
|
||||
|
||||
isStopping.value = true
|
||||
addLog('正在停止模拟...')
|
||||
addLog(t('log.stoppingSim'))
|
||||
|
||||
try {
|
||||
const res = await stopSimulation({ simulation_id: props.simulationId })
|
||||
|
||||
if (res.success) {
|
||||
addLog('✓ 模拟已停止')
|
||||
addLog(t('log.simStoppedSuccess'))
|
||||
phase.value = 2
|
||||
stopPolling()
|
||||
emit('update-status', 'completed')
|
||||
} else {
|
||||
addLog(`停止失败: ${res.error || '未知错误'}`)
|
||||
addLog(t('log.stopFailed', { error: res.error || t('common.unknownError') }))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`停止异常: ${err.message}`)
|
||||
addLog(t('log.stopException', { error: err.message }))
|
||||
} finally {
|
||||
isStopping.value = false
|
||||
}
|
||||
|
|
@ -517,9 +520,9 @@ const fetchRunStatus = async () => {
|
|||
|
||||
if (isCompleted || platformsCompleted) {
|
||||
if (platformsCompleted && !isCompleted) {
|
||||
addLog('✓ 检测到所有平台模拟已结束')
|
||||
addLog(t('log.allPlatformsCompleted'))
|
||||
}
|
||||
addLog('✓ 模拟已完成')
|
||||
addLog(t('log.simCompleted'))
|
||||
phase.value = 2
|
||||
stopPolling()
|
||||
emit('update-status', 'completed')
|
||||
|
|
@ -640,17 +643,17 @@ const formatActionTime = (timestamp) => {
|
|||
|
||||
const handleNextStep = async () => {
|
||||
if (!props.simulationId) {
|
||||
addLog('错误:缺少 simulationId')
|
||||
addLog(t('log.errorMissingSimId'))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (isGeneratingReport.value) {
|
||||
addLog('报告生成请求已发送,请稍候...')
|
||||
addLog(t('log.reportRequestSent'))
|
||||
return
|
||||
}
|
||||
|
||||
isGeneratingReport.value = true
|
||||
addLog('正在启动报告生成...')
|
||||
addLog(t('log.startingReportGen'))
|
||||
|
||||
try {
|
||||
const res = await generateReport({
|
||||
|
|
@ -660,16 +663,16 @@ const handleNextStep = async () => {
|
|||
|
||||
if (res.success && res.data) {
|
||||
const reportId = res.data.report_id
|
||||
addLog(`✓ 报告生成任务已启动: ${reportId}`)
|
||||
addLog(t('log.reportGenTaskStarted', { reportId }))
|
||||
|
||||
// 跳转到报告页面
|
||||
router.push({ name: 'Report', params: { reportId } })
|
||||
} else {
|
||||
addLog(`✗ 启动报告生成失败: ${res.error || '未知错误'}`)
|
||||
addLog(t('log.reportGenFailed', { error: res.error || t('common.unknownError') }))
|
||||
isGeneratingReport.value = false
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`✗ 启动报告生成异常: ${err.message}`)
|
||||
addLog(t('log.reportGenException', { error: err.message }))
|
||||
isGeneratingReport.value = false
|
||||
}
|
||||
}
|
||||
|
|
@ -685,7 +688,7 @@ watch(() => props.systemLogs?.length, () => {
|
|||
})
|
||||
|
||||
onMounted(() => {
|
||||
addLog('Step3 模拟运行初始化')
|
||||
addLog(t('log.step3Init'))
|
||||
if (props.simulationId) {
|
||||
doStartSimulation()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
<path d="M12 2a10 10 0 0 1 10 10" stroke-width="4" stroke="#4B5563" stroke-linecap="round"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="loading-text">正在生成{{ section.title }}...</span>
|
||||
<span class="loading-text">{{ $t('step4.generatingSection', { title: section.title }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -129,7 +129,7 @@
|
|||
|
||||
<!-- Next Step Button - 在完成后显示 -->
|
||||
<button v-if="isComplete" class="next-step-btn" @click="goToInteraction">
|
||||
<span>进入深度互动</span>
|
||||
<span>{{ $t('step4.goToInteraction') }}</span>
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
<polyline points="12 5 19 12 12 19"></polyline>
|
||||
|
|
@ -392,9 +392,11 @@
|
|||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick, h, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getAgentLog, getConsoleLog } from '../api/report'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
reportId: String,
|
||||
|
|
@ -962,6 +964,7 @@ const parseQuickSearch = (text) => {
|
|||
const InsightDisplay = {
|
||||
props: ['result', 'resultLength'],
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const activeTab = ref('facts') // 'facts', 'entities', 'relations', 'subqueries'
|
||||
const expandedFacts = ref(false)
|
||||
const expandedEntities = ref(false)
|
||||
|
|
@ -1003,7 +1006,7 @@ const InsightDisplay = {
|
|||
]),
|
||||
props.result.query && h('div', { class: 'header-topic' }, props.result.query),
|
||||
props.result.simulationRequirement && h('div', { class: 'header-scenario' }, [
|
||||
h('span', { class: 'scenario-label' }, '预测场景: '),
|
||||
h('span', { class: 'scenario-label' }, t('step4.scenarioLabel')),
|
||||
h('span', { class: 'scenario-text' }, props.result.simulationRequirement)
|
||||
])
|
||||
]),
|
||||
|
|
@ -1014,25 +1017,25 @@ const InsightDisplay = {
|
|||
class: ['insight-tab', { active: activeTab.value === 'facts' }],
|
||||
onClick: () => { activeTab.value = 'facts' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `当前关键记忆 (${props.result.facts.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabKeyFacts', { count: props.result.facts.length }))
|
||||
]),
|
||||
h('button', {
|
||||
class: ['insight-tab', { active: activeTab.value === 'entities' }],
|
||||
onClick: () => { activeTab.value = 'entities' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `核心实体 (${props.result.entities.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabCoreEntities', { count: props.result.entities.length }))
|
||||
]),
|
||||
h('button', {
|
||||
class: ['insight-tab', { active: activeTab.value === 'relations' }],
|
||||
onClick: () => { activeTab.value = 'relations' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `关系链 (${props.result.relations.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabRelationChains', { count: props.result.relations.length }))
|
||||
]),
|
||||
props.result.subQueries.length > 0 && h('button', {
|
||||
class: ['insight-tab', { active: activeTab.value === 'subqueries' }],
|
||||
onClick: () => { activeTab.value = 'subqueries' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `子问题 (${props.result.subQueries.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabSubQueries', { count: props.result.subQueries.length }))
|
||||
])
|
||||
]),
|
||||
|
||||
|
|
@ -1041,8 +1044,8 @@ const InsightDisplay = {
|
|||
// Facts Tab
|
||||
activeTab.value === 'facts' && props.result.facts.length > 0 && h('div', { class: 'facts-panel' }, [
|
||||
h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '时序记忆中所关联的最新关键事实'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.facts.length} 条`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelKeyFacts')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalCount', { count: props.result.facts.length }))
|
||||
]),
|
||||
h('div', { class: 'facts-list' },
|
||||
(expandedFacts.value ? props.result.facts : props.result.facts.slice(0, INITIAL_SHOW_COUNT)).map((fact, i) =>
|
||||
|
|
@ -1055,35 +1058,35 @@ const InsightDisplay = {
|
|||
props.result.facts.length > INITIAL_SHOW_COUNT && h('button', {
|
||||
class: 'expand-btn',
|
||||
onClick: () => { expandedFacts.value = !expandedFacts.value }
|
||||
}, expandedFacts.value ? `收起 ▲` : `展开全部 ${props.result.facts.length} 条 ▼`)
|
||||
}, expandedFacts.value ? t('step4.collapse') : t('step4.expandAll', { count: props.result.facts.length }))
|
||||
]),
|
||||
|
||||
|
||||
// Entities Tab
|
||||
activeTab.value === 'entities' && props.result.entities.length > 0 && h('div', { class: 'entities-panel' }, [
|
||||
h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '核心实体'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.entities.length} 个`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelCoreEntities')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalEntityCount', { count: props.result.entities.length }))
|
||||
]),
|
||||
h('div', { class: 'entities-grid' },
|
||||
(expandedEntities.value ? props.result.entities : props.result.entities.slice(0, 12)).map((entity, i) =>
|
||||
h('div', { class: 'entity-tag', key: i, title: entity.summary || '' }, [
|
||||
h('span', { class: 'entity-name' }, entity.name),
|
||||
h('span', { class: 'entity-type' }, entity.type),
|
||||
entity.relatedFactsCount > 0 && h('span', { class: 'entity-fact-count' }, `${entity.relatedFactsCount}条`)
|
||||
entity.relatedFactsCount > 0 && h('span', { class: 'entity-fact-count' }, t('step4.factCount', { count: entity.relatedFactsCount }))
|
||||
])
|
||||
)
|
||||
),
|
||||
props.result.entities.length > 12 && h('button', {
|
||||
class: 'expand-btn',
|
||||
onClick: () => { expandedEntities.value = !expandedEntities.value }
|
||||
}, expandedEntities.value ? `收起 ▲` : `展开全部 ${props.result.entities.length} 个 ▼`)
|
||||
}, expandedEntities.value ? t('step4.collapse') : t('step4.expandAllEntities', { count: props.result.entities.length }))
|
||||
]),
|
||||
|
||||
|
||||
// Relations Tab
|
||||
activeTab.value === 'relations' && props.result.relations.length > 0 && h('div', { class: 'relations-panel' }, [
|
||||
h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '关系链'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.relations.length} 条`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelRelationChains')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalCount', { count: props.result.relations.length }))
|
||||
]),
|
||||
h('div', { class: 'relations-list' },
|
||||
(expandedRelations.value ? props.result.relations : props.result.relations.slice(0, INITIAL_SHOW_COUNT)).map((rel, i) =>
|
||||
|
|
@ -1101,14 +1104,14 @@ const InsightDisplay = {
|
|||
props.result.relations.length > INITIAL_SHOW_COUNT && h('button', {
|
||||
class: 'expand-btn',
|
||||
onClick: () => { expandedRelations.value = !expandedRelations.value }
|
||||
}, expandedRelations.value ? `收起 ▲` : `展开全部 ${props.result.relations.length} 条 ▼`)
|
||||
}, expandedRelations.value ? t('step4.collapse') : t('step4.expandAll', { count: props.result.relations.length }))
|
||||
]),
|
||||
|
||||
|
||||
// Sub-queries Tab
|
||||
activeTab.value === 'subqueries' && props.result.subQueries.length > 0 && h('div', { class: 'subqueries-panel' }, [
|
||||
h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '漂移查询生成分析子问题'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.subQueries.length} 个`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelSubQueries')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalEntityCount', { count: props.result.subQueries.length }))
|
||||
]),
|
||||
h('div', { class: 'subqueries-list' },
|
||||
props.result.subQueries.map((sq, i) =>
|
||||
|
|
@ -1121,9 +1124,9 @@ const InsightDisplay = {
|
|||
]),
|
||||
|
||||
// Empty state
|
||||
activeTab.value === 'facts' && props.result.facts.length === 0 && h('div', { class: 'empty-state' }, '暂无当前关键记忆'),
|
||||
activeTab.value === 'entities' && props.result.entities.length === 0 && h('div', { class: 'empty-state' }, '暂无核心实体'),
|
||||
activeTab.value === 'relations' && props.result.relations.length === 0 && h('div', { class: 'empty-state' }, '暂无关系链')
|
||||
activeTab.value === 'facts' && props.result.facts.length === 0 && h('div', { class: 'empty-state' }, t('step4.emptyKeyFacts')),
|
||||
activeTab.value === 'entities' && props.result.entities.length === 0 && h('div', { class: 'empty-state' }, t('step4.emptyCoreEntities')),
|
||||
activeTab.value === 'relations' && props.result.relations.length === 0 && h('div', { class: 'empty-state' }, t('step4.emptyRelationChains'))
|
||||
])
|
||||
])
|
||||
}
|
||||
|
|
@ -1133,6 +1136,7 @@ const InsightDisplay = {
|
|||
const PanoramaDisplay = {
|
||||
props: ['result', 'resultLength'],
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const activeTab = ref('active') // 'active', 'historical', 'entities'
|
||||
const expandedActive = ref(false)
|
||||
const expandedHistorical = ref(false)
|
||||
|
|
@ -1176,19 +1180,19 @@ const PanoramaDisplay = {
|
|||
class: ['panorama-tab', { active: activeTab.value === 'active' }],
|
||||
onClick: () => { activeTab.value = 'active' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `当前有效记忆 (${props.result.activeFacts.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabActiveFacts', { count: props.result.activeFacts.length }))
|
||||
]),
|
||||
h('button', {
|
||||
class: ['panorama-tab', { active: activeTab.value === 'historical' }],
|
||||
onClick: () => { activeTab.value = 'historical' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `历史记忆 (${props.result.historicalFacts.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabHistoricalFacts', { count: props.result.historicalFacts.length }))
|
||||
]),
|
||||
h('button', {
|
||||
class: ['panorama-tab', { active: activeTab.value === 'entities' }],
|
||||
onClick: () => { activeTab.value = 'entities' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `涉及实体 (${props.result.entities.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabEntities', { count: props.result.entities.length }))
|
||||
])
|
||||
]),
|
||||
|
||||
|
|
@ -1197,8 +1201,8 @@ const PanoramaDisplay = {
|
|||
// Active Facts Tab
|
||||
activeTab.value === 'active' && h('div', { class: 'facts-panel active-facts' }, [
|
||||
h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '当前有效记忆'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.activeFacts.length} 条`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelActiveFacts')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalCount', { count: props.result.activeFacts.length }))
|
||||
]),
|
||||
props.result.activeFacts.length > 0 ? h('div', { class: 'facts-list' },
|
||||
(expandedActive.value ? props.result.activeFacts : props.result.activeFacts.slice(0, INITIAL_SHOW_COUNT)).map((fact, i) =>
|
||||
|
|
@ -1207,18 +1211,18 @@ const PanoramaDisplay = {
|
|||
h('div', { class: 'fact-content' }, fact)
|
||||
])
|
||||
)
|
||||
) : h('div', { class: 'empty-state' }, '暂无当前有效记忆'),
|
||||
) : h('div', { class: 'empty-state' }, t('step4.emptyActiveFacts')),
|
||||
props.result.activeFacts.length > INITIAL_SHOW_COUNT && h('button', {
|
||||
class: 'expand-btn',
|
||||
onClick: () => { expandedActive.value = !expandedActive.value }
|
||||
}, expandedActive.value ? `收起 ▲` : `展开全部 ${props.result.activeFacts.length} 条 ▼`)
|
||||
}, expandedActive.value ? t('step4.collapse') : t('step4.expandAll', { count: props.result.activeFacts.length }))
|
||||
]),
|
||||
|
||||
// Historical Facts Tab
|
||||
activeTab.value === 'historical' && h('div', { class: 'facts-panel historical-facts' }, [
|
||||
h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '历史记忆'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.historicalFacts.length} 条`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelHistoricalFacts')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalCount', { count: props.result.historicalFacts.length }))
|
||||
]),
|
||||
props.result.historicalFacts.length > 0 ? h('div', { class: 'facts-list' },
|
||||
(expandedHistorical.value ? props.result.historicalFacts : props.result.historicalFacts.slice(0, INITIAL_SHOW_COUNT)).map((fact, i) =>
|
||||
|
|
@ -1239,18 +1243,18 @@ const PanoramaDisplay = {
|
|||
])
|
||||
])
|
||||
)
|
||||
) : h('div', { class: 'empty-state' }, '暂无历史记忆'),
|
||||
) : h('div', { class: 'empty-state' }, t('step4.emptyHistoricalFacts')),
|
||||
props.result.historicalFacts.length > INITIAL_SHOW_COUNT && h('button', {
|
||||
class: 'expand-btn',
|
||||
onClick: () => { expandedHistorical.value = !expandedHistorical.value }
|
||||
}, expandedHistorical.value ? `收起 ▲` : `展开全部 ${props.result.historicalFacts.length} 条 ▼`)
|
||||
}, expandedHistorical.value ? t('step4.collapse') : t('step4.expandAll', { count: props.result.historicalFacts.length }))
|
||||
]),
|
||||
|
||||
// Entities Tab
|
||||
activeTab.value === 'entities' && h('div', { class: 'entities-panel' }, [
|
||||
h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '涉及实体'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.entities.length} 个`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelEntities')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalEntityCount', { count: props.result.entities.length }))
|
||||
]),
|
||||
props.result.entities.length > 0 ? h('div', { class: 'entities-grid' },
|
||||
(expandedEntities.value ? props.result.entities : props.result.entities.slice(0, 8)).map((entity, i) =>
|
||||
|
|
@ -1259,11 +1263,11 @@ const PanoramaDisplay = {
|
|||
entity.type && h('span', { class: 'entity-type' }, entity.type)
|
||||
])
|
||||
)
|
||||
) : h('div', { class: 'empty-state' }, '暂无涉及实体'),
|
||||
) : h('div', { class: 'empty-state' }, t('step4.emptyEntities')),
|
||||
props.result.entities.length > 8 && h('button', {
|
||||
class: 'expand-btn',
|
||||
onClick: () => { expandedEntities.value = !expandedEntities.value }
|
||||
}, expandedEntities.value ? `收起 ▲` : `展开全部 ${props.result.entities.length} 个 ▼`)
|
||||
}, expandedEntities.value ? t('step4.collapse') : t('step4.expandAllEntities', { count: props.result.entities.length }))
|
||||
])
|
||||
])
|
||||
])
|
||||
|
|
@ -1512,7 +1516,7 @@ const InterviewDisplay = {
|
|||
h('line', { x1: '2', y1: '12', x2: '22', y2: '12' }),
|
||||
h('path', { d: 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z' })
|
||||
]),
|
||||
h('span', {}, '世界1')
|
||||
h('span', {}, t('step4.world1'))
|
||||
]),
|
||||
h('button', {
|
||||
class: ['platform-btn', { active: currentPlatform === 'reddit' }],
|
||||
|
|
@ -1521,7 +1525,7 @@ const InterviewDisplay = {
|
|||
h('svg', { class: 'platform-icon', viewBox: '0 0 24 24', width: 12, height: 12, fill: 'none', stroke: 'currentColor', 'stroke-width': 2 }, [
|
||||
h('path', { d: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' })
|
||||
]),
|
||||
h('span', {}, '世界2')
|
||||
h('span', {}, t('step4.world2'))
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
|
@ -1577,6 +1581,7 @@ const InterviewDisplay = {
|
|||
const QuickSearchDisplay = {
|
||||
props: ['result', 'resultLength'],
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const activeTab = ref('facts') // 'facts', 'edges', 'nodes'
|
||||
const expandedFacts = ref(false)
|
||||
const INITIAL_SHOW_COUNT = 5
|
||||
|
|
@ -1610,7 +1615,7 @@ const QuickSearchDisplay = {
|
|||
])
|
||||
]),
|
||||
props.result.query && h('div', { class: 'header-query' }, [
|
||||
h('span', { class: 'query-label' }, '搜索: '),
|
||||
h('span', { class: 'query-label' }, t('step4.searchLabel')),
|
||||
h('span', { class: 'query-text' }, props.result.query)
|
||||
])
|
||||
]),
|
||||
|
|
@ -1621,19 +1626,19 @@ const QuickSearchDisplay = {
|
|||
class: ['quicksearch-tab', { active: activeTab.value === 'facts' }],
|
||||
onClick: () => { activeTab.value = 'facts' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `事实 (${props.result.facts.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabFacts', { count: props.result.facts.length }))
|
||||
]),
|
||||
hasEdges.value && h('button', {
|
||||
class: ['quicksearch-tab', { active: activeTab.value === 'edges' }],
|
||||
onClick: () => { activeTab.value = 'edges' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `关系 (${props.result.edges.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabEdges', { count: props.result.edges.length }))
|
||||
]),
|
||||
hasNodes.value && h('button', {
|
||||
class: ['quicksearch-tab', { active: activeTab.value === 'nodes' }],
|
||||
onClick: () => { activeTab.value = 'nodes' }
|
||||
}, [
|
||||
h('span', { class: 'tab-label' }, `节点 (${props.result.nodes.length})`)
|
||||
h('span', { class: 'tab-label' }, t('step4.tabNodes', { count: props.result.nodes.length }))
|
||||
])
|
||||
]),
|
||||
|
||||
|
|
@ -1642,8 +1647,8 @@ const QuickSearchDisplay = {
|
|||
// Facts (always show if no tabs, or when facts tab is active)
|
||||
((!showTabs.value) || activeTab.value === 'facts') && h('div', { class: 'facts-panel' }, [
|
||||
!showTabs.value && h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '搜索结果'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.facts.length} 条`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelSearchResults')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalCount', { count: props.result.facts.length }))
|
||||
]),
|
||||
props.result.facts.length > 0 ? h('div', { class: 'facts-list' },
|
||||
(expandedFacts.value ? props.result.facts : props.result.facts.slice(0, INITIAL_SHOW_COUNT)).map((fact, i) =>
|
||||
|
|
@ -1652,18 +1657,18 @@ const QuickSearchDisplay = {
|
|||
h('div', { class: 'fact-content' }, fact)
|
||||
])
|
||||
)
|
||||
) : h('div', { class: 'empty-state' }, '未找到相关结果'),
|
||||
) : h('div', { class: 'empty-state' }, t('step4.emptySearchResults')),
|
||||
props.result.facts.length > INITIAL_SHOW_COUNT && h('button', {
|
||||
class: 'expand-btn',
|
||||
onClick: () => { expandedFacts.value = !expandedFacts.value }
|
||||
}, expandedFacts.value ? `收起 ▲` : `展开全部 ${props.result.facts.length} 条 ▼`)
|
||||
}, expandedFacts.value ? t('step4.collapse') : t('step4.expandAll', { count: props.result.facts.length }))
|
||||
]),
|
||||
|
||||
// Edges Tab
|
||||
activeTab.value === 'edges' && hasEdges.value && h('div', { class: 'edges-panel' }, [
|
||||
h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '相关关系'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.edges.length} 条`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelRelatedEdges')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalCount', { count: props.result.edges.length }))
|
||||
]),
|
||||
h('div', { class: 'edges-list' },
|
||||
props.result.edges.map((edge, i) =>
|
||||
|
|
@ -1683,8 +1688,8 @@ const QuickSearchDisplay = {
|
|||
// Nodes Tab
|
||||
activeTab.value === 'nodes' && hasNodes.value && h('div', { class: 'nodes-panel' }, [
|
||||
h('div', { class: 'panel-header' }, [
|
||||
h('span', { class: 'panel-title' }, '相关节点'),
|
||||
h('span', { class: 'panel-count' }, `共 ${props.result.nodes.length} 个`)
|
||||
h('span', { class: 'panel-title' }, t('step4.panelRelatedNodes')),
|
||||
h('span', { class: 'panel-count' }, t('step4.totalEntityCount', { count: props.result.nodes.length }))
|
||||
]),
|
||||
h('div', { class: 'nodes-grid' },
|
||||
props.result.nodes.map((node, i) =>
|
||||
|
|
@ -5148,3 +5153,10 @@ watch(() => props.reportId, (newId) => {
|
|||
.log-msg.warning { color: #FFA726; }
|
||||
.log-msg.success { color: #66BB6A; }
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* English locale: smaller report title */
|
||||
html[lang="en"] .report-header-block .main-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
<path d="M12 2a10 10 0 0 1 10 10" stroke-width="4" stroke="#4B5563" stroke-linecap="round"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="loading-text">正在生成{{ section.title }}...</span>
|
||||
<span class="loading-text">{{ $t('step4.generatingSection', { title: section.title }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -85,8 +85,8 @@
|
|||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
||||
</svg>
|
||||
<div class="action-bar-text">
|
||||
<span class="action-bar-title">Interactive Tools</span>
|
||||
<span class="action-bar-subtitle mono">{{ profiles.length }} agents available</span>
|
||||
<span class="action-bar-title">{{ $t('step5.interactiveTools') }}</span>
|
||||
<span class="action-bar-subtitle mono">{{ $t('step5.agentsAvailable', { count: profiles.length }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-bar-tabs">
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>
|
||||
</svg>
|
||||
<span>与Report Agent对话</span>
|
||||
<span>{{ $t('step5.chatWithReportAgent') }}</span>
|
||||
</button>
|
||||
<div class="agent-dropdown" v-if="profiles.length > 0">
|
||||
<button
|
||||
|
|
@ -110,13 +110,13 @@
|
|||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
<span>{{ selectedAgent ? selectedAgent.username : '与世界中任意个体对话' }}</span>
|
||||
<span>{{ selectedAgent ? selectedAgent.username : $t('step5.chatWithAgent') }}</span>
|
||||
<svg class="dropdown-arrow" :class="{ open: showAgentDropdown }" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<div v-if="showAgentDropdown" class="dropdown-menu">
|
||||
<div class="dropdown-header">选择对话对象</div>
|
||||
<div class="dropdown-header">{{ $t('step5.selectChatTarget') }}</div>
|
||||
<div
|
||||
v-for="(agent, idx) in profiles"
|
||||
:key="idx"
|
||||
|
|
@ -126,13 +126,13 @@
|
|||
<div class="agent-avatar">{{ (agent.username || 'A')[0] }}</div>
|
||||
<div class="agent-info">
|
||||
<span class="agent-name">{{ agent.username }}</span>
|
||||
<span class="agent-role">{{ agent.profession || '未知职业' }}</span>
|
||||
<span class="agent-role">{{ agent.profession || $t('step2.unknownProfession') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-divider"></div>
|
||||
<button
|
||||
<button
|
||||
class="tab-pill survey-pill"
|
||||
:class="{ active: activeTab === 'survey' }"
|
||||
@click="selectSurveyTab"
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
<path d="M9 11l3 3L22 4"></path>
|
||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
||||
</svg>
|
||||
<span>发送问卷调查到世界中</span>
|
||||
<span>{{ $t('step5.sendSurvey') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -154,8 +154,8 @@
|
|||
<div class="tools-card-header">
|
||||
<div class="tools-card-avatar">R</div>
|
||||
<div class="tools-card-info">
|
||||
<div class="tools-card-name">Report Agent - Chat</div>
|
||||
<div class="tools-card-subtitle">报告生成智能体的快速对话版本,可调用 4 种专业工具,拥有MiroFish的完整记忆</div>
|
||||
<div class="tools-card-name">{{ $t('step5.reportAgentChat') }}</div>
|
||||
<div class="tools-card-subtitle">{{ $t('step5.reportAgentDesc') }}</div>
|
||||
</div>
|
||||
<button class="tools-card-toggle" @click="showToolsDetail = !showToolsDetail">
|
||||
<svg :class="{ 'is-expanded': showToolsDetail }" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2">
|
||||
|
|
@ -172,8 +172,8 @@
|
|||
</svg>
|
||||
</div>
|
||||
<div class="tool-content">
|
||||
<div class="tool-name">InsightForge 深度归因</div>
|
||||
<div class="tool-desc">对齐现实世界种子数据与模拟环境状态,结合Global/Local Memory机制,提供跨时空的深度归因分析</div>
|
||||
<div class="tool-name">{{ $t('step5.toolInsightForge') }}</div>
|
||||
<div class="tool-desc">{{ $t('step5.toolInsightForgeDesc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-item tool-blue">
|
||||
|
|
@ -184,8 +184,8 @@
|
|||
</svg>
|
||||
</div>
|
||||
<div class="tool-content">
|
||||
<div class="tool-name">PanoramaSearch 全景追踪</div>
|
||||
<div class="tool-desc">基于图结构的广度遍历算法,重构事件传播路径,捕获全量信息流动的拓扑结构</div>
|
||||
<div class="tool-name">{{ $t('step5.toolPanoramaSearch') }}</div>
|
||||
<div class="tool-desc">{{ $t('step5.toolPanoramaSearchDesc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-item tool-orange">
|
||||
|
|
@ -195,8 +195,8 @@
|
|||
</svg>
|
||||
</div>
|
||||
<div class="tool-content">
|
||||
<div class="tool-name">QuickSearch 快速检索</div>
|
||||
<div class="tool-desc">基于 GraphRAG 的即时查询接口,优化索引效率,用于快速提取具体的节点属性与离散事实</div>
|
||||
<div class="tool-name">{{ $t('step5.toolQuickSearch') }}</div>
|
||||
<div class="tool-desc">{{ $t('step5.toolQuickSearchDesc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-item tool-green">
|
||||
|
|
@ -208,8 +208,8 @@
|
|||
</svg>
|
||||
</div>
|
||||
<div class="tool-content">
|
||||
<div class="tool-name">InterviewSubAgent 虚拟访谈</div>
|
||||
<div class="tool-desc">自主式访谈,能够并行与模拟世界中个体进行多轮对话,采集非结构化的观点数据与心理状态</div>
|
||||
<div class="tool-name">{{ $t('step5.toolInterviewSubAgent') }}</div>
|
||||
<div class="tool-desc">{{ $t('step5.toolInterviewSubAgentDesc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -224,7 +224,7 @@
|
|||
<div class="profile-card-name">{{ selectedAgent.username }}</div>
|
||||
<div class="profile-card-meta">
|
||||
<span v-if="selectedAgent.name" class="profile-card-handle">@{{ selectedAgent.name }}</span>
|
||||
<span class="profile-card-profession">{{ selectedAgent.profession || '未知职业' }}</span>
|
||||
<span class="profile-card-profession">{{ selectedAgent.profession || $t('step2.unknownProfession') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="profile-card-toggle" @click="showFullProfile = !showFullProfile">
|
||||
|
|
@ -235,7 +235,7 @@
|
|||
</div>
|
||||
<div v-if="showFullProfile && selectedAgent.bio" class="profile-card-body">
|
||||
<div class="profile-card-bio">
|
||||
<div class="profile-card-label">简介</div>
|
||||
<div class="profile-card-label">{{ $t('step5.profileBio') }}</div>
|
||||
<p>{{ selectedAgent.bio }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -250,7 +250,7 @@
|
|||
</svg>
|
||||
</div>
|
||||
<p class="empty-text">
|
||||
{{ chatTarget === 'report_agent' ? '与 Report Agent 对话,深入了解报告内容' : '与模拟个体对话,了解他们的观点' }}
|
||||
{{ chatTarget === 'report_agent' ? $t('step5.chatEmptyReportAgent') : $t('step5.chatEmptyAgent') }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -292,7 +292,7 @@
|
|||
<textarea
|
||||
v-model="chatInput"
|
||||
class="chat-input"
|
||||
placeholder="输入您的问题..."
|
||||
:placeholder="$t('step5.chatInputPlaceholder')"
|
||||
@keydown.enter.exact.prevent="sendMessage"
|
||||
:disabled="isSending || (!selectedAgent && chatTarget === 'agent')"
|
||||
rows="1"
|
||||
|
|
@ -317,8 +317,8 @@
|
|||
<div class="survey-setup">
|
||||
<div class="setup-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">选择调查对象</span>
|
||||
<span class="selection-count">已选 {{ selectedAgents.size }} / {{ profiles.length }}</span>
|
||||
<span class="section-title">{{ $t('step5.selectSurveyTarget') }}</span>
|
||||
<span class="selection-count">{{ $t('step5.selectedCount', { selected: selectedAgents.size, total: profiles.length }) }}</span>
|
||||
</div>
|
||||
<div class="agents-grid">
|
||||
<label
|
||||
|
|
@ -335,7 +335,7 @@
|
|||
<div class="checkbox-avatar">{{ (agent.username || 'A')[0] }}</div>
|
||||
<div class="checkbox-info">
|
||||
<span class="checkbox-name">{{ agent.username }}</span>
|
||||
<span class="checkbox-role">{{ agent.profession || '未知职业' }}</span>
|
||||
<span class="checkbox-role">{{ agent.profession || $t('step2.unknownProfession') }}</span>
|
||||
</div>
|
||||
<div class="checkbox-indicator">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="3">
|
||||
|
|
@ -345,20 +345,20 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="selection-actions">
|
||||
<button class="action-link" @click="selectAllAgents">全选</button>
|
||||
<button class="action-link" @click="selectAllAgents">{{ $t('step5.selectAll') }}</button>
|
||||
<span class="action-divider">|</span>
|
||||
<button class="action-link" @click="clearAgentSelection">清空</button>
|
||||
<button class="action-link" @click="clearAgentSelection">{{ $t('step5.clearSelection') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setup-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">问卷问题</span>
|
||||
<span class="section-title">{{ $t('step5.surveyQuestions') }}</span>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="surveyQuestion"
|
||||
class="survey-input"
|
||||
placeholder="输入您想问所有被选中对象的问题..."
|
||||
:placeholder="$t('step5.surveyInputPlaceholder')"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
|
|
@ -369,15 +369,15 @@
|
|||
@click="submitSurvey"
|
||||
>
|
||||
<span v-if="isSurveying" class="loading-spinner"></span>
|
||||
<span v-else>发送问卷</span>
|
||||
<span v-else>{{ $t('step5.submitSurvey') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Survey Results -->
|
||||
<div v-if="surveyResults.length > 0" class="survey-results">
|
||||
<div class="results-header">
|
||||
<span class="results-title">调查结果</span>
|
||||
<span class="results-count">{{ surveyResults.length }} 条回复</span>
|
||||
<span class="results-title">{{ $t('step5.surveyResults') }}</span>
|
||||
<span class="results-count">{{ $t('step5.surveyResultsCount', { count: surveyResults.length }) }}</span>
|
||||
</div>
|
||||
<div class="results-list">
|
||||
<div
|
||||
|
|
@ -389,7 +389,7 @@
|
|||
<div class="result-avatar">{{ (result.agent_name || 'A')[0] }}</div>
|
||||
<div class="result-info">
|
||||
<span class="result-name">{{ result.agent_name }}</span>
|
||||
<span class="result-role">{{ result.profession || '未知职业' }}</span>
|
||||
<span class="result-role">{{ result.profession || $t('step2.unknownProfession') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-question">
|
||||
|
|
@ -412,9 +412,12 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { chatWithReport, getReport, getAgentLog } from '../api/report'
|
||||
import { interviewAgents, getSimulationProfilesRealtime } from '../api/simulation'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
reportId: String,
|
||||
simulationId: String
|
||||
|
|
@ -535,7 +538,7 @@ const selectAgent = (agent, idx) => {
|
|||
|
||||
// 恢复该 Agent 的对话记录
|
||||
chatHistory.value = chatHistoryCache.value[`agent_${idx}`] || []
|
||||
addLog(`选择对话对象: ${agent.username}`)
|
||||
addLog(t('log.selectChatTarget', { name: agent.username }))
|
||||
}
|
||||
|
||||
const formatTime = (timestamp) => {
|
||||
|
|
@ -662,10 +665,10 @@ const sendMessage = async () => {
|
|||
await sendToAgent(message)
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`发送失败: ${err.message}`)
|
||||
addLog(t('log.sendFailed', { error: err.message }))
|
||||
chatHistory.value.push({
|
||||
role: 'assistant',
|
||||
content: `抱歉,发生了错误: ${err.message}`,
|
||||
content: t('step5.errorOccurred', { error: err.message }),
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
} finally {
|
||||
|
|
@ -677,7 +680,7 @@ const sendMessage = async () => {
|
|||
}
|
||||
|
||||
const sendToReportAgent = async (message) => {
|
||||
addLog(`向 Report Agent 发送: ${message.substring(0, 50)}...`)
|
||||
addLog(t('log.sendToReportAgent', { message: message.substring(0, 50) }))
|
||||
|
||||
// Build chat history for API
|
||||
const historyForApi = chatHistory.value
|
||||
|
|
@ -697,21 +700,21 @@ const sendToReportAgent = async (message) => {
|
|||
if (res.success && res.data) {
|
||||
chatHistory.value.push({
|
||||
role: 'assistant',
|
||||
content: res.data.response || res.data.answer || '无响应',
|
||||
content: res.data.response || res.data.answer || t('step5.noResponse'),
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
addLog('Report Agent 已回复')
|
||||
addLog(t('log.reportAgentReplied'))
|
||||
} else {
|
||||
throw new Error(res.error || '请求失败')
|
||||
throw new Error(res.error || t('step5.requestFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
const sendToAgent = async (message) => {
|
||||
if (!selectedAgent.value || selectedAgentIndex.value === null) {
|
||||
throw new Error('请先选择一个模拟个体')
|
||||
throw new Error(t('step5.selectAgentFirst'))
|
||||
}
|
||||
|
||||
addLog(`向 ${selectedAgent.value.username} 发送: ${message.substring(0, 50)}...`)
|
||||
addLog(t('log.sendToAgent', { name: selectedAgent.value.username, message: message.substring(0, 50) }))
|
||||
|
||||
// Build prompt with chat history
|
||||
let prompt = message
|
||||
|
|
@ -761,12 +764,12 @@ const sendToAgent = async (message) => {
|
|||
content: responseContent,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
addLog(`${selectedAgent.value.username} 已回复`)
|
||||
addLog(t('log.agentReplied', { name: selectedAgent.value.username }))
|
||||
} else {
|
||||
throw new Error('无响应数据')
|
||||
throw new Error(t('step5.noResponse'))
|
||||
}
|
||||
} else {
|
||||
throw new Error(res.error || '请求失败')
|
||||
throw new Error(res.error || t('step5.requestFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -803,7 +806,7 @@ const submitSurvey = async () => {
|
|||
if (selectedAgents.value.size === 0 || !surveyQuestion.value.trim()) return
|
||||
|
||||
isSurveying.value = true
|
||||
addLog(`发送问卷给 ${selectedAgents.value.size} 个对象...`)
|
||||
addLog(t('log.sendSurvey', { count: selectedAgents.value.size }))
|
||||
|
||||
try {
|
||||
const interviews = Array.from(selectedAgents.value).map(idx => ({
|
||||
|
|
@ -830,20 +833,20 @@ const submitSurvey = async () => {
|
|||
const agent = profiles.value[agentIdx]
|
||||
|
||||
// 优先使用 reddit 平台回复,其次 twitter
|
||||
let responseContent = '无响应'
|
||||
|
||||
let responseContent = t('step5.noResponse')
|
||||
|
||||
if (typeof resultsDict === 'object' && !Array.isArray(resultsDict)) {
|
||||
const redditKey = `reddit_${agentIdx}`
|
||||
const twitterKey = `twitter_${agentIdx}`
|
||||
const agentResult = resultsDict[redditKey] || resultsDict[twitterKey]
|
||||
if (agentResult) {
|
||||
responseContent = agentResult.response || agentResult.answer || '无响应'
|
||||
responseContent = agentResult.response || agentResult.answer || t('step5.noResponse')
|
||||
}
|
||||
} else if (Array.isArray(resultsDict)) {
|
||||
// 兼容数组格式
|
||||
const matchedResult = resultsDict.find(r => r.agent_id === agentIdx)
|
||||
if (matchedResult) {
|
||||
responseContent = matchedResult.response || matchedResult.answer || '无响应'
|
||||
responseContent = matchedResult.response || matchedResult.answer || t('step5.noResponse')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -857,12 +860,12 @@ const submitSurvey = async () => {
|
|||
}
|
||||
|
||||
surveyResults.value = surveyResultsList
|
||||
addLog(`收到 ${surveyResults.value.length} 条回复`)
|
||||
addLog(t('log.receivedReplies', { count: surveyResults.value.length }))
|
||||
} else {
|
||||
throw new Error(res.error || '请求失败')
|
||||
throw new Error(res.error || t('step5.requestFailed'))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`问卷发送失败: ${err.message}`)
|
||||
addLog(t('log.surveySendFailed', { error: err.message }))
|
||||
} finally {
|
||||
isSurveying.value = false
|
||||
}
|
||||
|
|
@ -873,7 +876,7 @@ const loadReportData = async () => {
|
|||
if (!props.reportId) return
|
||||
|
||||
try {
|
||||
addLog(`加载报告数据: ${props.reportId}`)
|
||||
addLog(t('log.loadReportData', { id: props.reportId }))
|
||||
|
||||
// Get report info
|
||||
const reportRes = await getReport(props.reportId)
|
||||
|
|
@ -882,7 +885,7 @@ const loadReportData = async () => {
|
|||
await loadAgentLogs()
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`加载报告失败: ${err.message}`)
|
||||
addLog(t('log.loadReportFailed', { error: err.message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -904,10 +907,10 @@ const loadAgentLogs = async () => {
|
|||
}
|
||||
})
|
||||
|
||||
addLog('报告数据加载完成')
|
||||
addLog(t('log.reportDataLoaded'))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`加载报告日志失败: ${err.message}`)
|
||||
addLog(t('log.loadReportLogFailed', { error: err.message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -918,10 +921,10 @@ const loadProfiles = async () => {
|
|||
const res = await getSimulationProfilesRealtime(props.simulationId, 'reddit')
|
||||
if (res.success && res.data) {
|
||||
profiles.value = res.data.profiles || []
|
||||
addLog(`加载了 ${profiles.value.length} 个模拟个体`)
|
||||
addLog(t('log.loadedProfiles', { count: profiles.value.length }))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`加载模拟个体失败: ${err.message}`)
|
||||
addLog(t('log.loadProfilesFailed', { error: err.message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -935,7 +938,7 @@ const handleClickOutside = (e) => {
|
|||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
addLog('Step5 深度互动初始化')
|
||||
addLog(t('log.step5Init'))
|
||||
loadReportData()
|
||||
loadProfiles()
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
|
|
@ -2572,3 +2575,10 @@ watch(() => props.simulationId, (newId) => {
|
|||
margin: 24px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* English locale: smaller report title */
|
||||
html[lang="en"] .report-header-block .main-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
27
frontend/src/i18n/index.js
Normal file
27
frontend/src/i18n/index.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { createI18n } from 'vue-i18n'
|
||||
import languages from '../../../locales/languages.json'
|
||||
|
||||
const localeFiles = import.meta.glob('../../../locales/!(languages).json', { eager: true })
|
||||
|
||||
const messages = {}
|
||||
const availableLocales = []
|
||||
|
||||
for (const path in localeFiles) {
|
||||
const key = path.match(/\/([^/]+)\.json$/)[1]
|
||||
if (languages[key]) {
|
||||
messages[key] = localeFiles[path].default
|
||||
availableLocales.push({ key, label: languages[key].label })
|
||||
}
|
||||
}
|
||||
|
||||
const savedLocale = localStorage.getItem('locale') || 'zh'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: savedLocale,
|
||||
fallbackLocale: 'zh',
|
||||
messages
|
||||
})
|
||||
|
||||
export { availableLocales }
|
||||
export default i18n
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import i18n from './i18n'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
|
||||
app.mount('#app')
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@
|
|||
<nav class="navbar">
|
||||
<div class="nav-brand">MIROFISH</div>
|
||||
<div class="nav-links">
|
||||
<LanguageSwitcher />
|
||||
<a href="https://github.com/666ghj/MiroFish" target="_blank" class="github-link">
|
||||
访问我们的Github主页 <span class="arrow">↗</span>
|
||||
{{ $t('nav.visitGithub') }} <span class="arrow">↗</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
@ -15,21 +16,25 @@
|
|||
<section class="hero-section">
|
||||
<div class="hero-left">
|
||||
<div class="tag-row">
|
||||
<span class="orange-tag">简洁通用的群体智能引擎</span>
|
||||
<span class="version-text">/ v0.1-预览版</span>
|
||||
<span class="orange-tag">{{ $t('home.tagline') }}</span>
|
||||
<span class="version-text">{{ $t('home.version') }}</span>
|
||||
</div>
|
||||
|
||||
<h1 class="main-title">
|
||||
上传任意报告<br>
|
||||
<span class="gradient-text">即刻推演未来</span>
|
||||
{{ $t('home.heroTitle1') }}<br>
|
||||
<span class="gradient-text">{{ $t('home.heroTitle2') }}</span>
|
||||
</h1>
|
||||
|
||||
<div class="hero-desc">
|
||||
<p>
|
||||
即使只有一段文字,<span class="highlight-bold">MiroFish</span> 也能基于其中的现实种子,全自动生成与之对应的至多<span class="highlight-orange">百万级Agent</span>构成的平行世界。通过上帝视角注入变量,在复杂的群体交互中寻找动态环境下的<span class="highlight-code">“局部最优解”</span>
|
||||
<i18n-t keypath="home.heroDesc" tag="span">
|
||||
<template #brand><span class="highlight-bold">{{ $t('home.heroDescBrand') }}</span></template>
|
||||
<template #agentScale><span class="highlight-orange">{{ $t('home.heroDescAgentScale') }}</span></template>
|
||||
<template #optimalSolution><span class="highlight-code">{{ $t('home.heroDescOptimalSolution') }}</span></template>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<p class="slogan-text">
|
||||
让未来在 Agent 群中预演,让决策在百战后胜出<span class="blinking-cursor">_</span>
|
||||
{{ $t('home.slogan') }}<span class="blinking-cursor">_</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -53,65 +58,65 @@
|
|||
<!-- 左栏:状态与步骤 -->
|
||||
<div class="left-panel">
|
||||
<div class="panel-header">
|
||||
<span class="status-dot">■</span> 系统状态
|
||||
<span class="status-dot">■</span> {{ $t('home.systemStatus') }}
|
||||
</div>
|
||||
|
||||
<h2 class="section-title">准备就绪</h2>
|
||||
<h2 class="section-title">{{ $t('home.systemReady') }}</h2>
|
||||
<p class="section-desc">
|
||||
预测引擎待命中,可上传多份非结构化数据以初始化模拟序列
|
||||
{{ $t('home.systemReadyDesc') }}
|
||||
</p>
|
||||
|
||||
<!-- 数据指标卡片 -->
|
||||
<div class="metrics-row">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value">低成本</div>
|
||||
<div class="metric-label">常规模拟平均5$/次</div>
|
||||
<div class="metric-value">{{ $t('home.metricLowCost') }}</div>
|
||||
<div class="metric-label">{{ $t('home.metricLowCostDesc') }}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value">高可用</div>
|
||||
<div class="metric-label">最多百万级Agent模拟</div>
|
||||
<div class="metric-value">{{ $t('home.metricHighAvail') }}</div>
|
||||
<div class="metric-label">{{ $t('home.metricHighAvailDesc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目模拟步骤介绍 (新增区域) -->
|
||||
<div class="steps-container">
|
||||
<div class="steps-header">
|
||||
<span class="diamond-icon">◇</span> 工作流序列
|
||||
<span class="diamond-icon">◇</span> {{ $t('home.workflowSequence') }}
|
||||
</div>
|
||||
<div class="workflow-list">
|
||||
<div class="workflow-item">
|
||||
<span class="step-num">01</span>
|
||||
<div class="step-info">
|
||||
<div class="step-title">图谱构建</div>
|
||||
<div class="step-desc">现实种子提取 & 个体与群体记忆注入 & GraphRAG构建</div>
|
||||
<div class="step-title">{{ $t('home.step01Title') }}</div>
|
||||
<div class="step-desc">{{ $t('home.step01Desc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workflow-item">
|
||||
<span class="step-num">02</span>
|
||||
<div class="step-info">
|
||||
<div class="step-title">环境搭建</div>
|
||||
<div class="step-desc">实体关系抽取 & 人设生成 & 环境配置Agent注入仿真参数</div>
|
||||
<div class="step-title">{{ $t('home.step02Title') }}</div>
|
||||
<div class="step-desc">{{ $t('home.step02Desc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workflow-item">
|
||||
<span class="step-num">03</span>
|
||||
<div class="step-info">
|
||||
<div class="step-title">开始模拟</div>
|
||||
<div class="step-desc">双平台并行模拟 & 自动解析预测需求 & 动态更新时序记忆</div>
|
||||
<div class="step-title">{{ $t('home.step03Title') }}</div>
|
||||
<div class="step-desc">{{ $t('home.step03Desc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workflow-item">
|
||||
<span class="step-num">04</span>
|
||||
<div class="step-info">
|
||||
<div class="step-title">报告生成</div>
|
||||
<div class="step-desc">ReportAgent拥有丰富的工具集与模拟后环境进行深度交互</div>
|
||||
<div class="step-title">{{ $t('home.step04Title') }}</div>
|
||||
<div class="step-desc">{{ $t('home.step04Desc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workflow-item">
|
||||
<span class="step-num">05</span>
|
||||
<div class="step-info">
|
||||
<div class="step-title">深度互动</div>
|
||||
<div class="step-desc">与模拟世界中的任意一位进行对话 & 与ReportAgent进行对话</div>
|
||||
<div class="step-title">{{ $t('home.step05Title') }}</div>
|
||||
<div class="step-desc">{{ $t('home.step05Desc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -124,8 +129,8 @@
|
|||
<!-- 上传区域 -->
|
||||
<div class="console-section">
|
||||
<div class="console-header">
|
||||
<span class="console-label">01 / 现实种子</span>
|
||||
<span class="console-meta">支持格式: PDF, MD, TXT</span>
|
||||
<span class="console-label">{{ $t('home.realitySeed') }}</span>
|
||||
<span class="console-meta">{{ $t('home.supportedFormats') }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -148,8 +153,8 @@
|
|||
|
||||
<div v-if="files.length === 0" class="upload-placeholder">
|
||||
<div class="upload-icon">↑</div>
|
||||
<div class="upload-title">拖拽文件上传</div>
|
||||
<div class="upload-hint">或点击浏览文件系统</div>
|
||||
<div class="upload-title">{{ $t('home.dragToUpload') }}</div>
|
||||
<div class="upload-hint">{{ $t('home.orBrowse') }}</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="file-list">
|
||||
|
|
@ -164,23 +169,23 @@
|
|||
|
||||
<!-- 分割线 -->
|
||||
<div class="console-divider">
|
||||
<span>输入参数</span>
|
||||
<span>{{ $t('home.inputParams') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="console-section">
|
||||
<div class="console-header">
|
||||
<span class="console-label">>_ 02 / 模拟提示词</span>
|
||||
<span class="console-label">{{ $t('home.simulationPrompt') }}</span>
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<textarea
|
||||
v-model="formData.simulationRequirement"
|
||||
class="code-input"
|
||||
placeholder="// 用自然语言输入模拟或预测需求(例.武大若发布撤销肖某处分的公告,会引发什么舆情走向)"
|
||||
:placeholder="$t('home.promptPlaceholder')"
|
||||
rows="6"
|
||||
:disabled="loading"
|
||||
></textarea>
|
||||
<div class="model-badge">引擎: MiroFish-V1.0</div>
|
||||
<div class="model-badge">{{ $t('home.engineBadge') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -191,8 +196,8 @@
|
|||
@click="startSimulation"
|
||||
:disabled="!canSubmit || loading"
|
||||
>
|
||||
<span v-if="!loading">启动引擎</span>
|
||||
<span v-else>初始化中...</span>
|
||||
<span v-if="!loading">{{ $t('home.startEngine') }}</span>
|
||||
<span v-else>{{ $t('home.initializing') }}</span>
|
||||
<span class="btn-arrow">→</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -210,6 +215,7 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import HistoryDatabase from '../components/HistoryDatabase.vue'
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
|
|
@ -351,6 +357,7 @@ const startSimulation = () => {
|
|||
.nav-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.github-link {
|
||||
|
|
@ -888,3 +895,59 @@ const startSimulation = () => {
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* English locale adjustments (unscoped to target html[lang]) */
|
||||
html[lang="en"] .main-title {
|
||||
font-size: 3.5rem;
|
||||
font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
html[lang="en"] .hero-desc {
|
||||
text-align: left;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
html[lang="en"] .slogan-text {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
html[lang="en"] .tag-row {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
html[lang="en"] .navbar .nav-links {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Left pane: system status + workflow */
|
||||
html[lang="en"] .status-section {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
html[lang="en"] .status-section .status-ready {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
html[lang="en"] .status-section .metric-value {
|
||||
font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
html[lang="en"] .workflow-list .step-title {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
html[lang="en"] .workflow-list .step-desc {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
|
||||
font-size: 0.72rem !important;
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
|
||||
html[lang="en"] .workflow-list {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -15,15 +15,17 @@
|
|||
:class="{ active: viewMode === mode }"
|
||||
@click="viewMode = mode"
|
||||
>
|
||||
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
|
||||
{{ { graph: $t('main.layoutGraph'), split: $t('main.layoutSplit'), workbench: $t('main.layoutWorkbench') }[mode] }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<LanguageSwitcher />
|
||||
<div class="step-divider"></div>
|
||||
<div class="workflow-step">
|
||||
<span class="step-num">Step 5/5</span>
|
||||
<span class="step-name">深度互动</span>
|
||||
<span class="step-name">{{ $tm('main.stepNames')[4] }}</span>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<span class="status-indicator" :class="statusClass">
|
||||
|
|
@ -64,14 +66,17 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import GraphPanel from '../components/GraphPanel.vue'
|
||||
import Step5Interaction from '../components/Step5Interaction.vue'
|
||||
import { getProject, getGraphData } from '../api/graph'
|
||||
import { getSimulation } from '../api/simulation'
|
||||
import { getReport } from '../api/report'
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
|
|
@ -140,27 +145,27 @@ const toggleMaximize = (target) => {
|
|||
// --- Data Logic ---
|
||||
const loadReportData = async () => {
|
||||
try {
|
||||
addLog(`加载报告数据: ${currentReportId.value}`)
|
||||
|
||||
addLog(t('log.loadReportData', { id: currentReportId.value }))
|
||||
|
||||
// 获取 report 信息以获取 simulation_id
|
||||
const reportRes = await getReport(currentReportId.value)
|
||||
if (reportRes.success && reportRes.data) {
|
||||
const reportData = reportRes.data
|
||||
simulationId.value = reportData.simulation_id
|
||||
|
||||
|
||||
if (simulationId.value) {
|
||||
// 获取 simulation 信息
|
||||
const simRes = await getSimulation(simulationId.value)
|
||||
if (simRes.success && simRes.data) {
|
||||
const simData = simRes.data
|
||||
|
||||
|
||||
// 获取 project 信息
|
||||
if (simData.project_id) {
|
||||
const projRes = await getProject(simData.project_id)
|
||||
if (projRes.success && projRes.data) {
|
||||
projectData.value = projRes.data
|
||||
addLog(`项目加载成功: ${projRes.data.project_id}`)
|
||||
|
||||
addLog(t('log.projectLoadSuccess', { id: projRes.data.project_id }))
|
||||
|
||||
// 获取 graph 数据
|
||||
if (projRes.data.graph_id) {
|
||||
await loadGraph(projRes.data.graph_id)
|
||||
|
|
@ -170,10 +175,10 @@ const loadReportData = async () => {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
addLog(`获取报告信息失败: ${reportRes.error || '未知错误'}`)
|
||||
addLog(t('log.getReportInfoFailed', { error: reportRes.error || t('common.unknownError') }))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`加载异常: ${err.message}`)
|
||||
addLog(t('log.loadException', { error: err.message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,10 +189,10 @@ const loadGraph = async (graphId) => {
|
|||
const res = await getGraphData(graphId)
|
||||
if (res.success) {
|
||||
graphData.value = res.data
|
||||
addLog('图谱数据加载成功')
|
||||
addLog(t('log.graphDataLoadSuccess'))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`图谱加载失败: ${err.message}`)
|
||||
addLog(t('log.graphLoadFailed', { error: err.message }))
|
||||
} finally {
|
||||
graphLoading.value = false
|
||||
}
|
||||
|
|
@ -208,7 +213,7 @@ watch(() => route.params.reportId, (newId) => {
|
|||
}, { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
addLog('InteractionView 初始化')
|
||||
addLog(t('log.interactionViewInit'))
|
||||
loadReportData()
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -15,15 +15,17 @@
|
|||
:class="{ active: viewMode === mode }"
|
||||
@click="viewMode = mode"
|
||||
>
|
||||
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
|
||||
{{ { graph: $t('main.layoutGraph'), split: $t('main.layoutSplit'), workbench: $t('main.layoutWorkbench') }[mode] }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<LanguageSwitcher />
|
||||
<div class="step-divider"></div>
|
||||
<div class="workflow-step">
|
||||
<span class="step-num">Step {{ currentStep }}/5</span>
|
||||
<span class="step-name">{{ stepNames[currentStep - 1] }}</span>
|
||||
<span class="step-name">{{ $tm('main.stepNames')[currentStep - 1] }}</span>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<span class="status-indicator" :class="statusClass">
|
||||
|
|
@ -77,21 +79,24 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import GraphPanel from '../components/GraphPanel.vue'
|
||||
import Step1GraphBuild from '../components/Step1GraphBuild.vue'
|
||||
import Step2EnvSetup from '../components/Step2EnvSetup.vue'
|
||||
import { generateOntology, getProject, buildGraph, getTaskStatus, getGraphData } from '../api/graph'
|
||||
import { getPendingUpload, clearPendingUpload } from '../store/pendingUpload'
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t, tm } = useI18n()
|
||||
|
||||
// Layout State
|
||||
const viewMode = ref('split') // graph | split | workbench
|
||||
|
||||
// Step State
|
||||
const currentStep = ref(1) // 1: 图谱构建, 2: 环境搭建, 3: 开始模拟, 4: 报告生成, 5: 深度互动
|
||||
const stepNames = ['图谱构建', '环境搭建', '开始模拟', '报告生成', '深度互动']
|
||||
const stepNames = computed(() => tm('main.stepNames'))
|
||||
|
||||
// Data State
|
||||
const currentProjectId = ref(route.params.projectId)
|
||||
|
|
@ -159,11 +164,11 @@ const toggleMaximize = (target) => {
|
|||
const handleNextStep = (params = {}) => {
|
||||
if (currentStep.value < 5) {
|
||||
currentStep.value++
|
||||
addLog(`进入 Step ${currentStep.value}: ${stepNames[currentStep.value - 1]}`)
|
||||
addLog(t('log.enterStep', { step: currentStep.value, name: stepNames.value[currentStep.value - 1] }))
|
||||
|
||||
// 如果是从 Step 2 进入 Step 3,记录模拟轮数配置
|
||||
if (currentStep.value === 3 && params.maxRounds) {
|
||||
addLog(`自定义模拟轮数: ${params.maxRounds} 轮`)
|
||||
addLog(t('log.customSimRounds', { rounds: params.maxRounds }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -171,7 +176,7 @@ const handleNextStep = (params = {}) => {
|
|||
const handleGoBack = () => {
|
||||
if (currentStep.value > 1) {
|
||||
currentStep.value--
|
||||
addLog(`返回 Step ${currentStep.value}: ${stepNames[currentStep.value - 1]}`)
|
||||
addLog(t('log.returnToStep', { step: currentStep.value, name: stepNames.value[currentStep.value - 1] }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,15 +15,17 @@
|
|||
:class="{ active: viewMode === mode }"
|
||||
@click="viewMode = mode"
|
||||
>
|
||||
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
|
||||
{{ { graph: $t('main.layoutGraph'), split: $t('main.layoutSplit'), workbench: $t('main.layoutWorkbench') }[mode] }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<LanguageSwitcher />
|
||||
<div class="step-divider"></div>
|
||||
<div class="workflow-step">
|
||||
<span class="step-num">Step 4/5</span>
|
||||
<span class="step-name">报告生成</span>
|
||||
<span class="step-name">{{ $tm('main.stepNames')[3] }}</span>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<span class="status-indicator" :class="statusClass">
|
||||
|
|
@ -64,14 +66,17 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import GraphPanel from '../components/GraphPanel.vue'
|
||||
import Step4Report from '../components/Step4Report.vue'
|
||||
import { getProject, getGraphData } from '../api/graph'
|
||||
import { getSimulation } from '../api/simulation'
|
||||
import { getReport } from '../api/report'
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
|
|
@ -139,27 +144,27 @@ const toggleMaximize = (target) => {
|
|||
// --- Data Logic ---
|
||||
const loadReportData = async () => {
|
||||
try {
|
||||
addLog(`加载报告数据: ${currentReportId.value}`)
|
||||
|
||||
addLog(t('log.loadReportData', { id: currentReportId.value }))
|
||||
|
||||
// 获取 report 信息以获取 simulation_id
|
||||
const reportRes = await getReport(currentReportId.value)
|
||||
if (reportRes.success && reportRes.data) {
|
||||
const reportData = reportRes.data
|
||||
simulationId.value = reportData.simulation_id
|
||||
|
||||
|
||||
if (simulationId.value) {
|
||||
// 获取 simulation 信息
|
||||
const simRes = await getSimulation(simulationId.value)
|
||||
if (simRes.success && simRes.data) {
|
||||
const simData = simRes.data
|
||||
|
||||
|
||||
// 获取 project 信息
|
||||
if (simData.project_id) {
|
||||
const projRes = await getProject(simData.project_id)
|
||||
if (projRes.success && projRes.data) {
|
||||
projectData.value = projRes.data
|
||||
addLog(`项目加载成功: ${projRes.data.project_id}`)
|
||||
|
||||
addLog(t('log.projectLoadSuccess', { id: projRes.data.project_id }))
|
||||
|
||||
// 获取 graph 数据
|
||||
if (projRes.data.graph_id) {
|
||||
await loadGraph(projRes.data.graph_id)
|
||||
|
|
@ -169,10 +174,10 @@ const loadReportData = async () => {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
addLog(`获取报告信息失败: ${reportRes.error || '未知错误'}`)
|
||||
addLog(t('log.getReportInfoFailed', { error: reportRes.error || t('common.unknownError') }))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`加载异常: ${err.message}`)
|
||||
addLog(t('log.loadException', { error: err.message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,10 +188,10 @@ const loadGraph = async (graphId) => {
|
|||
const res = await getGraphData(graphId)
|
||||
if (res.success) {
|
||||
graphData.value = res.data
|
||||
addLog('图谱数据加载成功')
|
||||
addLog(t('log.graphDataLoadSuccess'))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`图谱加载失败: ${err.message}`)
|
||||
addLog(t('log.graphLoadFailed', { error: err.message }))
|
||||
} finally {
|
||||
graphLoading.value = false
|
||||
}
|
||||
|
|
@ -207,7 +212,7 @@ watch(() => route.params.reportId, (newId) => {
|
|||
}, { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
addLog('ReportView 初始化')
|
||||
addLog(t('log.reportViewInit'))
|
||||
loadReportData()
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -15,15 +15,17 @@
|
|||
:class="{ active: viewMode === mode }"
|
||||
@click="viewMode = mode"
|
||||
>
|
||||
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
|
||||
{{ { graph: $t('main.layoutGraph'), split: $t('main.layoutSplit'), workbench: $t('main.layoutWorkbench') }[mode] }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<LanguageSwitcher />
|
||||
<div class="step-divider"></div>
|
||||
<div class="workflow-step">
|
||||
<span class="step-num">Step 3/5</span>
|
||||
<span class="step-name">开始模拟</span>
|
||||
<span class="step-name">{{ $tm('main.stepNames')[2] }}</span>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<span class="status-indicator" :class="statusClass">
|
||||
|
|
@ -73,7 +75,10 @@ import GraphPanel from '../components/GraphPanel.vue'
|
|||
import Step3Simulation from '../components/Step3Simulation.vue'
|
||||
import { getProject, getGraphData } from '../api/graph'
|
||||
import { getSimulation, getSimulationConfig, stopSimulation, closeSimulationEnv, getEnvStatus } from '../api/simulation'
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
|
|
@ -146,7 +151,7 @@ const toggleMaximize = (target) => {
|
|||
|
||||
const handleGoBack = async () => {
|
||||
// 在返回 Step 2 之前,先关闭正在运行的模拟
|
||||
addLog('准备返回 Step 2,正在关闭模拟...')
|
||||
addLog(t('log.preparingGoBack'))
|
||||
|
||||
// 停止轮询
|
||||
stopGraphRefresh()
|
||||
|
|
@ -156,36 +161,36 @@ const handleGoBack = async () => {
|
|||
const envStatusRes = await getEnvStatus({ simulation_id: currentSimulationId.value })
|
||||
|
||||
if (envStatusRes.success && envStatusRes.data?.env_alive) {
|
||||
addLog('正在关闭模拟环境...')
|
||||
addLog(t('log.closingSimEnv'))
|
||||
try {
|
||||
await closeSimulationEnv({
|
||||
simulation_id: currentSimulationId.value,
|
||||
timeout: 10
|
||||
})
|
||||
addLog('✓ 模拟环境已关闭')
|
||||
addLog(t('log.simEnvClosed'))
|
||||
} catch (closeErr) {
|
||||
addLog(`关闭模拟环境失败,尝试强制停止...`)
|
||||
addLog(t('log.closeSimEnvFailed'))
|
||||
try {
|
||||
await stopSimulation({ simulation_id: currentSimulationId.value })
|
||||
addLog('✓ 模拟已强制停止')
|
||||
addLog(t('log.simForceStopSuccess'))
|
||||
} catch (stopErr) {
|
||||
addLog(`强制停止失败: ${stopErr.message}`)
|
||||
addLog(t('log.forceStopFailed', { error: stopErr.message }))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 环境未运行,检查是否需要停止进程
|
||||
if (isSimulating.value) {
|
||||
addLog('正在停止模拟进程...')
|
||||
addLog(t('log.stoppingSimProcess'))
|
||||
try {
|
||||
await stopSimulation({ simulation_id: currentSimulationId.value })
|
||||
addLog('✓ 模拟已停止')
|
||||
addLog(t('log.simStopped'))
|
||||
} catch (err) {
|
||||
addLog(`停止模拟失败: ${err.message}`)
|
||||
addLog(t('log.stopSimFailed', { error: err.message }))
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`检查模拟状态失败: ${err.message}`)
|
||||
addLog(t('log.checkStatusFailed', { error: err.message }))
|
||||
}
|
||||
|
||||
// 返回到 Step 2 (环境搭建)
|
||||
|
|
@ -195,13 +200,13 @@ const handleGoBack = async () => {
|
|||
const handleNextStep = () => {
|
||||
// Step3Simulation 组件会直接处理报告生成和路由跳转
|
||||
// 这个方法仅作为备用
|
||||
addLog('进入 Step 4: 报告生成')
|
||||
addLog(t('log.enterStep4'))
|
||||
}
|
||||
|
||||
// --- Data Logic ---
|
||||
const loadSimulationData = async () => {
|
||||
try {
|
||||
addLog(`加载模拟数据: ${currentSimulationId.value}`)
|
||||
addLog(t('log.loadingSimData', { id: currentSimulationId.value }))
|
||||
|
||||
// 获取 simulation 信息
|
||||
const simRes = await getSimulation(currentSimulationId.value)
|
||||
|
|
@ -213,10 +218,10 @@ const loadSimulationData = async () => {
|
|||
const configRes = await getSimulationConfig(currentSimulationId.value)
|
||||
if (configRes.success && configRes.data?.time_config?.minutes_per_round) {
|
||||
minutesPerRound.value = configRes.data.time_config.minutes_per_round
|
||||
addLog(`时间配置: 每轮 ${minutesPerRound.value} 分钟`)
|
||||
addLog(t('log.timeConfig', { minutes: minutesPerRound.value }))
|
||||
}
|
||||
} catch (configErr) {
|
||||
addLog(`获取时间配置失败,使用默认值: ${minutesPerRound.value}分钟/轮`)
|
||||
addLog(t('log.timeConfigFetchFailed', { minutes: minutesPerRound.value }))
|
||||
}
|
||||
|
||||
// 获取 project 信息
|
||||
|
|
@ -224,7 +229,7 @@ const loadSimulationData = async () => {
|
|||
const projRes = await getProject(simData.project_id)
|
||||
if (projRes.success && projRes.data) {
|
||||
projectData.value = projRes.data
|
||||
addLog(`项目加载成功: ${projRes.data.project_id}`)
|
||||
addLog(t('log.projectLoadSuccess', { id: projRes.data.project_id }))
|
||||
|
||||
// 获取 graph 数据
|
||||
if (projRes.data.graph_id) {
|
||||
|
|
@ -233,10 +238,10 @@ const loadSimulationData = async () => {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
addLog(`加载模拟数据失败: ${simRes.error || '未知错误'}`)
|
||||
addLog(t('log.loadSimDataFailed', { error: simRes.error || t('common.unknownError') }))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`加载异常: ${err.message}`)
|
||||
addLog(t('log.loadException', { error: err.message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -252,11 +257,11 @@ const loadGraph = async (graphId) => {
|
|||
if (res.success) {
|
||||
graphData.value = res.data
|
||||
if (!isSimulating.value) {
|
||||
addLog('图谱数据加载成功')
|
||||
addLog(t('log.graphDataLoadSuccess'))
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`图谱加载失败: ${err.message}`)
|
||||
addLog(t('log.graphLoadFailed', { error: err.message }))
|
||||
} finally {
|
||||
graphLoading.value = false
|
||||
}
|
||||
|
|
@ -273,7 +278,7 @@ let graphRefreshTimer = null
|
|||
|
||||
const startGraphRefresh = () => {
|
||||
if (graphRefreshTimer) return
|
||||
addLog('开启图谱实时刷新 (30s)')
|
||||
addLog(t('log.graphRealtimeRefreshStart'))
|
||||
// 立即刷新一次,然后每30秒刷新
|
||||
graphRefreshTimer = setInterval(refreshGraph, 30000)
|
||||
}
|
||||
|
|
@ -282,7 +287,7 @@ const stopGraphRefresh = () => {
|
|||
if (graphRefreshTimer) {
|
||||
clearInterval(graphRefreshTimer)
|
||||
graphRefreshTimer = null
|
||||
addLog('停止图谱实时刷新')
|
||||
addLog(t('log.graphRealtimeRefreshStop'))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -295,11 +300,11 @@ watch(isSimulating, (newValue) => {
|
|||
}, { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
addLog('SimulationRunView 初始化')
|
||||
addLog(t('log.simRunViewInit'))
|
||||
|
||||
// 记录 maxRounds 配置(值已在初始化时从 query 参数获取)
|
||||
if (maxRounds.value) {
|
||||
addLog(`自定义模拟轮数: ${maxRounds.value}`)
|
||||
addLog(t('log.customRounds', { rounds: maxRounds.value }))
|
||||
}
|
||||
|
||||
loadSimulationData()
|
||||
|
|
|
|||
|
|
@ -15,15 +15,17 @@
|
|||
:class="{ active: viewMode === mode }"
|
||||
@click="viewMode = mode"
|
||||
>
|
||||
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
|
||||
{{ { graph: $t('main.layoutGraph'), split: $t('main.layoutSplit'), workbench: $t('main.layoutWorkbench') }[mode] }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<LanguageSwitcher />
|
||||
<div class="step-divider"></div>
|
||||
<div class="workflow-step">
|
||||
<span class="step-num">Step 2/5</span>
|
||||
<span class="step-name">环境搭建</span>
|
||||
<span class="step-name">{{ $tm('main.stepNames')[1] }}</span>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<span class="status-indicator" :class="statusClass">
|
||||
|
|
@ -70,7 +72,10 @@ import GraphPanel from '../components/GraphPanel.vue'
|
|||
import Step2EnvSetup from '../components/Step2EnvSetup.vue'
|
||||
import { getProject, getGraphData } from '../api/graph'
|
||||
import { getSimulation, stopSimulation, getEnvStatus, closeSimulationEnv } from '../api/simulation'
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
|
|
@ -146,13 +151,13 @@ const handleGoBack = () => {
|
|||
}
|
||||
|
||||
const handleNextStep = (params = {}) => {
|
||||
addLog('进入 Step 3: 开始模拟')
|
||||
|
||||
addLog(t('log.enterStep3'))
|
||||
|
||||
// 记录模拟轮数配置
|
||||
if (params.maxRounds) {
|
||||
addLog(`自定义模拟轮数: ${params.maxRounds} 轮`)
|
||||
addLog(t('log.customRoundsConfig', { rounds: params.maxRounds }))
|
||||
} else {
|
||||
addLog('使用自动配置的模拟轮数')
|
||||
addLog(t('log.useAutoRounds'))
|
||||
}
|
||||
|
||||
// 构建路由参数
|
||||
|
|
@ -184,7 +189,7 @@ const checkAndStopRunningSimulation = async () => {
|
|||
const envStatusRes = await getEnvStatus({ simulation_id: currentSimulationId.value })
|
||||
|
||||
if (envStatusRes.success && envStatusRes.data?.env_alive) {
|
||||
addLog('检测到模拟环境正在运行,正在关闭...')
|
||||
addLog(t('log.detectedSimEnvRunning'))
|
||||
|
||||
// 尝试优雅关闭模拟环境
|
||||
try {
|
||||
|
|
@ -194,14 +199,14 @@ const checkAndStopRunningSimulation = async () => {
|
|||
})
|
||||
|
||||
if (closeRes.success) {
|
||||
addLog('✓ 模拟环境已关闭')
|
||||
addLog(t('log.simEnvClosed'))
|
||||
} else {
|
||||
addLog(`关闭模拟环境失败: ${closeRes.error || '未知错误'}`)
|
||||
addLog(t('log.closeSimEnvFailedWithError', { error: closeRes.error || t('common.unknownError') }))
|
||||
// 如果优雅关闭失败,尝试强制停止
|
||||
await forceStopSimulation()
|
||||
}
|
||||
} catch (closeErr) {
|
||||
addLog(`关闭模拟环境异常: ${closeErr.message}`)
|
||||
addLog(t('log.closeSimEnvException', { error: closeErr.message }))
|
||||
// 如果优雅关闭异常,尝试强制停止
|
||||
await forceStopSimulation()
|
||||
}
|
||||
|
|
@ -209,7 +214,7 @@ const checkAndStopRunningSimulation = async () => {
|
|||
// 环境未运行,但可能进程还在,检查模拟状态
|
||||
const simRes = await getSimulation(currentSimulationId.value)
|
||||
if (simRes.success && simRes.data?.status === 'running') {
|
||||
addLog('检测到模拟状态为运行中,正在停止...')
|
||||
addLog(t('log.detectedSimRunning'))
|
||||
await forceStopSimulation()
|
||||
}
|
||||
}
|
||||
|
|
@ -226,30 +231,30 @@ const forceStopSimulation = async () => {
|
|||
try {
|
||||
const stopRes = await stopSimulation({ simulation_id: currentSimulationId.value })
|
||||
if (stopRes.success) {
|
||||
addLog('✓ 模拟已强制停止')
|
||||
addLog(t('log.simForceStopSuccess'))
|
||||
} else {
|
||||
addLog(`强制停止模拟失败: ${stopRes.error || '未知错误'}`)
|
||||
addLog(t('log.forceStopSimFailed', { error: stopRes.error || t('common.unknownError') }))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`强制停止模拟异常: ${err.message}`)
|
||||
addLog(t('log.forceStopSimException', { error: err.message }))
|
||||
}
|
||||
}
|
||||
|
||||
const loadSimulationData = async () => {
|
||||
try {
|
||||
addLog(`加载模拟数据: ${currentSimulationId.value}`)
|
||||
|
||||
addLog(t('log.loadingSimData', { id: currentSimulationId.value }))
|
||||
|
||||
// 获取 simulation 信息
|
||||
const simRes = await getSimulation(currentSimulationId.value)
|
||||
if (simRes.success && simRes.data) {
|
||||
const simData = simRes.data
|
||||
|
||||
|
||||
// 获取 project 信息
|
||||
if (simData.project_id) {
|
||||
const projRes = await getProject(simData.project_id)
|
||||
if (projRes.success && projRes.data) {
|
||||
projectData.value = projRes.data
|
||||
addLog(`项目加载成功: ${projRes.data.project_id}`)
|
||||
addLog(t('log.projectLoadSuccess', { id: projRes.data.project_id }))
|
||||
|
||||
// 获取 graph 数据
|
||||
if (projRes.data.graph_id) {
|
||||
|
|
@ -258,10 +263,10 @@ const loadSimulationData = async () => {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
addLog(`加载模拟数据失败: ${simRes.error || '未知错误'}`)
|
||||
addLog(t('log.loadSimDataFailed', { error: simRes.error || t('common.unknownError') }))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`加载异常: ${err.message}`)
|
||||
addLog(t('log.loadException', { error: err.message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -271,10 +276,10 @@ const loadGraph = async (graphId) => {
|
|||
const res = await getGraphData(graphId)
|
||||
if (res.success) {
|
||||
graphData.value = res.data
|
||||
addLog('图谱数据加载成功')
|
||||
addLog(t('log.graphDataLoadSuccess'))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`图谱加载失败: ${err.message}`)
|
||||
addLog(t('log.graphLoadFailed', { error: err.message }))
|
||||
} finally {
|
||||
graphLoading.value = false
|
||||
}
|
||||
|
|
@ -287,7 +292,7 @@ const refreshGraph = () => {
|
|||
}
|
||||
|
||||
onMounted(async () => {
|
||||
addLog('SimulationView 初始化')
|
||||
addLog(t('log.simViewInit'))
|
||||
|
||||
// 检查并关闭正在运行的模拟(用户从 Step 3 返回时)
|
||||
await checkAndStopRunningSimulation()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
'@locales': path.resolve(__dirname, '../locales')
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true,
|
||||
|
|
|
|||
665
locales/en.json
Normal file
665
locales/en.json
Normal file
|
|
@ -0,0 +1,665 @@
|
|||
{
|
||||
"common": {
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"loading": "Loading...",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"completed": "Completed",
|
||||
"processing": "Generating",
|
||||
"pending": "Pending",
|
||||
"ready": "Ready",
|
||||
"running": "Running",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown",
|
||||
"unknownError": "Unknown error",
|
||||
"none": "None",
|
||||
"close": "Close",
|
||||
"back": "Back",
|
||||
"next": "Next",
|
||||
"retry": "Retry",
|
||||
"noData": "No data available",
|
||||
"hours": "hours",
|
||||
"minutes": "minutes",
|
||||
"rounds": "rounds",
|
||||
"items": "items",
|
||||
"files": "files"
|
||||
},
|
||||
"meta": {
|
||||
"title": "MiroFish - Predict Everything",
|
||||
"description": "MiroFish - Social Media Opinion Simulation System"
|
||||
},
|
||||
"nav": {
|
||||
"visitGithub": "Visit our Github page"
|
||||
},
|
||||
"home": {
|
||||
"tagline": "Concise & Universal Swarm Intelligence Engine",
|
||||
"version": "/ v0.1-Preview",
|
||||
"heroTitle1": "Upload Reports,",
|
||||
"heroTitle2": "Predict the Future",
|
||||
"heroDesc": "From a single document, {brand} extracts reality seeds to auto-generate a parallel world with up to {agentScale}. Inject variables from a god's-eye view to find the {optimalSolution} in complex group dynamics.",
|
||||
"heroDescBrand": "MiroFish",
|
||||
"heroDescAgentScale": "million-scale Agents",
|
||||
"heroDescOptimalSolution": "\"local optimum\"",
|
||||
"slogan": "Let Agents rehearse the future, let decisions prevail",
|
||||
"systemStatus": "System Status",
|
||||
"systemReady": "Ready",
|
||||
"systemReadyDesc": "Prediction engine on standby. Upload unstructured data to initialize a simulation sequence.",
|
||||
"metricLowCost": "Low Cost",
|
||||
"metricLowCostDesc": "Avg. $5/sim",
|
||||
"metricHighAvail": "Scalable",
|
||||
"metricHighAvailDesc": "Millions of Agents",
|
||||
"workflowSequence": "Workflow",
|
||||
"step01Title": "Graph Build",
|
||||
"step01Desc": "Seed extraction & memory injection & GraphRAG construction",
|
||||
"step02Title": "Env Setup",
|
||||
"step02Desc": "Entity extraction & persona generation & Agent config injection",
|
||||
"step03Title": "Simulation",
|
||||
"step03Desc": "Dual-platform parallel sim & auto-parse requirements & temporal memory",
|
||||
"step04Title": "Report",
|
||||
"step04Desc": "ReportAgent interacts with the post-simulation environment via rich tools",
|
||||
"step05Title": "Interaction",
|
||||
"step05Desc": "Chat with any simulated individual & converse with ReportAgent",
|
||||
"realitySeed": "01 / Reality Seed",
|
||||
"supportedFormats": "Formats: PDF, MD, TXT",
|
||||
"dragToUpload": "Drag files to upload",
|
||||
"orBrowse": "or click to browse files",
|
||||
"inputParams": "Input Parameters",
|
||||
"simulationPrompt": ">_ 02 / Simulation Prompt",
|
||||
"promptPlaceholder": "// Describe your simulation or prediction requirement in natural language",
|
||||
"engineBadge": "Engine: MiroFish-V1.0",
|
||||
"startEngine": "Start Engine",
|
||||
"initializing": "Initializing..."
|
||||
},
|
||||
"main": {
|
||||
"layoutGraph": "Graph",
|
||||
"layoutSplit": "Split",
|
||||
"layoutWorkbench": "Workbench",
|
||||
"stepNames": ["Graph Build", "Env Setup", "Run Simulation", "Report Generation", "Deep Interaction"]
|
||||
},
|
||||
"step1": {
|
||||
"ontologyGeneration": "Ontology Generation",
|
||||
"ontologyCompleted": "Completed",
|
||||
"ontologyGenerating": "Generating",
|
||||
"ontologyPending": "Pending",
|
||||
"ontologyDesc": "LLM analyzes document content and simulation requirements, extracts reality seeds, and auto-generates a suitable ontology structure",
|
||||
"analyzingDocs": "Analyzing documents...",
|
||||
"graphRagBuild": "GraphRAG Build",
|
||||
"graphRagDesc": "Based on the generated ontology, documents are auto-chunked and sent to Zep to build a knowledge graph, extracting entities and relations, forming temporal memory and community summaries",
|
||||
"entityNodes": "Entity Nodes",
|
||||
"relationEdges": "Relation Edges",
|
||||
"schemaTypes": "Schema Types",
|
||||
"buildComplete": "Build Complete",
|
||||
"buildCompleteDesc": "Graph build is complete. Proceed to the next step for simulation environment setup.",
|
||||
"inProgress": "In Progress",
|
||||
"creating": "Creating...",
|
||||
"enterEnvSetup": "Enter Environment Setup",
|
||||
"createSimulationFailed": "Failed to create simulation: {error}",
|
||||
"createSimulationException": "Simulation creation error: {error}"
|
||||
},
|
||||
"step2": {
|
||||
"simInstanceInit": "Simulation Instance Initialization",
|
||||
"simInstanceDesc": "Create a new simulation instance and pull world parameter templates",
|
||||
"asyncTaskDone": "Async task completed",
|
||||
"generateAgentPersona": "Generate Agent Personas",
|
||||
"generateAgentPersonaDesc": "Combine context to auto-extract entities and relations from the knowledge graph, initialize simulated individuals, and assign unique behaviors and memories based on reality seeds",
|
||||
"currentAgentCount": "Current Agents",
|
||||
"expectedAgentTotal": "Expected Total Agents",
|
||||
"relatedTopicsCount": "Reality Seed Related Topics",
|
||||
"generatedAgentPersonas": "Generated Agent Personas",
|
||||
"unknownProfession": "Unknown profession",
|
||||
"noBio": "No bio available",
|
||||
"dualPlatformConfig": "Generate Dual-Platform Config",
|
||||
"dualPlatformConfigDesc": "LLM intelligently sets world time flow, recommendation algorithms, each individual's active hours, posting frequency, event triggers, and more based on requirements and reality seeds",
|
||||
"simulationDuration": "Simulation Duration",
|
||||
"roundDuration": "Round Duration",
|
||||
"totalRounds": "Total Rounds",
|
||||
"activePerHour": "Active Per Hour",
|
||||
"peakHours": "Peak Hours",
|
||||
"workHours": "Work Hours",
|
||||
"morningHours": "Morning Hours",
|
||||
"offPeakHours": "Off-Peak Hours",
|
||||
"agentConfig": "Agent Config",
|
||||
"activeTimePeriod": "Active Hours",
|
||||
"postsPerHour": "Posts/hr",
|
||||
"commentsPerHour": "Comments/hr",
|
||||
"responseDelay": "Response Delay",
|
||||
"activityLevel": "Activity Level",
|
||||
"sentimentBias": "Sentiment Bias",
|
||||
"influenceWeight": "Influence",
|
||||
"recommendAlgoConfig": "Recommendation Algorithm Config",
|
||||
"platform1Name": "Platform 1: Plaza / Feed",
|
||||
"platform2Name": "Platform 2: Topic / Community",
|
||||
"recencyWeight": "Recency Weight",
|
||||
"popularityWeight": "Popularity Weight",
|
||||
"relevanceWeight": "Relevance Weight",
|
||||
"viralThreshold": "Viral Threshold",
|
||||
"echoChamberStrength": "Echo Chamber Strength",
|
||||
"llmConfigReasoning": "LLM Config Reasoning",
|
||||
"initialActivation": "Initial Activation Orchestration",
|
||||
"initialActivationDesc": "Auto-generate initial activation events and hot topics based on narrative direction to guide the simulation world's initial state",
|
||||
"orchestrating": "Orchestrating",
|
||||
"narrativeDirection": "Narrative Direction",
|
||||
"initialHotTopics": "Initial Hot Topics",
|
||||
"initialActivationSeq": "Initial Activation Sequence ({count})",
|
||||
"setupComplete": "Setup Complete",
|
||||
"setupCompleteDesc": "Simulation environment is ready. You can now start the simulation.",
|
||||
"roundsConfig": "Simulation Rounds Configuration",
|
||||
"roundsConfigDesc": "MiroFish auto-plans to simulate {hours} real-world hours, each round representing {minutesPerRound} minutes of elapsed time",
|
||||
"customToggle": "Custom",
|
||||
"roundsUnit": "rounds",
|
||||
"estimatedDuration": "For 100 Agents: est. ~{minutes} minutes",
|
||||
"estimatedDurationFull": "For 100 Agents: est. {minutes} minutes",
|
||||
"recommendedRounds": "{rounds} (recommended)",
|
||||
"customTip": "For first-time runs, we strongly recommend switching to 'Custom Mode' to reduce rounds for a quick preview and lower error risk",
|
||||
"backToGraphBuild": "Back to Graph Build",
|
||||
"startDualWorldSim": "Start Dual-World Parallel Simulation",
|
||||
"profileModalAge": "Apparent Age",
|
||||
"profileModalGender": "Apparent Gender",
|
||||
"profileModalCountry": "Country/Region",
|
||||
"profileModalMbti": "Apparent MBTI",
|
||||
"profileModalBio": "Persona Bio",
|
||||
"profileModalTopics": "Reality Seed Related Topics",
|
||||
"profileModalPersona": "Detailed Persona Background",
|
||||
"personaDimExperience": "Full Event Experience",
|
||||
"personaDimExperienceDesc": "Complete behavioral trajectory in this event",
|
||||
"personaDimBehavior": "Behavioral Profile",
|
||||
"personaDimBehaviorDesc": "Experience summary and behavioral preferences",
|
||||
"personaDimMemory": "Unique Memory Imprint",
|
||||
"personaDimMemoryDesc": "Memories formed from reality seeds",
|
||||
"personaDimSocial": "Social Network",
|
||||
"personaDimSocialDesc": "Individual connections and interaction graph",
|
||||
"genderMale": "Male",
|
||||
"genderFemale": "Female",
|
||||
"genderOther": "Other",
|
||||
"yearsOld": "years old",
|
||||
"initializing": "Initializing",
|
||||
"generating": "Generating"
|
||||
},
|
||||
"step3": {
|
||||
"startGenerateReport": "Generate Report",
|
||||
"generatingReport": "Starting...",
|
||||
"waitingForActions": "Waiting for agent actions...",
|
||||
"errorMissingSimId": "Error: missing simulationId",
|
||||
"startingDualSim": "Starting dual-platform parallel simulation...",
|
||||
"graphMemoryUpdateEnabled": "Dynamic graph memory update enabled",
|
||||
"setMaxRounds": "Max simulation rounds set to: {rounds}",
|
||||
"oldSimCleared": "Old simulation logs cleared, restarting simulation",
|
||||
"engineStarted": "Simulation engine started successfully",
|
||||
"startFailed": "Start failed: {error}",
|
||||
"startException": "Start error: {error}",
|
||||
"stoppingSim": "Stopping simulation...",
|
||||
"simStopped": "Simulation stopped",
|
||||
"stopFailed": "Stop failed: {error}",
|
||||
"stopException": "Stop error: {error}",
|
||||
"allPlatformsCompleted": "All platform simulations have ended",
|
||||
"simCompleted": "Simulation completed",
|
||||
"graphRealtimeRefresh": "Graph real-time refresh enabled (30s)",
|
||||
"graphRefreshStopped": "Graph real-time refresh stopped",
|
||||
"preparingGoBack": "Preparing to return to Step 2, closing simulation...",
|
||||
"closingSimEnv": "Closing simulation environment...",
|
||||
"simEnvClosed": "Simulation environment closed",
|
||||
"closeFailed": "Failed to close simulation environment, attempting force stop...",
|
||||
"stoppingProcess": "Stopping simulation process...",
|
||||
"checkStatusFailed": "Failed to check simulation status: {error}",
|
||||
"forceStopSuccess": "Simulation force stopped",
|
||||
"forceStopFailed": "Force stop failed: {error}",
|
||||
"startGenerateReportBtn": "Generate Report",
|
||||
"generatingReportBtn": "Starting..."
|
||||
},
|
||||
"step4": {
|
||||
"generatingSection": "Generating {title}...",
|
||||
"goToInteraction": "Enter Deep Interaction",
|
||||
"waitingForReportAgent": "Waiting for Report Agent...",
|
||||
"collapse": "Collapse ▲",
|
||||
"expandAll": "Show all {count} ▼",
|
||||
"expandAllEntities": "Show all {count} ▼",
|
||||
"scenarioLabel": "Scenario: ",
|
||||
"tabKeyFacts": "Key Facts ({count})",
|
||||
"tabCoreEntities": "Core Entities ({count})",
|
||||
"tabRelationChains": "Relation Chains ({count})",
|
||||
"tabSubQueries": "Sub-queries ({count})",
|
||||
"panelKeyFacts": "Latest key facts from temporal memory",
|
||||
"totalCount": "{count} total",
|
||||
"totalEntityCount": "{count} total",
|
||||
"panelCoreEntities": "Core Entities",
|
||||
"factCount": "{count} facts",
|
||||
"panelRelationChains": "Relation Chains",
|
||||
"panelSubQueries": "Drift query analysis sub-questions",
|
||||
"emptyKeyFacts": "No key facts available",
|
||||
"emptyCoreEntities": "No core entities available",
|
||||
"emptyRelationChains": "No relation chains available",
|
||||
"tabActiveFacts": "Active Facts ({count})",
|
||||
"tabHistoricalFacts": "Historical Facts ({count})",
|
||||
"tabEntities": "Entities ({count})",
|
||||
"panelActiveFacts": "Active Facts",
|
||||
"emptyActiveFacts": "No active facts available",
|
||||
"panelHistoricalFacts": "Historical Facts",
|
||||
"emptyHistoricalFacts": "No historical facts available",
|
||||
"panelEntities": "Entities",
|
||||
"emptyEntities": "No entities available",
|
||||
"searchLabel": "Search: ",
|
||||
"tabFacts": "Facts ({count})",
|
||||
"tabEdges": "Edges ({count})",
|
||||
"tabNodes": "Nodes ({count})",
|
||||
"panelSearchResults": "Search Results",
|
||||
"emptySearchResults": "No results found",
|
||||
"panelRelatedEdges": "Related Edges",
|
||||
"panelRelatedNodes": "Related Nodes",
|
||||
"world1": "World 1",
|
||||
"world2": "World 2"
|
||||
},
|
||||
"step5": {
|
||||
"interactiveTools": "Interactive Tools",
|
||||
"agentsAvailable": "{count} agents available",
|
||||
"chatWithReportAgent": "Chat with Report Agent",
|
||||
"chatWithAgent": "Chat with any individual in the world",
|
||||
"selectChatTarget": "Select chat target",
|
||||
"sendSurvey": "Send survey to the world",
|
||||
"reportAgentChat": "Report Agent - Chat",
|
||||
"reportAgentDesc": "A conversational version of the report generation agent with access to 4 professional tools and MiroFish's complete memory",
|
||||
"toolInsightForge": "InsightForge Deep Attribution",
|
||||
"toolInsightForgeDesc": "Aligns real-world seed data with simulation state, combining Global/Local Memory for cross-temporal deep attribution analysis",
|
||||
"toolPanoramaSearch": "PanoramaSearch Full Tracking",
|
||||
"toolPanoramaSearchDesc": "Graph-based BFS algorithm that reconstructs event propagation paths, capturing the full topology of information flow",
|
||||
"toolQuickSearch": "QuickSearch Fast Retrieval",
|
||||
"toolQuickSearchDesc": "GraphRAG-based instant query interface with optimized indexing for fast extraction of node attributes and discrete facts",
|
||||
"toolInterviewSubAgent": "InterviewSubAgent Virtual Interview",
|
||||
"toolInterviewSubAgentDesc": "Autonomous interviews that conduct parallel multi-round dialogues with simulated individuals, collecting unstructured opinion data and psychological states",
|
||||
"profileBio": "Bio",
|
||||
"chatEmptyReportAgent": "Chat with Report Agent to explore report content in depth",
|
||||
"chatEmptyAgent": "Chat with simulated individuals to understand their perspectives",
|
||||
"chatInputPlaceholder": "Type your question...",
|
||||
"selectSurveyTarget": "Select survey targets",
|
||||
"selectedCount": "Selected {selected} / {total}",
|
||||
"surveyQuestions": "Survey Questions",
|
||||
"surveyInputPlaceholder": "Enter the question you want to ask all selected targets...",
|
||||
"submitSurvey": "Send Survey",
|
||||
"surveyResults": "Survey Results",
|
||||
"surveyResultsCount": "{count} responses",
|
||||
"selectAll": "Select All",
|
||||
"clearSelection": "Clear",
|
||||
"errorOccurred": "Sorry, an error occurred: {error}",
|
||||
"noResponse": "No response",
|
||||
"requestFailed": "Request failed",
|
||||
"selectAgentFirst": "Please select a simulated individual first"
|
||||
},
|
||||
"graph": {
|
||||
"panelTitle": "Graph Relationship Visualization",
|
||||
"refreshGraph": "Refresh Graph",
|
||||
"graphMemoryRealtime": "GraphRAG short/long-term memory updating in real-time",
|
||||
"realtimeUpdating": "Updating in real-time...",
|
||||
"pendingContentHint": "Some content is still processing. Consider refreshing the graph manually later.",
|
||||
"nodeDetails": "Node Details",
|
||||
"relationship": "Relationship",
|
||||
"graphDataLoading": "Loading graph data...",
|
||||
"waitingOntology": "Waiting for ontology generation...",
|
||||
"toggleMaximize": "Maximize/Restore",
|
||||
"closeHint": "Close hint"
|
||||
},
|
||||
"history": {
|
||||
"title": "Simulation History",
|
||||
"graphBuild": "Graph Build",
|
||||
"envSetup": "Env Setup",
|
||||
"analysisReport": "Analysis Report",
|
||||
"moreFiles": "+{count} files",
|
||||
"noFiles": "No files",
|
||||
"loadingText": "Loading...",
|
||||
"simRequirement": "Simulation Requirement",
|
||||
"relatedFiles": "Related Files",
|
||||
"noRelatedFiles": "No related files",
|
||||
"replayTitle": "Simulation Replay",
|
||||
"step1Button": "Graph Build",
|
||||
"step2Button": "Env Setup",
|
||||
"step4Button": "Analysis Report",
|
||||
"replayHint": "Step 3 'Run Simulation' and Step 5 'Deep Interaction' must be started during runtime and do not support history replay",
|
||||
"notStarted": "Not started",
|
||||
"roundsProgress": "{current}/{total} rounds",
|
||||
"untitledSimulation": "Untitled simulation",
|
||||
"unknownFile": "Unknown file"
|
||||
},
|
||||
"api": {
|
||||
"projectNotFound": "Project not found: {id}",
|
||||
"projectDeleteFailed": "Project not found or deletion failed: {id}",
|
||||
"projectDeleted": "Project deleted: {id}",
|
||||
"projectReset": "Project reset: {id}",
|
||||
"requireSimulationRequirement": "Please provide a simulation requirement (simulation_requirement)",
|
||||
"requireFileUpload": "Please upload at least one document file",
|
||||
"noDocProcessed": "No documents were processed successfully. Please check file formats.",
|
||||
"requireProjectId": "Please provide project_id",
|
||||
"configError": "Configuration error: {details}",
|
||||
"zepApiKeyMissing": "ZEP_API_KEY not configured",
|
||||
"ontologyNotGenerated": "Ontology not yet generated. Please call /ontology/generate first.",
|
||||
"graphBuilding": "Graph build in progress. Do not resubmit. To force rebuild, add force: true.",
|
||||
"textNotFound": "Extracted text content not found",
|
||||
"ontologyNotFound": "Ontology definition not found",
|
||||
"graphBuildStarted": "Graph build task started. Query progress via /task/{taskId}.",
|
||||
"graphBuildComplete": "Graph build complete",
|
||||
"buildFailed": "Build failed: {error}",
|
||||
"taskNotFound": "Task not found: {id}",
|
||||
"graphDeleted": "Graph deleted: {id}",
|
||||
"entityNotFound": "Entity not found: {id}",
|
||||
"graphNotBuilt": "Graph not yet built. Please call /api/graph/build first.",
|
||||
"requireSimulationId": "Please provide simulation_id",
|
||||
"simulationNotFound": "Simulation not found: {id}",
|
||||
"projectMissingRequirement": "Project missing simulation requirement (simulation_requirement)",
|
||||
"prepareStarted": "Preparation task started. Query progress via /api/simulation/prepare/status.",
|
||||
"alreadyPrepared": "Preparation already complete. No need to regenerate.",
|
||||
"notStartedPrepare": "Preparation not started. Please call /api/simulation/prepare.",
|
||||
"taskCompletedPrepared": "Task completed (preparation already exists)",
|
||||
"requireTaskOrSimId": "Please provide task_id or simulation_id",
|
||||
"configNotFound": "Simulation config not found. Please call /prepare first.",
|
||||
"configFileNotFound": "Config file not found. Please call /prepare first.",
|
||||
"unknownScript": "Unknown script: {name}. Available: {allowed}",
|
||||
"scriptFileNotFound": "Script file not found: {name}",
|
||||
"requireGraphId": "Please provide graph_id",
|
||||
"noMatchingEntities": "No matching entities found",
|
||||
"maxRoundsPositive": "max_rounds must be a positive integer",
|
||||
"maxRoundsInvalid": "max_rounds must be a valid integer",
|
||||
"invalidPlatform": "Invalid platform type: {platform}. Options: twitter/reddit/parallel",
|
||||
"simRunningForceHint": "Simulation is running. Stop it first via /stop, or use force=true to restart.",
|
||||
"simNotReady": "Simulation not ready. Current status: {status}. Please call /prepare first.",
|
||||
"graphIdRequiredForMemory": "Graph memory update requires a valid graph_id. Ensure the graph is built.",
|
||||
"dbNotExist": "Database does not exist. The simulation may not have run yet.",
|
||||
"requireMessage": "Please provide a message",
|
||||
"missingGraphId": "Missing graph ID",
|
||||
"missingGraphIdEnsure": "Missing graph ID. Please ensure the graph has been built.",
|
||||
"missingSimRequirement": "Missing simulation requirement description",
|
||||
"reportAlreadyExists": "Report already exists",
|
||||
"reportGenerateStarted": "Report generation task started. Query progress via /api/report/generate/status.",
|
||||
"reportGenerated": "Report generated",
|
||||
"reportNotFound": "Report not found: {id}",
|
||||
"noReportForSim": "No report found for this simulation: {id}",
|
||||
"reportDeleted": "Report deleted: {id}",
|
||||
"reportGenerateFailed": "Report generation failed",
|
||||
"sectionNotFound": "Section not found: section_{index}.md",
|
||||
"reportProgressNotAvail": "Report not found or progress unavailable: {id}",
|
||||
"requireAgentId": "Please provide agent_id",
|
||||
"requirePrompt": "Please provide a prompt (interview question)",
|
||||
"invalidInterviewPlatform": "Platform must be either 'twitter' or 'reddit'",
|
||||
"envNotRunning": "Simulation environment not running or closed. Ensure simulation is complete and in command-wait mode.",
|
||||
"interviewTimeout": "Interview response timed out: {error}",
|
||||
"requireInterviews": "Please provide interviews (interview list)",
|
||||
"interviewListMissingAgentId": "Interview list item {index} missing agent_id",
|
||||
"interviewListMissingPrompt": "Interview list item {index} missing prompt",
|
||||
"interviewListInvalidPlatform": "Interview list item {index} platform must be 'twitter' or 'reddit'",
|
||||
"batchInterviewTimeout": "Batch interview response timed out: {error}",
|
||||
"globalInterviewTimeout": "Global interview response timed out: {error}",
|
||||
"envRunning": "Environment is running and ready for Interview commands",
|
||||
"envNotRunningShort": "Environment not running or closed",
|
||||
"requireGraphIdAndQuery": "Please provide graph_id and query",
|
||||
"initReportAgent": "Initializing Report Agent..."
|
||||
},
|
||||
"progress": {
|
||||
"initGraphService": "Initializing graph build service...",
|
||||
"textChunking": "Chunking text...",
|
||||
"creatingZepGraph": "Creating Zep graph...",
|
||||
"settingOntology": "Setting ontology definition...",
|
||||
"addingChunks": "Adding {count} text chunks...",
|
||||
"waitingZepProcess": "Waiting for Zep to process data...",
|
||||
"fetchingGraphData": "Fetching graph data...",
|
||||
"graphBuildComplete": "Graph build complete",
|
||||
"buildFailed": "Build failed: {error}",
|
||||
"startBuildingGraph": "Starting graph build...",
|
||||
"graphCreated": "Graph created: {graphId}",
|
||||
"ontologySet": "Ontology set",
|
||||
"textSplit": "Text split into {count} chunks",
|
||||
"fetchingGraphInfo": "Fetching graph info...",
|
||||
"sendingBatch": "Sending batch {current}/{total} ({chunks} chunks)...",
|
||||
"batchFailed": "Batch {batch} failed: {error}",
|
||||
"noEpisodesWait": "No episodes to wait for",
|
||||
"waitingEpisodes": "Waiting for {count} text chunks to process...",
|
||||
"episodesTimeout": "Some chunks timed out, {completed}/{total} completed",
|
||||
"zepProcessing": "Zep processing... {completed}/{total} done, {pending} pending ({elapsed}s)",
|
||||
"processingComplete": "Processing complete: {completed}/{total}",
|
||||
"taskComplete": "Task complete",
|
||||
"taskFailed": "Task failed",
|
||||
"startPreparingEnv": "Preparing simulation environment...",
|
||||
"connectingZepGraph": "Connecting to Zep graph...",
|
||||
"readingNodeData": "Reading node data...",
|
||||
"readingComplete": "Done, {count} entities found",
|
||||
"startGenerating": "Starting generation...",
|
||||
"analyzingRequirements": "Analyzing simulation requirements...",
|
||||
"generatingOutline": "Generating report outline...",
|
||||
"parsingOutline": "Parsing outline structure...",
|
||||
"outlinePlanComplete": "Outline planning complete",
|
||||
"deepSearchAndWrite": "Deep search & writing ({current}/{max})",
|
||||
"initReport": "Initializing report...",
|
||||
"startPlanningOutline": "Planning report outline...",
|
||||
"outlineDone": "Outline complete, {count} sections",
|
||||
"generatingSection": "Generating section: {title} ({current}/{total})",
|
||||
"sectionDone": "Section {title} complete",
|
||||
"assemblingReport": "Assembling full report...",
|
||||
"reportComplete": "Report generation complete",
|
||||
"reportFailed": "Report generation failed: {error}",
|
||||
"savingProfiles": "Saving profile files...",
|
||||
"profilesComplete": "Done, {count} profiles generated",
|
||||
"callingLLMConfig": "Calling LLM to generate config...",
|
||||
"savingConfigFiles": "Saving config files...",
|
||||
"configComplete": "Config generation complete",
|
||||
"generatingTimeConfig": "Generating time config...",
|
||||
"generatingEventConfig": "Generating event config and hot topics...",
|
||||
"generatingAgentConfig": "Generating agent config ({start}-{end}/{total})...",
|
||||
"generatingPlatformConfig": "Generating platform config...",
|
||||
"zepSearchQuery": "All information, activities, events, relationships and background about {name}",
|
||||
"timeConfigLabel": "Time Config",
|
||||
"eventConfigLabel": "Event Config",
|
||||
"agentConfigResult": "Agent Config: {count} generated",
|
||||
"postAssignResult": "Post Assignment: {count} posts assigned",
|
||||
"profileGenerated": "[Generated] {name} ({type})",
|
||||
"readingGraphEntities": "Reading Graph Entities",
|
||||
"generatingProfiles": "Generating Agent Profiles",
|
||||
"generatingSimConfig": "Generating Simulation Config",
|
||||
"preparingScripts": "Preparing Scripts"
|
||||
},
|
||||
"log": {
|
||||
"preparingGoBack": "Preparing to return to Step 2, closing simulation...",
|
||||
"closingSimEnv": "Closing simulation environment...",
|
||||
"simEnvClosed": "✓ Simulation environment closed",
|
||||
"closeSimEnvFailed": "Failed to close simulation environment, attempting force stop...",
|
||||
"simForceStopSuccess": "✓ Simulation force stopped",
|
||||
"forceStopFailed": "Force stop failed: {error}",
|
||||
"stoppingSimProcess": "Stopping simulation process...",
|
||||
"simStopped": "✓ Simulation stopped",
|
||||
"stopSimFailed": "Failed to stop simulation: {error}",
|
||||
"checkStatusFailed": "Failed to check simulation status: {error}",
|
||||
"enterStep4": "Entering Step 4: Report Generation",
|
||||
"loadingSimData": "Loading simulation data: {id}",
|
||||
"timeConfig": "Time config: {minutes} minutes per round",
|
||||
"timeConfigFetchFailed": "Failed to fetch time config, using default: {minutes} min/round",
|
||||
"projectLoadSuccess": "Project loaded: {id}",
|
||||
"loadSimDataFailed": "Failed to load simulation data: {error}",
|
||||
"loadException": "Load error: {error}",
|
||||
"graphDataLoadSuccess": "Graph data loaded successfully",
|
||||
"graphLoadFailed": "Graph load failed: {error}",
|
||||
"graphRealtimeRefreshStart": "Graph real-time refresh enabled (30s)",
|
||||
"graphRealtimeRefreshStop": "Graph real-time refresh stopped",
|
||||
"simRunViewInit": "SimulationRunView initialized",
|
||||
"customRounds": "Custom simulation rounds: {rounds}",
|
||||
"enterStep3": "Entering Step 3: Run Simulation",
|
||||
"customRoundsConfig": "Custom simulation rounds: {rounds} rounds",
|
||||
"useAutoRounds": "Using auto-configured simulation rounds",
|
||||
"detectedSimEnvRunning": "Detected running simulation environment, closing...",
|
||||
"closeSimEnvFailedWithError": "Failed to close simulation environment: {error}",
|
||||
"closeSimEnvException": "Simulation environment close error: {error}",
|
||||
"detectedSimRunning": "Detected simulation is running, stopping...",
|
||||
"forceStopSimFailed": "Force stop simulation failed: {error}",
|
||||
"forceStopSimException": "Force stop simulation error: {error}",
|
||||
"simViewInit": "SimulationView initialized",
|
||||
"errorMissingSimId": "Error: missing simulationId",
|
||||
"simInstanceCreated": "Simulation instance created: {id}",
|
||||
"preparingSimEnv": "Preparing simulation environment...",
|
||||
"detectedExistingPrep": "Detected existing preparation, using it directly",
|
||||
"prepareTaskStarted": "Preparation task started",
|
||||
"prepareTaskId": " └─ Task ID: {taskId}",
|
||||
"zepEntitiesFound": "Found {count} entities from Zep graph",
|
||||
"entityTypes": " └─ Entity types: {types}",
|
||||
"startPollingProgress": "Polling preparation progress...",
|
||||
"prepareFailed": "Preparation failed: {error}",
|
||||
"prepareException": "Preparation error: {error}",
|
||||
"prepareComplete": "✓ Preparation complete",
|
||||
"prepareFailedWithError": "✗ Preparation failed: {error}",
|
||||
"startGeneratingConfig": "Generating dual-platform simulation config...",
|
||||
"generatingAgentProfileConfig": "Generating agent persona config...",
|
||||
"generatingLLMConfig": "Calling LLM to generate simulation config parameters...",
|
||||
"configComplete": "✓ Simulation config generated",
|
||||
"configSummaryAgents": " ├─ Agents: {count}",
|
||||
"configSummaryHours": " ├─ Duration: {hours} hours",
|
||||
"configSummaryPosts": " ├─ Initial posts: {count}",
|
||||
"configSummaryTopics": " ├─ Hot topics: {count}",
|
||||
"configSummaryPlatforms": " └─ Platforms: Twitter {twitter}, Reddit {reddit}",
|
||||
"timeConfigDetail": "Time config: {minutes} min/round, {rounds} rounds total",
|
||||
"narrativeDirection": "Narrative direction: {direction}",
|
||||
"envSetupComplete": "✓ Environment setup complete, ready to simulate",
|
||||
"startSimCustomRounds": "Starting simulation, custom rounds: {rounds}",
|
||||
"startSimAutoRounds": "Starting simulation, auto-configured rounds: {rounds}",
|
||||
"startGeneratingAgentProfiles": "Generating agent personas...",
|
||||
"agentProfile": "→ Agent persona {current}/{total}: {name} ({profession})",
|
||||
"allProfilesComplete": "✓ All {count} agent personas generated",
|
||||
"loadingExistingConfig": "Loading existing config data...",
|
||||
"loadedAgentProfiles": "Loaded {count} agent personas",
|
||||
"configLoadSuccess": "✓ Simulation config loaded",
|
||||
"configSummaryPostsAlt": " └─ Initial posts: {count}",
|
||||
"configGenerating": "Config generating, polling...",
|
||||
"loadConfigFailed": "Failed to load config: {error}",
|
||||
"step2Init": "Step 2 environment setup initialized",
|
||||
"step3Init": "Step 3 simulation run initialized",
|
||||
"startingDualSim": "Starting dual-platform parallel simulation...",
|
||||
"setMaxRounds": "Max simulation rounds set to: {rounds}",
|
||||
"graphMemoryUpdateEnabled": "Dynamic graph memory update enabled",
|
||||
"oldSimCleared": "✓ Old simulation logs cleared, restarting simulation",
|
||||
"engineStarted": "✓ Simulation engine started successfully",
|
||||
"startFailed": "✗ Start failed: {error}",
|
||||
"startException": "✗ Start error: {error}",
|
||||
"stoppingSim": "Stopping simulation...",
|
||||
"simStoppedSuccess": "✓ Simulation stopped",
|
||||
"stopFailed": "Stop failed: {error}",
|
||||
"stopException": "Stop error: {error}",
|
||||
"allPlatformsCompleted": "✓ All platform simulations have ended",
|
||||
"simCompleted": "✓ Simulation completed",
|
||||
"reportRequestSent": "Report generation request sent, please wait...",
|
||||
"startingReportGen": "Starting report generation...",
|
||||
"reportGenTaskStarted": "✓ Report generation task started: {reportId}",
|
||||
"reportGenFailed": "✗ Failed to start report generation: {error}",
|
||||
"reportGenException": "✗ Report generation error: {error}",
|
||||
"step5Init": "Step 5 deep interaction initialized",
|
||||
"selectChatTarget": "Selected chat target: {name}",
|
||||
"sendFailed": "Send failed: {error}",
|
||||
"sendToReportAgent": "Sent to Report Agent: {message}...",
|
||||
"reportAgentReplied": "Report Agent replied",
|
||||
"sendToAgent": "Sent to {name}: {message}...",
|
||||
"agentReplied": "{name} replied",
|
||||
"sendSurvey": "Sending survey to {count} targets...",
|
||||
"receivedReplies": "Received {count} replies",
|
||||
"surveySendFailed": "Survey send failed: {error}",
|
||||
"loadReportData": "Loading report data: {id}",
|
||||
"loadReportFailed": "Failed to load report: {error}",
|
||||
"reportDataLoaded": "Report data loaded",
|
||||
"loadReportLogFailed": "Failed to load report logs: {error}",
|
||||
"loadedProfiles": "Loaded {count} simulated individuals",
|
||||
"loadProfilesFailed": "Failed to load simulated individuals: {error}",
|
||||
"interactionViewInit": "InteractionView initialized",
|
||||
"reportViewInit": "ReportView initialized",
|
||||
"getReportInfoFailed": "Failed to get report info: {error}",
|
||||
"enterStep": "Entering Step {step}: {name}",
|
||||
"returnToStep": "Returning to Step {step}: {name}",
|
||||
"customSimRounds": "Custom simulation rounds: {rounds} rounds"
|
||||
},
|
||||
"report": {
|
||||
"taskStarted": "Report generation task started",
|
||||
"planningStart": "Starting report outline planning",
|
||||
"fetchSimContext": "Fetching simulation context",
|
||||
"planningComplete": "Outline planning complete",
|
||||
"sectionStart": "Starting section generation: {title}",
|
||||
"reactThought": "ReACT round {iteration} thinking",
|
||||
"toolCall": "Calling tool: {toolName}",
|
||||
"toolResult": "Tool {toolName} returned result",
|
||||
"llmResponse": "LLM response (tool calls: {hasToolCalls}, final answer: {hasFinalAnswer})",
|
||||
"sectionContentDone": "Section {title} content generation complete",
|
||||
"sectionComplete": "Section {title} generation complete",
|
||||
"reportComplete": "Report generation complete",
|
||||
"errorOccurred": "Error occurred: {error}",
|
||||
"agentInitDone": "ReportAgent initialized: graph_id={graphId}, simulation_id={simulationId}",
|
||||
"executingTool": "Executing tool: {toolName}, params: {params}",
|
||||
"toolExecFailed": "Tool execution failed: {toolName}, error: {error}",
|
||||
"startPlanningOutline": "Starting report outline planning...",
|
||||
"outlinePlanDone": "Outline planning complete: {count} sections",
|
||||
"outlinePlanFailed": "Outline planning failed: {error}",
|
||||
"reactGenerateSection": "ReACT generating section: {title}",
|
||||
"sectionIterNone": "Section {title} iteration {iteration}: LLM returned None",
|
||||
"sectionConflict": "Section {title} round {iteration}: LLM output both tool call and Final Answer (conflict #{conflictCount})",
|
||||
"sectionConflictDowngrade": "Section {title}: {conflictCount} consecutive conflicts, downgrading to truncate and execute first tool call",
|
||||
"sectionGenDone": "Section {title} generation complete (tool calls: {count})",
|
||||
"multiToolOnlyFirst": "LLM attempted {total} tool calls, executing only the first: {toolName}",
|
||||
"sectionNoPrefix": "Section {title} missing 'Final Answer:' prefix, adopting LLM output as final content (tool calls: {count})",
|
||||
"sectionMaxIter": "Section {title} reached max iterations, forcing generation",
|
||||
"sectionForceFailed": "Section {title} force-finish LLM returned None, using default error message",
|
||||
"sectionGenFailedContent": "(This section failed to generate: LLM returned empty response, please retry later)",
|
||||
"outlineSavedToFile": "Outline saved to file: {reportId}/outline.json",
|
||||
"sectionSaved": "Section saved: {reportId}/section_{sectionNum}.md",
|
||||
"reportGenDone": "Report generation complete: {reportId}",
|
||||
"reportGenFailed": "Report generation failed: {error}",
|
||||
"agentChat": "Report Agent chat: {message}...",
|
||||
"fetchReportFailed": "Failed to fetch report content: {error}",
|
||||
"outlineSaved": "Outline saved: {reportId}",
|
||||
"sectionFileSaved": "Section saved: {reportId}/{fileSuffix}",
|
||||
"fullReportAssembled": "Full report assembled: {reportId}",
|
||||
"reportSaved": "Report saved: {reportId}",
|
||||
"reportFolderDeleted": "Report folder deleted: {reportId}",
|
||||
"redirectToQuickSearch": "search_graph redirected to quick_search",
|
||||
"redirectToInsightForge": "get_simulation_context redirected to insight_forge"
|
||||
},
|
||||
"console": {
|
||||
"zepToolsInitialized": "ZepToolsService initialized",
|
||||
"zepRetryAttempt": "Zep {operation} attempt {attempt} failed: {error}, retrying in {delay}s...",
|
||||
"zepAllRetriesFailed": "Zep {operation} failed after {retries} attempts: {error}",
|
||||
"graphSearch": "Graph search: graph_id={graphId}, query={query}...",
|
||||
"graphSearchOp": "Graph search (graph={graphId})",
|
||||
"searchComplete": "Search complete: found {count} relevant facts",
|
||||
"zepSearchApiFallback": "Zep Search API failed, falling back to local search: {error}",
|
||||
"usingLocalSearch": "Using local search: query={query}...",
|
||||
"localSearchComplete": "Local search complete: found {count} relevant facts",
|
||||
"localSearchFailed": "Local search failed: {error}",
|
||||
"fetchingAllNodes": "Fetching all nodes for graph {graphId}...",
|
||||
"fetchedNodes": "Fetched {count} nodes",
|
||||
"fetchingAllEdges": "Fetching all edges for graph {graphId}...",
|
||||
"fetchedEdges": "Fetched {count} edges",
|
||||
"fetchingNodeDetail": "Fetching node detail: {uuid}...",
|
||||
"fetchNodeDetailOp": "Fetch node detail (uuid={uuid}...)",
|
||||
"fetchNodeDetailFailed": "Failed to fetch node detail: {error}",
|
||||
"fetchingNodeEdges": "Fetching edges for node {uuid}...",
|
||||
"foundNodeEdges": "Found {count} edges related to node",
|
||||
"fetchNodeEdgesFailed": "Failed to fetch node edges: {error}",
|
||||
"fetchingEntitiesByType": "Fetching entities of type {type}...",
|
||||
"foundEntitiesByType": "Found {count} entities of type {type}",
|
||||
"fetchingEntitySummary": "Fetching relationship summary for entity {name}...",
|
||||
"fetchingGraphStats": "Fetching statistics for graph {graphId}...",
|
||||
"fetchingSimContext": "Fetching simulation context: {requirement}...",
|
||||
"insightForgeStart": "InsightForge deep insight retrieval: {query}...",
|
||||
"generatedSubQueries": "Generated {count} sub-queries",
|
||||
"insightForgeComplete": "InsightForge complete: {facts} facts, {entities} entities, {relationships} relationships",
|
||||
"generateSubQueriesFailed": "Failed to generate sub-queries: {error}, using defaults",
|
||||
"panoramaSearchStart": "PanoramaSearch broad search: {query}...",
|
||||
"panoramaSearchComplete": "PanoramaSearch complete: {active} active, {historical} historical",
|
||||
"quickSearchStart": "QuickSearch simple search: {query}...",
|
||||
"quickSearchComplete": "QuickSearch complete: {count} results",
|
||||
"interviewAgentsStart": "InterviewAgents deep interview (real API): {requirement}...",
|
||||
"profilesNotFound": "Profiles not found for simulation {simId}",
|
||||
"loadedProfiles": "Loaded {count} agent profiles",
|
||||
"selectedAgentsForInterview": "Selected {count} agents for interview: {indices}",
|
||||
"generatedInterviewQuestions": "Generated {count} interview questions",
|
||||
"callingBatchInterviewApi": "Calling batch interview API (dual platform): {count} agents",
|
||||
"interviewApiReturned": "Interview API returned: {count} results, success={success}",
|
||||
"interviewApiReturnedFailure": "Interview API returned failure: {error}",
|
||||
"interviewApiCallFailed": "Interview API call failed (env not running?): {error}",
|
||||
"interviewApiCallException": "Interview API call exception: {error}",
|
||||
"interviewAgentsComplete": "InterviewAgents complete: interviewed {count} agents (dual platform)",
|
||||
"loadedRedditProfiles": "Loaded {count} profiles from reddit_profiles.json",
|
||||
"readRedditProfilesFailed": "Failed to read reddit_profiles.json: {error}",
|
||||
"loadedTwitterProfiles": "Loaded {count} profiles from twitter_profiles.csv",
|
||||
"readTwitterProfilesFailed": "Failed to read twitter_profiles.csv: {error}",
|
||||
"llmSelectAgentFailed": "LLM agent selection failed, using default selection: {error}",
|
||||
"generateInterviewQuestionsFailed": "Failed to generate interview questions: {error}",
|
||||
"generateInterviewSummaryFailed": "Failed to generate interview summary: {error}"
|
||||
}
|
||||
}
|
||||
30
locales/languages.json
Normal file
30
locales/languages.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"zh": {
|
||||
"label": "中文",
|
||||
"llmInstruction": "请使用中文回答。"
|
||||
},
|
||||
"en": {
|
||||
"label": "English",
|
||||
"llmInstruction": "Please respond in English."
|
||||
},
|
||||
"es": {
|
||||
"label": "Español",
|
||||
"llmInstruction": "Por favor, responde en español."
|
||||
},
|
||||
"fr": {
|
||||
"label": "Français",
|
||||
"llmInstruction": "Veuillez répondre en français."
|
||||
},
|
||||
"pt": {
|
||||
"label": "Português",
|
||||
"llmInstruction": "Por favor, responda em português."
|
||||
},
|
||||
"ru": {
|
||||
"label": "Русский",
|
||||
"llmInstruction": "Пожалуйста, отвечайте на русском языке."
|
||||
},
|
||||
"de": {
|
||||
"label": "Deutsch",
|
||||
"llmInstruction": "Bitte antworten Sie auf Deutsch."
|
||||
}
|
||||
}
|
||||
665
locales/zh.json
Normal file
665
locales/zh.json
Normal file
|
|
@ -0,0 +1,665 @@
|
|||
{
|
||||
"common": {
|
||||
"confirm": "确认",
|
||||
"cancel": "取消",
|
||||
"loading": "加载中...",
|
||||
"error": "错误",
|
||||
"success": "成功",
|
||||
"completed": "已完成",
|
||||
"processing": "生成中",
|
||||
"pending": "等待",
|
||||
"ready": "就绪",
|
||||
"running": "运行中",
|
||||
"failed": "失败",
|
||||
"unknown": "未知",
|
||||
"unknownError": "未知错误",
|
||||
"none": "无",
|
||||
"close": "关闭",
|
||||
"back": "返回",
|
||||
"next": "下一步",
|
||||
"retry": "重试",
|
||||
"noData": "暂无数据",
|
||||
"hours": "小时",
|
||||
"minutes": "分钟",
|
||||
"rounds": "轮",
|
||||
"items": "个",
|
||||
"files": "个文件"
|
||||
},
|
||||
"meta": {
|
||||
"title": "MiroFish - 预测万物",
|
||||
"description": "MiroFish - 社交媒体舆论模拟系统"
|
||||
},
|
||||
"nav": {
|
||||
"visitGithub": "访问我们的Github主页"
|
||||
},
|
||||
"home": {
|
||||
"tagline": "简洁通用的群体智能引擎",
|
||||
"version": "/ v0.1-预览版",
|
||||
"heroTitle1": "上传任意报告",
|
||||
"heroTitle2": "即刻推演未来",
|
||||
"heroDesc": "即使只有一段文字,{brand} 也能基于其中的现实种子,全自动生成与之对应的至多{agentScale}构成的平行世界。通过上帝视角注入变量,在复杂的群体交互中寻找动态环境下的{optimalSolution}",
|
||||
"heroDescBrand": "MiroFish",
|
||||
"heroDescAgentScale": "百万级Agent",
|
||||
"heroDescOptimalSolution": "\"局部最优解\"",
|
||||
"slogan": "让未来在 Agent 群中预演,让决策在百战后胜出",
|
||||
"systemStatus": "系统状态",
|
||||
"systemReady": "准备就绪",
|
||||
"systemReadyDesc": "预测引擎待命中,可上传多份非结构化数据以初始化模拟序列",
|
||||
"metricLowCost": "低成本",
|
||||
"metricLowCostDesc": "常规模拟平均5$/次",
|
||||
"metricHighAvail": "高可用",
|
||||
"metricHighAvailDesc": "最多百万级Agent模拟",
|
||||
"workflowSequence": "工作流序列",
|
||||
"step01Title": "图谱构建",
|
||||
"step01Desc": "现实种子提取 & 个体与群体记忆注入 & GraphRAG构建",
|
||||
"step02Title": "环境搭建",
|
||||
"step02Desc": "实体关系抽取 & 人设生成 & 环境配置Agent注入仿真参数",
|
||||
"step03Title": "开始模拟",
|
||||
"step03Desc": "双平台并行模拟 & 自动解析预测需求 & 动态更新时序记忆",
|
||||
"step04Title": "报告生成",
|
||||
"step04Desc": "ReportAgent拥有丰富的工具集与模拟后环境进行深度交互",
|
||||
"step05Title": "深度互动",
|
||||
"step05Desc": "与模拟世界中的任意一位进行对话 & 与ReportAgent进行对话",
|
||||
"realitySeed": "01 / 现实种子",
|
||||
"supportedFormats": "支持格式: PDF, MD, TXT",
|
||||
"dragToUpload": "拖拽文件上传",
|
||||
"orBrowse": "或点击浏览文件系统",
|
||||
"inputParams": "输入参数",
|
||||
"simulationPrompt": ">_ 02 / 模拟提示词",
|
||||
"promptPlaceholder": "// 用自然语言输入模拟或预测需求(例.武大若发布撤销肖某处分的公告,会引发什么舆情走向)",
|
||||
"engineBadge": "引擎: MiroFish-V1.0",
|
||||
"startEngine": "启动引擎",
|
||||
"initializing": "初始化中..."
|
||||
},
|
||||
"main": {
|
||||
"layoutGraph": "图谱",
|
||||
"layoutSplit": "双栏",
|
||||
"layoutWorkbench": "工作台",
|
||||
"stepNames": ["图谱构建", "环境搭建", "开始模拟", "报告生成", "深度互动"]
|
||||
},
|
||||
"step1": {
|
||||
"ontologyGeneration": "本体生成",
|
||||
"ontologyCompleted": "已完成",
|
||||
"ontologyGenerating": "生成中",
|
||||
"ontologyPending": "等待",
|
||||
"ontologyDesc": "LLM分析文档内容与模拟需求,提取出现实种子,自动生成合适的本体结构",
|
||||
"analyzingDocs": "正在分析文档...",
|
||||
"graphRagBuild": "GraphRAG构建",
|
||||
"graphRagDesc": "基于生成的本体,将文档自动分块后调用 Zep 构建知识图谱,提取实体和关系,并形成时序记忆与社区摘要",
|
||||
"entityNodes": "实体节点",
|
||||
"relationEdges": "关系边",
|
||||
"schemaTypes": "SCHEMA类型",
|
||||
"buildComplete": "构建完成",
|
||||
"buildCompleteDesc": "图谱构建已完成,请进入下一步进行模拟环境搭建",
|
||||
"inProgress": "进行中",
|
||||
"creating": "创建中...",
|
||||
"enterEnvSetup": "进入环境搭建",
|
||||
"createSimulationFailed": "创建模拟失败: {error}",
|
||||
"createSimulationException": "创建模拟异常: {error}"
|
||||
},
|
||||
"step2": {
|
||||
"simInstanceInit": "模拟实例初始化",
|
||||
"simInstanceDesc": "新建simulation实例,拉取模拟世界参数模版",
|
||||
"asyncTaskDone": "异步任务已完成",
|
||||
"generateAgentPersona": "生成 Agent 人设",
|
||||
"generateAgentPersonaDesc": "结合上下文,自动调用工具从知识图谱梳理实体与关系,初始化模拟个体,并基于现实种子赋予他们独特的行为与记忆",
|
||||
"currentAgentCount": "当前Agent数",
|
||||
"expectedAgentTotal": "预期Agent总数",
|
||||
"relatedTopicsCount": "现实种子当前关联话题数",
|
||||
"generatedAgentPersonas": "已生成的 Agent 人设",
|
||||
"unknownProfession": "未知职业",
|
||||
"noBio": "暂无简介",
|
||||
"dualPlatformConfig": "生成双平台模拟配置",
|
||||
"dualPlatformConfigDesc": "LLM 根据模拟需求与现实种子,智能设置世界时间流速、推荐算法、每个个体的活跃时间段、发言频率、事件触发等参数",
|
||||
"simulationDuration": "模拟时长",
|
||||
"roundDuration": "每轮时长",
|
||||
"totalRounds": "总轮次",
|
||||
"activePerHour": "每小时活跃",
|
||||
"peakHours": "高峰时段",
|
||||
"workHours": "工作时段",
|
||||
"morningHours": "早间时段",
|
||||
"offPeakHours": "低谷时段",
|
||||
"agentConfig": "Agent 配置",
|
||||
"activeTimePeriod": "活跃时段",
|
||||
"postsPerHour": "发帖/时",
|
||||
"commentsPerHour": "评论/时",
|
||||
"responseDelay": "响应延迟",
|
||||
"activityLevel": "活跃度",
|
||||
"sentimentBias": "情感倾向",
|
||||
"influenceWeight": "影响力",
|
||||
"recommendAlgoConfig": "推荐算法配置",
|
||||
"platform1Name": "平台 1:广场 / 信息流",
|
||||
"platform2Name": "平台 2:话题 / 社区",
|
||||
"recencyWeight": "时效权重",
|
||||
"popularityWeight": "热度权重",
|
||||
"relevanceWeight": "相关性权重",
|
||||
"viralThreshold": "病毒阈值",
|
||||
"echoChamberStrength": "回音室强度",
|
||||
"llmConfigReasoning": "LLM 配置推理",
|
||||
"initialActivation": "初始激活编排",
|
||||
"initialActivationDesc": "基于叙事方向,自动生成初始激活事件与热点话题,引导模拟世界的初始状态",
|
||||
"orchestrating": "编排中",
|
||||
"narrativeDirection": "叙事引导方向",
|
||||
"initialHotTopics": "初始热点话题",
|
||||
"initialActivationSeq": "初始激活序列 ({count})",
|
||||
"setupComplete": "准备完成",
|
||||
"setupCompleteDesc": "模拟环境已准备完成,可以开始运行模拟",
|
||||
"roundsConfig": "模拟轮数设定",
|
||||
"roundsConfigDesc": "MiroFish 自动规划推演现实 {hours} 小时,每轮代表现实 {minutesPerRound} 分钟时间流逝",
|
||||
"customToggle": "自定义",
|
||||
"roundsUnit": "轮",
|
||||
"estimatedDuration": "若Agent规模为100:预计耗时约 {minutes} 分钟",
|
||||
"estimatedDurationFull": "若Agent规模为100:预计耗时 {minutes} 分钟",
|
||||
"recommendedRounds": "{rounds} (推荐)",
|
||||
"customTip": "若首次运行,强烈建议切换至'自定义模式'减少模拟轮数,以便快速预览效果并降低报错风险",
|
||||
"backToGraphBuild": "返回图谱构建",
|
||||
"startDualWorldSim": "开始双世界并行模拟",
|
||||
"profileModalAge": "事件外显年龄",
|
||||
"profileModalGender": "事件外显性别",
|
||||
"profileModalCountry": "国家/地区",
|
||||
"profileModalMbti": "事件外显MBTI",
|
||||
"profileModalBio": "人设简介",
|
||||
"profileModalTopics": "现实种子关联话题",
|
||||
"profileModalPersona": "详细人设背景",
|
||||
"personaDimExperience": "事件全景经历",
|
||||
"personaDimExperienceDesc": "在此事件中的完整行为轨迹",
|
||||
"personaDimBehavior": "行为模式侧写",
|
||||
"personaDimBehaviorDesc": "经验总结与行事风格偏好",
|
||||
"personaDimMemory": "独特记忆印记",
|
||||
"personaDimMemoryDesc": "基于现实种子形成的记忆",
|
||||
"personaDimSocial": "社会关系网络",
|
||||
"personaDimSocialDesc": "个体链接与交互图谱",
|
||||
"genderMale": "男",
|
||||
"genderFemale": "女",
|
||||
"genderOther": "其他",
|
||||
"yearsOld": "岁",
|
||||
"initializing": "初始化",
|
||||
"generating": "生成中"
|
||||
},
|
||||
"step3": {
|
||||
"startGenerateReport": "开始生成结果报告",
|
||||
"generatingReport": "启动中...",
|
||||
"waitingForActions": "Waiting for agent actions...",
|
||||
"errorMissingSimId": "错误:缺少 simulationId",
|
||||
"startingDualSim": "正在启动双平台并行模拟...",
|
||||
"graphMemoryUpdateEnabled": "已开启动态图谱更新模式",
|
||||
"setMaxRounds": "设置最大模拟轮数: {rounds}",
|
||||
"oldSimCleared": "已清理旧的模拟日志,重新开始模拟",
|
||||
"engineStarted": "模拟引擎启动成功",
|
||||
"startFailed": "启动失败: {error}",
|
||||
"startException": "启动异常: {error}",
|
||||
"stoppingSim": "正在停止模拟...",
|
||||
"simStopped": "模拟已停止",
|
||||
"stopFailed": "停止失败: {error}",
|
||||
"stopException": "停止异常: {error}",
|
||||
"allPlatformsCompleted": "检测到所有平台模拟已结束",
|
||||
"simCompleted": "模拟已完成",
|
||||
"graphRealtimeRefresh": "开启图谱实时刷新 (30s)",
|
||||
"graphRefreshStopped": "停止图谱实时刷新",
|
||||
"preparingGoBack": "准备返回 Step 2,正在关闭模拟...",
|
||||
"closingSimEnv": "正在关闭模拟环境...",
|
||||
"simEnvClosed": "模拟环境已关闭",
|
||||
"closeFailed": "关闭模拟环境失败,尝试强制停止...",
|
||||
"stoppingProcess": "正在停止模拟进程...",
|
||||
"checkStatusFailed": "检查模拟状态失败: {error}",
|
||||
"forceStopSuccess": "模拟已强制停止",
|
||||
"forceStopFailed": "强制停止失败: {error}",
|
||||
"startGenerateReportBtn": "开始生成结果报告",
|
||||
"generatingReportBtn": "启动中..."
|
||||
},
|
||||
"step4": {
|
||||
"generatingSection": "正在生成{title}...",
|
||||
"goToInteraction": "进入深度互动",
|
||||
"waitingForReportAgent": "Waiting for Report Agent...",
|
||||
"collapse": "收起 ▲",
|
||||
"expandAll": "展开全部 {count} 条 ▼",
|
||||
"expandAllEntities": "展开全部 {count} 个 ▼",
|
||||
"scenarioLabel": "预测场景: ",
|
||||
"tabKeyFacts": "当前关键记忆 ({count})",
|
||||
"tabCoreEntities": "核心实体 ({count})",
|
||||
"tabRelationChains": "关系链 ({count})",
|
||||
"tabSubQueries": "子问题 ({count})",
|
||||
"panelKeyFacts": "时序记忆中所关联的最新关键事实",
|
||||
"totalCount": "共 {count} 条",
|
||||
"totalEntityCount": "共 {count} 个",
|
||||
"panelCoreEntities": "核心实体",
|
||||
"factCount": "{count}条",
|
||||
"panelRelationChains": "关系链",
|
||||
"panelSubQueries": "漂移查询生成分析子问题",
|
||||
"emptyKeyFacts": "暂无当前关键记忆",
|
||||
"emptyCoreEntities": "暂无核心实体",
|
||||
"emptyRelationChains": "暂无关系链",
|
||||
"tabActiveFacts": "当前有效记忆 ({count})",
|
||||
"tabHistoricalFacts": "历史记忆 ({count})",
|
||||
"tabEntities": "涉及实体 ({count})",
|
||||
"panelActiveFacts": "当前有效记忆",
|
||||
"emptyActiveFacts": "暂无当前有效记忆",
|
||||
"panelHistoricalFacts": "历史记忆",
|
||||
"emptyHistoricalFacts": "暂无历史记忆",
|
||||
"panelEntities": "涉及实体",
|
||||
"emptyEntities": "暂无涉及实体",
|
||||
"searchLabel": "搜索: ",
|
||||
"tabFacts": "事实 ({count})",
|
||||
"tabEdges": "关系 ({count})",
|
||||
"tabNodes": "节点 ({count})",
|
||||
"panelSearchResults": "搜索结果",
|
||||
"emptySearchResults": "未找到相关结果",
|
||||
"panelRelatedEdges": "相关关系",
|
||||
"panelRelatedNodes": "相关节点",
|
||||
"world1": "世界1",
|
||||
"world2": "世界2"
|
||||
},
|
||||
"step5": {
|
||||
"interactiveTools": "Interactive Tools",
|
||||
"agentsAvailable": "{count} agents available",
|
||||
"chatWithReportAgent": "与Report Agent对话",
|
||||
"chatWithAgent": "与世界中任意个体对话",
|
||||
"selectChatTarget": "选择对话对象",
|
||||
"sendSurvey": "发送问卷调查到世界中",
|
||||
"reportAgentChat": "Report Agent - Chat",
|
||||
"reportAgentDesc": "报告生成智能体的快速对话版本,可调用 4 种专业工具,拥有MiroFish的完整记忆",
|
||||
"toolInsightForge": "InsightForge 深度归因",
|
||||
"toolInsightForgeDesc": "对齐现实世界种子数据与模拟环境状态,结合Global/Local Memory机制,提供跨时空的深度归因分析",
|
||||
"toolPanoramaSearch": "PanoramaSearch 全景追踪",
|
||||
"toolPanoramaSearchDesc": "基于图结构的广度遍历算法,重构事件传播路径,捕获全量信息流动的拓扑结构",
|
||||
"toolQuickSearch": "QuickSearch 快速检索",
|
||||
"toolQuickSearchDesc": "基于 GraphRAG 的即时查询接口,优化索引效率,用于快速提取具体的节点属性与离散事实",
|
||||
"toolInterviewSubAgent": "InterviewSubAgent 虚拟访谈",
|
||||
"toolInterviewSubAgentDesc": "自主式访谈,能够并行与模拟世界中个体进行多轮对话,采集非结构化的观点数据与心理状态",
|
||||
"profileBio": "简介",
|
||||
"chatEmptyReportAgent": "与 Report Agent 对话,深入了解报告内容",
|
||||
"chatEmptyAgent": "与模拟个体对话,了解他们的观点",
|
||||
"chatInputPlaceholder": "输入您的问题...",
|
||||
"selectSurveyTarget": "选择调查对象",
|
||||
"selectedCount": "已选 {selected} / {total}",
|
||||
"surveyQuestions": "问卷问题",
|
||||
"surveyInputPlaceholder": "输入您想问所有被选中对象的问题...",
|
||||
"submitSurvey": "发送问卷",
|
||||
"surveyResults": "调查结果",
|
||||
"surveyResultsCount": "{count} 条回复",
|
||||
"selectAll": "全选",
|
||||
"clearSelection": "清空",
|
||||
"errorOccurred": "抱歉,发生了错误: {error}",
|
||||
"noResponse": "无响应",
|
||||
"requestFailed": "请求失败",
|
||||
"selectAgentFirst": "请先选择一个模拟个体"
|
||||
},
|
||||
"graph": {
|
||||
"panelTitle": "Graph Relationship Visualization",
|
||||
"refreshGraph": "刷新图谱",
|
||||
"graphMemoryRealtime": "GraphRAG长短期记忆实时更新中",
|
||||
"realtimeUpdating": "实时更新中...",
|
||||
"pendingContentHint": "还有少量内容处理中,建议稍后手动刷新图谱",
|
||||
"nodeDetails": "Node Details",
|
||||
"relationship": "Relationship",
|
||||
"graphDataLoading": "图谱数据加载中...",
|
||||
"waitingOntology": "等待本体生成...",
|
||||
"toggleMaximize": "最大化/还原",
|
||||
"closeHint": "关闭提示"
|
||||
},
|
||||
"history": {
|
||||
"title": "推演记录",
|
||||
"graphBuild": "图谱构建",
|
||||
"envSetup": "环境搭建",
|
||||
"analysisReport": "分析报告",
|
||||
"moreFiles": "+{count} 个文件",
|
||||
"noFiles": "暂无文件",
|
||||
"loadingText": "加载中...",
|
||||
"simRequirement": "模拟需求",
|
||||
"relatedFiles": "关联文件",
|
||||
"noRelatedFiles": "暂无关联文件",
|
||||
"replayTitle": "推演回放",
|
||||
"step1Button": "图谱构建",
|
||||
"step2Button": "环境搭建",
|
||||
"step4Button": "分析报告",
|
||||
"replayHint": "Step3「开始模拟」与 Step5「深度互动」需在运行中启动,不支持历史回放",
|
||||
"notStarted": "未开始",
|
||||
"roundsProgress": "{current}/{total} 轮",
|
||||
"untitledSimulation": "未命名模拟",
|
||||
"unknownFile": "未知文件"
|
||||
},
|
||||
"api": {
|
||||
"projectNotFound": "项目不存在: {id}",
|
||||
"projectDeleteFailed": "项目不存在或删除失败: {id}",
|
||||
"projectDeleted": "项目已删除: {id}",
|
||||
"projectReset": "项目已重置: {id}",
|
||||
"requireSimulationRequirement": "请提供模拟需求描述 (simulation_requirement)",
|
||||
"requireFileUpload": "请至少上传一个文档文件",
|
||||
"noDocProcessed": "没有成功处理任何文档,请检查文件格式",
|
||||
"requireProjectId": "请提供 project_id",
|
||||
"configError": "配置错误: {details}",
|
||||
"zepApiKeyMissing": "ZEP_API_KEY未配置",
|
||||
"ontologyNotGenerated": "项目尚未生成本体,请先调用 /ontology/generate",
|
||||
"graphBuilding": "图谱正在构建中,请勿重复提交。如需强制重建,请添加 force: true",
|
||||
"textNotFound": "未找到提取的文本内容",
|
||||
"ontologyNotFound": "未找到本体定义",
|
||||
"graphBuildStarted": "图谱构建任务已启动,请通过 /task/{taskId} 查询进度",
|
||||
"graphBuildComplete": "图谱构建完成",
|
||||
"buildFailed": "构建失败: {error}",
|
||||
"taskNotFound": "任务不存在: {id}",
|
||||
"graphDeleted": "图谱已删除: {id}",
|
||||
"entityNotFound": "实体不存在: {id}",
|
||||
"graphNotBuilt": "项目尚未构建图谱,请先调用 /api/graph/build",
|
||||
"requireSimulationId": "请提供 simulation_id",
|
||||
"simulationNotFound": "模拟不存在: {id}",
|
||||
"projectMissingRequirement": "项目缺少模拟需求描述 (simulation_requirement)",
|
||||
"prepareStarted": "准备任务已启动,请通过 /api/simulation/prepare/status 查询进度",
|
||||
"alreadyPrepared": "已有完成的准备工作,无需重复生成",
|
||||
"notStartedPrepare": "尚未开始准备,请调用 /api/simulation/prepare 开始",
|
||||
"taskCompletedPrepared": "任务已完成(准备工作已存在)",
|
||||
"requireTaskOrSimId": "请提供 task_id 或 simulation_id",
|
||||
"configNotFound": "模拟配置不存在,请先调用 /prepare 接口",
|
||||
"configFileNotFound": "配置文件不存在,请先调用 /prepare 接口",
|
||||
"unknownScript": "未知脚本: {name},可选: {allowed}",
|
||||
"scriptFileNotFound": "脚本文件不存在: {name}",
|
||||
"requireGraphId": "请提供 graph_id",
|
||||
"noMatchingEntities": "没有找到符合条件的实体",
|
||||
"maxRoundsPositive": "max_rounds 必须是正整数",
|
||||
"maxRoundsInvalid": "max_rounds 必须是有效的整数",
|
||||
"invalidPlatform": "无效的平台类型: {platform},可选: twitter/reddit/parallel",
|
||||
"simRunningForceHint": "模拟正在运行中,请先调用 /stop 接口停止,或使用 force=true 强制重新开始",
|
||||
"simNotReady": "模拟未准备好,当前状态: {status},请先调用 /prepare 接口",
|
||||
"graphIdRequiredForMemory": "启用图谱记忆更新需要有效的 graph_id,请确保项目已构建图谱",
|
||||
"dbNotExist": "数据库不存在,模拟可能尚未运行",
|
||||
"requireMessage": "请提供 message",
|
||||
"missingGraphId": "缺少图谱ID",
|
||||
"missingGraphIdEnsure": "缺少图谱ID,请确保已构建图谱",
|
||||
"missingSimRequirement": "缺少模拟需求描述",
|
||||
"reportAlreadyExists": "报告已存在",
|
||||
"reportGenerateStarted": "报告生成任务已启动,请通过 /api/report/generate/status 查询进度",
|
||||
"reportGenerated": "报告已生成",
|
||||
"reportNotFound": "报告不存在: {id}",
|
||||
"noReportForSim": "该模拟暂无报告: {id}",
|
||||
"reportDeleted": "报告已删除: {id}",
|
||||
"reportGenerateFailed": "报告生成失败",
|
||||
"sectionNotFound": "章节不存在: section_{index}.md",
|
||||
"reportProgressNotAvail": "报告不存在或进度信息不可用: {id}",
|
||||
"requireAgentId": "请提供 agent_id",
|
||||
"requirePrompt": "请提供 prompt(采访问题)",
|
||||
"invalidInterviewPlatform": "platform 参数只能是 'twitter' 或 'reddit'",
|
||||
"envNotRunning": "模拟环境未运行或已关闭。请确保模拟已完成并进入等待命令模式。",
|
||||
"interviewTimeout": "等待Interview响应超时: {error}",
|
||||
"requireInterviews": "请提供 interviews(采访列表)",
|
||||
"interviewListMissingAgentId": "采访列表第{index}项缺少 agent_id",
|
||||
"interviewListMissingPrompt": "采访列表第{index}项缺少 prompt",
|
||||
"interviewListInvalidPlatform": "采访列表第{index}项的platform只能是 'twitter' 或 'reddit'",
|
||||
"batchInterviewTimeout": "等待批量Interview响应超时: {error}",
|
||||
"globalInterviewTimeout": "等待全局Interview响应超时: {error}",
|
||||
"envRunning": "环境正在运行,可以接收Interview命令",
|
||||
"envNotRunningShort": "环境未运行或已关闭",
|
||||
"requireGraphIdAndQuery": "请提供 graph_id 和 query",
|
||||
"initReportAgent": "初始化Report Agent..."
|
||||
},
|
||||
"progress": {
|
||||
"initGraphService": "初始化图谱构建服务...",
|
||||
"textChunking": "文本分块中...",
|
||||
"creatingZepGraph": "创建Zep图谱...",
|
||||
"settingOntology": "设置本体定义...",
|
||||
"addingChunks": "开始添加 {count} 个文本块...",
|
||||
"waitingZepProcess": "等待Zep处理数据...",
|
||||
"fetchingGraphData": "获取图谱数据...",
|
||||
"graphBuildComplete": "图谱构建完成",
|
||||
"buildFailed": "构建失败: {error}",
|
||||
"startBuildingGraph": "开始构建图谱...",
|
||||
"graphCreated": "图谱已创建: {graphId}",
|
||||
"ontologySet": "本体已设置",
|
||||
"textSplit": "文本已分割为 {count} 个块",
|
||||
"fetchingGraphInfo": "获取图谱信息...",
|
||||
"sendingBatch": "发送第 {current}/{total} 批数据 ({chunks} 块)...",
|
||||
"batchFailed": "批次 {batch} 发送失败: {error}",
|
||||
"noEpisodesWait": "无需等待(没有 episode)",
|
||||
"waitingEpisodes": "开始等待 {count} 个文本块处理...",
|
||||
"episodesTimeout": "部分文本块超时,已完成 {completed}/{total}",
|
||||
"zepProcessing": "Zep处理中... {completed}/{total} 完成, {pending} 待处理 ({elapsed}秒)",
|
||||
"processingComplete": "处理完成: {completed}/{total}",
|
||||
"taskComplete": "任务完成",
|
||||
"taskFailed": "任务失败",
|
||||
"startPreparingEnv": "开始准备模拟环境...",
|
||||
"connectingZepGraph": "正在连接Zep图谱...",
|
||||
"readingNodeData": "正在读取节点数据...",
|
||||
"readingComplete": "完成,共 {count} 个实体",
|
||||
"startGenerating": "开始生成...",
|
||||
"analyzingRequirements": "正在分析模拟需求...",
|
||||
"generatingOutline": "正在生成报告大纲...",
|
||||
"parsingOutline": "正在解析大纲结构...",
|
||||
"outlinePlanComplete": "大纲规划完成",
|
||||
"deepSearchAndWrite": "深度检索与撰写中 ({current}/{max})",
|
||||
"initReport": "初始化报告...",
|
||||
"startPlanningOutline": "开始规划报告大纲...",
|
||||
"outlineDone": "大纲规划完成,共{count}个章节",
|
||||
"generatingSection": "正在生成章节: {title} ({current}/{total})",
|
||||
"sectionDone": "章节 {title} 已完成",
|
||||
"assemblingReport": "正在组装完整报告...",
|
||||
"reportComplete": "报告生成完成",
|
||||
"reportFailed": "报告生成失败: {error}",
|
||||
"savingProfiles": "保存Profile文件...",
|
||||
"profilesComplete": "完成,共 {count} 个Profile",
|
||||
"callingLLMConfig": "正在调用LLM生成配置...",
|
||||
"savingConfigFiles": "正在保存配置文件...",
|
||||
"configComplete": "配置生成完成",
|
||||
"generatingTimeConfig": "生成时间配置...",
|
||||
"generatingEventConfig": "生成事件配置和热点话题...",
|
||||
"generatingAgentConfig": "生成Agent配置 ({start}-{end}/{total})...",
|
||||
"generatingPlatformConfig": "生成平台配置...",
|
||||
"zepSearchQuery": "关于{name}的所有信息、活动、事件、关系和背景",
|
||||
"timeConfigLabel": "时间配置",
|
||||
"eventConfigLabel": "事件配置",
|
||||
"agentConfigResult": "Agent配置: 成功生成 {count} 个",
|
||||
"postAssignResult": "初始帖子分配: {count} 个帖子已分配发布者",
|
||||
"profileGenerated": "[已生成] {name} ({type})",
|
||||
"readingGraphEntities": "读取图谱实体",
|
||||
"generatingProfiles": "生成Agent人设",
|
||||
"generatingSimConfig": "生成模拟配置",
|
||||
"preparingScripts": "准备模拟脚本"
|
||||
},
|
||||
"log": {
|
||||
"preparingGoBack": "准备返回 Step 2,正在关闭模拟...",
|
||||
"closingSimEnv": "正在关闭模拟环境...",
|
||||
"simEnvClosed": "✓ 模拟环境已关闭",
|
||||
"closeSimEnvFailed": "关闭模拟环境失败,尝试强制停止...",
|
||||
"simForceStopSuccess": "✓ 模拟已强制停止",
|
||||
"forceStopFailed": "强制停止失败: {error}",
|
||||
"stoppingSimProcess": "正在停止模拟进程...",
|
||||
"simStopped": "✓ 模拟已停止",
|
||||
"stopSimFailed": "停止模拟失败: {error}",
|
||||
"checkStatusFailed": "检查模拟状态失败: {error}",
|
||||
"enterStep4": "进入 Step 4: 报告生成",
|
||||
"loadingSimData": "加载模拟数据: {id}",
|
||||
"timeConfig": "时间配置: 每轮 {minutes} 分钟",
|
||||
"timeConfigFetchFailed": "获取时间配置失败,使用默认值: {minutes}分钟/轮",
|
||||
"projectLoadSuccess": "项目加载成功: {id}",
|
||||
"loadSimDataFailed": "加载模拟数据失败: {error}",
|
||||
"loadException": "加载异常: {error}",
|
||||
"graphDataLoadSuccess": "图谱数据加载成功",
|
||||
"graphLoadFailed": "图谱加载失败: {error}",
|
||||
"graphRealtimeRefreshStart": "开启图谱实时刷新 (30s)",
|
||||
"graphRealtimeRefreshStop": "停止图谱实时刷新",
|
||||
"simRunViewInit": "SimulationRunView 初始化",
|
||||
"customRounds": "自定义模拟轮数: {rounds}",
|
||||
"enterStep3": "进入 Step 3: 开始模拟",
|
||||
"customRoundsConfig": "自定义模拟轮数: {rounds} 轮",
|
||||
"useAutoRounds": "使用自动配置的模拟轮数",
|
||||
"detectedSimEnvRunning": "检测到模拟环境正在运行,正在关闭...",
|
||||
"closeSimEnvFailedWithError": "关闭模拟环境失败: {error}",
|
||||
"closeSimEnvException": "关闭模拟环境异常: {error}",
|
||||
"detectedSimRunning": "检测到模拟状态为运行中,正在停止...",
|
||||
"forceStopSimFailed": "强制停止模拟失败: {error}",
|
||||
"forceStopSimException": "强制停止模拟异常: {error}",
|
||||
"simViewInit": "SimulationView 初始化",
|
||||
"errorMissingSimId": "错误:缺少 simulationId",
|
||||
"simInstanceCreated": "模拟实例已创建: {id}",
|
||||
"preparingSimEnv": "正在准备模拟环境...",
|
||||
"detectedExistingPrep": "检测到已有完成的准备工作,直接使用",
|
||||
"prepareTaskStarted": "准备任务已启动",
|
||||
"prepareTaskId": " └─ Task ID: {taskId}",
|
||||
"zepEntitiesFound": "从Zep图谱读取到 {count} 个实体",
|
||||
"entityTypes": " └─ 实体类型: {types}",
|
||||
"startPollingProgress": "开始轮询准备进度...",
|
||||
"prepareFailed": "准备失败: {error}",
|
||||
"prepareException": "准备异常: {error}",
|
||||
"prepareComplete": "✓ 准备工作已完成",
|
||||
"prepareFailedWithError": "✗ 准备失败: {error}",
|
||||
"startGeneratingConfig": "开始生成双平台模拟配置...",
|
||||
"generatingAgentProfileConfig": "正在生成Agent人设配置...",
|
||||
"generatingLLMConfig": "正在调用LLM生成模拟配置参数...",
|
||||
"configComplete": "✓ 模拟配置生成完成",
|
||||
"configSummaryAgents": " ├─ Agent数量: {count}个",
|
||||
"configSummaryHours": " ├─ 模拟时长: {hours}小时",
|
||||
"configSummaryPosts": " ├─ 初始帖子: {count}条",
|
||||
"configSummaryTopics": " ├─ 热点话题: {count}个",
|
||||
"configSummaryPlatforms": " └─ 平台配置: Twitter {twitter}, Reddit {reddit}",
|
||||
"timeConfigDetail": "时间配置: 每轮{minutes}分钟, 共{rounds}轮",
|
||||
"narrativeDirection": "叙事方向: {direction}",
|
||||
"envSetupComplete": "✓ 环境搭建完成,可以开始模拟",
|
||||
"startSimCustomRounds": "开始模拟,自定义轮数: {rounds} 轮",
|
||||
"startSimAutoRounds": "开始模拟,使用自动配置轮数: {rounds} 轮",
|
||||
"startGeneratingAgentProfiles": "开始生成Agent人设...",
|
||||
"agentProfile": "→ Agent人设 {current}/{total}: {name} ({profession})",
|
||||
"allProfilesComplete": "✓ 全部 {count} 个Agent人设生成完成",
|
||||
"loadingExistingConfig": "正在加载已有配置数据...",
|
||||
"loadedAgentProfiles": "已加载 {count} 个Agent人设",
|
||||
"configLoadSuccess": "✓ 模拟配置加载成功",
|
||||
"configSummaryPostsAlt": " └─ 初始帖子: {count}条",
|
||||
"configGenerating": "配置生成中,开始轮询等待...",
|
||||
"loadConfigFailed": "加载配置失败: {error}",
|
||||
"step2Init": "Step2 环境搭建初始化",
|
||||
"step3Init": "Step3 模拟运行初始化",
|
||||
"startingDualSim": "正在启动双平台并行模拟...",
|
||||
"setMaxRounds": "设置最大模拟轮数: {rounds}",
|
||||
"graphMemoryUpdateEnabled": "已开启动态图谱更新模式",
|
||||
"oldSimCleared": "✓ 已清理旧的模拟日志,重新开始模拟",
|
||||
"engineStarted": "✓ 模拟引擎启动成功",
|
||||
"startFailed": "✗ 启动失败: {error}",
|
||||
"startException": "✗ 启动异常: {error}",
|
||||
"stoppingSim": "正在停止模拟...",
|
||||
"simStoppedSuccess": "✓ 模拟已停止",
|
||||
"stopFailed": "停止失败: {error}",
|
||||
"stopException": "停止异常: {error}",
|
||||
"allPlatformsCompleted": "✓ 检测到所有平台模拟已结束",
|
||||
"simCompleted": "✓ 模拟已完成",
|
||||
"reportRequestSent": "报告生成请求已发送,请稍候...",
|
||||
"startingReportGen": "正在启动报告生成...",
|
||||
"reportGenTaskStarted": "✓ 报告生成任务已启动: {reportId}",
|
||||
"reportGenFailed": "✗ 启动报告生成失败: {error}",
|
||||
"reportGenException": "✗ 启动报告生成异常: {error}",
|
||||
"step5Init": "Step5 深度互动初始化",
|
||||
"selectChatTarget": "选择对话对象: {name}",
|
||||
"sendFailed": "发送失败: {error}",
|
||||
"sendToReportAgent": "向 Report Agent 发送: {message}...",
|
||||
"reportAgentReplied": "Report Agent 已回复",
|
||||
"sendToAgent": "向 {name} 发送: {message}...",
|
||||
"agentReplied": "{name} 已回复",
|
||||
"sendSurvey": "发送问卷给 {count} 个对象...",
|
||||
"receivedReplies": "收到 {count} 条回复",
|
||||
"surveySendFailed": "问卷发送失败: {error}",
|
||||
"loadReportData": "加载报告数据: {id}",
|
||||
"loadReportFailed": "加载报告失败: {error}",
|
||||
"reportDataLoaded": "报告数据加载完成",
|
||||
"loadReportLogFailed": "加载报告日志失败: {error}",
|
||||
"loadedProfiles": "加载了 {count} 个模拟个体",
|
||||
"loadProfilesFailed": "加载模拟个体失败: {error}",
|
||||
"interactionViewInit": "InteractionView 初始化",
|
||||
"reportViewInit": "ReportView 初始化",
|
||||
"getReportInfoFailed": "获取报告信息失败: {error}",
|
||||
"enterStep": "进入 Step {step}: {name}",
|
||||
"returnToStep": "返回 Step {step}: {name}",
|
||||
"customSimRounds": "自定义模拟轮数: {rounds} 轮"
|
||||
},
|
||||
"report": {
|
||||
"taskStarted": "报告生成任务开始",
|
||||
"planningStart": "开始规划报告大纲",
|
||||
"fetchSimContext": "获取模拟上下文信息",
|
||||
"planningComplete": "大纲规划完成",
|
||||
"sectionStart": "开始生成章节: {title}",
|
||||
"reactThought": "ReACT 第{iteration}轮思考",
|
||||
"toolCall": "调用工具: {toolName}",
|
||||
"toolResult": "工具 {toolName} 返回结果",
|
||||
"llmResponse": "LLM 响应 (工具调用: {hasToolCalls}, 最终答案: {hasFinalAnswer})",
|
||||
"sectionContentDone": "章节 {title} 内容生成完成",
|
||||
"sectionComplete": "章节 {title} 生成完成",
|
||||
"reportComplete": "报告生成完成",
|
||||
"errorOccurred": "发生错误: {error}",
|
||||
"agentInitDone": "ReportAgent 初始化完成: graph_id={graphId}, simulation_id={simulationId}",
|
||||
"executingTool": "执行工具: {toolName}, 参数: {params}",
|
||||
"toolExecFailed": "工具执行失败: {toolName}, 错误: {error}",
|
||||
"startPlanningOutline": "开始规划报告大纲...",
|
||||
"outlinePlanDone": "大纲规划完成: {count} 个章节",
|
||||
"outlinePlanFailed": "大纲规划失败: {error}",
|
||||
"reactGenerateSection": "ReACT生成章节: {title}",
|
||||
"sectionIterNone": "章节 {title} 第 {iteration} 次迭代: LLM 返回 None",
|
||||
"sectionConflict": "章节 {title} 第 {iteration} 轮: LLM 同时输出工具调用和 Final Answer(第 {conflictCount} 次冲突)",
|
||||
"sectionConflictDowngrade": "章节 {title}: 连续 {conflictCount} 次冲突,降级为截断执行第一个工具调用",
|
||||
"sectionGenDone": "章节 {title} 生成完成(工具调用: {count}次)",
|
||||
"multiToolOnlyFirst": "LLM 尝试调用 {total} 个工具,只执行第一个: {toolName}",
|
||||
"sectionNoPrefix": "章节 {title} 未检测到 'Final Answer:' 前缀,直接采纳LLM输出作为最终内容(工具调用: {count}次)",
|
||||
"sectionMaxIter": "章节 {title} 达到最大迭代次数,强制生成",
|
||||
"sectionForceFailed": "章节 {title} 强制收尾时 LLM 返回 None,使用默认错误提示",
|
||||
"sectionGenFailedContent": "(本章节生成失败:LLM 返回空响应,请稍后重试)",
|
||||
"outlineSavedToFile": "大纲已保存到文件: {reportId}/outline.json",
|
||||
"sectionSaved": "章节已保存: {reportId}/section_{sectionNum}.md",
|
||||
"reportGenDone": "报告生成完成: {reportId}",
|
||||
"reportGenFailed": "报告生成失败: {error}",
|
||||
"agentChat": "Report Agent对话: {message}...",
|
||||
"fetchReportFailed": "获取报告内容失败: {error}",
|
||||
"outlineSaved": "大纲已保存: {reportId}",
|
||||
"sectionFileSaved": "章节已保存: {reportId}/{fileSuffix}",
|
||||
"fullReportAssembled": "完整报告已组装: {reportId}",
|
||||
"reportSaved": "报告已保存: {reportId}",
|
||||
"reportFolderDeleted": "报告文件夹已删除: {reportId}",
|
||||
"redirectToQuickSearch": "search_graph 已重定向到 quick_search",
|
||||
"redirectToInsightForge": "get_simulation_context 已重定向到 insight_forge"
|
||||
},
|
||||
"console": {
|
||||
"zepToolsInitialized": "ZepToolsService 初始化完成",
|
||||
"zepRetryAttempt": "Zep {operation} 第 {attempt} 次尝试失败: {error}, {delay}秒后重试...",
|
||||
"zepAllRetriesFailed": "Zep {operation} 在 {retries} 次尝试后仍失败: {error}",
|
||||
"graphSearch": "图谱搜索: graph_id={graphId}, query={query}...",
|
||||
"graphSearchOp": "图谱搜索(graph={graphId})",
|
||||
"searchComplete": "搜索完成: 找到 {count} 条相关事实",
|
||||
"zepSearchApiFallback": "Zep Search API失败,降级为本地搜索: {error}",
|
||||
"usingLocalSearch": "使用本地搜索: query={query}...",
|
||||
"localSearchComplete": "本地搜索完成: 找到 {count} 条相关事实",
|
||||
"localSearchFailed": "本地搜索失败: {error}",
|
||||
"fetchingAllNodes": "获取图谱 {graphId} 的所有节点...",
|
||||
"fetchedNodes": "获取到 {count} 个节点",
|
||||
"fetchingAllEdges": "获取图谱 {graphId} 的所有边...",
|
||||
"fetchedEdges": "获取到 {count} 条边",
|
||||
"fetchingNodeDetail": "获取节点详情: {uuid}...",
|
||||
"fetchNodeDetailOp": "获取节点详情(uuid={uuid}...)",
|
||||
"fetchNodeDetailFailed": "获取节点详情失败: {error}",
|
||||
"fetchingNodeEdges": "获取节点 {uuid}... 的相关边",
|
||||
"foundNodeEdges": "找到 {count} 条与节点相关的边",
|
||||
"fetchNodeEdgesFailed": "获取节点边失败: {error}",
|
||||
"fetchingEntitiesByType": "获取类型为 {type} 的实体...",
|
||||
"foundEntitiesByType": "找到 {count} 个 {type} 类型的实体",
|
||||
"fetchingEntitySummary": "获取实体 {name} 的关系摘要...",
|
||||
"fetchingGraphStats": "获取图谱 {graphId} 的统计信息...",
|
||||
"fetchingSimContext": "获取模拟上下文: {requirement}...",
|
||||
"insightForgeStart": "InsightForge 深度洞察检索: {query}...",
|
||||
"generatedSubQueries": "生成 {count} 个子问题",
|
||||
"insightForgeComplete": "InsightForge完成: {facts}条事实, {entities}个实体, {relationships}条关系",
|
||||
"generateSubQueriesFailed": "生成子问题失败: {error},使用默认子问题",
|
||||
"panoramaSearchStart": "PanoramaSearch 广度搜索: {query}...",
|
||||
"panoramaSearchComplete": "PanoramaSearch完成: {active}条有效, {historical}条历史",
|
||||
"quickSearchStart": "QuickSearch 简单搜索: {query}...",
|
||||
"quickSearchComplete": "QuickSearch完成: {count}条结果",
|
||||
"interviewAgentsStart": "InterviewAgents 深度采访(真实API): {requirement}...",
|
||||
"profilesNotFound": "未找到模拟 {simId} 的人设文件",
|
||||
"loadedProfiles": "加载到 {count} 个Agent人设",
|
||||
"selectedAgentsForInterview": "选择了 {count} 个Agent进行采访: {indices}",
|
||||
"generatedInterviewQuestions": "生成了 {count} 个采访问题",
|
||||
"callingBatchInterviewApi": "调用批量采访API(双平台): {count} 个Agent",
|
||||
"interviewApiReturned": "采访API返回: {count} 个结果, success={success}",
|
||||
"interviewApiReturnedFailure": "采访API返回失败: {error}",
|
||||
"interviewApiCallFailed": "采访API调用失败(环境未运行?): {error}",
|
||||
"interviewApiCallException": "采访API调用异常: {error}",
|
||||
"interviewAgentsComplete": "InterviewAgents完成: 采访了 {count} 个Agent(双平台)",
|
||||
"loadedRedditProfiles": "从 reddit_profiles.json 加载了 {count} 个人设",
|
||||
"readRedditProfilesFailed": "读取 reddit_profiles.json 失败: {error}",
|
||||
"loadedTwitterProfiles": "从 twitter_profiles.csv 加载了 {count} 个人设",
|
||||
"readTwitterProfilesFailed": "读取 twitter_profiles.csv 失败: {error}",
|
||||
"llmSelectAgentFailed": "LLM选择Agent失败,使用默认选择: {error}",
|
||||
"generateInterviewQuestionsFailed": "生成采访问题失败: {error}",
|
||||
"generateInterviewSummaryFailed": "生成采访摘要失败: {error}"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue