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
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""统一配置面板(TUI,Ctrl+T):Provider 与 SmartRouter 同级展示。
|
|
2
|
+
|
|
3
|
+
仅于 TUI 运行时 import。Provider 列表与 SmartRouter 四档配置在同一个
|
|
4
|
+
Modal 内左右分栏平级呈现;编辑/新建仍走各自的 Form Modal,开关状态变更
|
|
5
|
+
即时同步顶部 Header。动作落 :class:`provider_service.ProviderConfigService`
|
|
6
|
+
与 :class:`smart_router_service.SmartRouterConfigService`。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from textual.app import ComposeResult
|
|
12
|
+
from textual.containers import Horizontal, Vertical
|
|
13
|
+
from textual.screen import ModalScreen
|
|
14
|
+
from textual.widgets import Button, OptionList, Static
|
|
15
|
+
from textual.widgets.option_list import Option
|
|
16
|
+
|
|
17
|
+
from xg.config.provider_service import ProviderConfigService
|
|
18
|
+
from xg.config.smart_router_service import SmartRouterConfigService
|
|
19
|
+
from xg.tui.widgets.provider_form import ProviderForm
|
|
20
|
+
from xg.tui.widgets.smart_router_form import SmartRouterForm
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConfigScreen(ModalScreen[None]):
|
|
24
|
+
BINDINGS = [("escape", "close", "关闭")]
|
|
25
|
+
|
|
26
|
+
def __init__(self, manager, settings=None) -> None:
|
|
27
|
+
super().__init__()
|
|
28
|
+
self.manager = manager
|
|
29
|
+
self.settings = settings
|
|
30
|
+
self.provider_service = ProviderConfigService(manager, settings)
|
|
31
|
+
self.router_service = SmartRouterConfigService(manager, settings)
|
|
32
|
+
self._rows: list[dict] = []
|
|
33
|
+
self._sel = 0
|
|
34
|
+
|
|
35
|
+
def compose(self) -> ComposeResult:
|
|
36
|
+
with Vertical(id="config-dialog"):
|
|
37
|
+
yield Static("配置中心", id="cfg-title")
|
|
38
|
+
with Vertical(id="cfg-body"):
|
|
39
|
+
with Vertical(id="provider-section"):
|
|
40
|
+
yield Static("Provider 管理(或 /provider 命令)", id="cfg-sec-title")
|
|
41
|
+
yield Static("↑↓ 选中行 · 操作作用于当前选中", id="provider-hint")
|
|
42
|
+
yield OptionList(id="provider-list")
|
|
43
|
+
yield Static("", id="provider-detail")
|
|
44
|
+
with Horizontal(id="provider-actions"):
|
|
45
|
+
yield Button("新增", id="add", variant="primary")
|
|
46
|
+
yield Button("编辑", id="edit")
|
|
47
|
+
yield Button("设为base", id="switch")
|
|
48
|
+
yield Button("删除", id="remove", variant="error")
|
|
49
|
+
yield Button("写Key", id="key")
|
|
50
|
+
with Vertical(id="sr-section"):
|
|
51
|
+
yield Static("SmartRouter(或 /tier 命令)", id="cfg-sec-title")
|
|
52
|
+
yield Static(self._render_router(), id="sr-body")
|
|
53
|
+
with Horizontal(id="sr-actions"):
|
|
54
|
+
yield Button("开关", id="toggle")
|
|
55
|
+
yield Button("编辑档位", id="tier", variant="primary")
|
|
56
|
+
yield Button("刷新", id="refresh")
|
|
57
|
+
yield Button("关闭", id="close")
|
|
58
|
+
|
|
59
|
+
def _provider_label(self, row: dict) -> str:
|
|
60
|
+
return (
|
|
61
|
+
f"{row['name']:<12}"
|
|
62
|
+
f"{row['default_model'][:16]:<16}"
|
|
63
|
+
f"{'✓' if row['has_key'] else '✗':^5}"
|
|
64
|
+
f"{'●' if row['is_base'] else ' ':^6}"
|
|
65
|
+
f"{row['layer']}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def _rebuild_provider_list(self, ol: OptionList) -> None:
|
|
69
|
+
self._rows = self.provider_service.list()
|
|
70
|
+
ol.clear_options()
|
|
71
|
+
if not self._rows:
|
|
72
|
+
self._sel = 0
|
|
73
|
+
return
|
|
74
|
+
for row in self._rows:
|
|
75
|
+
ol.add_option(Option(self._provider_label(row), id=row["name"]))
|
|
76
|
+
if self._sel >= len(self._rows):
|
|
77
|
+
self._sel = 0
|
|
78
|
+
ol.highlighted = self._sel
|
|
79
|
+
|
|
80
|
+
async def on_mount(self) -> None:
|
|
81
|
+
self._rebuild_provider_list(self.query_one("#provider-list", OptionList))
|
|
82
|
+
self.query_one("#sr-body", Static).update(self._render_router())
|
|
83
|
+
self._update_detail()
|
|
84
|
+
|
|
85
|
+
def on_option_list_option_highlighted(self, event: OptionList.OptionHighlighted) -> None:
|
|
86
|
+
self._sel = event.option_index
|
|
87
|
+
self._update_detail()
|
|
88
|
+
|
|
89
|
+
def _update_detail(self) -> None:
|
|
90
|
+
"""展示当前选中 provider 的详情(等价 /provider show,key 脱敏)。"""
|
|
91
|
+
if not self._rows:
|
|
92
|
+
detail = "(无 provider,请新增)"
|
|
93
|
+
else:
|
|
94
|
+
name = self._rows[self._sel]["name"]
|
|
95
|
+
info = self.provider_service.get(name)
|
|
96
|
+
if info is None:
|
|
97
|
+
detail = f"{name}: 未知 provider"
|
|
98
|
+
else:
|
|
99
|
+
lines = [
|
|
100
|
+
f"{info['name']}{' ●base' if info['is_base'] else ''} [{info['layer']} 层]",
|
|
101
|
+
f"api_base : {info['api_base']}",
|
|
102
|
+
f"default_model: {info['default_model']}",
|
|
103
|
+
]
|
|
104
|
+
if info.get("display_name"):
|
|
105
|
+
lines.insert(1, f"display_name : {info['display_name']}")
|
|
106
|
+
models = info.get("models") or []
|
|
107
|
+
lines.append(f"models({len(models)}) : {', '.join(models) if models else '(空)'}")
|
|
108
|
+
lines.append(
|
|
109
|
+
f"api_key : {info['api_key_masked'] if info['has_key'] else '(未配置)'}"
|
|
110
|
+
)
|
|
111
|
+
refs = self.provider_service.referenced_by(name)
|
|
112
|
+
if refs:
|
|
113
|
+
lines.append(f"SmartRouter 引用: {', '.join(refs)}")
|
|
114
|
+
detail = "\n".join(lines)
|
|
115
|
+
self.query_one("#provider-detail", Static).update(detail)
|
|
116
|
+
|
|
117
|
+
def _render_router(self) -> str:
|
|
118
|
+
cfg = self.router_service.get()
|
|
119
|
+
enabled = bool(cfg.get("enabled", False))
|
|
120
|
+
rows = self.router_service.list_tiers()
|
|
121
|
+
lines = [f"SmartRouter: {'开启' if enabled else '关闭'}",
|
|
122
|
+
"TIER PROVIDER MODEL"]
|
|
123
|
+
for row in rows:
|
|
124
|
+
if row["configured"]:
|
|
125
|
+
lines.append(f"{row['name']:<10}{row['provider']:<14}{row['model']}")
|
|
126
|
+
else:
|
|
127
|
+
lines.append(f"{row['name']:<10}{'(回落 active)':<14}-")
|
|
128
|
+
return "\n".join(lines)
|
|
129
|
+
|
|
130
|
+
def _refresh(self) -> None:
|
|
131
|
+
self._rebuild_provider_list(self.query_one("#provider-list", OptionList))
|
|
132
|
+
self.query_one("#sr-body", Static).update(self._render_router())
|
|
133
|
+
self._update_detail()
|
|
134
|
+
|
|
135
|
+
def _sync_header(self) -> None:
|
|
136
|
+
"""开关 / 档位变更后即时重建顶部 Header 路由行。"""
|
|
137
|
+
controller = getattr(self.app, "controller", None)
|
|
138
|
+
if controller is not None:
|
|
139
|
+
controller._sync_smart_router_snapshot()
|
|
140
|
+
|
|
141
|
+
def action_close(self) -> None:
|
|
142
|
+
self.dismiss(None)
|
|
143
|
+
|
|
144
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
145
|
+
bid = event.button.id or ""
|
|
146
|
+
if bid == "close":
|
|
147
|
+
self.dismiss(None)
|
|
148
|
+
return
|
|
149
|
+
if bid == "refresh":
|
|
150
|
+
self._refresh()
|
|
151
|
+
return
|
|
152
|
+
if bid == "add":
|
|
153
|
+
return self.app.push_screen(
|
|
154
|
+
ProviderForm(self.provider_service, mode="add"),
|
|
155
|
+
callback=self._after_form,
|
|
156
|
+
)
|
|
157
|
+
if bid == "toggle":
|
|
158
|
+
cfg = self.router_service.get()
|
|
159
|
+
on = not bool(cfg.get("enabled", False))
|
|
160
|
+
self.notify(self.router_service.set_enabled(on).message)
|
|
161
|
+
self._refresh()
|
|
162
|
+
self._sync_header()
|
|
163
|
+
return
|
|
164
|
+
if bid == "tier":
|
|
165
|
+
return self.app.push_screen(
|
|
166
|
+
SmartRouterForm(self.router_service), callback=self._after_form
|
|
167
|
+
)
|
|
168
|
+
if not self._rows:
|
|
169
|
+
self.notify("请先新增 provider", severity="warning")
|
|
170
|
+
return
|
|
171
|
+
if bid == "edit":
|
|
172
|
+
return self.app.push_screen(
|
|
173
|
+
ProviderForm(self.provider_service, name=self._rows[self._sel]["name"], mode="edit"),
|
|
174
|
+
callback=self._after_form,
|
|
175
|
+
)
|
|
176
|
+
if bid == "switch":
|
|
177
|
+
self.notify(self.provider_service.switch(self._rows[self._sel]["name"]).message)
|
|
178
|
+
self._refresh()
|
|
179
|
+
return
|
|
180
|
+
if bid == "remove":
|
|
181
|
+
result = self.provider_service.remove(self._rows[self._sel]["name"], yes=True)
|
|
182
|
+
self.notify(result.message, severity="error" if not result.ok else "information")
|
|
183
|
+
self._refresh()
|
|
184
|
+
return
|
|
185
|
+
if bid == "key":
|
|
186
|
+
return self.app.push_screen(
|
|
187
|
+
ProviderForm(self.provider_service, name=self._rows[self._sel]["name"], mode="key"),
|
|
188
|
+
callback=self._after_form,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def _after_form(self, _result: object) -> None:
|
|
192
|
+
self._refresh()
|
|
193
|
+
self._sync_header()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def open_config_screen(app, manager, settings=None) -> None:
|
|
197
|
+
"""外部调用:打开统一配置面板(Ctrl+T)。"""
|
|
198
|
+
app.push_screen(ConfigScreen(manager, settings))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from textual.app import ComposeResult
|
|
4
|
+
from textual.containers import Vertical
|
|
5
|
+
from textual.screen import ModalScreen
|
|
6
|
+
from textual.widgets import Button, Static
|
|
7
|
+
|
|
8
|
+
from xg.tui.state import ConfirmationRequest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConfirmModal(ModalScreen[str]):
|
|
12
|
+
BINDINGS = [("escape", "cancel", "取消"), ("enter", "confirm", "确认"), ("y", "confirm", "确认")]
|
|
13
|
+
|
|
14
|
+
def __init__(self, request: ConfirmationRequest) -> None:
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.request = request
|
|
17
|
+
|
|
18
|
+
def compose(self) -> ComposeResult:
|
|
19
|
+
with Vertical(id="confirm-dialog"):
|
|
20
|
+
yield Static(f"{self.request.title}\n\n{self.request.body}")
|
|
21
|
+
yield Button("确认 (y/Enter)", id="confirm", variant="warning")
|
|
22
|
+
yield Button("取消 (Esc)", id="cancel")
|
|
23
|
+
|
|
24
|
+
def action_confirm(self) -> None:
|
|
25
|
+
self.dismiss("confirm")
|
|
26
|
+
|
|
27
|
+
def action_cancel(self) -> None:
|
|
28
|
+
self.dismiss("cancel")
|
|
29
|
+
|
|
30
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
31
|
+
self.dismiss(event.button.id or "cancel")
|
xg/tui/widgets/footer.py
ADDED
xg/tui/widgets/header.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from rich.text import Text
|
|
6
|
+
from textual.widgets import Static
|
|
7
|
+
|
|
8
|
+
from xg.tui.state import SmartRouterSnapshot, SmartRouterTierSnapshot, TuiState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _compact(value: int) -> str:
|
|
12
|
+
if value <= 0:
|
|
13
|
+
return "-"
|
|
14
|
+
if value < 1_000:
|
|
15
|
+
return str(value)
|
|
16
|
+
if value < 1_000_000:
|
|
17
|
+
return f"{value / 1_000:.1f}k".replace(".0k", "k")
|
|
18
|
+
return f"{value / 1_000_000:.1f}m".replace(".0m", "m")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _watermelon_mark() -> str:
|
|
22
|
+
"""Return the brand mark with a safe ASCII fallback for dumb terminals."""
|
|
23
|
+
return "[XG CLI]" if os.environ.get("TERM", "").lower() == "dumb" else "🍉 XG CLI"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _status_label(phase: str) -> str:
|
|
27
|
+
return {
|
|
28
|
+
"idle": "Idle",
|
|
29
|
+
"running": "Working",
|
|
30
|
+
"awaiting_approval": "Waiting approval",
|
|
31
|
+
"awaiting_plan_review": "Plan review",
|
|
32
|
+
"awaiting_team_input": "Waiting team input",
|
|
33
|
+
"error": "Error",
|
|
34
|
+
}.get(phase, phase)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _tier_segment(tier: SmartRouterTierSnapshot) -> Text:
|
|
38
|
+
"""Render one tier entry on the routing row.
|
|
39
|
+
|
|
40
|
+
当前档 bold reverse;未配置档 dim + ``model (x)``;其余档 dim。
|
|
41
|
+
"""
|
|
42
|
+
label = f"{tier.tier}: {tier.model}"
|
|
43
|
+
if not tier.configured:
|
|
44
|
+
label += " (x)"
|
|
45
|
+
if tier.is_active:
|
|
46
|
+
return Text(label, style="bold reverse")
|
|
47
|
+
return Text(label, style="dim")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _routing_row(snapshot: SmartRouterSnapshot) -> Text:
|
|
51
|
+
"""Assemble the four-tier routing row (only when enabled)."""
|
|
52
|
+
row = Text()
|
|
53
|
+
for index, tier in enumerate(snapshot.tiers):
|
|
54
|
+
if index:
|
|
55
|
+
row.append(" ")
|
|
56
|
+
row.append(_tier_segment(tier))
|
|
57
|
+
return row
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class HeaderBar(Static):
|
|
61
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
62
|
+
# The ASCII fallback is rendered literally; otherwise Textual markup
|
|
63
|
+
# would treat the square brackets in ``[XG]`` as a tag.
|
|
64
|
+
kwargs.setdefault("markup", False)
|
|
65
|
+
super().__init__(*args, **kwargs)
|
|
66
|
+
|
|
67
|
+
def update_state(self, state: TuiState) -> None:
|
|
68
|
+
status = _status_label(state.phase)
|
|
69
|
+
inspector = state.inspector
|
|
70
|
+
usage = inspector.usage
|
|
71
|
+
level = "usage-normal"
|
|
72
|
+
highest_ratio = max(usage.window_ratio, usage.budget_usage_ratio)
|
|
73
|
+
if highest_ratio > 1.0:
|
|
74
|
+
level = "usage-error"
|
|
75
|
+
elif highest_ratio >= 0.7:
|
|
76
|
+
level = "usage-warning"
|
|
77
|
+
for class_name in ("usage-normal", "usage-warning", "usage-error"):
|
|
78
|
+
self.remove_class(class_name)
|
|
79
|
+
self.add_class(level)
|
|
80
|
+
available = usage.estimated_prompt_tokens > 0 and usage.context_window > 0
|
|
81
|
+
context = (
|
|
82
|
+
f"{_compact(usage.estimated_prompt_tokens)}/{_compact(usage.context_window)}"
|
|
83
|
+
if available else "-/-"
|
|
84
|
+
)
|
|
85
|
+
percentage = f"{usage.window_ratio * 100:.1f}%" if available else "-"
|
|
86
|
+
provider_model = f"{inspector.provider}/{inspector.model}".strip("/") or "provider/model unavailable"
|
|
87
|
+
hitl_enabled = inspector.safety.hitl_enabled if inspector.safety else inspector.hitl_enabled
|
|
88
|
+
queue = f" · Queue {len(state.queue)}" if state.queue else ""
|
|
89
|
+
|
|
90
|
+
# Textual terminals cannot reliably display an SVG/PNG in the normal
|
|
91
|
+
# text pipeline. The watermelon emoji is the color-capable mark, with
|
|
92
|
+
# [XG] retained for TERM=dumb and other low-capability environments.
|
|
93
|
+
mark = _watermelon_mark()
|
|
94
|
+
# This is intentionally a welcome/brand block rather than a one-line
|
|
95
|
+
# status bar. Keeping the runtime fields in the same widget preserves
|
|
96
|
+
# the existing state-update contract while giving the left column a
|
|
97
|
+
# useful visual hierarchy.
|
|
98
|
+
text = Text()
|
|
99
|
+
text.append(mark, style="bold bright_green")
|
|
100
|
+
text.append("\n")
|
|
101
|
+
text.append(provider_model, style="bold")
|
|
102
|
+
text.append("\n")
|
|
103
|
+
# SmartRouter routing row (phase-02): rendered only when enabled so the
|
|
104
|
+
# off-state output stays byte-identical to the pre-feature Header.
|
|
105
|
+
if inspector.smart_router.enabled and inspector.smart_router.tiers:
|
|
106
|
+
text.append(_routing_row(inspector.smart_router))
|
|
107
|
+
text.append("\n")
|
|
108
|
+
status_style = "red" if state.phase == "error" else "yellow" if state.phase != "idle" else "dim"
|
|
109
|
+
text.append(status, style=status_style)
|
|
110
|
+
text.append(" · ")
|
|
111
|
+
text.append(f"Context {context} · {percentage}")
|
|
112
|
+
text.append("\n")
|
|
113
|
+
text.append("HITL ON" if hitl_enabled else "HITL OFF", style="yellow" if not hitl_enabled else "dim")
|
|
114
|
+
text.append(queue, style="cyan")
|
|
115
|
+
# Static.update(Rich Text) needs an active Textual app console in
|
|
116
|
+
# Textual 8.x. Keep standalone render tests and lightweight adapters
|
|
117
|
+
# usable by falling back to the plain representation when unmounted.
|
|
118
|
+
self.update(text if self.is_mounted else text.plain)
|