diff --git a/src/terminalService.js b/src/terminalService.js index bec595b..52e3b7b 100644 --- a/src/terminalService.js +++ b/src/terminalService.js @@ -118,10 +118,16 @@ class TerminalService extends EventEmitter { this.ptyProcess.kill(); this.ptyProcess = null; } + this.started = false; } catch { // ignore } } + + restart() { + try { this.dispose(); } catch {} + try { this.start(); } catch {} + } } const terminalService = new TerminalService(); diff --git a/src/ui/InkApp.jsx b/src/ui/InkApp.jsx index e402887..068aca3 100644 --- a/src/ui/InkApp.jsx +++ b/src/ui/InkApp.jsx @@ -124,6 +124,8 @@ export default class InkApp extends React.Component { this.toggleMenu = this.toggleMenu.bind(this); this.onKeypress = this.onKeypress.bind(this); this.menuAction = this.menuAction.bind(this); + this.getModelSettingsItems = this.getModelSettingsItems.bind(this); + this.handleModelSettingAdjust = this.handleModelSettingAdjust.bind(this); } componentDidMount() { @@ -230,6 +232,8 @@ export default class InkApp extends React.Component { const isUp = data.length >= 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41; const isDown = data.length >= 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42; const isEnter = data.length === 1 && data[0] === 0x0d; + const isLeft = data.length >= 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x44; + const isRight = data.length >= 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x43; const isCtrlC = data.length === 1 && data[0] === 0x03; if (!this.state.menuOpen) { @@ -245,7 +249,32 @@ export default class InkApp extends React.Component { return; } - // Menu navigation + // Submenu: Model settings adjustments + if (this.state.menuOpen && this.state.menuMode === 'model') { + const items = this.getModelSettingsItems(); + if (isUp) { + this.setState((s) => ({ menuIndex: (s.menuIndex - 1 + items.length) % items.length })); + return; + } + if (isDown) { + this.setState((s) => ({ menuIndex: (s.menuIndex + 1) % items.length })); + return; + } + if (isLeft || isRight) { + const idx = this.state.menuIndex; + const dir = isRight ? 1 : -1; + this.handleModelSettingAdjust(items[idx].key, dir); + return; + } + if (isEnter) { + // Enter exits model submenu back to main menu + this.setState({ menuMode: undefined, menuIndex: 0 }); + return; + } + return; + } + + // Menu navigation (main menu) const items = this.getMenuItems(); if (isUp) { this.setState((s) => ({ menuIndex: (s.menuIndex - 1 + items.length) % items.length })); @@ -278,12 +307,11 @@ export default class InkApp extends React.Component { try { terminalService.write('\x03'); } catch {} break; case 'Restart Terminal': - try { terminalService.dispose(); } catch {} - try { terminalService.start(); } catch {} + try { terminalService.restart(); } catch {} break; case 'Model settings': // Toggle a sub-menu state - this.setState({ menuMode: 'model' }); + this.setState({ menuMode: 'model', menuIndex: 0 }); break; case 'Exit the app': try { process.exit(0); } catch {} @@ -296,6 +324,30 @@ export default class InkApp extends React.Component { } } + getModelSettingsItems() { + return [ + { key: 'model', label: 'Model', options: ['gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano'] }, + { key: 'reasoningEffort', label: 'Reasoning effort', options: ['minimal', 'low', 'medium', 'high'] }, + { key: 'outputVerbosity', label: 'Output verbosity', options: ['low', 'medium', 'high'] }, + { key: 'back', label: 'Back to main menu' } + ]; + } + + handleModelSettingAdjust(key, dir) { + if (key === 'back') { + this.setState({ menuMode: undefined, menuIndex: 0 }); + return; + } + const items = this.getModelSettingsItems(); + const item = items.find((i) => i.key === key); + if (!item || !item.options) return; + const currentValue = this.state[key]; + const idx = item.options.indexOf(currentValue); + const nextIdx = ((idx === -1 ? 0 : idx) + dir + item.options.length) % item.options.length; + const nextValue = item.options[nextIdx]; + this.setState({ [key]: nextValue }); + } + render() { const { input, logs, terminal, chainOfThought, llmOutput } = this.state; const totalCols = (process && process.stdout && process.stdout.columns) ? process.stdout.columns : 80; @@ -337,7 +389,7 @@ export default class InkApp extends React.Component { - {this.state.menuOpen && ( + {this.state.menuOpen && this.state.menuMode !== 'model' && ( Main Menu (Up/Down to navigate, Enter to select) {menuItems.map((label, i) => ( @@ -345,17 +397,17 @@ export default class InkApp extends React.Component { {i === selected ? '› ' : ' '}{label} ))} - {this.state.menuMode === 'model' && ( - - Model: {this.state.model} - Reasoning effort: {this.state.reasoningEffort} - Output verbosity: {this.state.outputVerbosity} - - (Adjustments pending wiring: model list [gpt-5, gpt-5-mini, gpt-5-nano, gpt-4.1, gpt-4.1-mini, gpt-4.1-nano], - reasoning effort [minimal, low, medium, high], output verbosity [low, medium, high]) - - - )} + + )} + {this.state.menuOpen && this.state.menuMode === 'model' && ( + + Model Settings (Up/Down select, Left/Right change, Enter back) + {this.getModelSettingsItems().map((item, i) => ( + + {i === selected ? '› ' : ' '} + {item.label}{item.options ? `: ${this.state[item.key]}` : ''} + + ))} )}