项目查询与变更
This commit is contained in:
parent
6bd0bfda4d
commit
34b3f10c07
|
|
@ -5,8 +5,8 @@ from dotenv import load_dotenv
|
|||
load_dotenv()
|
||||
|
||||
# ── LLM ──────────────────────────────────────────────
|
||||
LLM_API_KEY = os.getenv("OPENAI_API_KEY", "sk-AUmOuFI731Ty5Nob38jY26d8lydfDT-QkE2giqb0sCuPCAE2JH6zjLM4lZLpvL5WMYPOocaMe2FwVDmqM_9KimmKACjR")
|
||||
LLM_API_BASE = os.getenv("OPENAI_BASE_URL", "https://openapi.monica.im/v1")
|
||||
LLM_API_KEY = os.getenv("OPENAI_API_KEY", "")
|
||||
LLM_API_BASE = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
|
||||
LLM_MODEL = os.getenv("LLM_MODEL", "gpt-4o")
|
||||
LLM_TIMEOUT = int(os.getenv("LLM_TIMEOUT", "60"))
|
||||
LLM_MAX_RETRY = int(os.getenv("LLM_MAX_RETRY", "3"))
|
||||
|
|
@ -121,3 +121,5 @@ MODULE_CLASSIFY_PROMPT_TEMPLATE = """\
|
|||
|
||||
只输出 JSON 数组,不要有任何额外说明。
|
||||
"""
|
||||
|
||||
CHANGE_HISTORY_FILE = "change_history.json"
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
# core/requirement_analyzer.py - 需求分解 & 函数签名生成
|
||||
# core/requirement_analyzer.py - 需求分解、模块分类、函数签名生成
|
||||
import json
|
||||
from typing import List, Optional, Callable
|
||||
|
||||
import config
|
||||
from core.llm_client import LLMClient
|
||||
from database.models import FunctionalRequirement
|
||||
from database.models import FunctionalRequirement, ChangeHistory
|
||||
from database.db_manager import DBManager
|
||||
|
||||
db = DBManager()
|
||||
|
||||
|
||||
class RequirementAnalyzer:
|
||||
|
|
@ -40,8 +43,8 @@ class RequirementAnalyzer:
|
|||
f"【参考知识库】\n{knowledge}\n" if knowledge else ""
|
||||
)
|
||||
prompt = config.DECOMPOSE_PROMPT_TEMPLATE.format(
|
||||
raw_requirement = raw_requirement,
|
||||
knowledge_section = knowledge_section,
|
||||
raw_requirement=raw_requirement,
|
||||
knowledge_section=knowledge_section,
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
@ -54,16 +57,16 @@ class RequirementAnalyzer:
|
|||
reqs = []
|
||||
for i, item in enumerate(items, 1):
|
||||
req = FunctionalRequirement(
|
||||
project_id = project_id,
|
||||
raw_req_id = raw_req_id,
|
||||
index_no = i,
|
||||
title = item.get("title", f"功能{i}"),
|
||||
description = item.get("description", ""),
|
||||
function_name = item.get("function_name", f"function_{i}"),
|
||||
priority = item.get("priority", "medium"),
|
||||
module = item.get("module", config.DEFAULT_MODULE),
|
||||
status = "pending",
|
||||
is_custom = False,
|
||||
project_id=project_id,
|
||||
raw_req_id=raw_req_id,
|
||||
index_no=i,
|
||||
title=item.get("title", f"功能{i}"),
|
||||
description=item.get("description", ""),
|
||||
function_name=item.get("function_name", f"function_{i}"),
|
||||
priority=item.get("priority", "medium"),
|
||||
module=item.get("module", config.DEFAULT_MODULE),
|
||||
status="pending",
|
||||
is_custom=False,
|
||||
)
|
||||
reqs.append(req)
|
||||
return reqs
|
||||
|
|
@ -98,8 +101,8 @@ class RequirementAnalyzer:
|
|||
]
|
||||
knowledge_section = f"【参考知识库】\n{knowledge}\n" if knowledge else ""
|
||||
prompt = config.MODULE_CLASSIFY_PROMPT_TEMPLATE.format(
|
||||
requirements_json = json.dumps(req_list, ensure_ascii=False, indent=2),
|
||||
knowledge_section = knowledge_section,
|
||||
requirements_json=json.dumps(req_list, ensure_ascii=False, indent=2),
|
||||
knowledge_section=knowledge_section,
|
||||
)
|
||||
try:
|
||||
result = self.llm.chat_json(prompt)
|
||||
|
|
@ -135,12 +138,12 @@ class RequirementAnalyzer:
|
|||
"""
|
||||
knowledge_section = f"【参考知识库】\n{knowledge}\n" if knowledge else ""
|
||||
prompt = config.FUNC_SIGNATURE_PROMPT_TEMPLATE.format(
|
||||
requirement_id = requirement_id or f"REQ.{func_req.index_no:02d}",
|
||||
title = func_req.title,
|
||||
description = func_req.description,
|
||||
function_name = func_req.function_name,
|
||||
module = func_req.module or config.DEFAULT_MODULE,
|
||||
knowledge_section = knowledge_section,
|
||||
requirement_id=requirement_id or f"REQ.{func_req.index_no:02d}",
|
||||
title=func_req.title,
|
||||
description=func_req.description,
|
||||
function_name=func_req.function_name,
|
||||
module=func_req.module or config.DEFAULT_MODULE,
|
||||
knowledge_section=knowledge_section,
|
||||
)
|
||||
try:
|
||||
sig = self.llm.chat_json(prompt)
|
||||
|
|
@ -207,3 +210,36 @@ class RequirementAnalyzer:
|
|||
"on_failure": {"value": "None", "description": "失败时返回 None"},
|
||||
},
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════
|
||||
# 记录变更
|
||||
# ══════════════════════════════════════════════════
|
||||
|
||||
def log_change(self, project_id: int, changes: str) -> None:
|
||||
"""记录需求变更到数据库"""
|
||||
change = ChangeHistory(project_id=project_id, changes=changes)
|
||||
db.create_change_history(change)
|
||||
|
||||
def get_change_history(self, project_id: int) -> List[ChangeHistory]:
|
||||
"""查询项目变更历史"""
|
||||
return db.list_change_history(project_id)
|
||||
|
||||
def analyze_changes(self, old_reqs: List[FunctionalRequirement], new_reqs: List[FunctionalRequirement]) -> List[
|
||||
str]:
|
||||
"""
|
||||
分析需求变更,返回需要变更的代码文件列表。
|
||||
|
||||
Args:
|
||||
old_reqs: 旧的功能需求列表
|
||||
new_reqs: 新的功能需求列表
|
||||
|
||||
Returns:
|
||||
需要变更的文件列表
|
||||
"""
|
||||
changed_files = []
|
||||
old_func_names = {req.function_name: req for req in old_reqs}
|
||||
for new_req in new_reqs:
|
||||
old_req = old_func_names.get(new_req.function_name)
|
||||
if not old_req or old_req.description != new_req.description or old_req.module != new_req.module:
|
||||
changed_files.append(new_req.function_name)
|
||||
return changed_files
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
# database/db_manager.py - 数据库 CRUD 操作封装
|
||||
import os
|
||||
import shutil
|
||||
from typing import List, Optional, Dict, Any
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
|
||||
import config
|
||||
from database.models import Base, Project, RawRequirement, FunctionalRequirement, CodeFile
|
||||
from database.models import Base, Project, RawRequirement, FunctionalRequirement, CodeFile, ChangeHistory
|
||||
|
||||
|
||||
class DBManager:
|
||||
|
|
@ -48,144 +47,16 @@ class DBManager:
|
|||
s.commit()
|
||||
|
||||
def list_projects(self) -> List[Project]:
|
||||
"""返回所有项目(按创建时间倒序)"""
|
||||
with self._session() as s:
|
||||
return s.query(Project).order_by(Project.created_at.desc()).all()
|
||||
|
||||
def delete_project(self, project_id: int, delete_output: bool = False) -> bool:
|
||||
"""
|
||||
删除指定项目及其所有关联数据(级联删除)。
|
||||
|
||||
Args:
|
||||
project_id: 项目 ID
|
||||
delete_output: 是否同时删除磁盘上的输出目录
|
||||
|
||||
Returns:
|
||||
True 表示删除成功,False 表示项目不存在
|
||||
"""
|
||||
def delete_project(self, project_id: int) -> None:
|
||||
with self._session() as s:
|
||||
project = s.get(Project, project_id)
|
||||
if project is None:
|
||||
return False
|
||||
output_dir = project.output_dir
|
||||
if project:
|
||||
s.delete(project)
|
||||
s.commit()
|
||||
|
||||
# 可选:删除磁盘输出目录
|
||||
if delete_output and output_dir and os.path.isdir(output_dir):
|
||||
shutil.rmtree(output_dir, ignore_errors=True)
|
||||
|
||||
return True
|
||||
|
||||
def get_project_stats(self, project_id: int) -> Dict[str, int]:
|
||||
"""
|
||||
获取项目统计信息:需求数、已生成代码数、模块数。
|
||||
|
||||
Returns:
|
||||
{"raw_req_count": n, "func_req_count": n,
|
||||
"generated_count": n, "module_count": n, "code_file_count": n}
|
||||
"""
|
||||
with self._session() as s:
|
||||
raw_count = s.query(RawRequirement).filter_by(project_id=project_id).count()
|
||||
func_reqs = (
|
||||
s.query(FunctionalRequirement)
|
||||
.filter_by(project_id=project_id)
|
||||
.all()
|
||||
)
|
||||
gen_count = sum(1 for r in func_reqs if r.status == "generated")
|
||||
modules = {r.module or config.DEFAULT_MODULE for r in func_reqs}
|
||||
code_count = (
|
||||
s.query(CodeFile)
|
||||
.join(FunctionalRequirement)
|
||||
.filter(FunctionalRequirement.project_id == project_id)
|
||||
.count()
|
||||
)
|
||||
return {
|
||||
"raw_req_count": raw_count,
|
||||
"func_req_count": len(func_reqs),
|
||||
"generated_count": gen_count,
|
||||
"module_count": len(modules),
|
||||
"code_file_count": code_count,
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════
|
||||
# Project Full Info(需求-模块-代码关系)
|
||||
# ══════════════════════════════════════════════════
|
||||
|
||||
def get_project_full_info(self, project_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
获取项目完整信息,包含需求-模块-代码之间的关系树。
|
||||
|
||||
Returns::
|
||||
|
||||
{
|
||||
"project": Project,
|
||||
"stats": {...},
|
||||
"modules": {
|
||||
"<module_name>": {
|
||||
"requirements": [
|
||||
{
|
||||
"req": FunctionalRequirement,
|
||||
"code_files": [CodeFile, ...]
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
},
|
||||
"raw_requirements": [RawRequirement, ...]
|
||||
}
|
||||
|
||||
Returns None 若项目不存在。
|
||||
"""
|
||||
with self._session() as s:
|
||||
project = s.get(Project, project_id)
|
||||
if project is None:
|
||||
return None
|
||||
|
||||
raw_reqs = (
|
||||
s.query(RawRequirement)
|
||||
.filter_by(project_id=project_id)
|
||||
.order_by(RawRequirement.created_at)
|
||||
.all()
|
||||
)
|
||||
func_reqs = (
|
||||
s.query(FunctionalRequirement)
|
||||
.filter_by(project_id=project_id)
|
||||
.order_by(FunctionalRequirement.index_no)
|
||||
.all()
|
||||
)
|
||||
code_files = (
|
||||
s.query(CodeFile)
|
||||
.join(FunctionalRequirement)
|
||||
.filter(FunctionalRequirement.project_id == project_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
# 构建 func_req_id → [CodeFile] 映射
|
||||
code_map: Dict[int, List[CodeFile]] = {}
|
||||
for cf in code_files:
|
||||
code_map.setdefault(cf.func_req_id, []).append(cf)
|
||||
|
||||
# 按模块分组
|
||||
modules: Dict[str, Dict] = {}
|
||||
for req in func_reqs:
|
||||
mod = req.module or config.DEFAULT_MODULE
|
||||
modules.setdefault(mod, {"requirements": []})
|
||||
modules[mod]["requirements"].append({
|
||||
"req": req,
|
||||
"code_files": code_map.get(req.id, []),
|
||||
})
|
||||
|
||||
stats = self.get_project_stats(project_id)
|
||||
|
||||
return {
|
||||
"project": project,
|
||||
"stats": stats,
|
||||
"modules": modules,
|
||||
"raw_requirements": raw_reqs,
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════
|
||||
# RawRequirement
|
||||
# ══════════════════════════════════════════════════
|
||||
|
|
@ -237,22 +108,6 @@ class DBManager:
|
|||
s.delete(obj)
|
||||
s.commit()
|
||||
|
||||
def bulk_update_modules(self, updates: List[dict]) -> None:
|
||||
"""
|
||||
批量更新功能需求的 module 字段。
|
||||
|
||||
Args:
|
||||
updates: [{"function_name": "...", "module": "..."}, ...]
|
||||
"""
|
||||
with self._session() as s:
|
||||
name_to_module = {u["function_name"]: u["module"] for u in updates}
|
||||
reqs = s.query(FunctionalRequirement).filter(
|
||||
FunctionalRequirement.function_name.in_(name_to_module.keys())
|
||||
).all()
|
||||
for req in reqs:
|
||||
req.module = name_to_module.get(req.function_name, config.DEFAULT_MODULE)
|
||||
s.commit()
|
||||
|
||||
# ══════════════════════════════════════════════════
|
||||
# CodeFile
|
||||
# ══════════════════════════════════════════════════
|
||||
|
|
@ -286,3 +141,18 @@ class DBManager:
|
|||
.filter(FunctionalRequirement.project_id == project_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
# ══════════════════════════════════════════════════
|
||||
# ChangeHistory
|
||||
# ══════════════════════════════════════════════════
|
||||
|
||||
def create_change_history(self, change: ChangeHistory) -> int:
|
||||
with self._session() as s:
|
||||
s.add(change)
|
||||
s.commit()
|
||||
s.refresh(change)
|
||||
return change.id
|
||||
|
||||
def list_change_history(self, project_id: int) -> List[ChangeHistory]:
|
||||
with self._session() as s:
|
||||
return s.query(ChangeHistory).filter_by(project_id=project_id).order_by(ChangeHistory.change_time.desc()).all()
|
||||
|
|
@ -23,6 +23,7 @@ class Project(Base):
|
|||
|
||||
raw_requirements = relationship("RawRequirement", back_populates="project", cascade="all, delete-orphan")
|
||||
functional_requirements = relationship("FunctionalRequirement", back_populates="project", cascade="all, delete-orphan")
|
||||
change_history = relationship("ChangeHistory", back_populates="project", cascade="all, delete-orphan")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Project(id={self.id}, name={self.name!r}, language={self.language!r})>"
|
||||
|
|
@ -93,3 +94,19 @@ class CodeFile(Base):
|
|||
|
||||
def __repr__(self):
|
||||
return f"<CodeFile(id={self.id}, file_name={self.file_name!r}, module={self.module!r})>"
|
||||
|
||||
|
||||
class ChangeHistory(Base):
|
||||
"""变更历史表"""
|
||||
__tablename__ = "change_history"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
|
||||
change_time = Column(DateTime, default=datetime.utcnow)
|
||||
changes = Column(Text, nullable=False) # 记录变更内容
|
||||
status = Column(String(50), nullable=False, default="pending") # pending / confirmed
|
||||
|
||||
project = relationship("Project", back_populates="change_history")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ChangeHistory(id={self.id}, project_id={self.project_id}, changes={self.changes[:20]}...)>"
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +1,8 @@
|
|||
# utils/output_writer.py - 代码文件 & JSON 输出 & 项目信息渲染
|
||||
# utils/output_writer.py - 代码文件 & JSON 输出工具
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any
|
||||
from typing import Dict, List
|
||||
|
||||
import config
|
||||
|
||||
|
|
@ -85,6 +85,15 @@ def build_signatures_document(
|
|||
project_description: str,
|
||||
signatures: List[dict],
|
||||
) -> dict:
|
||||
"""
|
||||
构建顶层签名文档结构::
|
||||
|
||||
{
|
||||
"project": "<name>",
|
||||
"description": "<description>",
|
||||
"functions": [ ... ]
|
||||
}
|
||||
"""
|
||||
return {
|
||||
"project": project_name,
|
||||
"description": project_description or "",
|
||||
|
|
@ -96,6 +105,16 @@ def patch_signatures_with_url(
|
|||
signatures: List[dict],
|
||||
func_name_to_url: Dict[str, str],
|
||||
) -> List[dict]:
|
||||
"""
|
||||
将代码文件路径回写到签名的 "url" 字段(紧跟 "type" 之后)。
|
||||
|
||||
Args:
|
||||
signatures: 签名列表(in-place 修改)
|
||||
func_name_to_url: {函数名: 文件绝对路径}
|
||||
|
||||
Returns:
|
||||
修改后的签名列表
|
||||
"""
|
||||
for sig in signatures:
|
||||
url = func_name_to_url.get(sig.get("name", ""), "")
|
||||
_insert_field_after(sig, after_key="type", new_key="url", new_value=url)
|
||||
|
|
@ -103,6 +122,7 @@ def patch_signatures_with_url(
|
|||
|
||||
|
||||
def _insert_field_after(d: dict, after_key: str, new_key: str, new_value) -> None:
|
||||
"""在有序 dict 中将 new_key 插入到 after_key 之后"""
|
||||
if new_key in d:
|
||||
d[new_key] = new_value
|
||||
return
|
||||
|
|
@ -120,6 +140,7 @@ def write_function_signatures_json(
|
|||
project_description: str,
|
||||
file_name: str = "function_signatures.json",
|
||||
) -> str:
|
||||
"""将签名列表导出为 JSON 文件"""
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
document = build_signatures_document(project_name, project_description, signatures)
|
||||
file_path = os.path.join(output_dir, file_name)
|
||||
|
|
@ -133,7 +154,9 @@ def write_function_signatures_json(
|
|||
# ══════════════════════════════════════════════════════
|
||||
|
||||
def validate_signature_schema(signature: dict) -> List[str]:
|
||||
"""校验单个函数签名结构,返回错误列表(空列表表示通过)"""
|
||||
errors: List[str] = []
|
||||
|
||||
for key in ("name", "requirement_id", "description", "type", "parameters"):
|
||||
if key not in signature:
|
||||
errors.append(f"缺少顶层字段: '{key}'")
|
||||
|
|
@ -192,192 +215,9 @@ def validate_signature_schema(signature: dict) -> List[str]:
|
|||
|
||||
|
||||
def validate_all_signatures(signatures: List[dict]) -> Dict[str, List[str]]:
|
||||
"""批量校验,返回 {函数名: [错误]} 字典(仅含有错误的条目)"""
|
||||
return {
|
||||
sig.get("name", f"unknown_{i}"): errs
|
||||
for i, sig in enumerate(signatures)
|
||||
if (errs := validate_signature_schema(sig))
|
||||
}
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# 项目信息渲染(需求-模块-代码关系树)
|
||||
# ══════════════════════════════════════════════════════
|
||||
|
||||
def render_project_info(full_info: Dict[str, Any], console) -> None:
|
||||
"""
|
||||
使用 rich 将项目完整信息(需求-模块-代码关系)渲染到终端。
|
||||
|
||||
Args:
|
||||
full_info: DBManager.get_project_full_info() 返回的字典
|
||||
console: rich.console.Console 实例
|
||||
"""
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
from rich.tree import Tree
|
||||
from rich.text import Text
|
||||
|
||||
project = full_info["project"]
|
||||
stats = full_info["stats"]
|
||||
modules = full_info["modules"]
|
||||
raw_reqs = full_info["raw_requirements"]
|
||||
|
||||
# ── 项目基本信息 ──────────────────────────────────
|
||||
info_table = Table(show_header=False, box=None, padding=(0, 2))
|
||||
info_table.add_column("key", style="bold cyan", width=14)
|
||||
info_table.add_column("value", style="white")
|
||||
info_table.add_row("项目 ID", str(project.id))
|
||||
info_table.add_row("项目名称", project.name)
|
||||
info_table.add_row("目标语言", project.language)
|
||||
info_table.add_row("描述", project.description or "(无)")
|
||||
info_table.add_row("输出目录", project.output_dir or "(未生成)")
|
||||
info_table.add_row("创建时间", str(project.created_at)[:19])
|
||||
info_table.add_row("更新时间", str(project.updated_at)[:19])
|
||||
console.print(Panel(info_table, title="[bold cyan]📁 项目信息[/bold cyan]",
|
||||
border_style="cyan"))
|
||||
|
||||
# ── 统计摘要 ──────────────────────────────────────
|
||||
stat_table = Table(show_header=False, box=None, padding=(0, 3))
|
||||
stat_table.add_column("k", style="bold yellow", width=16)
|
||||
stat_table.add_column("v", style="white")
|
||||
stat_table.add_row("原始需求数", str(stats["raw_req_count"]))
|
||||
stat_table.add_row("功能需求数", str(stats["func_req_count"]))
|
||||
stat_table.add_row("已生成代码", f"{stats['generated_count']} / {stats['func_req_count']}")
|
||||
stat_table.add_row("功能模块数", str(stats["module_count"]))
|
||||
stat_table.add_row("代码文件数", str(stats["code_file_count"]))
|
||||
console.print(Panel(stat_table, title="[bold yellow]📊 统计摘要[/bold yellow]",
|
||||
border_style="yellow"))
|
||||
|
||||
# ── 原始需求列表 ──────────────────────────────────
|
||||
if raw_reqs:
|
||||
raw_table = Table(title="📝 原始需求", show_lines=True)
|
||||
raw_table.add_column("ID", style="dim", width=5)
|
||||
raw_table.add_column("来源", style="cyan", width=8)
|
||||
raw_table.add_column("文件名", width=20)
|
||||
raw_table.add_column("内容摘要", width=55)
|
||||
raw_table.add_column("创建时间", width=20)
|
||||
for rr in raw_reqs:
|
||||
raw_table.add_row(
|
||||
str(rr.id),
|
||||
rr.source_type,
|
||||
rr.source_name or "-",
|
||||
(rr.content[:80] + "...") if len(rr.content) > 80 else rr.content,
|
||||
str(rr.created_at)[:19],
|
||||
)
|
||||
console.print(raw_table)
|
||||
|
||||
# ── 需求-模块-代码 关系树 ─────────────────────────
|
||||
if not modules:
|
||||
console.print("[dim]暂无功能需求[/dim]")
|
||||
return
|
||||
|
||||
priority_color = {"high": "red", "medium": "yellow", "low": "green"}
|
||||
status_icon = {"generated": "✅", "pending": "⏳", "failed": "❌"}
|
||||
|
||||
root = Tree(
|
||||
f"[bold cyan]🗂 {project.name}[/bold cyan] "
|
||||
f"[dim]({stats['func_req_count']} 需求 · "
|
||||
f"{stats['module_count']} 模块 · "
|
||||
f"{stats['code_file_count']} 代码文件)[/dim]"
|
||||
)
|
||||
|
||||
for module_name in sorted(modules.keys()):
|
||||
mod_data = modules[module_name]
|
||||
req_list = mod_data["requirements"]
|
||||
mod_count = len(req_list)
|
||||
gen_count = sum(1 for r in req_list if r["req"].status == "generated")
|
||||
|
||||
mod_branch = root.add(
|
||||
f"[magenta bold]📦 {module_name}[/magenta bold] "
|
||||
f"[dim]{gen_count}/{mod_count} 已生成[/dim]"
|
||||
)
|
||||
|
||||
for item in req_list:
|
||||
req = item["req"]
|
||||
code_files = item["code_files"]
|
||||
p_color = priority_color.get(req.priority, "white")
|
||||
s_icon = status_icon.get(req.status, "❓")
|
||||
|
||||
req_label = (
|
||||
f"{s_icon} [bold]{req.title}[/bold] "
|
||||
f"[{p_color}][{req.priority}][/{p_color}] "
|
||||
f"[dim]REQ.{req.index_no:02d} · ID={req.id}[/dim]"
|
||||
)
|
||||
req_branch = mod_branch.add(req_label)
|
||||
|
||||
# 需求详情子节点
|
||||
req_branch.add(
|
||||
f"[dim]函数名: [/dim][code]{req.function_name}[/code]"
|
||||
)
|
||||
req_branch.add(
|
||||
f"[dim]描述: [/dim]{req.description[:80]}"
|
||||
+ ("..." if len(req.description) > 80 else "")
|
||||
)
|
||||
|
||||
# 代码文件子节点
|
||||
if code_files:
|
||||
code_branch = req_branch.add("[green]📄 代码文件[/green]")
|
||||
for cf in code_files:
|
||||
exists = os.path.exists(cf.file_path)
|
||||
icon = "🟢" if exists else "🔴"
|
||||
cf_text = Text()
|
||||
cf_text.append(f"{icon} ", style="")
|
||||
cf_text.append(cf.file_name, style="bold green" if exists else "bold red")
|
||||
cf_text.append(f" [{cf.language}]", style="dim")
|
||||
cf_text.append(f" {cf.file_path}", style="dim italic")
|
||||
code_branch.add(cf_text)
|
||||
else:
|
||||
req_branch.add("[dim]📄 暂无代码文件[/dim]")
|
||||
|
||||
console.print(Panel(root, title="[bold green]🔗 需求 · 模块 · 代码 关系树[/bold green]",
|
||||
border_style="green", padding=(1, 2)))
|
||||
|
||||
|
||||
def render_project_list(projects: list, stats_map: Dict[int, Dict], console) -> None:
|
||||
"""
|
||||
渲染所有项目列表(含统计列)。
|
||||
|
||||
Args:
|
||||
projects: Project 对象列表
|
||||
stats_map: {project_id: stats_dict}
|
||||
console: rich.console.Console 实例
|
||||
"""
|
||||
from rich.table import Table
|
||||
|
||||
if not projects:
|
||||
console.print("[dim]暂无项目,请先运行 `python main.py run` 创建项目。[/dim]")
|
||||
return
|
||||
|
||||
table = Table(title=f"📋 项目列表(共 {len(projects)} 个)", show_lines=True)
|
||||
table.add_column("ID", style="cyan bold", width=5)
|
||||
table.add_column("项目名称", style="bold", width=22)
|
||||
table.add_column("语言", style="magenta", width=8)
|
||||
table.add_column("功能需求", style="yellow", width=8)
|
||||
table.add_column("已生成", style="green", width=8)
|
||||
table.add_column("模块数", width=6)
|
||||
table.add_column("代码文件", width=8)
|
||||
table.add_column("描述", width=28)
|
||||
table.add_column("创建时间", width=20)
|
||||
|
||||
for p in projects:
|
||||
st = stats_map.get(p.id, {})
|
||||
func_total = st.get("func_req_count", 0)
|
||||
gen_count = st.get("generated_count", 0)
|
||||
gen_cell = (
|
||||
f"[green]{gen_count}[/green]"
|
||||
if gen_count == func_total and func_total > 0
|
||||
else f"[yellow]{gen_count}[/yellow]"
|
||||
)
|
||||
table.add_row(
|
||||
str(p.id),
|
||||
p.name,
|
||||
p.language,
|
||||
str(func_total),
|
||||
gen_cell,
|
||||
str(st.get("module_count", 0)),
|
||||
str(st.get("code_file_count", 0)),
|
||||
(p.description[:28] + "...") if p.description and len(p.description) > 28
|
||||
else (p.description or "[dim](无)[/dim]"),
|
||||
str(p.created_at)[:19],
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
Loading…
Reference in New Issue