From 57263d533776ba7340ab818332f4fe7438442ff2 Mon Sep 17 00:00:00 2001 From: liyizhouAI Date: Mon, 8 Jun 2026 19:51:36 +0800 Subject: [PATCH] Preserve completed simulation replay flow --- backend/app/api/graph.py | 27 +++++++++-- backend/app/api/simulation.py | 44 ++++++++++++++++- frontend/src/components/Step3Simulation.vue | 54 +++++++++++++++++++-- 3 files changed, 115 insertions(+), 10 deletions(-) diff --git a/backend/app/api/graph.py b/backend/app/api/graph.py index dadb38d5..03e4f2e3 100644 --- a/backend/app/api/graph.py +++ b/backend/app/api/graph.py @@ -32,6 +32,21 @@ def allowed_file(filename: str) -> bool: return ext in Config.ALLOWED_EXTENSIONS +def _derive_project_name(raw_name: str, uploaded_files) -> str: + """Use a readable project name when the client did not provide one.""" + name = (raw_name or "").strip() + if name and name.lower() not in {"unnamed project", "untitled", "new project"}: + return name + + filenames = [getattr(f, "filename", "") for f in (uploaded_files or []) if getattr(f, "filename", "")] + if filenames: + stem = os.path.splitext(os.path.basename(filenames[0]))[0].strip() + if stem: + return stem + + return "Foresight 演示项目" + + def _prepare_graph_for_visualization(graph_data: dict) -> dict: """Keep graph visualization dense and readable by showing the top connected nodes.""" if not isinstance(graph_data, dict): @@ -205,24 +220,26 @@ def generate_ontology(): # 获取参数 simulation_requirement = request.form.get('simulation_requirement', '') - project_name = request.form.get('project_name', 'Unnamed Project') + raw_project_name = request.form.get('project_name', 'Unnamed Project') additional_context = request.form.get('additional_context', '') # token 追踪:标记当前 stage from ..utils import token_tracker token_tracker.set_stage("step1_ontology") + # 获取上传的文件 + uploaded_files = request.files.getlist('files') + project_name = _derive_project_name(raw_project_name, uploaded_files) + logger.debug(f"项目名称: {project_name}") logger.debug(f"模拟需求: {simulation_requirement[:100]}...") - + if not simulation_requirement: return jsonify({ "success": False, "error": t('api.requireSimulationRequirement') }), 400 - - # 获取上传的文件 - uploaded_files = request.files.getlist('files') + if not uploaded_files or all(not f.filename for f in uploaded_files): return jsonify({ "success": False, diff --git a/backend/app/api/simulation.py b/backend/app/api/simulation.py index c57af96f..ef48aada 100644 --- a/backend/app/api/simulation.py +++ b/backend/app/api/simulation.py @@ -38,6 +38,37 @@ logger = get_logger('foresight.api.simulation') INTERVIEW_PROMPT_PREFIX = "结合你的人设、所有的过往记忆与行动,不调用任何工具直接用文本回复我:" +def _runner_status_value(run_state) -> str: + if not run_state: + return "" + status = getattr(run_state, "runner_status", "") + return status.value if hasattr(status, "value") else str(status) + + +def _has_completed_run(run_state) -> bool: + if not run_state: + return False + status = _runner_status_value(run_state) + total_rounds = int(getattr(run_state, "total_rounds", 0) or 0) + current_round = int(getattr(run_state, "current_round", 0) or 0) + return status in {"completed", "stopped"} or (total_rounds > 0 and current_round >= total_rounds) + + +def _display_project_name(project) -> str: + if not project: + return "未命名项目" + name = (getattr(project, "name", "") or "").strip() + if name and name.lower() not in {"unnamed project", "untitled", "new project"}: + return name + files = getattr(project, "files", []) or [] + if files: + filename = files[0].get("filename") or files[0].get("original_filename") or "" + stem = os.path.splitext(os.path.basename(filename))[0].strip() + if stem: + return stem + return "未命名项目" + + def optimize_interview_prompt(prompt: str) -> str: """ 优化Interview提问,添加前缀避免Agent调用工具 @@ -1063,9 +1094,13 @@ def get_simulation_history(): run_state = SimulationRunner.get_run_state(sim.simulation_id) if run_state: sim_dict["current_round"] = run_state.current_round - sim_dict["runner_status"] = run_state.runner_status.value + sim_dict["runner_status"] = _runner_status_value(run_state) # 使用用户设置的 total_rounds,若无则使用推荐轮数 sim_dict["total_rounds"] = run_state.total_rounds if run_state.total_rounds > 0 else recommended_rounds + if _has_completed_run(run_state): + sim_dict["status"] = "completed" + sim_dict["current_round"] = sim_dict["total_rounds"] + sim_dict["has_replay"] = True else: sim_dict["current_round"] = 0 sim_dict["runner_status"] = "idle" @@ -1074,11 +1109,13 @@ def get_simulation_history(): # 获取关联项目的文件列表(最多3个) project = ProjectManager.get_project(sim.project_id) if project and hasattr(project, 'files') and project.files: + sim_dict["project_name"] = _display_project_name(project) sim_dict["files"] = [ {"filename": f.get("filename", "未知文件")} for f in project.files[:3] ] else: + sim_dict["project_name"] = sim_dict.get("project_name") or "未命名项目" sim_dict["files"] = [] # 获取关联的 report_id(查找该 simulation 最新的 report) @@ -2279,7 +2316,7 @@ def get_simulation_replay(simulation_id: str): if project: project_info = { "project_id": project.project_id, - "name": project.name, + "name": _display_project_name(project), "status": project.status.value if hasattr(project.status, 'value') else str(project.status), "graph_id": project.graph_id, "simulation_requirement": project.simulation_requirement, @@ -2421,6 +2458,9 @@ def get_simulation_replay(simulation_id: str): # ---------- 6. Workflow 时间线 ---------- status_str = state.status.value if hasattr(state.status, 'value') else str(state.status) + run_state = SimulationRunner.get_run_state(simulation_id) + if _has_completed_run(run_state): + status_str = "completed" workflow = [ { "step": 1, diff --git a/frontend/src/components/Step3Simulation.vue b/frontend/src/components/Step3Simulation.vue index 995781c7..7e0e2ea7 100644 --- a/frontend/src/components/Step3Simulation.vue +++ b/frontend/src/components/Step3Simulation.vue @@ -287,7 +287,7 @@