# handlers/project_handler.py - 项目级菜单处理(新建 / 列表 / 详情 / 删除) import json import os from rich.console import Console from rich.panel import Panel from rich.prompt import Confirm, Prompt from rich.rule import Rule import config from core_utils import ( active_reqs, generate_code, generate_signatures, merge_signatures_to_main, patch_and_save_signatures, write_readme, ) from core.code_generator import CodeGenerator from core.llm_client import LLMClient from core.requirement_analyzer import RequirementAnalyzer from database.db_manager import DBManager from database.models import Project, RawRequirement from ui.display import ( print_functional_requirements, print_module_summary, print_signatures_preview, print_change_history, ) from ui.prompts import ( interactive_add_req, interactive_adjust_modules, interactive_delete_req, interactive_edit_req, load_knowledge_optional, pause, select_project, ) from utils.file_handler import read_file_auto from utils.output_writer import ( build_project_output_dir, ensure_project_dir, validate_all_signatures, write_function_signatures_json, ) console = Console() db = DBManager() # ══════════════════════════════════════════════════════ # 菜单 1:新建项目 # ══════════════════════════════════════════════════════ def menu_create_project() -> None: console.print(Rule("[bold blue]新建项目[/bold blue]")) # ── 1. 项目基本信息 ────────────────────────────── project_name = Prompt.ask("📁 项目名称") if not project_name.strip(): console.print("[red]项目名称不能为空[/red]") return existing = db.get_project_by_name(project_name) if existing: if not Confirm.ask(f"⚠️ 项目 '{project_name}' 已存在,继续使用该项目?"): return project = existing console.print(f"[green]✓ 已加载项目: {project_name} (ID={project.id})[/green]") else: language = Prompt.ask( "💻 目标语言", default=config.DEFAULT_LANGUAGE, choices=["python", "javascript", "typescript", "java", "go", "rust"], ) description = Prompt.ask("📝 项目描述(可选)", default="") project = Project( name = project_name, language = language, output_dir = build_project_output_dir(project_name), description = description, ) project.id = db.create_project(project) console.print(f"[green]✓ 项目已创建: {project_name} (ID={project.id})[/green]") # ── 2. 知识库(可选)──────────────────────────── knowledge_text = load_knowledge_optional() # ── 3. 原始需求输入 ────────────────────────────── console.print("\n[bold]请选择需求来源:[/bold]") source = Prompt.ask("来源", choices=["text", "file"], default="text") if source == "file": file_path = Prompt.ask("需求文件路径") raw_text = read_file_auto(file_path) source_name = file_path source_type = "file" else: console.print("[dim]请输入原始需求(输入空行结束):[/dim]") lines = [] while True: line = input() if line == "": break lines.append(line) raw_text = "\n".join(lines).strip() source_name = "" source_type = "text" if not raw_text: console.print("[red]原始需求不能为空[/red]") return raw_req = RawRequirement( project_id = project.id, content = raw_text, source_type = source_type, source_name = source_name, knowledge = knowledge_text, ) raw_req_id = db.create_raw_requirement(raw_req) console.print(f"[green]✓ 原始需求已保存 (ID={raw_req_id})[/green]") # ── 4. LLM 分解功能需求 ────────────────────────── llm = LLMClient() analyzer = RequirementAnalyzer(llm) with console.status("[cyan]LLM 正在分解需求...[/cyan]"): func_reqs = analyzer.decompose( raw_requirement = raw_text, project_id = project.id, raw_req_id = raw_req_id, knowledge = knowledge_text, ) console.print(f"[green]✓ 分解完成,共 {len(func_reqs)} 条功能需求[/green]") print_functional_requirements(func_reqs) while True: action = Prompt.ask( "操作", choices=["continue", "edit", "delete", "add"], default="continue", ) if action == "continue": break elif action == "edit": func_reqs = interactive_edit_req(func_reqs) elif action == "delete": func_reqs = interactive_delete_req(func_reqs) elif action == "add": func_reqs = interactive_add_req(func_reqs, project, raw_req_id) for req in func_reqs: req.id = db.create_functional_requirement(req) console.print(f"[green]✓ 功能需求已持久化,共 {len(func_reqs)} 条[/green]") # ── 5. 模块分类 ────────────────────────────────── with console.status("[cyan]LLM 正在进行模块分类...[/cyan]"): module_map = analyzer.classify_modules(func_reqs, knowledge_text) name_to_module = {item["function_name"]: item["module"] for item in module_map} for req in func_reqs: if req.function_name in name_to_module: req.module = name_to_module[req.function_name] db.update_functional_requirement(req) print_module_summary(func_reqs) if Confirm.ask("是否手动调整模块归属?", default=False): func_reqs = interactive_adjust_modules(func_reqs) # ── 6. 输出目录 ────────────────────────────────── output_dir = ensure_project_dir(project.name) # ── 7. 生成函数签名 ────────────────────────────── console.print("\n[bold]Step · 生成函数签名[/bold]", style="blue") signatures = generate_signatures(analyzer, func_reqs, knowledge_text) json_path = write_function_signatures_json( output_dir = output_dir, signatures = signatures, project_name = project.name, project_description = project.description or "", file_name = "function_signatures.json", ) console.print(f"[green]✓ 签名 JSON 已写入: {json_path}[/green]") print_signatures_preview(signatures) errors = validate_all_signatures(signatures) if errors: console.print(f"[yellow]⚠️ {len(errors)} 个签名存在结构问题[/yellow]") # ── 8. 生成代码文件 ────────────────────────────── console.print("\n[bold]Step · 生成代码文件[/bold]", style="blue") generator = CodeGenerator(llm) code_files = generate_code( generator = generator, project = project, func_reqs = func_reqs, output_dir = output_dir, knowledge_text = knowledge_text, signatures = signatures, ) for cf in code_files: db.upsert_code_file(cf) for req in func_reqs: req.status = "generated" db.update_functional_requirement(req) console.print(f"[green]✓ 代码生成完成,共 {len(code_files)} 个文件[/green]") # ── 9. 生成 README ─────────────────────────────── write_readme(project, func_reqs, output_dir) # ── 10. 回填签名 URL ───────────────────────────── patch_and_save_signatures( project = project, signatures = signatures, code_files = code_files, output_dir = output_dir, file_name = "function_signatures.json", ) console.print(Panel.fit( f"[bold green]🎉 项目创建完成![/bold green]\n" f"输出目录: [cyan]{output_dir}[/cyan]", border_style="green", )) pause() # ══════════════════════════════════════════════════════ # 菜单 3:查看所有项目 # ══════════════════════════════════════════════════════ def menu_list_projects() -> None: console.print(Rule("[bold blue]所有项目[/bold blue]")) from ui.display import print_projects_table print_projects_table(db.list_projects()) pause() # ══════════════════════════════════════════════════════ # 菜单 4:查看项目详情 # ══════════════════════════════════════════════════════ def menu_project_detail() -> None: console.print(Rule("[bold blue]查看项目详情[/bold blue]")) project = select_project("请选择要查看的项目 ID") if not project: return console.print(Panel( f"[bold]项目名称:[/bold]{project.name}\n" f"[bold]语言: [/bold]{project.language}\n" f"[bold]描述: [/bold]{project.description or '(无)'}\n" f"[bold]输出目录:[/bold]{project.output_dir or '(未生成)'}", title=f"📁 项目 ID={project.id}", border_style="cyan", )) func_reqs = db.list_functional_requirements(project.id) print_functional_requirements(active_reqs(func_reqs)) if func_reqs: print_module_summary(func_reqs) # 变更历史 histories = db.list_change_histories(project.id) print_change_history(histories) # 签名 JSON 预览 if project.output_dir: sig_path = os.path.join(project.output_dir, "function_signatures.json") if os.path.exists(sig_path): with open(sig_path, "r", encoding="utf-8") as f: doc = json.load(f) sigs = doc.get("functions", []) if sigs: console.print(f"\n[dim]签名 JSON 共 {len(sigs)} 条:{sig_path}[/dim]") print_signatures_preview(sigs) pause() # ══════════════════════════════════════════════════════ # 菜单 5:删除指定项目 # ══════════════════════════════════════════════════════ def menu_delete_project() -> None: console.print(Rule("[bold red]删除指定项目[/bold red]")) project = select_project("请选择要删除的项目 ID") if not project: return console.print(Panel( f"[bold]项目名称:[/bold]{project.name}\n" f"[bold]语言: [/bold]{project.language}\n" f"[bold]描述: [/bold]{project.description or '(无)'}", title="⚠️ 即将删除以下项目", border_style="red", )) if not Confirm.ask( f"[bold red]确认删除项目 '{project.name}'(ID={project.id})?" f"此操作不可恢复![/bold red]", default=False, ): console.print("[yellow]已取消删除。[/yellow]") pause() return confirm_name = Prompt.ask("请再次输入项目名称以确认") if confirm_name != project.name: console.print("[red]项目名称不匹配,删除已取消。[/red]") pause() return db.delete_project(project.id) console.print(f"[green]✓ 项目 '{project.name}'(ID={project.id})已删除。[/green]") pause()