feat: improve tui status line

This commit is contained in:
Peter Steinberger
2026-01-10 03:53:23 +01:00
parent 895cd06ecc
commit 8e63cd9a76

View File

@@ -152,6 +152,9 @@ export async function runTui(opts: TuiOptions) {
let autoMessageSent = false;
let sessionInfo: SessionInfo = {};
let lastCtrlCAt = 0;
let activityStatus = "idle";
let connectionStatus = "connecting";
let statusTimeout: NodeJS.Timeout | null = null;
const client = new GatewayChatClient({
url: opts.url,
@@ -218,6 +221,30 @@ export async function runTui(opts: TuiOptions) {
status.setText(theme.dim(text));
};
const renderStatus = () => {
const text = activityStatus
? `${connectionStatus} | ${activityStatus}`
: connectionStatus;
setStatus(text);
};
const setConnectionStatus = (text: string, ttlMs?: number) => {
connectionStatus = text;
renderStatus();
if (statusTimeout) clearTimeout(statusTimeout);
if (ttlMs && ttlMs > 0) {
statusTimeout = setTimeout(() => {
connectionStatus = isConnected ? "connected" : "disconnected";
renderStatus();
}, ttlMs);
}
};
const setActivityStatus = (text: string) => {
activityStatus = text;
renderStatus();
};
const updateFooter = () => {
const connection = isConnected ? "connected" : "disconnected";
const sessionKeyLabel = formatSessionKey(currentSessionKey);
@@ -246,7 +273,7 @@ export async function runTui(opts: TuiOptions) {
const deliver = deliverDefault ? "on" : "off";
footer.setText(
theme.dim(
`${connection} | agent ${agentLabel} | session ${sessionLabel} | model ${modelLabel} | think ${think} | verbose ${verbose}${reasoningLabel ? ` | ${reasoningLabel}` : ""} | ${tokens} | deliver ${deliver}`,
`${connection} | agent ${agentLabel} | session ${sessionLabel} | ${modelLabel} | think ${think} | verbose ${verbose}${reasoningLabel ? ` | ${reasoningLabel}` : ""} | ${tokens} | deliver ${deliver}`,
),
);
};
@@ -440,10 +467,10 @@ export async function runTui(opts: TuiOptions) {
sessionKey: currentSessionKey,
runId: activeChatRunId,
});
setStatus("aborted");
setActivityStatus("aborted");
} catch (err) {
chatLog.addSystem(`abort failed: ${String(err)}`);
setStatus("abort failed");
setActivityStatus("abort failed");
}
tui.requestRender();
};
@@ -478,7 +505,7 @@ export async function runTui(opts: TuiOptions) {
});
if (!text) return;
chatLog.updateAssistant(text, evt.runId);
setStatus("streaming");
setActivityStatus("streaming");
}
if (evt.state === "final") {
const text = extractTextFromMessage(evt.message, {
@@ -487,17 +514,17 @@ export async function runTui(opts: TuiOptions) {
chatLog.finalizeAssistant(text || "(no output)", evt.runId);
noteFinalizedRun(evt.runId);
activeChatRunId = null;
setStatus("idle");
setActivityStatus("idle");
}
if (evt.state === "aborted") {
chatLog.addSystem("run aborted");
activeChatRunId = null;
setStatus("aborted");
setActivityStatus("aborted");
}
if (evt.state === "error") {
chatLog.addSystem(`run error: ${evt.errorMessage ?? "unknown"}`);
activeChatRunId = null;
setStatus("error");
setActivityStatus("error");
}
tui.requestRender();
};
@@ -528,9 +555,9 @@ export async function runTui(opts: TuiOptions) {
}
if (evt.stream === "lifecycle") {
const phase = typeof evt.data?.phase === "string" ? evt.data.phase : "";
if (phase === "start") setStatus("running");
if (phase === "end") setStatus("idle");
if (phase === "error") setStatus("error");
if (phase === "start") setActivityStatus("running");
if (phase === "end") setActivityStatus("idle");
if (phase === "error") setActivityStatus("error");
tui.requestRender();
}
};
@@ -895,7 +922,7 @@ export async function runTui(opts: TuiOptions) {
try {
chatLog.addUser(text);
tui.requestRender();
setStatus("sending");
setActivityStatus("sending");
const { runId } = await client.sendChat({
sessionKey: currentSessionKey,
message: text,
@@ -904,10 +931,10 @@ export async function runTui(opts: TuiOptions) {
timeoutMs: opts.timeoutMs,
});
activeChatRunId = runId;
setStatus("waiting");
setActivityStatus("waiting");
} catch (err) {
chatLog.addSystem(`send failed: ${String(err)}`);
setStatus("error");
setActivityStatus("error");
}
tui.requestRender();
};
@@ -933,7 +960,7 @@ export async function runTui(opts: TuiOptions) {
const now = Date.now();
if (editor.getText().trim().length > 0) {
editor.setText("");
setStatus("cleared input");
setActivityStatus("cleared input");
tui.requestRender();
return;
}
@@ -943,7 +970,7 @@ export async function runTui(opts: TuiOptions) {
process.exit(0);
}
lastCtrlCAt = now;
setStatus("press ctrl+c again to exit");
setActivityStatus("press ctrl+c again to exit");
tui.requestRender();
};
editor.onCtrlD = () => {
@@ -954,7 +981,7 @@ export async function runTui(opts: TuiOptions) {
editor.onCtrlO = () => {
toolsExpanded = !toolsExpanded;
chatLog.setToolsExpanded(toolsExpanded);
setStatus(toolsExpanded ? "tools expanded" : "tools collapsed");
setActivityStatus(toolsExpanded ? "tools expanded" : "tools collapsed");
tui.requestRender();
};
editor.onCtrlL = () => {
@@ -978,20 +1005,20 @@ export async function runTui(opts: TuiOptions) {
client.onConnected = () => {
isConnected = true;
setStatus("connected");
setConnectionStatus("connected");
void (async () => {
await refreshAgents();
updateHeader();
if (!historyLoaded) {
await loadHistory();
chatLog.addSystem("gateway connected");
setConnectionStatus("gateway connected", 4000);
tui.requestRender();
if (!autoMessageSent && autoMessage) {
autoMessageSent = true;
await sendMessage(autoMessage);
}
} else {
chatLog.addSystem("gateway reconnected");
setConnectionStatus("gateway reconnected", 4000);
}
updateFooter();
tui.requestRender();
@@ -1000,23 +1027,24 @@ export async function runTui(opts: TuiOptions) {
client.onDisconnected = (reason) => {
isConnected = false;
chatLog.addSystem(`gateway disconnected: ${reason || "closed"}`);
setStatus("disconnected");
const reasonLabel = reason?.trim() ? reason.trim() : "closed";
setConnectionStatus(`gateway disconnected: ${reasonLabel}`, 5000);
setActivityStatus("idle");
updateFooter();
tui.requestRender();
};
client.onGap = (info) => {
chatLog.addSystem(
setConnectionStatus(
`event gap: expected ${info.expected}, got ${info.received}`,
5000,
);
tui.requestRender();
};
updateHeader();
setStatus("connecting");
setConnectionStatus("connecting");
updateFooter();
chatLog.addSystem("connecting...");
tui.start();
client.start();
}