Refactor CLI and InkApp components for improved functionality and user experience. Update model settings in InkApp, enhance terminal input handling, and streamline dependency management in package.json. Remove unused dependencies and update dotenv version for better environment variable management.
This commit is contained in:
7
cli.js
7
cli.js
@@ -84,6 +84,7 @@ while(true){
|
||||
// Block for user input before kicking off the LLM loop
|
||||
const userText = await askUserForInput();
|
||||
await streamOnce(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), userText );
|
||||
//await streamOnce(new OpenAI({ baseURL: "https://api.cerebras.ai/v1",apiKey: "csk-8jftdte6r6vf8fdvp9xkyek5t3jnc6jfhh93d3ewfcwxxvh9" }), userText );
|
||||
async function streamOnce(openai, userText) {
|
||||
const toolsByFile = await loadTools();
|
||||
|
||||
@@ -106,10 +107,10 @@ websearch - eine Google Suche machen mit Schlüsselwörtern
|
||||
do{
|
||||
|
||||
const call = {
|
||||
model: 'gpt-5-mini',
|
||||
model: 'gpt-4.1-nano',
|
||||
input: counter == 0 ? [systemprompt,...structuredClone(input)] : structuredClone(input),
|
||||
text: { format: { type: 'text' }, verbosity: 'low' },
|
||||
reasoning: { effort: 'minimal', summary: 'detailed' },
|
||||
text: { format: { type: 'text' }/*, verbosity: 'low' */},
|
||||
//reasoning: { effort: 'minimal', summary: 'detailed' },
|
||||
tools: Object.values(toolsByFile).map(t => t.def),
|
||||
store: true,
|
||||
}
|
||||
|
||||
132
index.html
Normal file
132
index.html
Normal file
@@ -0,0 +1,132 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Pelikan fährt Rad – SVG</title>
|
||||
<style>
|
||||
html, body { height: 100%; margin: 0; background:#0e1320; color:#e7ecf3; font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; }
|
||||
.wrap { display:grid; place-items:center; height:100%; }
|
||||
.cap { position: fixed; bottom: 0.75rem; left: 0; right: 0; text-align:center; font-size: 0.9rem; opacity: .8; }
|
||||
svg { width: min(92vw, 960px); height: auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<!-- Rein vektoriell gezeichneter Pelikan auf einem Fahrrad. -->
|
||||
<svg viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="t d">
|
||||
<title id="t">Pelikan fährt Fahrrad</title>
|
||||
<desc id="d">Ein stilisierter Pelikan mit großem Schnabel fährt ein Fahrrad mit zwei Rädern; einfache Formen, klare Farben.</desc>
|
||||
|
||||
<!-- Hintergrund-Deko -->
|
||||
<defs>
|
||||
<radialGradient id="sky" cx="50%" cy="40%" r="70%">
|
||||
<stop offset="0%" stop-color="#1b2a4a" />
|
||||
<stop offset="100%" stop-color="#0e1320" />
|
||||
</radialGradient>
|
||||
<linearGradient id="beakGrad" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0%" stop-color="#ffb347"/>
|
||||
<stop offset="100%" stop-color="#ff7f11"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="frameGrad" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0%" stop-color="#4bd5ff"/>
|
||||
<stop offset="100%" stop-color="#6a5cff"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#000" flood-opacity="0.35"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#sky)"/>
|
||||
|
||||
<!-- Bodenlinie -->
|
||||
<g opacity="0.35">
|
||||
<ellipse cx="400" cy="520" rx="300" ry="40" fill="#000" />
|
||||
</g>
|
||||
|
||||
<!-- Fahrrad -->
|
||||
<g id="bike" transform="translate(0,0)" filter="url(#shadow)">
|
||||
<!-- Räder -->
|
||||
<g id="wheels" stroke="#cfe7ff" stroke-width="6" fill="none">
|
||||
<circle cx="260" cy="460" r="90" />
|
||||
<circle cx="540" cy="460" r="90" />
|
||||
<!-- Speichen -->
|
||||
<g stroke="#9fd0ff" stroke-width="3" opacity="0.6">
|
||||
<line x1="260" y1="370" x2="260" y2="550" />
|
||||
<line x1="170" y1="460" x2="350" y2="460" />
|
||||
<line x1="200" y1="400" x2="320" y2="520" />
|
||||
<line x1="200" y1="520" x2="320" y2="400" />
|
||||
<line x1="540" y1="370" x2="540" y2="550" />
|
||||
<line x1="450" y1="460" x2="630" y2="460" />
|
||||
<line x1="480" y1="400" x2="600" y2="520" />
|
||||
<line x1="480" y1="520" x2="600" y2="400" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Rahmen -->
|
||||
<g id="frame" stroke="url(#frameGrad)" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" fill="none">
|
||||
<polyline points="260,460 360,420 450,460 540,460" />
|
||||
<line x1="360" y1="420" x2="410" y2="360" />
|
||||
<line x1="410" y1="360" x2="520" y2="360" />
|
||||
<line x1="520" y1="360" x2="540" y2="460" />
|
||||
</g>
|
||||
|
||||
<!-- Kurbel und Pedale -->
|
||||
<g id="crank" transform="translate(405,440)">
|
||||
<circle r="16" fill="#b7d8ff" />
|
||||
<g stroke="#b7d8ff" stroke-width="6" stroke-linecap="round">
|
||||
<line x1="0" y1="0" x2="28" y2="-28" />
|
||||
</g>
|
||||
<circle cx="28" cy="-28" r="8" fill="#e5f3ff" />
|
||||
</g>
|
||||
|
||||
<!-- Lenker und Sattel -->
|
||||
<g stroke="#cfe7ff" stroke-width="10" stroke-linecap="round" fill="none">
|
||||
<path d="M520 360 C 555 335, 585 345, 600 360"/>
|
||||
</g>
|
||||
<rect x="325" y="330" width="70" height="16" rx="8" fill="#cfe7ff" />
|
||||
</g>
|
||||
|
||||
<!-- Pelikan -->
|
||||
<g id="pelican" transform="translate(0,0)">
|
||||
<!-- Körper -->
|
||||
<ellipse cx="360" cy="360" rx="90" ry="70" fill="#f2f6fb" stroke="#e1e8f5" stroke-width="4" />
|
||||
<!-- Flügel -->
|
||||
<path d="M330 345 C 275 350, 250 395, 270 430 C 295 470, 355 470, 390 440 C 410 420, 400 380, 370 360 Z" fill="#eaf2fb" stroke="#d7e4f6" stroke-width="3" />
|
||||
<!-- Hals -->
|
||||
<path d="M400 335 C 445 320, 470 300, 500 295 C 520 292, 540 305, 540 325 C 540 345, 520 360, 500 360 C 470 360, 445 350, 415 355" fill="none" stroke="#f2f6fb" stroke-width="20" stroke-linecap="round" />
|
||||
<!-- Kopf -->
|
||||
<circle cx="520" cy="325" r="28" fill="#f7fbff" stroke="#e1e8f5" stroke-width="3" />
|
||||
<!-- Auge -->
|
||||
<circle cx="528" cy="322" r="5" fill="#21314f" />
|
||||
<circle cx="526" cy="320" r="2" fill="#ffffff" />
|
||||
<!-- Schnabel -->
|
||||
<path d="M535 330 C 585 330, 630 338, 675 352 C 640 360, 600 368, 555 368 C 545 360, 538 348, 535 330 Z" fill="url(#beakGrad)" stroke="#ff9a2a" stroke-width="3" />
|
||||
<!-- Kehlsack angedeutet -->
|
||||
<path d="M540 338 C 585 345, 590 352, 552 365" fill="none" stroke="#ffb347" stroke-width="4" opacity="0.7" />
|
||||
<!-- Beine -->
|
||||
<g stroke="#ffb347" stroke-width="10" stroke-linecap="round">
|
||||
<path d="M340 420 L 320 455" />
|
||||
<path d="M380 420 L 390 450" />
|
||||
</g>
|
||||
<!-- Füße auf Pedalen -->
|
||||
<g fill="#ffb347">
|
||||
<circle cx="320" cy="455" r="8" />
|
||||
<circle cx="390" cy="450" r="8" />
|
||||
</g>
|
||||
<!-- Schwanz -->
|
||||
<path d="M280 365 C 255 360, 240 350, 230 340 C 248 368, 260 390, 290 392" fill="#eaf2fb" stroke="#d7e4f6" stroke-width="3" />
|
||||
</g>
|
||||
|
||||
<!-- Sterne / Deko -->
|
||||
<g fill="#9fd0ff" opacity="0.8">
|
||||
<circle cx="90" cy="80" r="2"/>
|
||||
<circle cx="160" cy="120" r="1.5"/>
|
||||
<circle cx="720" cy="90" r="2.2"/>
|
||||
<circle cx="650" cy="160" r="1.8"/>
|
||||
<circle cx="120" cy="200" r="1.6"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cap">SVG Illustration: Pelikan auf Fahrrad. Datei lokal öffnen oder auf einen Webserver legen.</div>
|
||||
</body>
|
||||
</html>
|
||||
719
package-lock.json
generated
719
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
63
package.json
63
package.json
@@ -3,55 +3,6 @@
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "cli.js",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"agentkeepalive": "^4.6.0",
|
||||
"asynckit": "^0.4.0",
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"chalk": "^5.5.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"delayed-stream": "^1.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"esbuild": "^0.25.8",
|
||||
"event-target-shim": "^5.0.1",
|
||||
"exa-js": "^1.8.27",
|
||||
"form-data": "^4.0.4",
|
||||
"form-data-encoder": "^1.7.2",
|
||||
"formdata-node": "^4.4.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.3.0",
|
||||
"get-proto": "^1.0.1",
|
||||
"get-tsconfig": "^4.10.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2",
|
||||
"humanize-ms": "^1.2.1",
|
||||
"ink": "^6.1.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"math-intrinsics": "^1.1.0",
|
||||
"mime-db": "^1.52.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"ms": "^2.1.3",
|
||||
"node-domexception": "^1.0.0",
|
||||
"node-fetch": "^2.7.0",
|
||||
"node-pty": "^1.0.0",
|
||||
"openai": "^4.104.0",
|
||||
"resolve-pkg-maps": "^1.0.0",
|
||||
"terminal-kit": "^3.1.2",
|
||||
"tr46": "^0.0.3",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.9.2",
|
||||
"undici-types": "^5.26.5",
|
||||
"web-streams-polyfill": "^4.0.0-beta.3",
|
||||
"webidl-conversions": "^3.0.1",
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node cli.js",
|
||||
"start:ink": "tsx cli-ink.js",
|
||||
@@ -64,5 +15,17 @@
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": ""
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.2.1",
|
||||
"exa-js": "^1.8.27",
|
||||
"ink": "^6.1.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"node-pty": "^1.0.0",
|
||||
"react": "^19.1.1",
|
||||
"terminal-kit": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.20.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,22 +30,24 @@ class TerminalService extends EventEmitter {
|
||||
env: {
|
||||
...process.env,
|
||||
TERM: 'xterm-256color',
|
||||
PS1: '> '
|
||||
PS1: 'bash> '
|
||||
},
|
||||
});
|
||||
|
||||
this.ptyProcess.onData((data) => {
|
||||
const str = String(data);
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
const ch = str[i];
|
||||
if (ch === '\\n') {
|
||||
// Normalize CRLF to LF to avoid double-handling \r and \n
|
||||
const normalized = str.replace(/\r\n/g, '\n');
|
||||
for (let i = 0; i < normalized.length; i += 1) {
|
||||
const ch = normalized[i];
|
||||
if (ch === '\n') {
|
||||
// Line feed completes the current line
|
||||
this.lines.push(this.partial);
|
||||
this.partial = '';
|
||||
} else if (ch === '\\r') {
|
||||
// Carriage return: move to start of line; start overwriting
|
||||
} else if (ch === '\r') {
|
||||
// Standalone carriage return: simulate return to start of line (overwrite)
|
||||
this.partial = '';
|
||||
} else if (ch === '\\b' || ch === '\\x7f') {
|
||||
} else if (ch === '\b' || ch === '\x7f') {
|
||||
// Backspace or DEL: remove last char if present
|
||||
if (this.partial.length > 0) {
|
||||
this.partial = this.partial.slice(0, -1);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import uiService from './uiService.js';
|
||||
import TextInput from 'ink-text-input';
|
||||
import terminalService from '../terminalService.js';
|
||||
|
||||
@@ -56,25 +57,23 @@ class Pane extends React.Component {
|
||||
return out;
|
||||
}
|
||||
// Apply a blinking cursor to the given line according to width constraints
|
||||
withCursor(line, maxWidth) {
|
||||
const cursorChar = typeof this.props.cursorChar === 'string' && this.props.cursorChar.length > 0 ? this.props.cursorChar[0] : '█';
|
||||
const visible = !!this.state.cursorVisible;
|
||||
if (!visible) {
|
||||
return line;
|
||||
}
|
||||
if (typeof maxWidth === 'number' && maxWidth > 0) {
|
||||
if (line.length < maxWidth) {
|
||||
return `${line}${cursorChar}`;
|
||||
}
|
||||
if (line.length === maxWidth) {
|
||||
// Replace last char to avoid overflow
|
||||
return `${line.slice(0, maxWidth - 1)}${cursorChar}`;
|
||||
}
|
||||
// If somehow longer, just ensure width
|
||||
return `${line.slice(0, maxWidth - 1)}${cursorChar}`;
|
||||
}
|
||||
return `${line}${cursorChar}`;
|
||||
}
|
||||
withCursor(line, maxWidth) {
|
||||
const cursorChar = typeof this.props.cursorChar === 'string' && this.props.cursorChar.length > 0 ? this.props.cursorChar[0] : '█';
|
||||
const visible = !!this.state.cursorVisible;
|
||||
if (typeof maxWidth !== 'number' || maxWidth <= 0) {
|
||||
return line;
|
||||
}
|
||||
// Place cursor at the logical end of content, clamped to last column
|
||||
const width = maxWidth;
|
||||
const base = (line || '').slice(0, width);
|
||||
const cursorIndex = Math.min(base.length, width - 1);
|
||||
const targetLen = Math.min(width, cursorIndex + 1);
|
||||
const padLen = Math.max(0, targetLen - base.length);
|
||||
const padded = padLen > 0 ? `${base}${' '.repeat(padLen)}` : base.slice(0, targetLen);
|
||||
const chars = padded.split('');
|
||||
chars[cursorIndex] = visible ? cursorChar : ' ';
|
||||
return chars.join('');
|
||||
}
|
||||
render() {
|
||||
const { title, lines, maxWidth } = this.props;
|
||||
return (
|
||||
@@ -109,7 +108,12 @@ export default class InkApp extends React.Component {
|
||||
logs: [],
|
||||
terminal: [],
|
||||
chainOfThought: [],
|
||||
llmOutput: []
|
||||
llmOutput: [],
|
||||
menuOpen: false,
|
||||
menuIndex: 0,
|
||||
model: 'gpt-5',
|
||||
reasoningEffort: 'minimal',
|
||||
outputVerbosity: 'low'
|
||||
};
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
@@ -117,6 +121,9 @@ export default class InkApp extends React.Component {
|
||||
this.setChainOfThought = this.setChainOfThought.bind(this);
|
||||
this.setTerminal = this.setTerminal.bind(this);
|
||||
this.setLogs = this.setLogs.bind(this);
|
||||
this.toggleMenu = this.toggleMenu.bind(this);
|
||||
this.onKeypress = this.onKeypress.bind(this);
|
||||
this.menuAction = this.menuAction.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -141,6 +148,12 @@ export default class InkApp extends React.Component {
|
||||
process.stdout.on('resize', this.onResize);
|
||||
}
|
||||
this.onResize();
|
||||
|
||||
// Keyboard handling for menu
|
||||
if (process.stdin && process.stdin.on) {
|
||||
try { process.stdin.setRawMode(true); } catch {}
|
||||
process.stdin.on('data', this.onKeypress);
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this.terminalUnsub) {
|
||||
@@ -151,6 +164,9 @@ export default class InkApp extends React.Component {
|
||||
process.stdout.off('resize', this.onResize);
|
||||
this.onResize = null;
|
||||
}
|
||||
if (process.stdin && process.stdin.off) {
|
||||
process.stdin.off('data', this.onKeypress);
|
||||
}
|
||||
}
|
||||
|
||||
setPaneLines(stateKey, lines) {
|
||||
@@ -204,6 +220,82 @@ export default class InkApp extends React.Component {
|
||||
}));
|
||||
}
|
||||
|
||||
toggleMenu(open) {
|
||||
this.setState((s) => ({ menuOpen: typeof open === 'boolean' ? open : !s.menuOpen }));
|
||||
}
|
||||
|
||||
onKeypress(buf) {
|
||||
const data = Buffer.isBuffer(buf) ? buf : Buffer.from(String(buf));
|
||||
// ESC [ A => Up arrow
|
||||
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 isCtrlC = data.length === 1 && data[0] === 0x03;
|
||||
|
||||
if (!this.state.menuOpen) {
|
||||
if (isUp) {
|
||||
this.toggleMenu(true);
|
||||
return;
|
||||
}
|
||||
// let Ink TextInput handle normal typing; we don't intercept here
|
||||
if (isCtrlC) {
|
||||
// Pass through to exit behavior handled in cli-ink.js
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Menu navigation
|
||||
const items = this.getMenuItems();
|
||||
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 (isEnter) {
|
||||
const idx = this.state.menuIndex;
|
||||
this.menuAction(items[idx]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
getMenuItems() {
|
||||
return [
|
||||
'Send CTRL-C to terminal',
|
||||
'Restart Terminal',
|
||||
'Model settings',
|
||||
'Exit the app',
|
||||
'Close menu'
|
||||
];
|
||||
}
|
||||
|
||||
menuAction(label) {
|
||||
switch (label) {
|
||||
case 'Send CTRL-C to terminal':
|
||||
try { terminalService.write('\x03'); } catch {}
|
||||
break;
|
||||
case 'Restart Terminal':
|
||||
try { terminalService.dispose(); } catch {}
|
||||
try { terminalService.start(); } catch {}
|
||||
break;
|
||||
case 'Model settings':
|
||||
// Toggle a sub-menu state
|
||||
this.setState({ menuMode: 'model' });
|
||||
break;
|
||||
case 'Exit the app':
|
||||
try { process.exit(0); } catch {}
|
||||
break;
|
||||
case 'Close menu':
|
||||
this.toggleMenu(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { input, logs, terminal, chainOfThought, llmOutput } = this.state;
|
||||
const totalCols = (process && process.stdout && process.stdout.columns) ? process.stdout.columns : 80;
|
||||
@@ -230,6 +322,9 @@ export default class InkApp extends React.Component {
|
||||
const chainOfThoughtView = sliceLast(chainOfThought);
|
||||
const terminalView = sliceLast(terminal);
|
||||
const logsView = sliceLast(logs);
|
||||
|
||||
const menuItems = this.getMenuItems();
|
||||
const selected = this.state.menuIndex;
|
||||
return (
|
||||
<Box flexDirection="column" height="100%">
|
||||
<Box flexGrow={1} flexDirection="row" minWidth={0}>
|
||||
@@ -242,6 +337,27 @@ export default class InkApp extends React.Component {
|
||||
<Pane title="Logging" lines={logsView} maxWidth={paneContentWidth} />
|
||||
</Box>
|
||||
</Box>
|
||||
{this.state.menuOpen && (
|
||||
<Box borderStyle="round" paddingX={1} paddingY={0} marginTop={1} flexDirection="column">
|
||||
<Text color="yellow">Main Menu (Up/Down to navigate, Enter to select)</Text>
|
||||
{menuItems.map((label, i) => (
|
||||
<Text key={label} color={i === selected ? 'cyan' : undefined}>
|
||||
{i === selected ? '› ' : ' '}{label}
|
||||
</Text>
|
||||
))}
|
||||
{this.state.menuMode === 'model' && (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<Text>Model: {this.state.model}</Text>
|
||||
<Text>Reasoning effort: {this.state.reasoningEffort}</Text>
|
||||
<Text>Output verbosity: {this.state.outputVerbosity}</Text>
|
||||
<Text dimColor>
|
||||
(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])
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<Box marginTop={1}>
|
||||
<Text>Input: </Text>
|
||||
<TextInput
|
||||
|
||||
9
src/ui/uiService.js
Normal file
9
src/ui/uiService.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import EventEmitter from 'events';
|
||||
|
||||
class UIService extends EventEmitter {}
|
||||
|
||||
const uiService = new UIService();
|
||||
export default uiService;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user