openaxies 0.3.0 → 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 +54 -4
- package/src/components/ChatViewport.js +64 -4
- package/src/components/RouterBar.js +21 -21
- package/src/config/commands.js +1 -1
- package/src/config/models.js +2 -3
- package/src/providers/index.js +1 -1
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
|
|
|
@@ -73,6 +75,33 @@ function AppRoot() {
|
|
|
73
75
|
|
|
74
76
|
const abortRef = React.useRef(null);
|
|
75
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]);
|
|
76
105
|
|
|
77
106
|
// ---------------------------------------------------------------------------
|
|
78
107
|
// Input handlers
|
|
@@ -118,10 +147,10 @@ function AppRoot() {
|
|
|
118
147
|
if (trigger === '/model') {
|
|
119
148
|
// Toggle between the two local models
|
|
120
149
|
setActiveModel(function (prev) {
|
|
121
|
-
if (prev === 'openaxis/openaxis-
|
|
150
|
+
if (prev === 'openaxis/openaxis-flash') {
|
|
122
151
|
return 'openaxis/openaxis-heavy';
|
|
123
152
|
}
|
|
124
|
-
return 'openaxis/openaxis-
|
|
153
|
+
return 'openaxis/openaxis-flash';
|
|
125
154
|
});
|
|
126
155
|
}
|
|
127
156
|
|
|
@@ -134,7 +163,7 @@ function AppRoot() {
|
|
|
134
163
|
'**Available commands:**\n' +
|
|
135
164
|
'- `/help` — Show this help\n' +
|
|
136
165
|
'- `/clear` — Clear the conversation\n' +
|
|
137
|
-
'- `/model` — Toggle between
|
|
166
|
+
'- `/model` — Toggle between Flash and Heavy\n' +
|
|
138
167
|
'- `/exit` — Quit the application\n\n' +
|
|
139
168
|
'Type your message and press **Enter** to chat.',
|
|
140
169
|
id: 'help-' + Date.now(),
|
|
@@ -197,6 +226,18 @@ function AppRoot() {
|
|
|
197
226
|
abortRef.current = abortController;
|
|
198
227
|
|
|
199
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
|
+
|
|
200
241
|
const gen = callModel(modelInfo, history, abortController.signal);
|
|
201
242
|
let accumulated = '';
|
|
202
243
|
let firstEvent = true;
|
|
@@ -216,8 +257,13 @@ function AppRoot() {
|
|
|
216
257
|
const content = typeof event.content === 'string' ? event.content : '';
|
|
217
258
|
for (let i = 0; i < content.length; i++) {
|
|
218
259
|
accumulated = accumulated + content[i];
|
|
260
|
+
const isInsideThink = checkThinkState(accumulated);
|
|
219
261
|
setScrollOffset(0);
|
|
220
262
|
setStreamText(accumulated);
|
|
263
|
+
setIsThinking(isInsideThink);
|
|
264
|
+
if (isInsideThink === true) {
|
|
265
|
+
thinkingAccumRef.current = accumulated;
|
|
266
|
+
}
|
|
221
267
|
await new Promise(function (resolve) {
|
|
222
268
|
setTimeout(resolve, 0);
|
|
223
269
|
});
|
|
@@ -239,6 +285,8 @@ function AppRoot() {
|
|
|
239
285
|
clearInterval(connectingRef.current);
|
|
240
286
|
connectingRef.current = null;
|
|
241
287
|
}
|
|
288
|
+
setIsThinking(false);
|
|
289
|
+
thinkingAccumRef.current = '';
|
|
242
290
|
setStreamingActive(false);
|
|
243
291
|
setStreamText('');
|
|
244
292
|
|
|
@@ -256,6 +304,8 @@ function AppRoot() {
|
|
|
256
304
|
clearInterval(connectingRef.current);
|
|
257
305
|
connectingRef.current = null;
|
|
258
306
|
}
|
|
307
|
+
setIsThinking(false);
|
|
308
|
+
thinkingAccumRef.current = '';
|
|
259
309
|
setStreamingActive(false);
|
|
260
310
|
setStreamText('');
|
|
261
311
|
setScrollOffset(0);
|
|
@@ -363,7 +413,7 @@ function AppRoot() {
|
|
|
363
413
|
|
|
364
414
|
const header = createBrandHeader(cols);
|
|
365
415
|
const routerBar = createRouterBar(activeModel, cols);
|
|
366
|
-
const viewport = createChatViewport(messages, streamText, availLines, cols, scrollOffset);
|
|
416
|
+
const viewport = createChatViewport(messages, streamText, availLines, cols, scrollOffset, isThinking, spinnerChar);
|
|
367
417
|
const slashOverlay = showOverlay === true
|
|
368
418
|
? createSlashOverlay(commands, overlayIndex)
|
|
369
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 [];
|
|
@@ -61,6 +64,39 @@ function pushWrapped(lines, text, width, color, bold) {
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
|
|
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 '';
|
|
96
|
+
}
|
|
97
|
+
return text.slice(closeIdx + THINK_CLOSE.length);
|
|
98
|
+
}
|
|
99
|
+
|
|
64
100
|
function pushRawMessage(lines, msg, safeCols) {
|
|
65
101
|
if (msg === null || msg === undefined || typeof msg !== 'object') {
|
|
66
102
|
return;
|
|
@@ -71,15 +107,27 @@ function pushRawMessage(lines, msg, safeCols) {
|
|
|
71
107
|
return;
|
|
72
108
|
}
|
|
73
109
|
|
|
110
|
+
const clean = stripThinkTags(content);
|
|
111
|
+
if (clean.length === 0) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
74
115
|
if (msg.role === 'user') {
|
|
75
|
-
pushWrapped(lines,
|
|
116
|
+
pushWrapped(lines, clean, safeCols, hex.neonBlue, false);
|
|
76
117
|
return;
|
|
77
118
|
}
|
|
78
119
|
|
|
79
|
-
pushWrapped(lines,
|
|
120
|
+
pushWrapped(lines, clean, safeCols, hex.primary, false);
|
|
80
121
|
}
|
|
81
122
|
|
|
82
|
-
|
|
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);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function createChatViewport(messages, streamText, availLines, cols, scrollOffset, isThinking, spinnerChar) {
|
|
83
131
|
const safeMessages = checkArray(messages);
|
|
84
132
|
const safeAvail = typeof availLines === 'number' && availLines > 0 ? availLines : 1;
|
|
85
133
|
const safeCols = clampWidth(cols);
|
|
@@ -87,6 +135,7 @@ export function createChatViewport(messages, streamText, availLines, cols, scrol
|
|
|
87
135
|
? Math.floor(scrollOffset)
|
|
88
136
|
: 0;
|
|
89
137
|
const hasStream = typeof streamText === 'string' && streamText.length > 0;
|
|
138
|
+
const isStreamingThink = isThinking === true && hasStream === true;
|
|
90
139
|
const transcriptLines = [];
|
|
91
140
|
|
|
92
141
|
for (let i = 0; i < safeMessages.length; i++) {
|
|
@@ -94,7 +143,18 @@ export function createChatViewport(messages, streamText, availLines, cols, scrol
|
|
|
94
143
|
}
|
|
95
144
|
|
|
96
145
|
if (hasStream === true) {
|
|
97
|
-
|
|
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
|
+
}
|
|
98
158
|
}
|
|
99
159
|
|
|
100
160
|
if (transcriptLines.length === 0) {
|
|
@@ -8,7 +8,7 @@ export const ROUTER_HEIGHT = 2;
|
|
|
8
8
|
|
|
9
9
|
function checkActiveModel(modelId) {
|
|
10
10
|
if (typeof modelId !== 'string') {
|
|
11
|
-
return 'openaxis/openaxis-
|
|
11
|
+
return 'openaxis/openaxis-flash';
|
|
12
12
|
}
|
|
13
13
|
return modelId;
|
|
14
14
|
}
|
|
@@ -34,7 +34,7 @@ function buildSeparator(cols) {
|
|
|
34
34
|
// Row 2: full-width separator line
|
|
35
35
|
export function createRouterBar(activeModel, cols) {
|
|
36
36
|
const modelStr = checkActiveModel(activeModel);
|
|
37
|
-
const
|
|
37
|
+
const isFlash = modelStr === 'openaxis/openaxis-flash';
|
|
38
38
|
const sepLine = buildSeparator(cols);
|
|
39
39
|
|
|
40
40
|
return h(Box, {
|
|
@@ -54,40 +54,40 @@ export function createRouterBar(activeModel, cols) {
|
|
|
54
54
|
|
|
55
55
|
// ── Mixtral slot ──
|
|
56
56
|
h(Text, {
|
|
57
|
-
color:
|
|
58
|
-
bold:
|
|
59
|
-
},
|
|
57
|
+
color: isFlash ? hex.neonBlue : '#333355',
|
|
58
|
+
bold: isFlash,
|
|
59
|
+
}, isFlash ? '\u25B6 ' : '\u25B7 '),
|
|
60
60
|
h(Text, {
|
|
61
|
-
color:
|
|
62
|
-
bold:
|
|
63
|
-
}, '
|
|
61
|
+
color: isFlash ? hex.primary : '#444466',
|
|
62
|
+
bold: isFlash,
|
|
63
|
+
}, 'Flash'),
|
|
64
64
|
h(Text, {
|
|
65
|
-
color:
|
|
65
|
+
color: isFlash ? hex.greenOnline : '#2A3A2A',
|
|
66
66
|
}, ' \u25CF '),
|
|
67
67
|
h(Text, {
|
|
68
|
-
color:
|
|
69
|
-
dimColor: !
|
|
70
|
-
},
|
|
68
|
+
color: isFlash ? hex.greenOnline : '#2A3A2A',
|
|
69
|
+
dimColor: !isFlash,
|
|
70
|
+
}, isFlash ? 'ACTIVE' : 'STANDBY'),
|
|
71
71
|
|
|
72
72
|
// ── Divider ──
|
|
73
73
|
h(Text, { color: '#1E1E2E' }, ' \u2502 '),
|
|
74
74
|
|
|
75
75
|
// ── Heavy slot ──
|
|
76
76
|
h(Text, {
|
|
77
|
-
color: !
|
|
78
|
-
bold: !
|
|
79
|
-
}, !
|
|
77
|
+
color: !isFlash ? hex.neonBlue : '#333355',
|
|
78
|
+
bold: !isFlash,
|
|
79
|
+
}, !isFlash ? '\u25B6 ' : '\u25B7 '),
|
|
80
80
|
h(Text, {
|
|
81
|
-
color: !
|
|
82
|
-
bold: !
|
|
81
|
+
color: !isFlash ? hex.primary : '#444466',
|
|
82
|
+
bold: !isFlash,
|
|
83
83
|
}, 'Heavy'),
|
|
84
84
|
h(Text, {
|
|
85
|
-
color: !
|
|
85
|
+
color: !isFlash ? hex.greenOnline : '#2A3A2A',
|
|
86
86
|
}, ' \u25CF '),
|
|
87
87
|
h(Text, {
|
|
88
|
-
color: !
|
|
89
|
-
dimColor:
|
|
90
|
-
}, !
|
|
88
|
+
color: !isFlash ? hex.greenOnline : '#2A3A2A',
|
|
89
|
+
dimColor: isFlash,
|
|
90
|
+
}, !isFlash ? 'ACTIVE' : 'STANDBY'),
|
|
91
91
|
|
|
92
92
|
// ── Right-side hint ──
|
|
93
93
|
h(Text, { color: '#1E1E2E' }, ' \u2502 '),
|
package/src/config/commands.js
CHANGED
|
@@ -34,7 +34,7 @@ function checkCommands(arr) {
|
|
|
34
34
|
const COMMAND_DEFS = Object.freeze([
|
|
35
35
|
{ trigger: '/help', description: 'Show available commands' },
|
|
36
36
|
{ trigger: '/clear', description: 'Clear the conversation' },
|
|
37
|
-
{ trigger: '/model', description: 'Switch between
|
|
37
|
+
{ trigger: '/model', description: 'Switch between Flash and Heavy' },
|
|
38
38
|
{ trigger: '/exit', description: 'Quit the application' },
|
|
39
39
|
]);
|
|
40
40
|
|
package/src/config/models.js
CHANGED
|
@@ -33,12 +33,11 @@ function checkModels(arr) {
|
|
|
33
33
|
return arr;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
// Mixtral (fast/light) and Heavy (quality).
|
|
37
36
|
const MODELS = Object.freeze([
|
|
38
37
|
{
|
|
39
|
-
id: 'openaxis/openaxis-
|
|
38
|
+
id: 'openaxis/openaxis-flash',
|
|
40
39
|
provider: 'openaxis',
|
|
41
|
-
label: 'OpenAxies
|
|
40
|
+
label: 'OpenAxies Flash',
|
|
42
41
|
badge: 'Fast',
|
|
43
42
|
},
|
|
44
43
|
{
|
package/src/providers/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { runWebSearchGraph, shouldUseWebSearch } from './websearch.js';
|
|
|
5
5
|
// HF Spaces cold-start on first request (30–120 s model download).
|
|
6
6
|
// If the primary is waking up, we fall through to the next Space.
|
|
7
7
|
const MODEL_ROUTES = Object.freeze({
|
|
8
|
-
'openaxis/openaxis-
|
|
8
|
+
'openaxis/openaxis-flash': Object.freeze([
|
|
9
9
|
'https://universal-618-clarity-main.hf.space/v1/chat/completions',
|
|
10
10
|
'https://universal-618-clarity-2.hf.space/v1/chat/completions',
|
|
11
11
|
'https://universal-618-clarity-3.hf.space/v1/chat/completions',
|