openaxies 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/App.js +137 -285
- package/src/components/ComposerDock.js +33 -72
- package/src/components/ModelBar.js +0 -42
- package/src/components/Particles.js +0 -47
package/package.json
CHANGED
package/src/App.js
CHANGED
|
@@ -4,49 +4,53 @@ import { hex } from './config/theme.js';
|
|
|
4
4
|
import { getCommands } from './config/commands.js';
|
|
5
5
|
import { getModels, getModelById, DEFAULT_MODEL_ID } from './config/models.js';
|
|
6
6
|
import { callModel } from './providers/index.js';
|
|
7
|
-
import { createModelBar } from './components/ModelBar.js';
|
|
8
|
-
import { createParticleRow } from './components/Particles.js';
|
|
9
7
|
import { createComposerDock, DOCK_HEIGHT } from './components/ComposerDock.js';
|
|
10
8
|
|
|
11
9
|
const h = React.createElement;
|
|
12
|
-
const BAR_COLOR = '#1A1A2E';
|
|
13
10
|
|
|
14
11
|
export default AppRoot;
|
|
15
12
|
|
|
13
|
+
const LOGO = [
|
|
14
|
+
' \u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588',
|
|
15
|
+
'\u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 ',
|
|
16
|
+
'\u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588',
|
|
17
|
+
'\u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588',
|
|
18
|
+
' \u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 ',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const LOGO_COLORS = ['#00F0FF', '#00DDFF', '#00CCFF', '#00BBFF', '#00AAFF'];
|
|
22
|
+
|
|
16
23
|
function buildHistory(messages, newText) {
|
|
17
|
-
const
|
|
24
|
+
const h = [];
|
|
18
25
|
for (let i = 0; i < messages.length; i++) {
|
|
19
26
|
const m = messages[i];
|
|
20
|
-
if (m === null || m === undefined || typeof m !== 'object')
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
27
|
+
if (m === null || m === undefined || typeof m !== 'object') continue;
|
|
23
28
|
if (m.role === 'user' || m.role === 'assistant') {
|
|
24
|
-
const
|
|
25
|
-
|
|
29
|
+
const c = typeof m.content === 'string' ? m.content : '';
|
|
30
|
+
h.push({ role: m.role, content: c });
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
33
|
if (typeof newText === 'string' && newText.length > 0) {
|
|
29
|
-
|
|
34
|
+
h.push({ role: 'user', content: newText });
|
|
30
35
|
}
|
|
31
|
-
return
|
|
36
|
+
return h;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
function formatTimer(
|
|
35
|
-
if (typeof
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
return seconds.toFixed(1) + 's';
|
|
39
|
+
function formatTimer(s) {
|
|
40
|
+
if (typeof s !== 'number' || s < 0) return '0.0s';
|
|
41
|
+
return s.toFixed(1) + 's';
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
function
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
return h(Text, { color: BAR_COLOR }, line);
|
|
44
|
+
function hr(cols, color) {
|
|
45
|
+
const n = typeof cols === 'number' && cols > 0 ? cols : 80;
|
|
46
|
+
const c = typeof color === 'string' ? color : '#1A1A28';
|
|
47
|
+
let s = '';
|
|
48
|
+
for (let i = 0; i < n; i++) s = s + '\u2500';
|
|
49
|
+
return h(Text, { color: c }, s);
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
const SPINNER = ['\u25CB', '\u25D4', '\u25D0', '\u25D5', '\u25CF', '\u25D5', '\u25D0', '\u25D4'];
|
|
53
|
+
|
|
50
54
|
function AppRoot() {
|
|
51
55
|
const { stdout } = useStdout();
|
|
52
56
|
const rows = stdout.rows || 24;
|
|
@@ -57,20 +61,17 @@ function AppRoot() {
|
|
|
57
61
|
const [streamText, setStreamText] = React.useState('');
|
|
58
62
|
const [streamingActive, setStreamingActive] = React.useState(false);
|
|
59
63
|
const [isThinking, setIsThinking] = React.useState(false);
|
|
60
|
-
const [
|
|
64
|
+
const [spinnerIdx, setSpinnerIdx] = React.useState(0);
|
|
61
65
|
const [thinkingElapsed, setThinkingElapsed] = React.useState(0);
|
|
62
66
|
const [inputBuffer, setInputBuffer] = React.useState('');
|
|
63
67
|
const [showOverlay, setShowOverlay] = React.useState(false);
|
|
64
68
|
const [overlayIndex, setOverlayIndex] = React.useState(0);
|
|
65
|
-
const [particleFrame, setParticleFrame] = React.useState(0);
|
|
66
69
|
const [toolInfo, setToolInfo] = React.useState(null);
|
|
67
70
|
|
|
68
71
|
const commands = getCommands();
|
|
69
|
-
const
|
|
70
|
-
const SEP_H = 1;
|
|
71
|
-
const DOCK = DOCK_HEIGHT;
|
|
72
|
+
const LOGO_H = 5;
|
|
72
73
|
const overlayH = showOverlay ? 6 : 0;
|
|
73
|
-
const fixedH =
|
|
74
|
+
const fixedH = LOGO_H + overlayH + DOCK_HEIGHT;
|
|
74
75
|
const availLines = Math.max(1, rows - fixedH);
|
|
75
76
|
|
|
76
77
|
const abortRef = React.useRef(null);
|
|
@@ -78,27 +79,6 @@ function AppRoot() {
|
|
|
78
79
|
const spinnerRef = React.useRef(null);
|
|
79
80
|
const timerRef = React.useRef(null);
|
|
80
81
|
const thinkStartRef = React.useRef(null);
|
|
81
|
-
const particleRef = React.useRef(null);
|
|
82
|
-
|
|
83
|
-
const SPINNER_FRAMES = [
|
|
84
|
-
'\u25CB', '\u25D4', '\u25D0', '\u25D5', '\u25CF',
|
|
85
|
-
'\u25D5', '\u25D0', '\u25D4', '\u25CB', '\u25D1',
|
|
86
|
-
'\u25D2', '\u25D3', '\u25CF', '\u25D3', '\u25D2',
|
|
87
|
-
'\u25D1', '\u25CC', '\u25CB', '\u25CD', '\u25CE',
|
|
88
|
-
'\u25CF', '\u25CE', '\u25CD', '\u25CB',
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
React.useEffect(function () {
|
|
92
|
-
particleRef.current = setInterval(function () {
|
|
93
|
-
setParticleFrame(function (p) { return (p + 1) % 64; });
|
|
94
|
-
}, 100);
|
|
95
|
-
return function () {
|
|
96
|
-
if (particleRef.current !== null) {
|
|
97
|
-
clearInterval(particleRef.current);
|
|
98
|
-
particleRef.current = null;
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
}, []);
|
|
102
82
|
|
|
103
83
|
React.useEffect(function () {
|
|
104
84
|
if (isThinking === false) {
|
|
@@ -110,14 +90,12 @@ function AppRoot() {
|
|
|
110
90
|
clearInterval(timerRef.current);
|
|
111
91
|
timerRef.current = null;
|
|
112
92
|
}
|
|
113
|
-
setSpinnerChar('');
|
|
114
93
|
return;
|
|
115
94
|
}
|
|
116
95
|
let idx = 0;
|
|
117
|
-
setSpinnerChar(SPINNER_FRAMES[0]);
|
|
118
96
|
spinnerRef.current = setInterval(function () {
|
|
119
|
-
idx = (idx + 1) %
|
|
120
|
-
|
|
97
|
+
idx = (idx + 1) % SPINNER.length;
|
|
98
|
+
setSpinnerIdx(idx);
|
|
121
99
|
}, 100);
|
|
122
100
|
timerRef.current = setInterval(function () {
|
|
123
101
|
if (thinkStartRef.current !== null) {
|
|
@@ -148,101 +126,71 @@ function AppRoot() {
|
|
|
148
126
|
});
|
|
149
127
|
}
|
|
150
128
|
|
|
151
|
-
function
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
155
|
-
return
|
|
129
|
+
function checkThink(text) {
|
|
130
|
+
const o = text.lastIndexOf('<think>');
|
|
131
|
+
const c = text.lastIndexOf('</think>');
|
|
132
|
+
if (o === -1 && c === -1) return false;
|
|
133
|
+
return o > c;
|
|
156
134
|
}
|
|
157
135
|
|
|
158
136
|
async function handleSubmit(text) {
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
161
|
-
|
|
137
|
+
const safe = typeof text === 'string' ? text : '';
|
|
138
|
+
if (safe.length === 0 || streamingActive === true) return;
|
|
162
139
|
setStreamingActive(true);
|
|
163
140
|
setToolInfo(null);
|
|
164
141
|
setStreamText(' connecting');
|
|
165
142
|
setThinkingElapsed(0);
|
|
166
143
|
connRef.current = setInterval(function () {
|
|
167
|
-
setStreamText(function (
|
|
168
|
-
if (
|
|
169
|
-
if (
|
|
170
|
-
if (
|
|
144
|
+
setStreamText(function (p) {
|
|
145
|
+
if (p === ' connecting') return ' connecting.';
|
|
146
|
+
if (p === ' connecting.') return ' connecting..';
|
|
147
|
+
if (p === ' connecting..') return ' connecting...';
|
|
171
148
|
return ' connecting';
|
|
172
149
|
});
|
|
173
150
|
}, 400);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return prev.concat([{
|
|
177
|
-
role: 'user',
|
|
178
|
-
content: safeText,
|
|
179
|
-
id: 'u-' + Date.now(),
|
|
180
|
-
}]);
|
|
151
|
+
setMessages(function (p) {
|
|
152
|
+
return p.concat([{ role: 'user', content: safe, id: 'u-' + Date.now() }]);
|
|
181
153
|
});
|
|
182
154
|
setInputBuffer('');
|
|
183
|
-
|
|
184
155
|
const modelInfo = getModelById(activeModel);
|
|
185
156
|
if (modelInfo === null) {
|
|
186
|
-
if (connRef.current !== null)
|
|
187
|
-
clearInterval(connRef.current);
|
|
188
|
-
connRef.current = null;
|
|
189
|
-
}
|
|
157
|
+
if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
|
|
190
158
|
setStreamingActive(false);
|
|
191
159
|
setStreamText('');
|
|
192
|
-
setMessages(function (
|
|
193
|
-
return prev.concat([{
|
|
194
|
-
role: 'assistant',
|
|
195
|
-
content: 'No model selected. Use `/model` to switch.',
|
|
196
|
-
id: 'e-' + Date.now(),
|
|
197
|
-
}]);
|
|
198
|
-
});
|
|
160
|
+
setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'No model selected. Use /model.', id: 'e-' + Date.now() }]); });
|
|
199
161
|
return;
|
|
200
162
|
}
|
|
201
|
-
|
|
202
|
-
const history = buildHistory(messages, safeText);
|
|
163
|
+
const history = buildHistory(messages, safe);
|
|
203
164
|
const abortController = new AbortController();
|
|
204
165
|
abortRef.current = abortController;
|
|
205
|
-
|
|
206
166
|
try {
|
|
207
167
|
const gen = callModel(modelInfo, history, abortController.signal);
|
|
208
168
|
let accumulated = '';
|
|
209
169
|
let firstEvent = true;
|
|
210
170
|
let thinkStarted = false;
|
|
211
|
-
|
|
212
171
|
for await (const event of gen) {
|
|
213
172
|
if (abortController.signal.aborted === true) break;
|
|
214
|
-
|
|
215
173
|
if (firstEvent === true) {
|
|
216
174
|
firstEvent = false;
|
|
217
|
-
if (connRef.current !== null)
|
|
218
|
-
clearInterval(connRef.current);
|
|
219
|
-
connRef.current = null;
|
|
220
|
-
}
|
|
175
|
+
if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
|
|
221
176
|
}
|
|
222
|
-
|
|
223
177
|
if (event.type === 'tool') {
|
|
224
|
-
setToolInfo({
|
|
225
|
-
tool: event.tool,
|
|
226
|
-
used: event.used,
|
|
227
|
-
sites: event.sites,
|
|
228
|
-
query: event.query,
|
|
229
|
-
});
|
|
178
|
+
setToolInfo({ tool: event.tool, used: event.used, sites: event.sites, query: event.query });
|
|
230
179
|
continue;
|
|
231
180
|
}
|
|
232
|
-
|
|
233
181
|
if (event.type === 'thinking' || event.type === 'token') {
|
|
234
182
|
const content = typeof event.content === 'string' ? event.content : '';
|
|
235
183
|
for (let i = 0; i < content.length; i++) {
|
|
236
184
|
accumulated = accumulated + content[i];
|
|
237
|
-
const
|
|
185
|
+
const inside = checkThink(accumulated);
|
|
238
186
|
setStreamText(accumulated);
|
|
239
|
-
setIsThinking(
|
|
240
|
-
if (
|
|
187
|
+
setIsThinking(inside);
|
|
188
|
+
if (inside === true && thinkStarted === false) {
|
|
241
189
|
thinkStarted = true;
|
|
242
190
|
thinkStartRef.current = Date.now();
|
|
243
191
|
setThinkingElapsed(0);
|
|
244
192
|
}
|
|
245
|
-
if (
|
|
193
|
+
if (inside === false && thinkStarted === true) {
|
|
246
194
|
thinkStarted = false;
|
|
247
195
|
thinkStartRef.current = null;
|
|
248
196
|
setThinkingElapsed(0);
|
|
@@ -250,98 +198,53 @@ function AppRoot() {
|
|
|
250
198
|
await new Promise(function (r) { setTimeout(r, 0); });
|
|
251
199
|
}
|
|
252
200
|
}
|
|
253
|
-
|
|
254
201
|
if (event.type === 'done') break;
|
|
255
202
|
if (event.type === 'timeout') {
|
|
256
|
-
if (accumulated.length > 0)
|
|
257
|
-
accumulated = accumulated + '\n\n\u2014 stream timed out \u2014';
|
|
258
|
-
}
|
|
203
|
+
if (accumulated.length > 0) accumulated = accumulated + '\n\n\u2014 stream timed out \u2014';
|
|
259
204
|
setStreamText(accumulated);
|
|
260
205
|
break;
|
|
261
206
|
}
|
|
262
207
|
}
|
|
263
|
-
|
|
264
|
-
if (connRef.current !== null) {
|
|
265
|
-
clearInterval(connRef.current);
|
|
266
|
-
connRef.current = null;
|
|
267
|
-
}
|
|
208
|
+
if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
|
|
268
209
|
setIsThinking(false);
|
|
269
210
|
thinkStartRef.current = null;
|
|
270
211
|
setThinkingElapsed(0);
|
|
271
212
|
setStreamingActive(false);
|
|
272
|
-
|
|
273
213
|
const finalText = streamText;
|
|
274
214
|
setStreamText('');
|
|
275
|
-
|
|
276
215
|
if (finalText.length > 0) {
|
|
277
|
-
setMessages(function (
|
|
278
|
-
return prev.concat([{
|
|
279
|
-
role: 'assistant',
|
|
280
|
-
content: finalText,
|
|
281
|
-
id: 'a-' + Date.now(),
|
|
282
|
-
}]);
|
|
283
|
-
});
|
|
216
|
+
setMessages(function (p) { return p.concat([{ role: 'assistant', content: finalText, id: 'a-' + Date.now() }]); });
|
|
284
217
|
}
|
|
285
218
|
} catch (err) {
|
|
286
|
-
if (connRef.current !== null)
|
|
287
|
-
clearInterval(connRef.current);
|
|
288
|
-
connRef.current = null;
|
|
289
|
-
}
|
|
219
|
+
if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
|
|
290
220
|
setIsThinking(false);
|
|
291
221
|
thinkStartRef.current = null;
|
|
292
222
|
setThinkingElapsed(0);
|
|
293
223
|
setStreamingActive(false);
|
|
294
224
|
setStreamText('');
|
|
295
|
-
const errMsg = err !== null && err !== undefined
|
|
296
|
-
|
|
297
|
-
: '
|
|
298
|
-
setMessages(function (prev) {
|
|
299
|
-
return prev.concat([{
|
|
300
|
-
role: 'assistant',
|
|
301
|
-
content: '**Error:** ' + errMsg + '\n\nThe Space may be cold-starting (30\u2013120 s first request). Try again.',
|
|
302
|
-
id: 'er-' + Date.now(),
|
|
303
|
-
}]);
|
|
225
|
+
const errMsg = err !== null && err !== undefined ? (err.message || String(err)) : 'Unknown error';
|
|
226
|
+
setMessages(function (p) {
|
|
227
|
+
return p.concat([{ role: 'assistant', content: 'Error: ' + errMsg + '\n\nSpace cold-starting? Try again.', id: 'er-' + Date.now() }]);
|
|
304
228
|
});
|
|
305
229
|
}
|
|
306
230
|
}
|
|
307
231
|
|
|
308
|
-
function
|
|
309
|
-
const
|
|
310
|
-
setInputBuffer(
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
setOverlayIndex(0);
|
|
314
|
-
}
|
|
315
|
-
if (showOverlay === true && (safe.length === 0 || safe[0] !== '/')) {
|
|
316
|
-
setShowOverlay(false);
|
|
317
|
-
}
|
|
232
|
+
function handleChange(v) {
|
|
233
|
+
const s = typeof v === 'string' ? v : '';
|
|
234
|
+
setInputBuffer(s);
|
|
235
|
+
if (s.length === 1 && s[0] === '/' && showOverlay === false) { setShowOverlay(true); setOverlayIndex(0); }
|
|
236
|
+
if (showOverlay === true && (s.length === 0 || s[0] !== '/')) { setShowOverlay(false); }
|
|
318
237
|
}
|
|
319
238
|
|
|
320
|
-
function
|
|
239
|
+
function handleCmd(item) {
|
|
321
240
|
if (item === null || item === undefined) return;
|
|
322
|
-
const
|
|
323
|
-
if (typeof
|
|
324
|
-
|
|
325
|
-
if (
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (trigger === '/model') {
|
|
330
|
-
cycleModel();
|
|
331
|
-
}
|
|
332
|
-
if (trigger === '/help') {
|
|
333
|
-
setMessages(function (prev) {
|
|
334
|
-
return prev.concat([{
|
|
335
|
-
role: 'assistant',
|
|
336
|
-
content:
|
|
337
|
-
'**Commands:**\n' +
|
|
338
|
-
'- `/help` \u2014 this help\n' +
|
|
339
|
-
'- `/clear` \u2014 clear chat\n' +
|
|
340
|
-
'- `/model` \u2014 cycle: Llama \u2192 GPT \u2192 DeepSeek\n' +
|
|
341
|
-
'- `/exit` \u2014 quit',
|
|
342
|
-
id: 'hlp-' + Date.now(),
|
|
343
|
-
}]);
|
|
344
|
-
});
|
|
241
|
+
const t = item.value;
|
|
242
|
+
if (typeof t !== 'string') return;
|
|
243
|
+
if (t === '/exit') process.exit(0);
|
|
244
|
+
if (t === '/clear') setMessages([]);
|
|
245
|
+
if (t === '/model') cycleModel();
|
|
246
|
+
if (t === '/help') {
|
|
247
|
+
setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Commands: /help /clear /model /exit', id: 'h-' + Date.now() }]); });
|
|
345
248
|
}
|
|
346
249
|
setInputBuffer('');
|
|
347
250
|
setShowOverlay(false);
|
|
@@ -350,49 +253,33 @@ function AppRoot() {
|
|
|
350
253
|
|
|
351
254
|
useInput(function handleInput(input, key) {
|
|
352
255
|
if (showOverlay === true) {
|
|
353
|
-
if (key.escape === true) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
if (key.upArrow === true) {
|
|
360
|
-
setOverlayIndex(function (p) { return p > 0 ? p - 1 : commands.length - 1; });
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
if (key.downArrow === true) {
|
|
364
|
-
setOverlayIndex(function (p) { return p < commands.length - 1 ? p + 1 : 0; });
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
if (key.return === true) {
|
|
368
|
-
const cmd = commands[overlayIndex];
|
|
369
|
-
if (cmd !== null && cmd !== undefined) handleSlashSelect({ value: cmd.trigger });
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
256
|
+
if (key.escape === true) { setShowOverlay(false); setInputBuffer(''); setOverlayIndex(0); return; }
|
|
257
|
+
if (key.upArrow === true) { setOverlayIndex(function (p) { return p > 0 ? p - 1 : commands.length - 1; }); return; }
|
|
258
|
+
if (key.downArrow === true) { setOverlayIndex(function (p) { return p < commands.length - 1 ? p + 1 : 0; }); return; }
|
|
259
|
+
if (key.return === true) { const cmd = commands[overlayIndex]; if (cmd !== null && cmd !== undefined) handleCmd({ value: cmd.trigger }); return; }
|
|
372
260
|
return;
|
|
373
261
|
}
|
|
374
|
-
|
|
375
262
|
if (key.return === true) {
|
|
376
|
-
const
|
|
377
|
-
if (
|
|
378
|
-
handleSubmit(safe);
|
|
379
|
-
}
|
|
263
|
+
const s = typeof inputBuffer === 'string' ? inputBuffer : '';
|
|
264
|
+
if (s.length > 0 && streamingActive === false) handleSubmit(s);
|
|
380
265
|
return;
|
|
381
266
|
}
|
|
382
|
-
|
|
383
267
|
if (key.ctrl === true && (input === 'c' || input === 'C')) {
|
|
384
|
-
if (abortRef.current !== null) {
|
|
385
|
-
try { abortRef.current.abort(); } catch (_) {}
|
|
386
|
-
abortRef.current = null;
|
|
387
|
-
setStreamingActive(false);
|
|
388
|
-
setStreamText('');
|
|
389
|
-
}
|
|
268
|
+
if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; setStreamingActive(false); setStreamText(''); }
|
|
390
269
|
process.exit(0);
|
|
391
270
|
}
|
|
392
271
|
});
|
|
393
272
|
|
|
394
|
-
const
|
|
395
|
-
const
|
|
273
|
+
const activeInfo = getModelById(activeModel);
|
|
274
|
+
const activeLabel = activeInfo !== null ? activeInfo.label : 'OpenAxies';
|
|
275
|
+
|
|
276
|
+
const logoEls = [];
|
|
277
|
+
for (let r = 0; r < LOGO.length; r++) {
|
|
278
|
+
logoEls.push(
|
|
279
|
+
h(Text, { key: 'l' + r, color: LOGO_COLORS[r % LOGO_COLORS.length], bold: true }, LOGO[r])
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const logo = h(Box, { flexDirection: 'column', width: '100%', flexShrink: 0, paddingLeft: 1 }, ...logoEls);
|
|
396
283
|
|
|
397
284
|
const viewLines = [];
|
|
398
285
|
for (let i = 0; i < messages.length; i++) {
|
|
@@ -400,104 +287,72 @@ function AppRoot() {
|
|
|
400
287
|
if (msg === null || typeof msg !== 'object') continue;
|
|
401
288
|
const content = typeof msg.content === 'string' ? msg.content : '';
|
|
402
289
|
if (content.length === 0) continue;
|
|
403
|
-
|
|
404
|
-
if (i > 0) viewLines.push({ type: 'sep' });
|
|
405
|
-
|
|
406
290
|
if (msg.role === 'user') {
|
|
407
|
-
viewLines.push({
|
|
408
|
-
viewLines.push({ type: 'sep' });
|
|
291
|
+
viewLines.push({ t: 'user', text: '\u25B7 ' + content });
|
|
409
292
|
} else {
|
|
410
|
-
viewLines.push({
|
|
293
|
+
viewLines.push({ t: 'text', text: content });
|
|
411
294
|
}
|
|
412
295
|
}
|
|
413
296
|
|
|
414
297
|
const hasStream = typeof streamText === 'string' && streamText.length > 0;
|
|
415
298
|
if (hasStream === true) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (toolInfo !== null && toolInfo.used === true) {
|
|
419
|
-
viewLines.push({
|
|
420
|
-
type: 'tool',
|
|
421
|
-
text: ' \u2500\u2500 ' + toolInfo.tool + ': "' + toolInfo.query + '" (' + toolInfo.sites + ' sites)',
|
|
422
|
-
});
|
|
299
|
+
if (toolInfo !== null && toolInfo.used === true && toolInfo.query.length > 0) {
|
|
300
|
+
viewLines.push({ t: 'tool', text: '\u2500\u2500 ' + toolInfo.tool + ': "' + toolInfo.query + '" (' + toolInfo.sites + ' sites)' });
|
|
423
301
|
}
|
|
424
|
-
|
|
425
302
|
if (isThinking === true) {
|
|
426
|
-
|
|
427
|
-
viewLines.push({
|
|
428
|
-
type: 'think',
|
|
429
|
-
spin: sp,
|
|
430
|
-
elapsed: formatTimer(thinkingElapsed),
|
|
431
|
-
});
|
|
303
|
+
viewLines.push({ t: 'think', spin: SPINNER[spinnerIdx], elapsed: formatTimer(thinkingElapsed) });
|
|
432
304
|
const clean = streamText.split('<think>').join('').split('</think>').join('');
|
|
433
|
-
viewLines.push({
|
|
305
|
+
viewLines.push({ t: 'think-text', text: clean });
|
|
434
306
|
} else {
|
|
435
307
|
const clean = streamText.split('<think>').join('').split('</think>').join('');
|
|
436
|
-
viewLines.push({
|
|
308
|
+
viewLines.push({ t: 'text', text: clean });
|
|
437
309
|
}
|
|
438
310
|
}
|
|
439
311
|
|
|
440
312
|
if (viewLines.length === 0) {
|
|
441
|
-
viewLines.push({
|
|
313
|
+
viewLines.push({ t: 'idle', text: ' \u25B8 type a message to begin... [/help for commands]' });
|
|
442
314
|
}
|
|
443
315
|
|
|
444
|
-
const
|
|
445
|
-
const
|
|
446
|
-
const
|
|
316
|
+
const visible = Math.max(1, availLines - 1);
|
|
317
|
+
const display = viewLines.slice(-visible);
|
|
318
|
+
const viewEls = [];
|
|
447
319
|
|
|
448
|
-
for (let i = 0; i <
|
|
449
|
-
const
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
h(Box, { key: 'vt-' + i, height: 1 },
|
|
455
|
-
h(Text, { color: '#FF9F43', bold: true },
|
|
456
|
-
' ' + line.spin + ' Thinking \u2022 ' + line.elapsed
|
|
457
|
-
)
|
|
320
|
+
for (let i = 0; i < display.length; i++) {
|
|
321
|
+
const l = display[i];
|
|
322
|
+
if (l.t === 'think') {
|
|
323
|
+
viewEls.push(
|
|
324
|
+
h(Box, { key: 'k-' + i, height: 1, paddingLeft: 2 },
|
|
325
|
+
h(Text, { color: '#FF9F43', bold: true }, l.spin + ' Thinking \u2022 ' + l.elapsed)
|
|
458
326
|
)
|
|
459
327
|
);
|
|
460
|
-
} else if (
|
|
461
|
-
|
|
462
|
-
h(Box, { key: '
|
|
463
|
-
h(Text, { color: '#
|
|
328
|
+
} else if (l.t === 'think-text') {
|
|
329
|
+
viewEls.push(
|
|
330
|
+
h(Box, { key: 'kt-' + i, height: 1, paddingLeft: 3 },
|
|
331
|
+
h(Text, { color: '#FF9F43' }, l.text)
|
|
464
332
|
)
|
|
465
333
|
);
|
|
466
|
-
} else if (
|
|
467
|
-
|
|
468
|
-
h(Box, {
|
|
469
|
-
|
|
470
|
-
height: 1,
|
|
471
|
-
paddingLeft: 1,
|
|
472
|
-
},
|
|
473
|
-
h(Text, { color: hex.neonBlue, bold: true }, '\u25B7 '),
|
|
474
|
-
h(Text, { color: hex.neonBlue }, line.text)
|
|
334
|
+
} else if (l.t === 'tool') {
|
|
335
|
+
viewEls.push(
|
|
336
|
+
h(Box, { key: 'tl-' + i, height: 1, paddingLeft: 2 },
|
|
337
|
+
h(Text, { color: '#5B5B8A' }, l.text)
|
|
475
338
|
)
|
|
476
339
|
);
|
|
477
|
-
} else if (
|
|
478
|
-
|
|
479
|
-
h(Box, {
|
|
480
|
-
|
|
481
|
-
height: 1,
|
|
482
|
-
paddingLeft: 1,
|
|
483
|
-
},
|
|
484
|
-
h(Text, { color: hex.primary }, line.text)
|
|
340
|
+
} else if (l.t === 'user') {
|
|
341
|
+
viewEls.push(
|
|
342
|
+
h(Box, { key: 'u-' + i, height: 1, paddingLeft: 1 },
|
|
343
|
+
h(Text, { color: hex.neonBlue }, l.text)
|
|
485
344
|
)
|
|
486
345
|
);
|
|
487
|
-
} else if (
|
|
488
|
-
|
|
489
|
-
h(Box, {
|
|
490
|
-
|
|
491
|
-
height: 1,
|
|
492
|
-
paddingLeft: 1,
|
|
493
|
-
},
|
|
494
|
-
h(Text, { color: line.color }, line.text)
|
|
346
|
+
} else if (l.t === 'text') {
|
|
347
|
+
viewEls.push(
|
|
348
|
+
h(Box, { key: 'a-' + i, height: 1, paddingLeft: 2 },
|
|
349
|
+
h(Text, { color: hex.primary }, l.text)
|
|
495
350
|
)
|
|
496
351
|
);
|
|
497
|
-
} else if (
|
|
498
|
-
|
|
499
|
-
h(Box, { key: '
|
|
500
|
-
h(Text, { color: '#
|
|
352
|
+
} else if (l.t === 'idle') {
|
|
353
|
+
viewEls.push(
|
|
354
|
+
h(Box, { key: 'i-' + i, height: 1, paddingLeft: 1 },
|
|
355
|
+
h(Text, { color: '#444466' }, l.text)
|
|
501
356
|
)
|
|
502
357
|
);
|
|
503
358
|
}
|
|
@@ -508,22 +363,21 @@ function AppRoot() {
|
|
|
508
363
|
height: availLines,
|
|
509
364
|
flexDirection: 'column',
|
|
510
365
|
overflow: 'hidden',
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const particleBot = createParticleRow(particleFrame, cols, 'bt');
|
|
366
|
+
paddingTop: 0,
|
|
367
|
+
paddingBottom: 0,
|
|
368
|
+
}, ...viewEls);
|
|
515
369
|
|
|
516
|
-
const slashOverlay = showOverlay === true
|
|
517
|
-
? createSlashOverlay(commands, overlayIndex)
|
|
518
|
-
: null;
|
|
370
|
+
const slashOverlay = showOverlay === true ? createSlashOverlay(commands, overlayIndex) : null;
|
|
519
371
|
|
|
520
372
|
const dock = createComposerDock({
|
|
521
373
|
value: inputBuffer,
|
|
522
|
-
onChange:
|
|
374
|
+
onChange: handleChange,
|
|
523
375
|
onSubmit: function () {},
|
|
524
376
|
inputActive: showOverlay === false,
|
|
525
377
|
isStreaming: streamingActive,
|
|
526
378
|
terminalWidth: cols,
|
|
379
|
+
modelLabel: activeLabel + ' \u25CF',
|
|
380
|
+
modelStatus: true,
|
|
527
381
|
});
|
|
528
382
|
|
|
529
383
|
return h(Box, {
|
|
@@ -533,11 +387,9 @@ function AppRoot() {
|
|
|
533
387
|
backgroundColor: hex.black,
|
|
534
388
|
overflow: 'hidden',
|
|
535
389
|
},
|
|
536
|
-
|
|
537
|
-
h(Box, { height: 1 },
|
|
538
|
-
h(Box, { height: 1, paddingLeft: 1 }, particleTop),
|
|
390
|
+
logo,
|
|
391
|
+
h(Box, { height: 1 }, hr(cols, '#1A1A2E')),
|
|
539
392
|
viewport,
|
|
540
|
-
h(Box, { height: 1, paddingLeft: 1 }, particleBot),
|
|
541
393
|
slashOverlay,
|
|
542
394
|
dock
|
|
543
395
|
);
|
|
@@ -1,105 +1,66 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
2
|
+
import { Box, Text, useInput, useStdout } from 'ink';
|
|
3
3
|
import TextInput from 'ink-text-input';
|
|
4
4
|
import { hex } from '../config/theme.js';
|
|
5
5
|
|
|
6
6
|
const h = React.createElement;
|
|
7
7
|
|
|
8
|
-
export const DOCK_HEIGHT =
|
|
9
|
-
const MAX_FRAME_WIDTH = 80;
|
|
10
|
-
const PROMPT = 'openaxies@root:~$ ';
|
|
8
|
+
export const DOCK_HEIGHT = 4;
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
if (typeof value !== 'string') {
|
|
14
|
-
return '';
|
|
15
|
-
}
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function checkCallback(fn, label) {
|
|
20
|
-
if (fn !== null && fn !== undefined && typeof fn !== 'function') {
|
|
21
|
-
throw new Error(label + ' must be a function');
|
|
22
|
-
}
|
|
23
|
-
}
|
|
10
|
+
const PROMPT = '> ';
|
|
24
11
|
|
|
25
|
-
// ComposerDock — 3 rows, permanently at the bottom of the layout.
|
|
26
|
-
//
|
|
27
|
-
// Layout:
|
|
28
|
-
// ┌──────────────────────────────────────────────────────────────────────────────┐
|
|
29
|
-
// │ openaxies@root:~$ [TextInput] │
|
|
30
|
-
// └──────────────────────────────────────────────────────────────────────────────┘
|
|
31
|
-
//
|
|
32
|
-
// The frame is capped at 80 columns for narrow terminal clients.
|
|
33
|
-
// flexShrink: 0 prevents Ink from ever yielding these rows to the viewport.
|
|
34
|
-
//
|
|
35
|
-
// config.inputActive (bool, default true):
|
|
36
|
-
// Set to false when SlashOverlay is open so TextInput releases keyboard
|
|
37
|
-
// focus and arrow keys don't corrupt the input buffer.
|
|
38
12
|
export function createComposerDock(config) {
|
|
39
13
|
if (config === null || config === undefined || typeof config !== 'object') {
|
|
40
14
|
throw new Error('createComposerDock requires a config object');
|
|
41
15
|
}
|
|
42
16
|
|
|
43
|
-
const value =
|
|
17
|
+
const value = typeof config.value === 'string' ? config.value : '';
|
|
44
18
|
const onChange = config.onChange;
|
|
45
19
|
const onSubmit = config.onSubmit;
|
|
46
20
|
const inputActive = config.inputActive !== false;
|
|
47
21
|
const isStreaming = config.isStreaming === true;
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
checkCallback(onChange, 'onChange');
|
|
53
|
-
checkCallback(onSubmit, 'onSubmit');
|
|
22
|
+
const modelLabel = typeof config.modelLabel === 'string' ? config.modelLabel : '';
|
|
23
|
+
const modelStatus = config.modelStatus === true;
|
|
24
|
+
const safeCols = typeof config.terminalWidth === 'number' && config.terminalWidth > 0 ? config.terminalWidth : 80;
|
|
54
25
|
|
|
55
|
-
const placeholder = isStreaming
|
|
56
|
-
|
|
57
|
-
: '/ commands';
|
|
58
|
-
const frameWidth = Math.max(2, Math.min(MAX_FRAME_WIDTH, terminalWidth));
|
|
59
|
-
const innerWidth = frameWidth - 2;
|
|
60
|
-
const inputWidth = Math.max(1, innerWidth - PROMPT.length);
|
|
61
|
-
const horizontalRule = '\u2500'.repeat(innerWidth);
|
|
26
|
+
const placeholder = isStreaming ? 'Ctrl+C to interrupt...' : 'type a message...';
|
|
27
|
+
const innerW = safeCols - 2;
|
|
62
28
|
|
|
63
29
|
return h(Box, {
|
|
64
30
|
width: '100%',
|
|
65
31
|
height: DOCK_HEIGHT,
|
|
66
32
|
flexDirection: 'column',
|
|
67
|
-
alignItems: 'center',
|
|
68
33
|
flexShrink: 0,
|
|
69
34
|
},
|
|
70
|
-
h(Box, {
|
|
71
|
-
h(Text, { color:
|
|
35
|
+
h(Box, { height: 1, paddingLeft: 1 },
|
|
36
|
+
h(Text, { color: '#1A1A28' }, '\u2500'.repeat(innerW))
|
|
72
37
|
),
|
|
73
|
-
h(Box, {
|
|
74
|
-
width: frameWidth,
|
|
75
|
-
height: 1,
|
|
76
|
-
flexDirection: 'row',
|
|
77
|
-
flexShrink: 0,
|
|
78
|
-
overflow: 'hidden',
|
|
79
|
-
},
|
|
38
|
+
h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
|
|
80
39
|
h(Text, { color: hex.border }, '\u2502'),
|
|
81
|
-
h(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
onChange: onChange,
|
|
92
|
-
onSubmit: onSubmit,
|
|
93
|
-
placeholder: placeholder,
|
|
94
|
-
focus: inputActive,
|
|
95
|
-
showCursor: true,
|
|
96
|
-
})
|
|
97
|
-
)
|
|
40
|
+
h(Text, { color: inputActive ? hex.neonBlue : '#444466', bold: true }, ' ' + PROMPT),
|
|
41
|
+
h(Box, { flexGrow: 1, height: 1, overflow: 'hidden' },
|
|
42
|
+
h(TextInput, {
|
|
43
|
+
value: value,
|
|
44
|
+
onChange: onChange,
|
|
45
|
+
onSubmit: onSubmit,
|
|
46
|
+
placeholder: placeholder,
|
|
47
|
+
focus: inputActive,
|
|
48
|
+
showCursor: true,
|
|
49
|
+
})
|
|
98
50
|
),
|
|
51
|
+
h(Text, { color: '#444466' }, ' '),
|
|
99
52
|
h(Text, { color: hex.border }, '\u2502')
|
|
100
53
|
),
|
|
101
|
-
h(Box, {
|
|
102
|
-
h(Text, { color: hex.border }, '\u2514'
|
|
54
|
+
h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
|
|
55
|
+
h(Text, { color: hex.border }, '\u2514'),
|
|
56
|
+
h(Box, { flexGrow: 1, height: 1, overflow: 'hidden' },
|
|
57
|
+
h(Text, { color: '#333344' }, '\u2500'.repeat(innerW - 2))
|
|
58
|
+
),
|
|
59
|
+
h(Text, { color: hex.border }, '\u2518')
|
|
60
|
+
),
|
|
61
|
+
h(Box, { height: 1, paddingRight: 1 },
|
|
62
|
+
h(Box, { flexGrow: 1 }),
|
|
63
|
+
h(Text, { color: '#444466', bold: modelStatus }, modelLabel || '')
|
|
103
64
|
)
|
|
104
65
|
);
|
|
105
66
|
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { hex } from '../config/theme.js';
|
|
4
|
-
import { getModels } from '../config/models.js';
|
|
5
|
-
|
|
6
|
-
const h = React.createElement;
|
|
7
|
-
|
|
8
|
-
const BAR_COLOR = '#1A1A2E';
|
|
9
|
-
|
|
10
|
-
export function createModelBar(activeId, cols) {
|
|
11
|
-
const models = getModels();
|
|
12
|
-
const safeCols = typeof cols === 'number' && cols > 0 ? cols : 80;
|
|
13
|
-
const parts = [];
|
|
14
|
-
|
|
15
|
-
for (let i = 0; i < models.length; i++) {
|
|
16
|
-
const m = models[i];
|
|
17
|
-
const isActive = m.id === activeId;
|
|
18
|
-
if (i > 0) {
|
|
19
|
-
parts.push(h(Text, { key: 'sp-' + i, color: BAR_COLOR }, ' \u2502 '));
|
|
20
|
-
}
|
|
21
|
-
const prefix = isActive ? '\u25B8 ' : ' ';
|
|
22
|
-
const dot = isActive ? '\u25CF' : '\u25CB';
|
|
23
|
-
const dotColor = isActive ? hex.greenOnline : '#333355';
|
|
24
|
-
const nameColor = isActive ? hex.neonBlue : '#555577';
|
|
25
|
-
const badgeColor = isActive ? hex.muted : '#333344';
|
|
26
|
-
|
|
27
|
-
parts.push(
|
|
28
|
-
h(Text, { key: 'pre-' + i, color: hex.primary }, prefix)
|
|
29
|
-
);
|
|
30
|
-
parts.push(
|
|
31
|
-
h(Text, { key: 'nm-' + i, color: nameColor, bold: isActive }, m.label)
|
|
32
|
-
);
|
|
33
|
-
parts.push(
|
|
34
|
-
h(Text, { key: 'bd-' + i, color: badgeColor }, ' [' + m.badge + ']')
|
|
35
|
-
);
|
|
36
|
-
parts.push(
|
|
37
|
-
h(Text, { key: 'dt-' + i, color: dotColor }, ' ' + dot)
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return h(Box, { width: '100%', height: 1, paddingLeft: 1 }, ...parts);
|
|
42
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Text } from 'ink';
|
|
3
|
-
import { hex } from '../config/theme.js';
|
|
4
|
-
|
|
5
|
-
const h = React.createElement;
|
|
6
|
-
|
|
7
|
-
const CHAR_SET = [
|
|
8
|
-
'\u2726', '\u00B7', '\u2727', '\u00B7', '\u2605', '\u00B7',
|
|
9
|
-
'\u2726', '\u00B7', '\u2219', '\u2727', '\u00B7', '\u2726',
|
|
10
|
-
'\u2219', '\u2605', '\u2219', '\u2727', '\u2219', '\u2726',
|
|
11
|
-
'\u00B7', '\u2727', '\u2605', '\u00B7', '\u2726', '\u00B7',
|
|
12
|
-
'\u2219', '\u2727',
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
const COLORS = [
|
|
16
|
-
'#00F0FF', '#FF9F43', '#00FF88', '#FF6B9D', '#8B5CF6',
|
|
17
|
-
'#00F0FF', '#FFD700', '#00FF88',
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
function getColor(frame, index, total) {
|
|
21
|
-
const phase = Math.floor((index / total) * 52);
|
|
22
|
-
const idx = (frame + phase) % COLORS.length;
|
|
23
|
-
return COLORS[idx];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function createParticleRow(frame, cols, key) {
|
|
27
|
-
const safeCols = typeof cols === 'number' && cols > 0 ? cols : 80;
|
|
28
|
-
const safeFrame = typeof frame === 'number' ? frame : 0;
|
|
29
|
-
const parts = [];
|
|
30
|
-
|
|
31
|
-
for (let i = 0; i < 26; i++) {
|
|
32
|
-
const color = getColor(safeFrame, i, 52);
|
|
33
|
-
const ch = CHAR_SET[i % CHAR_SET.length];
|
|
34
|
-
parts.push(
|
|
35
|
-
h(Text, { key: key + '-' + i, color: color }, ch)
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return h(Text, null, parts);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function createParticlePair(frame, cols) {
|
|
43
|
-
return [
|
|
44
|
-
createParticleRow(frame, cols, 'top'),
|
|
45
|
-
createParticleRow(frame, cols, 'bot'),
|
|
46
|
-
];
|
|
47
|
-
}
|