feat(ui-tui): slash command history/display, CoT fade, live skin switch, fix double reasoning
This commit is contained in:
@@ -104,7 +104,7 @@ COMMAND_REGISTRY: list[CommandDef] = [
|
|||||||
args_hint="[level|show|hide]",
|
args_hint="[level|show|hide]",
|
||||||
subcommands=("none", "low", "minimal", "medium", "high", "xhigh", "show", "hide", "on", "off")),
|
subcommands=("none", "low", "minimal", "medium", "high", "xhigh", "show", "hide", "on", "off")),
|
||||||
CommandDef("skin", "Show or change the display skin/theme", "Configuration",
|
CommandDef("skin", "Show or change the display skin/theme", "Configuration",
|
||||||
cli_only=True, args_hint="[name]"),
|
args_hint="[name]"),
|
||||||
CommandDef("voice", "Toggle voice mode", "Configuration",
|
CommandDef("voice", "Toggle voice mode", "Configuration",
|
||||||
args_hint="[on|off|tts|status]", subcommands=("on", "off", "tts", "status")),
|
args_hint="[on|off|tts|status]", subcommands=("on", "off", "tts", "status")),
|
||||||
|
|
||||||
@@ -861,6 +861,23 @@ class SlashCommandCompleter(Completer):
|
|||||||
)
|
)
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _skin_completions(sub_text: str, sub_lower: str):
|
||||||
|
"""Yield completions for /skin from available skins."""
|
||||||
|
try:
|
||||||
|
from hermes_cli.skin_engine import list_skins
|
||||||
|
for s in list_skins():
|
||||||
|
name = s["name"]
|
||||||
|
if name.startswith(sub_lower) and name != sub_lower:
|
||||||
|
yield Completion(
|
||||||
|
name,
|
||||||
|
start_position=-len(sub_text),
|
||||||
|
display=name,
|
||||||
|
display_meta=s.get("description", "") or s.get("source", ""),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def _model_completions(self, sub_text: str, sub_lower: str):
|
def _model_completions(self, sub_text: str, sub_lower: str):
|
||||||
"""Yield completions for /model from config aliases + built-in aliases."""
|
"""Yield completions for /model from config aliases + built-in aliases."""
|
||||||
seen = set()
|
seen = set()
|
||||||
@@ -915,10 +932,14 @@ class SlashCommandCompleter(Completer):
|
|||||||
sub_text = parts[1] if len(parts) > 1 else ""
|
sub_text = parts[1] if len(parts) > 1 else ""
|
||||||
sub_lower = sub_text.lower()
|
sub_lower = sub_text.lower()
|
||||||
|
|
||||||
# Dynamic model alias completions for /model
|
# Dynamic completions for commands with runtime lists
|
||||||
if " " not in sub_text and base_cmd == "/model":
|
if " " not in sub_text:
|
||||||
yield from self._model_completions(sub_text, sub_lower)
|
if base_cmd == "/model":
|
||||||
return
|
yield from self._model_completions(sub_text, sub_lower)
|
||||||
|
return
|
||||||
|
if base_cmd == "/skin":
|
||||||
|
yield from self._skin_completions(sub_text, sub_lower)
|
||||||
|
return
|
||||||
|
|
||||||
# Static subcommand completions
|
# Static subcommand completions
|
||||||
if " " not in sub_text and base_cmd in SUBCOMMANDS:
|
if " " not in sub_text and base_cmd in SUBCOMMANDS:
|
||||||
|
|||||||
@@ -5722,7 +5722,7 @@ class AIAgent:
|
|||||||
# (gateway, batch, quiet) still get reasoning.
|
# (gateway, batch, quiet) still get reasoning.
|
||||||
# Any reasoning that wasn't shown during streaming is caught by the
|
# Any reasoning that wasn't shown during streaming is caught by the
|
||||||
# CLI post-response display fallback (cli.py _reasoning_shown_this_turn).
|
# CLI post-response display fallback (cli.py _reasoning_shown_this_turn).
|
||||||
if not self.stream_delta_callback:
|
if not self.stream_delta_callback and not self._stream_callback:
|
||||||
try:
|
try:
|
||||||
self.reasoning_callback(reasoning_text)
|
self.reasoning_callback(reasoning_text)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ def write_json(obj: dict) -> bool:
|
|||||||
|
|
||||||
def _emit(event: str, sid: str, payload: dict | None = None):
|
def _emit(event: str, sid: str, payload: dict | None = None):
|
||||||
params = {"type": event, "session_id": sid}
|
params = {"type": event, "session_id": sid}
|
||||||
if payload:
|
if payload is not None:
|
||||||
params["payload"] = payload
|
params["payload"] = payload
|
||||||
write_json({"jsonrpc": "2.0", "method": "event", "params": params})
|
write_json({"jsonrpc": "2.0", "method": "event", "params": params})
|
||||||
|
|
||||||
@@ -842,6 +842,8 @@ def _(rid, params: dict) -> dict:
|
|||||||
cfg.setdefault("display", {})[key] = value
|
cfg.setdefault("display", {})[key] = value
|
||||||
nv = value
|
nv = value
|
||||||
_save_cfg(cfg)
|
_save_cfg(cfg)
|
||||||
|
if key == "skin":
|
||||||
|
_emit("skin.changed", "", resolve_skin())
|
||||||
return _ok(rid, {"key": key, "value": nv})
|
return _ok(rid, {"key": key, "value": nv})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return _err(rid, 5001, str(e))
|
return _err(rid, 5001, str(e))
|
||||||
@@ -868,6 +870,8 @@ def _(rid, params: dict) -> dict:
|
|||||||
return _ok(rid, {"config": _load_cfg()})
|
return _ok(rid, {"config": _load_cfg()})
|
||||||
if key == "prompt":
|
if key == "prompt":
|
||||||
return _ok(rid, {"prompt": _load_cfg().get("custom_prompt", "")})
|
return _ok(rid, {"prompt": _load_cfg().get("custom_prompt", "")})
|
||||||
|
if key == "skin":
|
||||||
|
return _ok(rid, {"value": _load_cfg().get("display", {}).get("skin", "default")})
|
||||||
return _err(rid, 4002, f"unknown config key: {key}")
|
return _err(rid, 4002, f"unknown config key: {key}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -840,7 +840,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completions.length && input && (key.upArrow || key.downArrow)) {
|
if (completions.length && input && historyIdx === null && (key.upArrow || key.downArrow)) {
|
||||||
setCompIdx(i => (key.upArrow ? (i - 1 + completions.length) % completions.length : (i + 1) % completions.length))
|
setCompIdx(i => (key.upArrow ? (i - 1 + completions.length) % completions.length : (i + 1) % completions.length))
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1004,6 +1004,13 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'skin.changed':
|
||||||
|
if (p) {
|
||||||
|
setTheme(fromSkin(p.colors ?? {}, p.branding ?? {}, p.banner_logo ?? '', p.banner_hero ?? ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
case 'session.info':
|
case 'session.info':
|
||||||
setInfo(p as SessionInfo)
|
setInfo(p as SessionInfo)
|
||||||
|
|
||||||
@@ -1520,6 +1527,15 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
case 'skin':
|
||||||
|
if (arg) {
|
||||||
|
rpc('config.set', { key: 'skin', value: arg }).then((r: any) => sys(`skin → ${r.value}`))
|
||||||
|
} else {
|
||||||
|
rpc('config.get', { key: 'skin' }).then((r: any) => sys(`skin: ${r.value || 'default'}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
case 'yolo':
|
case 'yolo':
|
||||||
rpc('config.set', { key: 'yolo' }).then((r: any) => sys(`yolo ${r.value === '1' ? 'on' : 'off'}`))
|
rpc('config.set', { key: 'yolo' }).then((r: any) => sys(`yolo ${r.value === '1' ? 'on' : 'off'}`))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user