Files
kwcode/kaiwu/cli/status_bar.py
Val-sss c6197d0316 v0.7.0: P1+P2+搜索重构+UI全面优化
P1: KWCODE.md规则注入、/plan风险评估、Checkpoint快照、DocReader
P2: 模型能力自适应、飞轮通知、价值量化仪表盘
搜索: 四级提取管道、并行搜索+BM25重排、意图感知、ChatExpert门控
UI: spinner动画、结果摘要、静默日志、重影大字Header
新增: kwcode setup-search 一键安装SearXNG
测试: 282/282 PASS (含17个E2E真实模型测试)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 17:52:59 +08:00

105 lines
3.2 KiB
Python
Raw 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.
"""
状态栏数据容器 + 渲染器 + tok/s 估算器。
状态栏通过 prompt_toolkit 的 bottom_toolbar 常驻显示,
这里只负责数据和渲染文本,不做终端控制。
"""
import psutil
class StatusBar:
"""状态栏数据容器 + 渲染器。"""
def __init__(self):
self.model: str = ""
self.ctx_used: int = 0
self.ctx_max: int = 8192
self.compress_count: int = 0
self.tok_per_sec: float = 0.0
self.vram_used: float = 0.0
self.vram_total: float = 0.0
self.ram_used: float = 0.0
self.ram_total: float = 0.0
def refresh_ram(self):
"""刷新RAM数据开销极低"""
try:
vm = psutil.virtual_memory()
self.ram_used = vm.used / 1024**3
self.ram_total = vm.total / 1024**3
except Exception:
pass
def render(self, width: int) -> str:
"""根据终端宽度渲染状态栏纯文本。"""
pct = self.ctx_used / max(self.ctx_max, 1)
ctx_k = self.ctx_used / 1000
max_k = self.ctx_max / 1000
bar_w = 6
filled = int(pct * bar_w)
bar = "" * filled + "" * (bar_w - filled)
compress = f"压缩×{self.compress_count}" if self.compress_count > 0 else ""
if width >= 100:
p = [f"{self.model}"]
p.append(f"ctx {ctx_k:.1f}K/{max_k:.0f}K {bar} {pct*100:.0f}%")
if compress:
p.append(compress)
p.append(f"{self.tok_per_sec:.1f} tok/s")
if self.vram_total > 0:
p.append(f"VRAM {self.vram_used:.1f}G/{self.vram_total:.0f}G")
p.append(f"RAM {self.ram_used:.1f}G/{self.ram_total:.0f}G")
return "".join(p)
elif width >= 80:
p = [f"{self.model}"]
p.append(f"ctx {ctx_k:.1f}K/{max_k:.0f}K {pct*100:.0f}%")
if compress:
p.append(compress)
p.append(f"{self.tok_per_sec:.0f}t/s")
if self.vram_total > 0:
p.append(f"VRAM {self.vram_used:.1f}G")
return "".join(p)
elif width >= 60:
p = [f"ctx {ctx_k:.1f}K/{max_k:.0f}K"]
if compress:
p.append(compress)
p.append(f"{self.tok_per_sec:.0f}t/s")
return "".join(p)
else:
return f"{ctx_k:.1f}K/{max_k:.0f}K tokens"
class TokPerSecEstimator:
"""模糊计算 tok/sEMA平滑不依赖Ollama eval_rate。"""
def __init__(self, alpha: float = 0.3):
self.alpha = alpha
self._ema_tps: float = 0.0
def record(self, output_text: str, elapsed_sec: float):
if elapsed_sec <= 0:
return
tokens = _estimate_tokens(output_text)
tps = tokens / elapsed_sec
if self._ema_tps == 0:
self._ema_tps = tps
else:
self._ema_tps = self.alpha * tps + (1 - self.alpha) * self._ema_tps
@property
def value(self) -> float:
return round(self._ema_tps, 1)
def _estimate_tokens(text: str) -> int:
"""粗估 token 数。中文 ~1.5 字/token英文 ~4 字符/token。"""
cn = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
en = len(text) - cn
return int(cn * 1.5 + en / 4)