openaxies 0.5.0 → 0.5.1
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 +108 -202
- package/src/components/ComposerDock.js +23 -74
- package/src/components/ModelBar.js +0 -42
- package/src/components/Particles.js +0 -47
package/package.json
CHANGED
package/src/App.js
CHANGED
|
@@ -4,12 +4,9 @@ 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
|
|
|
@@ -17,9 +14,7 @@ function buildHistory(messages, newText) {
|
|
|
17
14
|
const history = [];
|
|
18
15
|
for (let i = 0; i < messages.length; i++) {
|
|
19
16
|
const m = messages[i];
|
|
20
|
-
if (m === null || m === undefined || typeof m !== 'object')
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
17
|
+
if (m === null || m === undefined || typeof m !== 'object') continue;
|
|
23
18
|
if (m.role === 'user' || m.role === 'assistant') {
|
|
24
19
|
const content = typeof m.content === 'string' ? m.content : '';
|
|
25
20
|
history.push({ role: m.role, content: content });
|
|
@@ -32,21 +27,19 @@ function buildHistory(messages, newText) {
|
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
function formatTimer(seconds) {
|
|
35
|
-
if (typeof seconds !== 'number' || seconds < 0)
|
|
36
|
-
return '0.0s';
|
|
37
|
-
}
|
|
30
|
+
if (typeof seconds !== 'number' || seconds < 0) return '0.0s';
|
|
38
31
|
return seconds.toFixed(1) + 's';
|
|
39
32
|
}
|
|
40
33
|
|
|
41
|
-
function
|
|
42
|
-
const
|
|
43
|
-
let
|
|
44
|
-
for (let i = 0; i <
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
return h(Text, { color: BAR_COLOR }, line);
|
|
34
|
+
function hr(cols) {
|
|
35
|
+
const n = typeof cols === 'number' && cols > 0 ? cols : 80;
|
|
36
|
+
let s = '';
|
|
37
|
+
for (let i = 0; i < n; i++) s = s + '\u2500';
|
|
38
|
+
return h(Text, { color: '#1A1A28' }, s);
|
|
48
39
|
}
|
|
49
40
|
|
|
41
|
+
const SPINNER = ['\u25CB', '\u25D4', '\u25D0', '\u25D5', '\u25CF', '\u25D5', '\u25D0', '\u25D4'];
|
|
42
|
+
|
|
50
43
|
function AppRoot() {
|
|
51
44
|
const { stdout } = useStdout();
|
|
52
45
|
const rows = stdout.rows || 24;
|
|
@@ -57,20 +50,16 @@ function AppRoot() {
|
|
|
57
50
|
const [streamText, setStreamText] = React.useState('');
|
|
58
51
|
const [streamingActive, setStreamingActive] = React.useState(false);
|
|
59
52
|
const [isThinking, setIsThinking] = React.useState(false);
|
|
60
|
-
const [
|
|
53
|
+
const [spinnerIdx, setSpinnerIdx] = React.useState(0);
|
|
61
54
|
const [thinkingElapsed, setThinkingElapsed] = React.useState(0);
|
|
62
55
|
const [inputBuffer, setInputBuffer] = React.useState('');
|
|
63
56
|
const [showOverlay, setShowOverlay] = React.useState(false);
|
|
64
57
|
const [overlayIndex, setOverlayIndex] = React.useState(0);
|
|
65
|
-
const [particleFrame, setParticleFrame] = React.useState(0);
|
|
66
58
|
const [toolInfo, setToolInfo] = React.useState(null);
|
|
67
59
|
|
|
68
60
|
const commands = getCommands();
|
|
69
|
-
const MODEL_BAR_H = 1;
|
|
70
|
-
const SEP_H = 1;
|
|
71
|
-
const DOCK = DOCK_HEIGHT;
|
|
72
61
|
const overlayH = showOverlay ? 6 : 0;
|
|
73
|
-
const fixedH =
|
|
62
|
+
const fixedH = 1 + overlayH + DOCK_HEIGHT;
|
|
74
63
|
const availLines = Math.max(1, rows - fixedH);
|
|
75
64
|
|
|
76
65
|
const abortRef = React.useRef(null);
|
|
@@ -78,27 +67,6 @@ function AppRoot() {
|
|
|
78
67
|
const spinnerRef = React.useRef(null);
|
|
79
68
|
const timerRef = React.useRef(null);
|
|
80
69
|
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
70
|
|
|
103
71
|
React.useEffect(function () {
|
|
104
72
|
if (isThinking === false) {
|
|
@@ -110,14 +78,12 @@ function AppRoot() {
|
|
|
110
78
|
clearInterval(timerRef.current);
|
|
111
79
|
timerRef.current = null;
|
|
112
80
|
}
|
|
113
|
-
setSpinnerChar('');
|
|
114
81
|
return;
|
|
115
82
|
}
|
|
116
83
|
let idx = 0;
|
|
117
|
-
setSpinnerChar(SPINNER_FRAMES[0]);
|
|
118
84
|
spinnerRef.current = setInterval(function () {
|
|
119
|
-
idx = (idx + 1) %
|
|
120
|
-
|
|
85
|
+
idx = (idx + 1) % SPINNER.length;
|
|
86
|
+
setSpinnerIdx(idx);
|
|
121
87
|
}, 100);
|
|
122
88
|
timerRef.current = setInterval(function () {
|
|
123
89
|
if (thinkStartRef.current !== null) {
|
|
@@ -148,36 +114,33 @@ function AppRoot() {
|
|
|
148
114
|
});
|
|
149
115
|
}
|
|
150
116
|
|
|
151
|
-
function
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
155
|
-
return
|
|
117
|
+
function checkThink(text) {
|
|
118
|
+
const o = text.lastIndexOf('<think>');
|
|
119
|
+
const c = text.lastIndexOf('</think>');
|
|
120
|
+
if (o === -1 && c === -1) return false;
|
|
121
|
+
return o > c;
|
|
156
122
|
}
|
|
157
123
|
|
|
158
124
|
async function handleSubmit(text) {
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
125
|
+
const safe = typeof text === 'string' ? text : '';
|
|
126
|
+
if (safe.length === 0 || streamingActive === true) return;
|
|
161
127
|
|
|
162
128
|
setStreamingActive(true);
|
|
163
129
|
setToolInfo(null);
|
|
164
130
|
setStreamText(' connecting');
|
|
165
131
|
setThinkingElapsed(0);
|
|
132
|
+
|
|
166
133
|
connRef.current = setInterval(function () {
|
|
167
|
-
setStreamText(function (
|
|
168
|
-
if (
|
|
169
|
-
if (
|
|
170
|
-
if (
|
|
134
|
+
setStreamText(function (p) {
|
|
135
|
+
if (p === ' connecting') return ' connecting.';
|
|
136
|
+
if (p === ' connecting.') return ' connecting..';
|
|
137
|
+
if (p === ' connecting..') return ' connecting...';
|
|
171
138
|
return ' connecting';
|
|
172
139
|
});
|
|
173
140
|
}, 400);
|
|
174
141
|
|
|
175
|
-
setMessages(function (
|
|
176
|
-
return
|
|
177
|
-
role: 'user',
|
|
178
|
-
content: safeText,
|
|
179
|
-
id: 'u-' + Date.now(),
|
|
180
|
-
}]);
|
|
142
|
+
setMessages(function (p) {
|
|
143
|
+
return p.concat([{ role: 'user', content: safe, id: 'u-' + Date.now() }]);
|
|
181
144
|
});
|
|
182
145
|
setInputBuffer('');
|
|
183
146
|
|
|
@@ -189,17 +152,13 @@ function AppRoot() {
|
|
|
189
152
|
}
|
|
190
153
|
setStreamingActive(false);
|
|
191
154
|
setStreamText('');
|
|
192
|
-
setMessages(function (
|
|
193
|
-
return
|
|
194
|
-
role: 'assistant',
|
|
195
|
-
content: 'No model selected. Use `/model` to switch.',
|
|
196
|
-
id: 'e-' + Date.now(),
|
|
197
|
-
}]);
|
|
155
|
+
setMessages(function (p) {
|
|
156
|
+
return p.concat([{ role: 'assistant', content: 'No model selected. Use /model.', id: 'e-' + Date.now() }]);
|
|
198
157
|
});
|
|
199
158
|
return;
|
|
200
159
|
}
|
|
201
160
|
|
|
202
|
-
const history = buildHistory(messages,
|
|
161
|
+
const history = buildHistory(messages, safe);
|
|
203
162
|
const abortController = new AbortController();
|
|
204
163
|
abortRef.current = abortController;
|
|
205
164
|
|
|
@@ -221,12 +180,7 @@ function AppRoot() {
|
|
|
221
180
|
}
|
|
222
181
|
|
|
223
182
|
if (event.type === 'tool') {
|
|
224
|
-
setToolInfo({
|
|
225
|
-
tool: event.tool,
|
|
226
|
-
used: event.used,
|
|
227
|
-
sites: event.sites,
|
|
228
|
-
query: event.query,
|
|
229
|
-
});
|
|
183
|
+
setToolInfo({ tool: event.tool, used: event.used, sites: event.sites, query: event.query });
|
|
230
184
|
continue;
|
|
231
185
|
}
|
|
232
186
|
|
|
@@ -234,15 +188,15 @@ function AppRoot() {
|
|
|
234
188
|
const content = typeof event.content === 'string' ? event.content : '';
|
|
235
189
|
for (let i = 0; i < content.length; i++) {
|
|
236
190
|
accumulated = accumulated + content[i];
|
|
237
|
-
const
|
|
191
|
+
const inside = checkThink(accumulated);
|
|
238
192
|
setStreamText(accumulated);
|
|
239
|
-
setIsThinking(
|
|
240
|
-
if (
|
|
193
|
+
setIsThinking(inside);
|
|
194
|
+
if (inside === true && thinkStarted === false) {
|
|
241
195
|
thinkStarted = true;
|
|
242
196
|
thinkStartRef.current = Date.now();
|
|
243
197
|
setThinkingElapsed(0);
|
|
244
198
|
}
|
|
245
|
-
if (
|
|
199
|
+
if (inside === false && thinkStarted === true) {
|
|
246
200
|
thinkStarted = false;
|
|
247
201
|
thinkStartRef.current = null;
|
|
248
202
|
setThinkingElapsed(0);
|
|
@@ -274,12 +228,8 @@ function AppRoot() {
|
|
|
274
228
|
setStreamText('');
|
|
275
229
|
|
|
276
230
|
if (finalText.length > 0) {
|
|
277
|
-
setMessages(function (
|
|
278
|
-
return
|
|
279
|
-
role: 'assistant',
|
|
280
|
-
content: finalText,
|
|
281
|
-
id: 'a-' + Date.now(),
|
|
282
|
-
}]);
|
|
231
|
+
setMessages(function (p) {
|
|
232
|
+
return p.concat([{ role: 'assistant', content: finalText, id: 'a-' + Date.now() }]);
|
|
283
233
|
});
|
|
284
234
|
}
|
|
285
235
|
} catch (err) {
|
|
@@ -295,52 +245,38 @@ function AppRoot() {
|
|
|
295
245
|
const errMsg = err !== null && err !== undefined
|
|
296
246
|
? (err.message || String(err))
|
|
297
247
|
: 'Unknown error';
|
|
298
|
-
setMessages(function (
|
|
299
|
-
return
|
|
248
|
+
setMessages(function (p) {
|
|
249
|
+
return p.concat([{
|
|
300
250
|
role: 'assistant',
|
|
301
|
-
content: '
|
|
251
|
+
content: 'Error: ' + errMsg + '\n\nSpace cold-starting? Try again.',
|
|
302
252
|
id: 'er-' + Date.now(),
|
|
303
253
|
}]);
|
|
304
254
|
});
|
|
305
255
|
}
|
|
306
256
|
}
|
|
307
257
|
|
|
308
|
-
function
|
|
309
|
-
const
|
|
310
|
-
setInputBuffer(
|
|
311
|
-
if (
|
|
258
|
+
function handleChange(v) {
|
|
259
|
+
const s = typeof v === 'string' ? v : '';
|
|
260
|
+
setInputBuffer(s);
|
|
261
|
+
if (s.length === 1 && s[0] === '/' && showOverlay === false) {
|
|
312
262
|
setShowOverlay(true);
|
|
313
263
|
setOverlayIndex(0);
|
|
314
264
|
}
|
|
315
|
-
if (showOverlay === true && (
|
|
265
|
+
if (showOverlay === true && (s.length === 0 || s[0] !== '/')) {
|
|
316
266
|
setShowOverlay(false);
|
|
317
267
|
}
|
|
318
268
|
}
|
|
319
269
|
|
|
320
|
-
function
|
|
270
|
+
function handleCmd(item) {
|
|
321
271
|
if (item === null || item === undefined) return;
|
|
322
|
-
const
|
|
323
|
-
if (typeof
|
|
324
|
-
|
|
325
|
-
if (
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
}]);
|
|
272
|
+
const t = item.value;
|
|
273
|
+
if (typeof t !== 'string') return;
|
|
274
|
+
if (t === '/exit') process.exit(0);
|
|
275
|
+
if (t === '/clear') setMessages([]);
|
|
276
|
+
if (t === '/model') cycleModel();
|
|
277
|
+
if (t === '/help') {
|
|
278
|
+
setMessages(function (p) {
|
|
279
|
+
return p.concat([{ role: 'assistant', content: 'Commands: /help /clear /model /exit', id: 'h-' + Date.now() }]);
|
|
344
280
|
});
|
|
345
281
|
}
|
|
346
282
|
setInputBuffer('');
|
|
@@ -366,17 +302,15 @@ function AppRoot() {
|
|
|
366
302
|
}
|
|
367
303
|
if (key.return === true) {
|
|
368
304
|
const cmd = commands[overlayIndex];
|
|
369
|
-
if (cmd !== null && cmd !== undefined)
|
|
305
|
+
if (cmd !== null && cmd !== undefined) handleCmd({ value: cmd.trigger });
|
|
370
306
|
return;
|
|
371
307
|
}
|
|
372
308
|
return;
|
|
373
309
|
}
|
|
374
310
|
|
|
375
311
|
if (key.return === true) {
|
|
376
|
-
const
|
|
377
|
-
if (
|
|
378
|
-
handleSubmit(safe);
|
|
379
|
-
}
|
|
312
|
+
const s = typeof inputBuffer === 'string' ? inputBuffer : '';
|
|
313
|
+
if (s.length > 0 && streamingActive === false) handleSubmit(s);
|
|
380
314
|
return;
|
|
381
315
|
}
|
|
382
316
|
|
|
@@ -391,8 +325,9 @@ function AppRoot() {
|
|
|
391
325
|
}
|
|
392
326
|
});
|
|
393
327
|
|
|
394
|
-
const
|
|
395
|
-
const
|
|
328
|
+
const activeInfo = getModelById(activeModel);
|
|
329
|
+
const activeLabel = activeInfo !== null ? activeInfo.label : 'OpenAxies';
|
|
330
|
+
const activeBadge = activeInfo !== null ? activeInfo.badge : '';
|
|
396
331
|
|
|
397
332
|
const viewLines = [];
|
|
398
333
|
for (let i = 0; i < messages.length; i++) {
|
|
@@ -401,103 +336,77 @@ function AppRoot() {
|
|
|
401
336
|
const content = typeof msg.content === 'string' ? msg.content : '';
|
|
402
337
|
if (content.length === 0) continue;
|
|
403
338
|
|
|
404
|
-
if (i > 0) viewLines.push({ type: 'sep' });
|
|
405
|
-
|
|
406
339
|
if (msg.role === 'user') {
|
|
407
|
-
viewLines.push({
|
|
408
|
-
viewLines.push({
|
|
340
|
+
if (i > 0) viewLines.push({ t: 'sep' });
|
|
341
|
+
viewLines.push({ t: 'user', text: content });
|
|
342
|
+
viewLines.push({ t: 'sep' });
|
|
409
343
|
} else {
|
|
410
|
-
viewLines.push({
|
|
344
|
+
viewLines.push({ t: 'text', text: content });
|
|
411
345
|
}
|
|
412
346
|
}
|
|
413
347
|
|
|
414
348
|
const hasStream = typeof streamText === 'string' && streamText.length > 0;
|
|
415
349
|
if (hasStream === true) {
|
|
416
|
-
viewLines.push({
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
viewLines.push({
|
|
420
|
-
type: 'tool',
|
|
421
|
-
text: ' \u2500\u2500 ' + toolInfo.tool + ': "' + toolInfo.query + '" (' + toolInfo.sites + ' sites)',
|
|
422
|
-
});
|
|
350
|
+
viewLines.push({ t: 'sep' });
|
|
351
|
+
if (toolInfo !== null && toolInfo.used === true && toolInfo.query.length > 0) {
|
|
352
|
+
viewLines.push({ t: 'tool', text: '\u2500\u2500 ' + toolInfo.tool + ': "' + toolInfo.query + '" (' + toolInfo.sites + ' sites)' });
|
|
423
353
|
}
|
|
424
|
-
|
|
425
354
|
if (isThinking === true) {
|
|
426
|
-
|
|
427
|
-
viewLines.push({
|
|
428
|
-
type: 'think',
|
|
429
|
-
spin: sp,
|
|
430
|
-
elapsed: formatTimer(thinkingElapsed),
|
|
431
|
-
});
|
|
355
|
+
viewLines.push({ t: 'think', spin: SPINNER[spinnerIdx], elapsed: formatTimer(thinkingElapsed) });
|
|
432
356
|
const clean = streamText.split('<think>').join('').split('</think>').join('');
|
|
433
|
-
viewLines.push({
|
|
357
|
+
viewLines.push({ t: 'think-text', text: clean });
|
|
434
358
|
} else {
|
|
435
359
|
const clean = streamText.split('<think>').join('').split('</think>').join('');
|
|
436
|
-
viewLines.push({
|
|
360
|
+
viewLines.push({ t: 'text', text: clean });
|
|
437
361
|
}
|
|
438
362
|
}
|
|
439
363
|
|
|
440
364
|
if (viewLines.length === 0) {
|
|
441
|
-
viewLines.push({
|
|
365
|
+
viewLines.push({ t: 'idle', text: ' start typing to chat...' });
|
|
442
366
|
}
|
|
443
367
|
|
|
444
|
-
const
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
for (let i = 0; i <
|
|
449
|
-
const
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
} else if (
|
|
453
|
-
|
|
454
|
-
h(Box, { key: '
|
|
455
|
-
h(Text, { color: '#FF9F43', bold: true },
|
|
456
|
-
' ' + line.spin + ' Thinking \u2022 ' + line.elapsed
|
|
457
|
-
)
|
|
368
|
+
const visible = Math.max(1, availLines - 1);
|
|
369
|
+
const display = viewLines.slice(-visible);
|
|
370
|
+
const viewEls = [];
|
|
371
|
+
|
|
372
|
+
for (let i = 0; i < display.length; i++) {
|
|
373
|
+
const l = display[i];
|
|
374
|
+
if (l.t === 'sep') {
|
|
375
|
+
viewEls.push(h(Box, { key: 's-' + i, height: 1 }, hr(cols)));
|
|
376
|
+
} else if (l.t === 'think') {
|
|
377
|
+
viewEls.push(
|
|
378
|
+
h(Box, { key: 'k-' + i, height: 1, paddingLeft: 1 },
|
|
379
|
+
h(Text, { color: '#FF9F43', bold: true }, ' ' + l.spin + ' Thinking \u2022 ' + l.elapsed)
|
|
458
380
|
)
|
|
459
381
|
);
|
|
460
|
-
} else if (
|
|
461
|
-
|
|
462
|
-
h(Box, { key: '
|
|
463
|
-
h(Text, { color: '#
|
|
382
|
+
} else if (l.t === 'think-text') {
|
|
383
|
+
viewEls.push(
|
|
384
|
+
h(Box, { key: 'kt-' + i, height: 1, paddingLeft: 2 },
|
|
385
|
+
h(Text, { color: '#FF9F43' }, l.text)
|
|
464
386
|
)
|
|
465
387
|
);
|
|
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)
|
|
388
|
+
} else if (l.t === 'tool') {
|
|
389
|
+
viewEls.push(
|
|
390
|
+
h(Box, { key: 'tl-' + i, height: 1, paddingLeft: 1 },
|
|
391
|
+
h(Text, { color: '#5B5B8A' }, l.text)
|
|
475
392
|
)
|
|
476
393
|
);
|
|
477
|
-
} else if (
|
|
478
|
-
|
|
479
|
-
h(Box, {
|
|
480
|
-
|
|
481
|
-
height: 1,
|
|
482
|
-
paddingLeft: 1,
|
|
483
|
-
},
|
|
484
|
-
h(Text, { color: hex.primary }, line.text)
|
|
394
|
+
} else if (l.t === 'user') {
|
|
395
|
+
viewEls.push(
|
|
396
|
+
h(Box, { key: 'u-' + i, height: 1, paddingLeft: 1 },
|
|
397
|
+
h(Text, { color: hex.neonBlue }, l.text)
|
|
485
398
|
)
|
|
486
399
|
);
|
|
487
|
-
} else if (
|
|
488
|
-
|
|
489
|
-
h(Box, {
|
|
490
|
-
|
|
491
|
-
height: 1,
|
|
492
|
-
paddingLeft: 1,
|
|
493
|
-
},
|
|
494
|
-
h(Text, { color: line.color }, line.text)
|
|
400
|
+
} else if (l.t === 'text') {
|
|
401
|
+
viewEls.push(
|
|
402
|
+
h(Box, { key: 'a-' + i, height: 1, paddingLeft: 1 },
|
|
403
|
+
h(Text, { color: hex.primary }, l.text)
|
|
495
404
|
)
|
|
496
405
|
);
|
|
497
|
-
} else if (
|
|
498
|
-
|
|
499
|
-
h(Box, { key: '
|
|
500
|
-
h(Text, { color: '#333355' },
|
|
406
|
+
} else if (l.t === 'idle') {
|
|
407
|
+
viewEls.push(
|
|
408
|
+
h(Box, { key: 'i-' + i, height: 1, paddingLeft: 1 },
|
|
409
|
+
h(Text, { color: '#333355' }, l.text)
|
|
501
410
|
)
|
|
502
411
|
);
|
|
503
412
|
}
|
|
@@ -509,9 +418,7 @@ function AppRoot() {
|
|
|
509
418
|
flexDirection: 'column',
|
|
510
419
|
overflow: 'hidden',
|
|
511
420
|
paddingBottom: 1,
|
|
512
|
-
}, ...
|
|
513
|
-
|
|
514
|
-
const particleBot = createParticleRow(particleFrame, cols, 'bt');
|
|
421
|
+
}, ...viewEls);
|
|
515
422
|
|
|
516
423
|
const slashOverlay = showOverlay === true
|
|
517
424
|
? createSlashOverlay(commands, overlayIndex)
|
|
@@ -519,11 +426,13 @@ function AppRoot() {
|
|
|
519
426
|
|
|
520
427
|
const dock = createComposerDock({
|
|
521
428
|
value: inputBuffer,
|
|
522
|
-
onChange:
|
|
429
|
+
onChange: handleChange,
|
|
523
430
|
onSubmit: function () {},
|
|
524
431
|
inputActive: showOverlay === false,
|
|
525
432
|
isStreaming: streamingActive,
|
|
526
433
|
terminalWidth: cols,
|
|
434
|
+
modelLabel: activeLabel + ' \u25CF',
|
|
435
|
+
modelStatus: true,
|
|
527
436
|
});
|
|
528
437
|
|
|
529
438
|
return h(Box, {
|
|
@@ -533,11 +442,8 @@ function AppRoot() {
|
|
|
533
442
|
backgroundColor: hex.black,
|
|
534
443
|
overflow: 'hidden',
|
|
535
444
|
},
|
|
536
|
-
|
|
537
|
-
h(Box, { height: 1 }, barLine(cols)),
|
|
538
|
-
h(Box, { height: 1, paddingLeft: 1 }, particleTop),
|
|
445
|
+
h(Box, { height: 1 }, hr(cols)),
|
|
539
446
|
viewport,
|
|
540
|
-
h(Box, { height: 1, paddingLeft: 1 }, particleBot),
|
|
541
447
|
slashOverlay,
|
|
542
448
|
dock
|
|
543
449
|
);
|
|
@@ -1,105 +1,54 @@
|
|
|
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
8
|
export const DOCK_HEIGHT = 3;
|
|
9
|
-
const MAX_FRAME_WIDTH = 80;
|
|
10
|
-
const PROMPT = 'openaxies@root:~$ ';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
if (typeof value !== 'string') {
|
|
14
|
-
return '';
|
|
15
|
-
}
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
10
|
+
const PROMPT = '> ';
|
|
18
11
|
|
|
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
|
-
}
|
|
24
|
-
|
|
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
|
-
: MAX_FRAME_WIDTH;
|
|
51
|
-
|
|
52
|
-
checkCallback(onChange, 'onChange');
|
|
53
|
-
checkCallback(onSubmit, 'onSubmit');
|
|
22
|
+
const modelLabel = typeof config.modelLabel === 'string' ? config.modelLabel : '';
|
|
23
|
+
const modelStatus = config.modelStatus === true;
|
|
54
24
|
|
|
55
|
-
const placeholder = isStreaming
|
|
56
|
-
? 'Ctrl+C to interrupt...'
|
|
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);
|
|
25
|
+
const placeholder = isStreaming ? 'Ctrl+C to interrupt...' : 'type a message...';
|
|
62
26
|
|
|
63
27
|
return h(Box, {
|
|
64
28
|
width: '100%',
|
|
65
29
|
height: DOCK_HEIGHT,
|
|
66
30
|
flexDirection: 'column',
|
|
67
|
-
alignItems: 'center',
|
|
68
31
|
flexShrink: 0,
|
|
69
32
|
},
|
|
70
|
-
h(Box, {
|
|
71
|
-
h(Text, { color: hex.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
h(Box, {
|
|
82
|
-
width: innerWidth,
|
|
83
|
-
height: 1,
|
|
84
|
-
flexDirection: 'row',
|
|
85
|
-
overflow: 'hidden',
|
|
86
|
-
},
|
|
87
|
-
h(Text, { color: inputActive ? hex.greenOnline : hex.muted, bold: true }, PROMPT),
|
|
88
|
-
h(Box, { width: inputWidth, height: 1, overflow: 'hidden' },
|
|
89
|
-
h(TextInput, {
|
|
90
|
-
value: value,
|
|
91
|
-
onChange: onChange,
|
|
92
|
-
onSubmit: onSubmit,
|
|
93
|
-
placeholder: placeholder,
|
|
94
|
-
focus: inputActive,
|
|
95
|
-
showCursor: true,
|
|
96
|
-
})
|
|
97
|
-
)
|
|
33
|
+
h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
|
|
34
|
+
h(Text, { color: inputActive ? hex.primary : '#444466' }, PROMPT),
|
|
35
|
+
h(Box, { flexGrow: 1, height: 1, overflow: 'hidden' },
|
|
36
|
+
h(TextInput, {
|
|
37
|
+
value: value,
|
|
38
|
+
onChange: onChange,
|
|
39
|
+
onSubmit: onSubmit,
|
|
40
|
+
placeholder: placeholder,
|
|
41
|
+
focus: inputActive,
|
|
42
|
+
showCursor: true,
|
|
43
|
+
})
|
|
98
44
|
),
|
|
99
|
-
h(Text, { color:
|
|
45
|
+
h(Text, { color: '#444466' }, ' '),
|
|
46
|
+
h(Text, { color: modelStatus ? hex.greenOnline : '#444466', bold: modelStatus }, modelLabel || '')
|
|
100
47
|
),
|
|
101
|
-
h(Box, {
|
|
102
|
-
h(Text, { color:
|
|
48
|
+
h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
|
|
49
|
+
h(Text, { color: '#333344' }, '/help /clear /model /exit'),
|
|
50
|
+
h(Box, { flexGrow: 1 }),
|
|
51
|
+
h(Text, { color: '#333344' }, isStreaming ? 'Ctrl+C to stop' : '')
|
|
103
52
|
)
|
|
104
53
|
);
|
|
105
54
|
}
|
|
@@ -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
|
-
}
|