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]}` : ''}
+
+ ))}
)}