493 lines
18 KiB
Python
493 lines
18 KiB
Python
# ui/prompts.py - 交互式输入 / 选择工具函数
|
||
|
||
from typing import List, Optional
|
||
|
||
from rich.console import Console
|
||
from rich.prompt import Confirm, Prompt
|
||
|
||
import config
|
||
from database.db_manager import DBManager
|
||
from database.models import FunctionalRequirement, Project
|
||
from ui.display import print_projects_table
|
||
from utils.file_handler import merge_knowledge_files
|
||
|
||
console = Console()
|
||
db = DBManager()
|
||
|
||
|
||
def select_project(prompt_text: str = "请选择项目 ID") -> Optional[Project]:
|
||
"""展示项目列表,交互选择并返回 Project 对象;输入 0 返回 None。"""
|
||
projects = db.list_projects()
|
||
if not projects:
|
||
console.print("[yellow]当前暂无项目,请先新建项目。[/yellow]")
|
||
return None
|
||
print_projects_table(projects)
|
||
pid_str = Prompt.ask(f"{prompt_text}(输入 0 返回)", default="0")
|
||
if pid_str == "0":
|
||
return None
|
||
try:
|
||
pid = int(pid_str)
|
||
except ValueError:
|
||
console.print("[red]ID 必须为整数[/red]")
|
||
return None
|
||
project = db.get_project_by_id(pid)
|
||
if not project:
|
||
console.print(f"[red]项目 ID={pid} 不存在[/red]")
|
||
return None
|
||
return project
|
||
|
||
|
||
def load_knowledge_optional() -> str:
|
||
"""询问是否加载知识库文件,加载后返回合并文本;否则返回空字符串。"""
|
||
if Confirm.ask("是否加载知识库文件?", default=False):
|
||
kb_path = Prompt.ask("知识库文件路径(多个用逗号分隔)")
|
||
paths = [p.strip() for p in kb_path.split(",") if p.strip()]
|
||
text = merge_knowledge_files(paths)
|
||
console.print(f"[dim]已加载 {len(paths)} 个知识库文件[/dim]")
|
||
return text
|
||
return ""
|
||
|
||
|
||
def input_multiline(prompt_hint: str = "请输入内容") -> str:
|
||
"""多行文本输入,输入空行结束,返回合并字符串。"""
|
||
console.print(f"[dim]{prompt_hint}(输入空行结束):[/dim]")
|
||
lines: List[str] = []
|
||
while True:
|
||
line = input()
|
||
if line == "":
|
||
break
|
||
lines.append(line)
|
||
return "\n".join(lines).strip()
|
||
|
||
|
||
def pause() -> None:
|
||
"""暂停,等待用户按 Enter 键返回主菜单。"""
|
||
console.print()
|
||
input(" 按 Enter 键返回主菜单...")
|
||
|
||
|
||
def interactive_edit_req(
|
||
reqs: List[FunctionalRequirement],
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式编辑指定序号的需求字段。"""
|
||
idx_str = Prompt.ask("输入要编辑的序号")
|
||
try:
|
||
idx = int(idx_str)
|
||
except ValueError:
|
||
console.print("[red]序号必须为整数[/red]")
|
||
return reqs
|
||
for req in reqs:
|
||
if req.index_no == idx:
|
||
req.title = Prompt.ask("新标题", default=req.title)
|
||
req.description = Prompt.ask("新描述", default=req.description)
|
||
req.function_name = Prompt.ask("新函数名", default=req.function_name)
|
||
req.priority = Prompt.ask(
|
||
"新优先级", default=req.priority,
|
||
choices=["high", "medium", "low"],
|
||
)
|
||
req.module = Prompt.ask("新模块", default=req.module)
|
||
console.print(f"[green]✓ 序号 {idx} 已更新[/green]")
|
||
return reqs
|
||
console.print(f"[red]序号 {idx} 不存在[/red]")
|
||
return reqs
|
||
|
||
|
||
def interactive_delete_req(
|
||
reqs: List[FunctionalRequirement],
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式删除指定序号的需求(内存操作,未持久化)。"""
|
||
idx_str = Prompt.ask("输入要删除的序号")
|
||
try:
|
||
idx = int(idx_str)
|
||
except ValueError:
|
||
console.print("[red]序号必须为整数[/red]")
|
||
return reqs
|
||
new_reqs = [r for r in reqs if r.index_no != idx]
|
||
if len(new_reqs) < len(reqs):
|
||
console.print(f"[green]✓ 序号 {idx} 已删除[/green]")
|
||
else:
|
||
console.print(f"[red]序号 {idx} 不存在[/red]")
|
||
return new_reqs
|
||
|
||
|
||
def interactive_add_req(
|
||
reqs: List[FunctionalRequirement],
|
||
project: Project,
|
||
raw_req_id: int,
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式新增一条需求到列表(内存操作,未持久化)。"""
|
||
next_idx = max((r.index_no for r in reqs), default=0) + 1
|
||
title = Prompt.ask("新需求标题")
|
||
description = Prompt.ask("新需求描述")
|
||
function_name = Prompt.ask("函数名(snake_case)")
|
||
priority = Prompt.ask(
|
||
"优先级", choices=["high", "medium", "low"], default="medium",
|
||
)
|
||
module = Prompt.ask("所属模块", default=config.DEFAULT_MODULE)
|
||
new_req = FunctionalRequirement(
|
||
project_id = project.id,
|
||
raw_req_id = raw_req_id,
|
||
index_no = next_idx,
|
||
title = title,
|
||
description = description,
|
||
function_name = function_name,
|
||
priority = priority,
|
||
module = module,
|
||
status = "pending",
|
||
is_custom = True,
|
||
)
|
||
reqs.append(new_req)
|
||
console.print(f"[green]✓ 已添加新需求: {title}(序号 {next_idx})[/green]")
|
||
return reqs
|
||
|
||
|
||
def interactive_adjust_modules(
|
||
reqs: List[FunctionalRequirement],
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式逐条调整需求的模块归属,并立即持久化。"""
|
||
while True:
|
||
idx_str = Prompt.ask("输入要调整的需求序号(输入 0 结束)", default="0")
|
||
if idx_str == "0":
|
||
break
|
||
for req in reqs:
|
||
if str(req.index_no) == idx_str:
|
||
new_module = Prompt.ask(
|
||
f"'{req.function_name}' 新模块名", default=req.module,
|
||
)
|
||
req.module = new_module
|
||
db.update_functional_requirement(req)
|
||
console.print(
|
||
f"[green]✓ 已更新模块: {req.function_name} → {new_module}[/green]"
|
||
)
|
||
break
|
||
else:
|
||
console.print(f"[red]序号 {idx_str} 不存在[/red]")
|
||
return reqs# ui/prompts.py - 交互式输入 / 选择工具函数
|
||
|
||
from typing import List, Optional
|
||
|
||
from rich.console import Console
|
||
from rich.prompt import Confirm, Prompt
|
||
|
||
import config
|
||
from database.db_manager import DBManager
|
||
from database.models import FunctionalRequirement, Project
|
||
from ui.display import print_projects_table
|
||
from utils.file_handler import merge_knowledge_files
|
||
|
||
console = Console()
|
||
db = DBManager()
|
||
|
||
|
||
def select_project(prompt_text: str = "请选择项目 ID") -> Optional[Project]:
|
||
"""展示项目列表,交互选择并返回 Project 对象;输入 0 返回 None。"""
|
||
projects = db.list_projects()
|
||
if not projects:
|
||
console.print("[yellow]当前暂无项目,请先新建项目。[/yellow]")
|
||
return None
|
||
print_projects_table(projects)
|
||
pid_str = Prompt.ask(f"{prompt_text}(输入 0 返回)", default="0")
|
||
if pid_str == "0":
|
||
return None
|
||
try:
|
||
pid = int(pid_str)
|
||
except ValueError:
|
||
console.print("[red]ID 必须为整数[/red]")
|
||
return None
|
||
project = db.get_project(pid)
|
||
if not project:
|
||
console.print(f"[red]项目 ID={pid} 不存在[/red]")
|
||
return None
|
||
return project
|
||
|
||
|
||
def load_knowledge_optional() -> str:
|
||
"""询问是否加载知识库文件,加载后返回合并文本;否则返回空字符串。"""
|
||
if Confirm.ask("是否加载知识库文件?", default=False):
|
||
kb_path = Prompt.ask("知识库文件路径(多个用逗号分隔)")
|
||
paths = [p.strip() for p in kb_path.split(",") if p.strip()]
|
||
text = merge_knowledge_files(paths)
|
||
console.print(f"[dim]已加载 {len(paths)} 个知识库文件[/dim]")
|
||
return text
|
||
return ""
|
||
|
||
|
||
def input_multiline(prompt_hint: str = "请输入内容") -> str:
|
||
"""多行文本输入,输入空行结束,返回合并字符串。"""
|
||
console.print(f"[dim]{prompt_hint}(输入空行结束):[/dim]")
|
||
lines: List[str] = []
|
||
while True:
|
||
line = input()
|
||
if line == "":
|
||
break
|
||
lines.append(line)
|
||
return "\n".join(lines).strip()
|
||
|
||
|
||
def pause() -> None:
|
||
"""暂停,等待用户按 Enter 键返回主菜单。"""
|
||
console.print()
|
||
input(" 按 Enter 键返回主菜单...")
|
||
|
||
|
||
def interactive_edit_req(
|
||
reqs: List[FunctionalRequirement],
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式编辑指定序号的需求字段。"""
|
||
idx_str = Prompt.ask("输入要编辑的序号")
|
||
try:
|
||
idx = int(idx_str)
|
||
except ValueError:
|
||
console.print("[red]序号必须为整数[/red]")
|
||
return reqs
|
||
for req in reqs:
|
||
if req.index_no == idx:
|
||
req.title = Prompt.ask("新标题", default=req.title)
|
||
req.description = Prompt.ask("新描述", default=req.description)
|
||
req.function_name = Prompt.ask("新函数名", default=req.function_name)
|
||
req.priority = Prompt.ask(
|
||
"新优先级", default=req.priority,
|
||
choices=["high", "medium", "low"],
|
||
)
|
||
req.module = Prompt.ask("新模块", default=req.module)
|
||
console.print(f"[green]✓ 序号 {idx} 已更新[/green]")
|
||
return reqs
|
||
console.print(f"[red]序号 {idx} 不存在[/red]")
|
||
return reqs
|
||
|
||
|
||
def interactive_delete_req(
|
||
reqs: List[FunctionalRequirement],
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式删除指定序号的需求(内存操作,未持久化)。"""
|
||
idx_str = Prompt.ask("输入要删除的序号")
|
||
try:
|
||
idx = int(idx_str)
|
||
except ValueError:
|
||
console.print("[red]序号必须为整数[/red]")
|
||
return reqs
|
||
new_reqs = [r for r in reqs if r.index_no != idx]
|
||
if len(new_reqs) < len(reqs):
|
||
console.print(f"[green]✓ 序号 {idx} 已删除[/green]")
|
||
else:
|
||
console.print(f"[red]序号 {idx} 不存在[/red]")
|
||
return new_reqs
|
||
|
||
|
||
def interactive_add_req(
|
||
reqs: List[FunctionalRequirement],
|
||
project: Project,
|
||
raw_req_id: int,
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式新增一条需求到列表(内存操作,未持久化)。"""
|
||
next_idx = max((r.index_no for r in reqs), default=0) + 1
|
||
title = Prompt.ask("新需求标题")
|
||
description = Prompt.ask("新需求描述")
|
||
function_name = Prompt.ask("函数名(snake_case)")
|
||
priority = Prompt.ask(
|
||
"优先级", choices=["high", "medium", "low"], default="medium",
|
||
)
|
||
module = Prompt.ask("所属模块", default=config.DEFAULT_MODULE)
|
||
new_req = FunctionalRequirement(
|
||
project_id = project.id,
|
||
raw_req_id = raw_req_id,
|
||
index_no = next_idx,
|
||
title = title,
|
||
description = description,
|
||
function_name = function_name,
|
||
priority = priority,
|
||
module = module,
|
||
status = "pending",
|
||
is_custom = True,
|
||
)
|
||
reqs.append(new_req)
|
||
console.print(f"[green]✓ 已添加新需求: {title}(序号 {next_idx})[/green]")
|
||
return reqs
|
||
|
||
|
||
def interactive_adjust_modules(
|
||
reqs: List[FunctionalRequirement],
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式逐条调整需求的模块归属,并立即持久化。"""
|
||
while True:
|
||
idx_str = Prompt.ask("输入要调整的需求序号(输入 0 结束)", default="0")
|
||
if idx_str == "0":
|
||
break
|
||
for req in reqs:
|
||
if str(req.index_no) == idx_str:
|
||
new_module = Prompt.ask(
|
||
f"'{req.function_name}' 新模块名", default=req.module,
|
||
)
|
||
req.module = new_module
|
||
db.update_functional_requirement(req)
|
||
console.print(
|
||
f"[green]✓ 已更新模块: {req.function_name} → {new_module}[/green]"
|
||
)
|
||
break
|
||
else:
|
||
console.print(f"[red]序号 {idx_str} 不存在[/red]")
|
||
return reqs# ui/prompts.py - 交互式输入 / 选择工具函数
|
||
|
||
from typing import List, Optional
|
||
|
||
from rich.console import Console
|
||
from rich.prompt import Confirm, Prompt
|
||
|
||
import config
|
||
from database.db_manager import DBManager
|
||
from database.models import FunctionalRequirement, Project
|
||
from ui.display import print_projects_table
|
||
from utils.file_handler import merge_knowledge_files
|
||
|
||
console = Console()
|
||
db = DBManager()
|
||
|
||
|
||
def select_project(prompt_text: str = "请选择项目 ID") -> Optional[Project]:
|
||
"""展示项目列表,交互选择并返回 Project 对象;输入 0 返回 None。"""
|
||
projects = db.list_projects()
|
||
if not projects:
|
||
console.print("[yellow]当前暂无项目,请先新建项目。[/yellow]")
|
||
return None
|
||
print_projects_table(projects)
|
||
pid_str = Prompt.ask(f"{prompt_text}(输入 0 返回)", default="0")
|
||
if pid_str == "0":
|
||
return None
|
||
try:
|
||
pid = int(pid_str)
|
||
except ValueError:
|
||
console.print("[red]ID 必须为整数[/red]")
|
||
return None
|
||
project = db.get_project(pid)
|
||
if not project:
|
||
console.print(f"[red]项目 ID={pid} 不存在[/red]")
|
||
return None
|
||
return project
|
||
|
||
|
||
def load_knowledge_optional() -> str:
|
||
"""询问是否加载知识库文件,加载后返回合并文本;否则返回空字符串。"""
|
||
if Confirm.ask("是否加载知识库文件?", default=False):
|
||
kb_path = Prompt.ask("知识库文件路径(多个用逗号分隔)")
|
||
paths = [p.strip() for p in kb_path.split(",") if p.strip()]
|
||
text = merge_knowledge_files(paths)
|
||
console.print(f"[dim]已加载 {len(paths)} 个知识库文件[/dim]")
|
||
return text
|
||
return ""
|
||
|
||
|
||
def input_multiline(prompt_hint: str = "请输入内容") -> str:
|
||
"""多行文本输入,输入空行结束,返回合并字符串。"""
|
||
console.print(f"[dim]{prompt_hint}(输入空行结束):[/dim]")
|
||
lines: List[str] = []
|
||
while True:
|
||
line = input()
|
||
if line == "":
|
||
break
|
||
lines.append(line)
|
||
return "\n".join(lines).strip()
|
||
|
||
|
||
def pause() -> None:
|
||
"""暂停,等待用户按 Enter 键返回主菜单。"""
|
||
console.print()
|
||
input(" 按 Enter 键返回主菜单...")
|
||
|
||
|
||
def interactive_edit_req(
|
||
reqs: List[FunctionalRequirement],
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式编辑指定序号的需求字段。"""
|
||
idx_str = Prompt.ask("输入要编辑的序号")
|
||
try:
|
||
idx = int(idx_str)
|
||
except ValueError:
|
||
console.print("[red]序号必须为整数[/red]")
|
||
return reqs
|
||
for req in reqs:
|
||
if req.index_no == idx:
|
||
req.title = Prompt.ask("新标题", default=req.title)
|
||
req.description = Prompt.ask("新描述", default=req.description)
|
||
req.function_name = Prompt.ask("新函数名", default=req.function_name)
|
||
req.priority = Prompt.ask(
|
||
"新优先级", default=req.priority,
|
||
choices=["high", "medium", "low"],
|
||
)
|
||
req.module = Prompt.ask("新模块", default=req.module)
|
||
console.print(f"[green]✓ 序号 {idx} 已更新[/green]")
|
||
return reqs
|
||
console.print(f"[red]序号 {idx} 不存在[/red]")
|
||
return reqs
|
||
|
||
|
||
def interactive_delete_req(
|
||
reqs: List[FunctionalRequirement],
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式删除指定序号的需求(内存操作,未持久化)。"""
|
||
idx_str = Prompt.ask("输入要删除的序号")
|
||
try:
|
||
idx = int(idx_str)
|
||
except ValueError:
|
||
console.print("[red]序号必须为整数[/red]")
|
||
return reqs
|
||
new_reqs = [r for r in reqs if r.index_no != idx]
|
||
if len(new_reqs) < len(reqs):
|
||
console.print(f"[green]✓ 序号 {idx} 已删除[/green]")
|
||
else:
|
||
console.print(f"[red]序号 {idx} 不存在[/red]")
|
||
return new_reqs
|
||
|
||
|
||
def interactive_add_req(
|
||
reqs: List[FunctionalRequirement],
|
||
project: Project,
|
||
raw_req_id: int,
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式新增一条需求到列表(内存操作,未持久化)。"""
|
||
next_idx = max((r.index_no for r in reqs), default=0) + 1
|
||
title = Prompt.ask("新需求标题")
|
||
description = Prompt.ask("新需求描述")
|
||
function_name = Prompt.ask("函数名(snake_case)")
|
||
priority = Prompt.ask(
|
||
"优先级", choices=["high", "medium", "low"], default="medium",
|
||
)
|
||
module = Prompt.ask("所属模块", default=config.DEFAULT_MODULE)
|
||
new_req = FunctionalRequirement(
|
||
project_id = project.id,
|
||
raw_req_id = raw_req_id,
|
||
index_no = next_idx,
|
||
title = title,
|
||
description = description,
|
||
function_name = function_name,
|
||
priority = priority,
|
||
module = module,
|
||
status = "pending",
|
||
is_custom = True,
|
||
)
|
||
reqs.append(new_req)
|
||
console.print(f"[green]✓ 已添加新需求: {title}(序号 {next_idx})[/green]")
|
||
return reqs
|
||
|
||
|
||
def interactive_adjust_modules(
|
||
reqs: List[FunctionalRequirement],
|
||
) -> List[FunctionalRequirement]:
|
||
"""交互式逐条调整需求的模块归属,并立即持久化。"""
|
||
while True:
|
||
idx_str = Prompt.ask("输入要调整的需求序号(输入 0 结束)", default="0")
|
||
if idx_str == "0":
|
||
break
|
||
for req in reqs:
|
||
if str(req.index_no) == idx_str:
|
||
new_module = Prompt.ask(
|
||
f"'{req.function_name}' 新模块名", default=req.module,
|
||
)
|
||
req.module = new_module
|
||
db.update_functional_requirement(req)
|
||
console.print(
|
||
f"[green]✓ 已更新模块: {req.function_name} → {new_module}[/green]"
|
||
)
|
||
break
|
||
else:
|
||
console.print(f"[red]序号 {idx_str} 不存在[/red]")
|
||
return reqs |