307 lines
12 KiB
Python
307 lines
12 KiB
Python
|
|
# 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()
|