AIDeveloper-PC/requirements_generator/handlers/project_handler.py

307 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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()