openaxies 0.2.1 → 0.4.0
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 +83 -30
- package/src/components/ChatViewport.js +64 -65
- package/src/components/RouterBar.js +1 -1
- package/src/config/models.js +0 -1
- package/src/providers/index.js +5 -13
- package/src/providers/streaming.js +2 -2
package/package.json
CHANGED
package/src/App.js
CHANGED
|
@@ -56,6 +56,8 @@ function AppRoot() {
|
|
|
56
56
|
const [streamText, setStreamText] = React.useState('');
|
|
57
57
|
const [streamingActive, setStreamingActive] = React.useState(false);
|
|
58
58
|
const [scrollOffset, setScrollOffset] = React.useState(0);
|
|
59
|
+
const [isThinking, setIsThinking] = React.useState(false);
|
|
60
|
+
const [spinnerChar, setSpinnerChar] = React.useState('');
|
|
59
61
|
|
|
60
62
|
const commands = getCommands();
|
|
61
63
|
|
|
@@ -72,6 +74,34 @@ function AppRoot() {
|
|
|
72
74
|
const availLines = Math.max(1, rows - fixedH);
|
|
73
75
|
|
|
74
76
|
const abortRef = React.useRef(null);
|
|
77
|
+
const connectingRef = React.useRef(null);
|
|
78
|
+
const spinnerRef = React.useRef(null);
|
|
79
|
+
const thinkingAccumRef = React.useRef('');
|
|
80
|
+
|
|
81
|
+
const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'];
|
|
82
|
+
|
|
83
|
+
React.useEffect(function () {
|
|
84
|
+
if (isThinking === false) {
|
|
85
|
+
if (spinnerRef.current !== null) {
|
|
86
|
+
clearInterval(spinnerRef.current);
|
|
87
|
+
spinnerRef.current = null;
|
|
88
|
+
}
|
|
89
|
+
setSpinnerChar('');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let idx = 0;
|
|
93
|
+
setSpinnerChar(SPINNER_FRAMES[0]);
|
|
94
|
+
spinnerRef.current = setInterval(function () {
|
|
95
|
+
idx = (idx + 1) % SPINNER_FRAMES.length;
|
|
96
|
+
setSpinnerChar(SPINNER_FRAMES[idx]);
|
|
97
|
+
}, 120);
|
|
98
|
+
return function cleanup() {
|
|
99
|
+
if (spinnerRef.current !== null) {
|
|
100
|
+
clearInterval(spinnerRef.current);
|
|
101
|
+
spinnerRef.current = null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}, [isThinking]);
|
|
75
105
|
|
|
76
106
|
// ---------------------------------------------------------------------------
|
|
77
107
|
// Input handlers
|
|
@@ -120,7 +150,7 @@ function AppRoot() {
|
|
|
120
150
|
if (prev === 'openaxis/openaxis-flash') {
|
|
121
151
|
return 'openaxis/openaxis-heavy';
|
|
122
152
|
}
|
|
123
|
-
|
|
153
|
+
return 'openaxis/openaxis-flash';
|
|
124
154
|
});
|
|
125
155
|
}
|
|
126
156
|
|
|
@@ -133,7 +163,7 @@ function AppRoot() {
|
|
|
133
163
|
'**Available commands:**\n' +
|
|
134
164
|
'- `/help` — Show this help\n' +
|
|
135
165
|
'- `/clear` — Clear the conversation\n' +
|
|
136
|
-
'- `/model` — Toggle between
|
|
166
|
+
'- `/model` — Toggle between Flash and Heavy\n' +
|
|
137
167
|
'- `/exit` — Quit the application\n\n' +
|
|
138
168
|
'Type your message and press **Enter** to chat.',
|
|
139
169
|
id: 'help-' + Date.now(),
|
|
@@ -161,10 +191,23 @@ function AppRoot() {
|
|
|
161
191
|
return prev.concat([{ role: 'user', content: safeText, id: msgId }]);
|
|
162
192
|
});
|
|
163
193
|
setInputBuffer('');
|
|
164
|
-
setStreamText('
|
|
194
|
+
setStreamText(' connecting');
|
|
195
|
+
connectingRef.current = setInterval(function () {
|
|
196
|
+
setStreamText(function (prev) {
|
|
197
|
+
if (prev === ' connecting') return ' connecting.';
|
|
198
|
+
if (prev === ' connecting.') return ' connecting..';
|
|
199
|
+
if (prev === ' connecting..') return ' connecting...';
|
|
200
|
+
if (prev === ' connecting...') return ' connecting';
|
|
201
|
+
return prev;
|
|
202
|
+
});
|
|
203
|
+
}, 400);
|
|
165
204
|
|
|
166
205
|
const modelInfo = getModelById(activeModel);
|
|
167
206
|
if (modelInfo === null || modelInfo === undefined) {
|
|
207
|
+
if (connectingRef.current !== null) {
|
|
208
|
+
clearInterval(connectingRef.current);
|
|
209
|
+
connectingRef.current = null;
|
|
210
|
+
}
|
|
168
211
|
setStreamingActive(false);
|
|
169
212
|
setStreamText('');
|
|
170
213
|
setScrollOffset(0);
|
|
@@ -183,46 +226,44 @@ function AppRoot() {
|
|
|
183
226
|
abortRef.current = abortController;
|
|
184
227
|
|
|
185
228
|
try {
|
|
229
|
+
function checkThinkState(text) {
|
|
230
|
+
const openIdx = text.lastIndexOf('<think>');
|
|
231
|
+
const closeIdx = text.lastIndexOf('</think>');
|
|
232
|
+
if (openIdx === -1 && closeIdx === -1) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
if (openIdx > closeIdx) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
186
241
|
const gen = callModel(modelInfo, history, abortController.signal);
|
|
187
242
|
let accumulated = '';
|
|
188
|
-
let
|
|
189
|
-
const thinkingLines = [];
|
|
243
|
+
let firstEvent = true;
|
|
190
244
|
|
|
191
245
|
for await (const event of gen) {
|
|
192
246
|
if (abortController.signal.aborted === true) {
|
|
193
247
|
break;
|
|
194
248
|
}
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
249
|
+
if (firstEvent === true) {
|
|
250
|
+
firstEvent = false;
|
|
251
|
+
if (connectingRef.current !== null) {
|
|
252
|
+
clearInterval(connectingRef.current);
|
|
253
|
+
connectingRef.current = null;
|
|
198
254
|
}
|
|
199
|
-
thinkingLines.push(typeof event.content === 'string' ? event.content : '');
|
|
200
|
-
const content = thinkingLines.join('\n');
|
|
201
|
-
setMessages(function (prev) {
|
|
202
|
-
let replaced = false;
|
|
203
|
-
const next = [];
|
|
204
|
-
for (let i = 0; i < prev.length; i++) {
|
|
205
|
-
const item = prev[i];
|
|
206
|
-
if (item !== null && item !== undefined && item.id === thinkingId) {
|
|
207
|
-
next.push({ role: 'thinking', content: content, id: thinkingId });
|
|
208
|
-
replaced = true;
|
|
209
|
-
} else {
|
|
210
|
-
next.push(item);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (replaced === false) {
|
|
214
|
-
next.push({ role: 'thinking', content: content, id: thinkingId });
|
|
215
|
-
}
|
|
216
|
-
return next;
|
|
217
|
-
});
|
|
218
|
-
setScrollOffset(0);
|
|
219
255
|
}
|
|
220
|
-
if (event.type === 'token') {
|
|
256
|
+
if (event.type === 'thinking' || event.type === 'token') {
|
|
221
257
|
const content = typeof event.content === 'string' ? event.content : '';
|
|
222
258
|
for (let i = 0; i < content.length; i++) {
|
|
223
259
|
accumulated = accumulated + content[i];
|
|
260
|
+
const isInsideThink = checkThinkState(accumulated);
|
|
224
261
|
setScrollOffset(0);
|
|
225
262
|
setStreamText(accumulated);
|
|
263
|
+
setIsThinking(isInsideThink);
|
|
264
|
+
if (isInsideThink === true) {
|
|
265
|
+
thinkingAccumRef.current = accumulated;
|
|
266
|
+
}
|
|
226
267
|
await new Promise(function (resolve) {
|
|
227
268
|
setTimeout(resolve, 0);
|
|
228
269
|
});
|
|
@@ -240,6 +281,12 @@ function AppRoot() {
|
|
|
240
281
|
}
|
|
241
282
|
}
|
|
242
283
|
|
|
284
|
+
if (connectingRef.current !== null) {
|
|
285
|
+
clearInterval(connectingRef.current);
|
|
286
|
+
connectingRef.current = null;
|
|
287
|
+
}
|
|
288
|
+
setIsThinking(false);
|
|
289
|
+
thinkingAccumRef.current = '';
|
|
243
290
|
setStreamingActive(false);
|
|
244
291
|
setStreamText('');
|
|
245
292
|
|
|
@@ -253,6 +300,12 @@ function AppRoot() {
|
|
|
253
300
|
});
|
|
254
301
|
}
|
|
255
302
|
} catch (err) {
|
|
303
|
+
if (connectingRef.current !== null) {
|
|
304
|
+
clearInterval(connectingRef.current);
|
|
305
|
+
connectingRef.current = null;
|
|
306
|
+
}
|
|
307
|
+
setIsThinking(false);
|
|
308
|
+
thinkingAccumRef.current = '';
|
|
256
309
|
setStreamingActive(false);
|
|
257
310
|
setStreamText('');
|
|
258
311
|
setScrollOffset(0);
|
|
@@ -360,7 +413,7 @@ function AppRoot() {
|
|
|
360
413
|
|
|
361
414
|
const header = createBrandHeader(cols);
|
|
362
415
|
const routerBar = createRouterBar(activeModel, cols);
|
|
363
|
-
const viewport = createChatViewport(messages, streamText, availLines, cols, scrollOffset);
|
|
416
|
+
const viewport = createChatViewport(messages, streamText, availLines, cols, scrollOffset, isThinking, spinnerChar);
|
|
364
417
|
const slashOverlay = showOverlay === true
|
|
365
418
|
? createSlashOverlay(commands, overlayIndex)
|
|
366
419
|
: null;
|
|
@@ -4,6 +4,9 @@ import { hex } from '../config/theme.js';
|
|
|
4
4
|
|
|
5
5
|
const h = React.createElement;
|
|
6
6
|
|
|
7
|
+
const THINK_OPEN = '<think>';
|
|
8
|
+
const THINK_CLOSE = '</think>';
|
|
9
|
+
|
|
7
10
|
function checkArray(arr) {
|
|
8
11
|
if (Array.isArray(arr) === false) {
|
|
9
12
|
return [];
|
|
@@ -11,49 +14,6 @@ function checkArray(arr) {
|
|
|
11
14
|
return arr;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
// Splits content on <think>...</think> tags.
|
|
15
|
-
// Returns an array of { type: 'text' | 'think', content: string } parts.
|
|
16
|
-
function parseThinkTags(content) {
|
|
17
|
-
if (typeof content !== 'string') {
|
|
18
|
-
return [];
|
|
19
|
-
}
|
|
20
|
-
const parts = [];
|
|
21
|
-
let remaining = content;
|
|
22
|
-
let inThink = false;
|
|
23
|
-
|
|
24
|
-
while (remaining.length > 0) {
|
|
25
|
-
if (inThink === false) {
|
|
26
|
-
const startIdx = remaining.indexOf('<think>');
|
|
27
|
-
if (startIdx === -1) {
|
|
28
|
-
if (remaining.length > 0) {
|
|
29
|
-
parts.push({ type: 'text', content: remaining });
|
|
30
|
-
}
|
|
31
|
-
break;
|
|
32
|
-
}
|
|
33
|
-
if (startIdx > 0) {
|
|
34
|
-
parts.push({ type: 'text', content: remaining.slice(0, startIdx) });
|
|
35
|
-
}
|
|
36
|
-
remaining = remaining.slice(startIdx + 7);
|
|
37
|
-
inThink = true;
|
|
38
|
-
} else {
|
|
39
|
-
const endIdx = remaining.indexOf('</think>');
|
|
40
|
-
if (endIdx === -1) {
|
|
41
|
-
if (remaining.length > 0) {
|
|
42
|
-
parts.push({ type: 'think', content: remaining });
|
|
43
|
-
}
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
if (endIdx > 0) {
|
|
47
|
-
parts.push({ type: 'think', content: remaining.slice(0, endIdx) });
|
|
48
|
-
}
|
|
49
|
-
remaining = remaining.slice(endIdx + 8);
|
|
50
|
-
inThink = false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return parts;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
17
|
function clampWidth(width) {
|
|
58
18
|
if (typeof width !== 'number' || width < 20) {
|
|
59
19
|
return 80;
|
|
@@ -104,42 +64,70 @@ function pushWrapped(lines, text, width, color, bold) {
|
|
|
104
64
|
}
|
|
105
65
|
}
|
|
106
66
|
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
67
|
+
function stripThinkTags(text) {
|
|
68
|
+
if (typeof text !== 'string') {
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
return text.split(THINK_OPEN).join('').split(THINK_CLOSE).join('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function extractThinkContent(text) {
|
|
75
|
+
if (typeof text !== 'string') {
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
const openIdx = text.indexOf(THINK_OPEN);
|
|
79
|
+
if (openIdx === -1) {
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
const closeIdx = text.indexOf(THINK_CLOSE, openIdx + THINK_OPEN.length);
|
|
83
|
+
if (closeIdx === -1) {
|
|
84
|
+
return text.slice(openIdx + THINK_OPEN.length);
|
|
85
|
+
}
|
|
86
|
+
return text.slice(openIdx + THINK_OPEN.length, closeIdx);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function extractAfterThink(text) {
|
|
90
|
+
if (typeof text !== 'string') {
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
const closeIdx = text.lastIndexOf(THINK_CLOSE);
|
|
94
|
+
if (closeIdx === -1) {
|
|
95
|
+
return '';
|
|
116
96
|
}
|
|
97
|
+
return text.slice(closeIdx + THINK_CLOSE.length);
|
|
117
98
|
}
|
|
118
99
|
|
|
119
|
-
|
|
120
|
-
function pushMessageLines(lines, msg, safeCols) {
|
|
100
|
+
function pushRawMessage(lines, msg, safeCols) {
|
|
121
101
|
if (msg === null || msg === undefined || typeof msg !== 'object') {
|
|
122
102
|
return;
|
|
123
103
|
}
|
|
124
104
|
|
|
125
105
|
const content = typeof msg.content === 'string' ? msg.content : '';
|
|
106
|
+
if (content.length === 0) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
126
109
|
|
|
127
|
-
|
|
128
|
-
|
|
110
|
+
const clean = stripThinkTags(content);
|
|
111
|
+
if (clean.length === 0) {
|
|
129
112
|
return;
|
|
130
113
|
}
|
|
131
114
|
|
|
132
115
|
if (msg.role === 'user') {
|
|
133
|
-
pushWrapped(lines,
|
|
116
|
+
pushWrapped(lines, clean, safeCols, hex.neonBlue, false);
|
|
134
117
|
return;
|
|
135
118
|
}
|
|
136
119
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
120
|
+
pushWrapped(lines, clean, safeCols, hex.primary, false);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function pushSpinnerLine(lines, spinnerChar, safeCols) {
|
|
124
|
+
const safeChar = typeof spinnerChar === 'string' && spinnerChar.length > 0
|
|
125
|
+
? spinnerChar
|
|
126
|
+
: '◐';
|
|
127
|
+
pushLine(lines, ' ' + safeChar + ' thinking', '#FF9F43', true);
|
|
140
128
|
}
|
|
141
129
|
|
|
142
|
-
export function createChatViewport(messages, streamText, availLines, cols, scrollOffset) {
|
|
130
|
+
export function createChatViewport(messages, streamText, availLines, cols, scrollOffset, isThinking, spinnerChar) {
|
|
143
131
|
const safeMessages = checkArray(messages);
|
|
144
132
|
const safeAvail = typeof availLines === 'number' && availLines > 0 ? availLines : 1;
|
|
145
133
|
const safeCols = clampWidth(cols);
|
|
@@ -147,19 +135,30 @@ export function createChatViewport(messages, streamText, availLines, cols, scrol
|
|
|
147
135
|
? Math.floor(scrollOffset)
|
|
148
136
|
: 0;
|
|
149
137
|
const hasStream = typeof streamText === 'string' && streamText.length > 0;
|
|
138
|
+
const isStreamingThink = isThinking === true && hasStream === true;
|
|
150
139
|
const transcriptLines = [];
|
|
151
140
|
|
|
152
141
|
for (let i = 0; i < safeMessages.length; i++) {
|
|
153
|
-
|
|
142
|
+
pushRawMessage(transcriptLines, safeMessages[i], safeCols);
|
|
154
143
|
}
|
|
155
144
|
|
|
156
145
|
if (hasStream === true) {
|
|
157
|
-
|
|
158
|
-
|
|
146
|
+
if (isStreamingThink === true) {
|
|
147
|
+
pushSpinnerLine(transcriptLines, spinnerChar, safeCols);
|
|
148
|
+
const thinkContent = extractThinkContent(streamText);
|
|
149
|
+
if (thinkContent.length > 0) {
|
|
150
|
+
pushWrapped(transcriptLines, thinkContent, safeCols, '#FF9F43', false);
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
const clean = stripThinkTags(streamText);
|
|
154
|
+
if (clean.length > 0) {
|
|
155
|
+
pushWrapped(transcriptLines, clean, safeCols, hex.primary, false);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
if (transcriptLines.length === 0) {
|
|
162
|
-
pushLine(transcriptLines, '
|
|
161
|
+
pushLine(transcriptLines, ' awaiting input...', '#333355', false);
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
const visibleHeight = Math.max(1, safeAvail - 1);
|
package/src/config/models.js
CHANGED
package/src/providers/index.js
CHANGED
|
@@ -55,26 +55,18 @@ export async function* callModel(modelConfig, messages, signal) {
|
|
|
55
55
|
let modelMessages = messages;
|
|
56
56
|
|
|
57
57
|
if (shouldUseWebSearch(messages) === true) {
|
|
58
|
-
yield { type: 'thinking', content: 'OpenAxies thinking > [Internal reasoning]' };
|
|
59
|
-
yield { type: 'thinking', content: 'Checking tools...' };
|
|
60
|
-
yield { type: 'thinking', content: 'Let me do websearch' };
|
|
61
|
-
yield { type: 'thinking', content: 'Doing websearch' };
|
|
62
|
-
|
|
63
58
|
try {
|
|
64
59
|
const webResult = await runWebSearchGraph(messages, signal);
|
|
65
|
-
if (webResult.used === true) {
|
|
66
|
-
yield { type: 'thinking', content: 'Searched ' + webResult.sites + ' sites' };
|
|
67
|
-
yield { type: 'thinking', content: 'Thinking...' };
|
|
60
|
+
if (webResult.used === true && webResult.sites > 0) {
|
|
68
61
|
modelMessages = [{
|
|
69
62
|
role: 'system',
|
|
70
63
|
content:
|
|
71
|
-
'
|
|
72
|
-
webResult.summary
|
|
64
|
+
'Current web search results (use when relevant, cite URLs inline):\n\n' +
|
|
65
|
+
webResult.summary +
|
|
66
|
+
'\n\nNow respond to the user using these results as needed.',
|
|
73
67
|
}].concat(messages);
|
|
74
68
|
}
|
|
75
|
-
} catch (
|
|
76
|
-
yield { type: 'thinking', content: 'Searched 0 sites' };
|
|
77
|
-
yield { type: 'thinking', content: 'Thinking...' };
|
|
69
|
+
} catch (_) {
|
|
78
70
|
}
|
|
79
71
|
}
|
|
80
72
|
|