xg-cli 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xg/__init__.py +3 -0
- xg/__main__.py +3 -0
- xg/adaptive/__init__.py +27 -0
- xg/adaptive/calibrate.py +191 -0
- xg/adaptive/feedback.py +170 -0
- xg/adaptive/learned_rules.py +284 -0
- xg/adaptive/signals.py +125 -0
- xg/adaptive/store.py +164 -0
- xg/agent/__init__.py +0 -0
- xg/agent/plan.py +631 -0
- xg/agent/react.py +268 -0
- xg/agent/team.py +1793 -0
- xg/assets/router.lgb +0 -0
- xg/assets/router_semantics.json +21278 -0
- xg/assets/router_semantics.onnx +0 -0
- xg/cli/__init__.py +0 -0
- xg/cli/app.py +1372 -0
- xg/cli/commands.py +921 -0
- xg/cli/completion.py +740 -0
- xg/cli/help.py +185 -0
- xg/cli/train.py +200 -0
- xg/config/__init__.py +0 -0
- xg/config/env_writer.py +121 -0
- xg/config/manager.py +446 -0
- xg/config/mcp.py +229 -0
- xg/config/provider_service.py +267 -0
- xg/config/providers.py +46 -0
- xg/config/settings.py +258 -0
- xg/config/skills.py +100 -0
- xg/config/smart_router_service.py +121 -0
- xg/config/web.py +140 -0
- xg/input_history/__init__.py +7 -0
- xg/input_history/models.py +28 -0
- xg/input_history/persistence.py +126 -0
- xg/input_history/policy.py +39 -0
- xg/input_history/prompt_toolkit.py +36 -0
- xg/input_history/store.py +118 -0
- xg/llm/__init__.py +0 -0
- xg/llm/client.py +49 -0
- xg/llm/factory.py +40 -0
- xg/llm/openai_compat.py +275 -0
- xg/llm/types.py +98 -0
- xg/mcp/__init__.py +4 -0
- xg/mcp/http.py +192 -0
- xg/mcp/manager.py +726 -0
- xg/mcp/models.py +86 -0
- xg/mcp/protocol.py +62 -0
- xg/mcp/resources.py +72 -0
- xg/mcp/schema.py +137 -0
- xg/mcp/stdio.py +210 -0
- xg/mcp/transport.py +66 -0
- xg/memory/__init__.py +15 -0
- xg/memory/context.py +327 -0
- xg/memory/manager.py +111 -0
- xg/memory/models.py +41 -0
- xg/memory/project.py +187 -0
- xg/memory/store.py +144 -0
- xg/router/__init__.py +124 -0
- xg/router/features.py +66 -0
- xg/router/keywords.py +50 -0
- xg/router/ml_router.py +178 -0
- xg/router/model_tiers.py +73 -0
- xg/router/postprocess.py +167 -0
- xg/router/rule_router.py +77 -0
- xg/router/semantic.py +138 -0
- xg/safety/__init__.py +0 -0
- xg/safety/audit.py +96 -0
- xg/safety/guards.py +106 -0
- xg/safety/hitl.py +73 -0
- xg/skill/__init__.py +9 -0
- xg/skill/errors.py +45 -0
- xg/skill/loader.py +42 -0
- xg/skill/models.py +57 -0
- xg/skill/parser.py +93 -0
- xg/skill/policy.py +40 -0
- xg/skill/prompt.py +45 -0
- xg/skill/registry.py +169 -0
- xg/tool/__init__.py +0 -0
- xg/tool/builtin.py +356 -0
- xg/tool/registry.py +228 -0
- xg/tui/__init__.py +34 -0
- xg/tui/app.py +612 -0
- xg/tui/controller.py +1296 -0
- xg/tui/diagrams/__init__.py +22 -0
- xg/tui/diagrams/layout.py +110 -0
- xg/tui/diagrams/markdown.py +39 -0
- xg/tui/diagrams/model.py +36 -0
- xg/tui/diagrams/parser.py +119 -0
- xg/tui/diagrams/renderer.py +551 -0
- xg/tui/i18n.py +169 -0
- xg/tui/messages.py +45 -0
- xg/tui/plan_renderables.py +147 -0
- xg/tui/reducer.py +1029 -0
- xg/tui/renderables.py +252 -0
- xg/tui/state.py +240 -0
- xg/tui/theme.tcss +208 -0
- xg/tui/widgets/__init__.py +1 -0
- xg/tui/widgets/action_card.py +94 -0
- xg/tui/widgets/agent_group_card.py +39 -0
- xg/tui/widgets/approval_modal.py +59 -0
- xg/tui/widgets/collapsible_card.py +40 -0
- xg/tui/widgets/command_suggestions.py +128 -0
- xg/tui/widgets/composer.py +151 -0
- xg/tui/widgets/config_panel.py +198 -0
- xg/tui/widgets/confirm_modal.py +31 -0
- xg/tui/widgets/footer.py +9 -0
- xg/tui/widgets/header.py +118 -0
- xg/tui/widgets/inspector.py +378 -0
- xg/tui/widgets/plan_modal.py +53 -0
- xg/tui/widgets/provider_form.py +141 -0
- xg/tui/widgets/queue_status.py +30 -0
- xg/tui/widgets/smart_router_form.py +95 -0
- xg/tui/widgets/transcript.py +354 -0
- xg/tui/workers.py +14 -0
- xg/web/__init__.py +21 -0
- xg/web/errors.py +57 -0
- xg/web/extract.py +127 -0
- xg/web/fetch.py +106 -0
- xg/web/markdown.py +77 -0
- xg/web/models.py +91 -0
- xg/web/providers.py +79 -0
- xg/web/search.py +118 -0
- xg/web/searxng.py +27 -0
- xg/web/serpapi.py +29 -0
- xg/web/url_policy.py +110 -0
- xg/web/zhipu.py +27 -0
- xg_cli-1.0.dist-info/METADATA +284 -0
- xg_cli-1.0.dist-info/RECORD +130 -0
- xg_cli-1.0.dist-info/WHEEL +4 -0
- xg_cli-1.0.dist-info/entry_points.txt +2 -0
xg/tui/i18n.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Small, dependency-free UI translations used by the Inspector."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
UiLanguage = Literal["en", "zh"]
|
|
9
|
+
SUPPORTED_UI_LANGUAGES: tuple[UiLanguage, ...] = ("en", "zh")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def normalize_language(value: object) -> UiLanguage:
|
|
13
|
+
"""Return a supported UI language, falling back to the default English."""
|
|
14
|
+
return "zh" if str(value).strip().lower() == "zh" else "en"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
INSPECTOR_TEXT: dict[str, dict[str, str]] = {
|
|
18
|
+
"en": {
|
|
19
|
+
"view.session": "Session",
|
|
20
|
+
"view.plan": "Plan",
|
|
21
|
+
"view.memory": "Memory",
|
|
22
|
+
"view.safety": "Safety",
|
|
23
|
+
"label.provider": "provider",
|
|
24
|
+
"label.model": "model",
|
|
25
|
+
"label.status": "status",
|
|
26
|
+
"label.estimated_input": "estimated input",
|
|
27
|
+
"label.model_window": "model window",
|
|
28
|
+
"label.window_usage": "window usage",
|
|
29
|
+
"label.input_budget": "input budget",
|
|
30
|
+
"label.budget_usage": "budget usage",
|
|
31
|
+
"label.source": "source",
|
|
32
|
+
"label.last_request": "last request",
|
|
33
|
+
"label.prompt": "prompt",
|
|
34
|
+
"label.completion": "completion",
|
|
35
|
+
"label.total": "total",
|
|
36
|
+
"label.session_usage": "session usage",
|
|
37
|
+
"label.compaction": "compaction",
|
|
38
|
+
"label.count": "count",
|
|
39
|
+
"label.last": "last",
|
|
40
|
+
"label.goal": "goal",
|
|
41
|
+
"label.current_round": "current round",
|
|
42
|
+
"label.progress": "progress",
|
|
43
|
+
"label.failures": "failures",
|
|
44
|
+
"label.project_root": "project root",
|
|
45
|
+
"label.warnings": "warnings",
|
|
46
|
+
"label.entries": "entries",
|
|
47
|
+
"label.last_operation": "last operation",
|
|
48
|
+
"label.store": "store",
|
|
49
|
+
"label.session_allow_all": "session allow all",
|
|
50
|
+
"label.tool": "tool",
|
|
51
|
+
"label.level": "level",
|
|
52
|
+
"label.decision": "decision",
|
|
53
|
+
"label.reason": "reason",
|
|
54
|
+
"label.last_rejection": "last rejection",
|
|
55
|
+
"section.context": "Context",
|
|
56
|
+
"section.last_request": "Last request",
|
|
57
|
+
"section.session_usage": "Session usage",
|
|
58
|
+
"section.compaction": "Compaction",
|
|
59
|
+
"section.tasks": "Tasks",
|
|
60
|
+
"section.project_memory": "Project memory",
|
|
61
|
+
"section.long_term_memory": "Long-term memory",
|
|
62
|
+
"section.current_approval": "Current approval",
|
|
63
|
+
"section.last_decision": "Last decision",
|
|
64
|
+
"section.policy": "Policy",
|
|
65
|
+
"empty.no_plan": "No plan",
|
|
66
|
+
"unit.token": "token",
|
|
67
|
+
"unit.tasks": "tasks",
|
|
68
|
+
"unit.round": "Round {current} / {total}",
|
|
69
|
+
},
|
|
70
|
+
"zh": {
|
|
71
|
+
"view.session": "Session",
|
|
72
|
+
"view.plan": "Plan",
|
|
73
|
+
"view.memory": "Memory",
|
|
74
|
+
"view.safety": "Safety",
|
|
75
|
+
"label.provider": "Provider",
|
|
76
|
+
"label.model": "Model",
|
|
77
|
+
"label.status": "Status",
|
|
78
|
+
"label.estimated_input": "预估输入",
|
|
79
|
+
"label.model_window": "上下文窗口",
|
|
80
|
+
"label.window_usage": "窗口使用率",
|
|
81
|
+
"label.input_budget": "输入预算",
|
|
82
|
+
"label.budget_usage": "预算使用率",
|
|
83
|
+
"label.source": "来源",
|
|
84
|
+
"label.last_request": "最近一次请求",
|
|
85
|
+
"label.prompt": "Prompt",
|
|
86
|
+
"label.completion": "Completion",
|
|
87
|
+
"label.total": "Total",
|
|
88
|
+
"label.session_usage": "Session 使用量",
|
|
89
|
+
"label.compaction": "Compaction",
|
|
90
|
+
"label.count": "次数",
|
|
91
|
+
"label.last": "最近一次",
|
|
92
|
+
"label.goal": "目标",
|
|
93
|
+
"label.current_round": "当前轮次",
|
|
94
|
+
"label.progress": "进度",
|
|
95
|
+
"label.failures": "失败次数",
|
|
96
|
+
"label.project_root": "项目根目录",
|
|
97
|
+
"label.warnings": "警告数",
|
|
98
|
+
"label.entries": "条目数",
|
|
99
|
+
"label.last_operation": "最近一次操作",
|
|
100
|
+
"label.store": "Store",
|
|
101
|
+
"label.session_allow_all": "Session 全部允许",
|
|
102
|
+
"label.tool": "Tool",
|
|
103
|
+
"label.level": "级别",
|
|
104
|
+
"label.decision": "Decision",
|
|
105
|
+
"label.reason": "原因",
|
|
106
|
+
"label.last_rejection": "最近一次拒绝",
|
|
107
|
+
"section.context": "Context",
|
|
108
|
+
"section.last_request": "最近一次请求",
|
|
109
|
+
"section.session_usage": "Session 使用量",
|
|
110
|
+
"section.compaction": "Compaction",
|
|
111
|
+
"section.tasks": "Tasks",
|
|
112
|
+
"section.project_memory": "项目 Memory",
|
|
113
|
+
"section.long_term_memory": "长期 Memory",
|
|
114
|
+
"section.current_approval": "当前 approval",
|
|
115
|
+
"section.last_decision": "最近一次决策",
|
|
116
|
+
"section.policy": "Policy",
|
|
117
|
+
"empty.no_plan": "暂无 Plan",
|
|
118
|
+
"unit.token": "token",
|
|
119
|
+
"unit.tasks": "个 Task",
|
|
120
|
+
"unit.round": "第 {current} / {total} 轮",
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
STATUS_ZH: dict[str, str] = {
|
|
126
|
+
"idle": "空闲 (Idle)",
|
|
127
|
+
"running": "执行中 (Running)",
|
|
128
|
+
"working": "工作中 (Working)",
|
|
129
|
+
"waiting": "等待中 (waiting)",
|
|
130
|
+
"waiting approval": "等待审批 (Waiting approval)",
|
|
131
|
+
"review": "审阅中 (review)",
|
|
132
|
+
"plan review": "等待 Plan 审阅 (Plan review)",
|
|
133
|
+
"done": "已完成 (Done)",
|
|
134
|
+
"success": "成功 (success)",
|
|
135
|
+
"approved": "已批准 (approved)",
|
|
136
|
+
"active": "生效中 (active)",
|
|
137
|
+
"on": "开启 (on)",
|
|
138
|
+
"off": "关闭 (off)",
|
|
139
|
+
"yes": "是 (yes)",
|
|
140
|
+
"no": "否 (no)",
|
|
141
|
+
"loaded": "已加载 (loaded)",
|
|
142
|
+
"available": "可用 (available)",
|
|
143
|
+
"failed": "失败 (failed)",
|
|
144
|
+
"error": "错误 (Error)",
|
|
145
|
+
"rejected": "已拒绝 (rejected)",
|
|
146
|
+
"denied": "已拒绝 (denied)",
|
|
147
|
+
"cancelled": "已取消 (cancelled)",
|
|
148
|
+
"inactive": "未生效 (inactive)",
|
|
149
|
+
"unavailable": "不可用 (unavailable)",
|
|
150
|
+
"not found": "未找到 (not found)",
|
|
151
|
+
"blocked": "已阻塞 (blocked)",
|
|
152
|
+
"skipped": "已跳过 (skipped)",
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def translate(language: UiLanguage, key: str, **values: object) -> str:
|
|
157
|
+
"""Translate a fixed UI key and format only its explicit placeholders."""
|
|
158
|
+
lang = normalize_language(language)
|
|
159
|
+
value = INSPECTOR_TEXT.get(lang, {}).get(key)
|
|
160
|
+
if value is None:
|
|
161
|
+
value = INSPECTOR_TEXT["en"].get(key, key)
|
|
162
|
+
return value.format(**values) if values else value
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def translate_status(language: UiLanguage, status: str) -> str:
|
|
166
|
+
"""Translate a canonical status while retaining its English meaning."""
|
|
167
|
+
if normalize_language(language) == "en":
|
|
168
|
+
return status
|
|
169
|
+
return STATUS_ZH.get(status.lower(), status)
|
xg/tui/messages.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Small Textual messages used to bridge controller callbacks to widgets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from textual.message import Message
|
|
6
|
+
|
|
7
|
+
from xg.tui.state import InspectorView, TuiState
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StateChanged(Message):
|
|
11
|
+
def __init__(self, state: TuiState) -> None:
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.state = state
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TraceCardToggled(Message):
|
|
17
|
+
"""A user clicked or activated one collapsible execution card."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, item_id: str) -> None:
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.item_id = item_id
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AgentGroupToggled(Message):
|
|
25
|
+
"""A user expanded or collapsed one Team Agent conversation group."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, group_id: str) -> None:
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.group_id = group_id
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CommandSuggestionSelected(Message):
|
|
33
|
+
"""A local command suggestion was selected without being submitted."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, candidate) -> None:
|
|
36
|
+
super().__init__()
|
|
37
|
+
self.candidate = candidate
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class InspectorViewSelected(Message):
|
|
41
|
+
"""A user selected one of the Inspector's stable view tabs."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, view: InspectorView) -> None:
|
|
44
|
+
super().__init__()
|
|
45
|
+
self.view = view
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Plan review flowchart renderables for the Textual transcript."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from rich.console import Console, ConsoleOptions, Group, RenderResult
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
from xg.agent.plan import Plan, PlanTask
|
|
12
|
+
from xg.agent.team import TeamPlan
|
|
13
|
+
from xg.tui.diagrams.model import FlowchartEdge, FlowchartModel, FlowchartNode
|
|
14
|
+
from xg.tui.diagrams.renderer import render_flowchart
|
|
15
|
+
from xg.tui.state import TranscriptItem
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class PlanFlowchartData:
|
|
20
|
+
model: FlowchartModel
|
|
21
|
+
rank_by_node: dict[str, int]
|
|
22
|
+
ordered_ids: tuple[str, ...]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def plan_to_flowchart(plan: Plan | TeamPlan) -> PlanFlowchartData:
|
|
26
|
+
"""Adapt an already validated Plan DAG to the text flowchart model."""
|
|
27
|
+
tasks = {task.id: task for task in plan.tasks}
|
|
28
|
+
ordered_ids: list[str] = []
|
|
29
|
+
rank_by_node: dict[str, int] = {}
|
|
30
|
+
for batch_no, batch in enumerate(plan.batches):
|
|
31
|
+
for task_id in batch:
|
|
32
|
+
if task_id in tasks and task_id not in ordered_ids:
|
|
33
|
+
ordered_ids.append(task_id)
|
|
34
|
+
rank_by_node[task_id] = batch_no
|
|
35
|
+
# Defensive fallback for a manually-created Plan whose batches are stale.
|
|
36
|
+
for task in plan.tasks:
|
|
37
|
+
if task.id not in ordered_ids:
|
|
38
|
+
ordered_ids.append(task.id)
|
|
39
|
+
rank_by_node[task.id] = len(plan.batches)
|
|
40
|
+
|
|
41
|
+
nodes = [
|
|
42
|
+
FlowchartNode(id=task_id, label=f"{task_id} {tasks[task_id].title}")
|
|
43
|
+
for task_id in ordered_ids
|
|
44
|
+
]
|
|
45
|
+
known = set(tasks)
|
|
46
|
+
edges = [
|
|
47
|
+
FlowchartEdge(source=dependency, target=task.id)
|
|
48
|
+
for task in plan.tasks
|
|
49
|
+
for dependency in task.deps
|
|
50
|
+
if dependency in known and task.id in known
|
|
51
|
+
]
|
|
52
|
+
warnings = []
|
|
53
|
+
missing = [
|
|
54
|
+
f"{task.id} 依赖不存在的任务 {dependency}"
|
|
55
|
+
for task in plan.tasks
|
|
56
|
+
for dependency in task.deps
|
|
57
|
+
if dependency not in known
|
|
58
|
+
]
|
|
59
|
+
warnings.extend(missing)
|
|
60
|
+
return PlanFlowchartData(
|
|
61
|
+
model=FlowchartModel(direction="TD", nodes=nodes, edges=edges, warnings=warnings),
|
|
62
|
+
rank_by_node=rank_by_node,
|
|
63
|
+
ordered_ids=tuple(ordered_ids),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _task_title_text(task: PlanTask) -> Text:
|
|
68
|
+
"""Render a task title with a consistent visual hierarchy."""
|
|
69
|
+
line = Text(style="bold")
|
|
70
|
+
line.append(task.id, style="cyan bold")
|
|
71
|
+
line.append(f" {task.title}", style="bold")
|
|
72
|
+
return line
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _task_summary_renderable(plan: Plan | TeamPlan, ordered_ids: tuple[str, ...], detailed: bool) -> Group:
|
|
76
|
+
"""Render the task heading, keyboard hint, and task content separately."""
|
|
77
|
+
tasks = {task.id: task for task in plan.tasks}
|
|
78
|
+
renderables: list[object] = [
|
|
79
|
+
Text("任务详情" if detailed else "任务概括"),
|
|
80
|
+
Text("按 d 收起详情" if detailed else "按 d 显示详情", style="dim"),
|
|
81
|
+
]
|
|
82
|
+
for task_id in ordered_ids:
|
|
83
|
+
task = tasks.get(task_id)
|
|
84
|
+
if task is None:
|
|
85
|
+
continue
|
|
86
|
+
renderables.append(_task_title_text(task))
|
|
87
|
+
if detailed:
|
|
88
|
+
deps = ", ".join(task.deps) if task.deps else "无"
|
|
89
|
+
renderables.extend([
|
|
90
|
+
Text(f" 描述:{task.description or '暂无描述'}", style="dim"),
|
|
91
|
+
Text(f" 依赖:{deps}", style="dim"),
|
|
92
|
+
])
|
|
93
|
+
tools = getattr(task, "allowed_tools", ())
|
|
94
|
+
if tools:
|
|
95
|
+
renderables.append(Text(f" 工具:{', '.join(tools)}", style="dim"))
|
|
96
|
+
for warning in getattr(task, "tool_warnings", ()):
|
|
97
|
+
renderables.append(Text(f" 工具提示:{warning}", style="yellow"))
|
|
98
|
+
mode = getattr(task, "resource_scope_mode", "")
|
|
99
|
+
if mode:
|
|
100
|
+
renderables.append(Text(f" 资源模式:{mode}", style="dim"))
|
|
101
|
+
for claim in getattr(task, "resource_claims", ()):
|
|
102
|
+
renderables.append(
|
|
103
|
+
Text(f" 资源声明:{claim.access} {claim.pattern}", style="dim")
|
|
104
|
+
)
|
|
105
|
+
return Group(*renderables)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class PlanReviewCard:
|
|
109
|
+
"""Static review card: flowchart plus summary/details below it."""
|
|
110
|
+
|
|
111
|
+
def __init__(self, item: TranscriptItem) -> None:
|
|
112
|
+
self.item = item
|
|
113
|
+
|
|
114
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
115
|
+
plan = self.item.plan
|
|
116
|
+
# /plan uses Plan while /team uses TeamPlan. Both expose the same
|
|
117
|
+
# validated task/batch shape needed by this read-only renderer.
|
|
118
|
+
if not isinstance(plan, (Plan, TeamPlan)):
|
|
119
|
+
yield Panel(Text(self.item.text or "计划不可用"), title="计划审阅", border_style="magenta")
|
|
120
|
+
return
|
|
121
|
+
data = plan_to_flowchart(plan)
|
|
122
|
+
chart = render_flowchart(
|
|
123
|
+
data.model,
|
|
124
|
+
width=max(1, options.max_width - 8),
|
|
125
|
+
rank_by_node=data.rank_by_node,
|
|
126
|
+
)
|
|
127
|
+
parts: list[object] = [
|
|
128
|
+
Text(f"目标:{plan.goal}\n共 {len(plan.batches)} 轮", style="bold"),
|
|
129
|
+
]
|
|
130
|
+
# Skip the flow-chart panel entirely when rendering falls back to
|
|
131
|
+
# structured text: the panel would just repeat the task IDs already
|
|
132
|
+
# listed below, and the warning inside a cyan border looks noisy.
|
|
133
|
+
if chart.mode != "structured":
|
|
134
|
+
chart_parts: list[object] = [Text(chart.text, no_wrap=True, overflow="crop")]
|
|
135
|
+
if chart.warnings:
|
|
136
|
+
chart_parts.append(Text("\n\n⚠ " + ";".join(chart.warnings)))
|
|
137
|
+
parts.append(Panel(Group(*chart_parts), title="轮次流程", border_style="cyan"))
|
|
138
|
+
elif chart.warnings:
|
|
139
|
+
parts.append(Text("⚠ " + ";".join(chart.warnings), style="dim"))
|
|
140
|
+
batch_lines = [
|
|
141
|
+
f"第 {batch_no} 轮:{', '.join(batch)}"
|
|
142
|
+
for batch_no, batch in enumerate(plan.batches, 1)
|
|
143
|
+
]
|
|
144
|
+
parts.append(Text("\n".join(batch_lines), style="dim"))
|
|
145
|
+
parts.append(_task_summary_renderable(plan, data.ordered_ids, not self.item.collapsed))
|
|
146
|
+
parts.append(Text("\nEnter 执行 · r 重规划 · Esc 取消", style="dim"))
|
|
147
|
+
yield Panel(Group(*parts), title="计划审阅", border_style="magenta")
|