'use strict'; const readline = require('readline'); const { visibleWidth, truncatePlain } = require('../utils/text_width'); function truncateNoEllipsis(text, maxCols) { const out = truncatePlain(text, maxCols); if (out.endsWith('...') && visibleWidth(text) > maxCols) { return out.slice(0, -3); } return out; } function createStatusBar({ getTokens, maxTokens, getMaxTokens }) { let mode = 'input'; let enabled = true; let scrollApplied = false; let lastRows = null; const render = () => { if (!enabled || !process.stdout.isTTY) return; const cols = process.stdout.columns || 80; const rows = process.stdout.rows || 24; if (rows < 3) return; const left = mode === 'running' ? '按下Esc停止' : '输入/查看所有指令'; const total = typeof getTokens === 'function' ? getTokens() : 0; const formatCount = (value) => { const num = Number(value) || 0; if (num < 1000) return String(num); const k = Math.round((num / 1000) * 10) / 10; return `${k % 1 === 0 ? k.toFixed(0) : k.toFixed(1)}k`; }; const maxValue = typeof getMaxTokens === 'function' ? getMaxTokens() : maxTokens; const maxText = typeof maxValue === 'number' ? formatCount(maxValue) : (maxValue || '?'); const right = `当前上下文 ${formatCount(total)}/${maxText}`; const rightTail = ` ${formatCount(total)}/${maxText}`; const leftWidth = visibleWidth(left); const rightWidth = visibleWidth(right); const usableCols = Math.max(1, cols - 1); let safeLine = ''; const availableLeft = usableCols - rightWidth - 1; if (availableLeft >= 1) { const displayLeft = truncateNoEllipsis(left, availableLeft); const gap = usableCols - visibleWidth(displayLeft) - rightWidth; safeLine = `${displayLeft}${' '.repeat(Math.max(0, gap))}${right}`; } else { safeLine = truncateNoEllipsis(right, usableCols); } process.stdout.write('\x1b7'); // save cursor if (!scrollApplied || lastRows !== rows) { process.stdout.write('\x1b[r'); // reset scroll region process.stdout.write(`\x1b[1;${rows - 2}r`); // reserve last 2 lines scrollApplied = true; lastRows = rows; } // keep one blank spacer line above status line readline.cursorTo(process.stdout, 0, rows - 2); readline.clearLine(process.stdout, 0); // render status line on last row readline.cursorTo(process.stdout, 0, rows - 1); readline.clearLine(process.stdout, 0); process.stdout.write('\x1b[?7l'); // disable auto-wrap process.stdout.write(safeLine); process.stdout.write('\x1b[?7h'); // re-enable auto-wrap process.stdout.write('\x1b8'); // restore cursor }; const setMode = (nextMode) => { if (mode === nextMode) return; mode = nextMode; render(); }; const setEnabled = (nextEnabled) => { enabled = nextEnabled; if (enabled) render(); }; const destroy = () => { if (!process.stdout.isTTY) return; if (scrollApplied) { process.stdout.write('\x1b[r'); // reset scroll region scrollApplied = false; } const rows = process.stdout.rows || 24; if (rows >= 2) { readline.cursorTo(process.stdout, 0, rows - 2); readline.clearLine(process.stdout, 0); } if (rows >= 1) { readline.cursorTo(process.stdout, 0, rows - 1); readline.clearLine(process.stdout, 0); } }; if (process.stdout.isTTY) { process.stdout.on('resize', () => render()); } return { render, setMode, setEnabled, destroy }; } module.exports = { createStatusBar };