@ai-accounts/vue-headless 0.3.0-alpha.2 → 0.3.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/dist/index.cjs +644 -8
- package/dist/index.d.cts +121 -3
- package/dist/index.d.ts +121 -3
- package/dist/index.js +629 -8
- package/package.json +12 -5
- package/LICENSE +0 -202
package/dist/index.js
CHANGED
|
@@ -144,6 +144,7 @@ function useLoginSession() {
|
|
|
144
144
|
const accountId = ref4(null);
|
|
145
145
|
const urlPrompt = ref4(null);
|
|
146
146
|
const textPrompt = ref4(null);
|
|
147
|
+
const menuPrompt = ref4(null);
|
|
147
148
|
const stdoutLines = ref4([]);
|
|
148
149
|
const errorCode = ref4(null);
|
|
149
150
|
const errorMessage = ref4(null);
|
|
@@ -152,15 +153,33 @@ function useLoginSession() {
|
|
|
152
153
|
status.value = "running";
|
|
153
154
|
urlPrompt.value = null;
|
|
154
155
|
textPrompt.value = null;
|
|
156
|
+
menuPrompt.value = null;
|
|
155
157
|
stdoutLines.value = [];
|
|
156
158
|
errorCode.value = null;
|
|
157
159
|
errorMessage.value = null;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
try {
|
|
161
|
+
const { session_id } = await client.beginLogin(id, flow, inputs);
|
|
162
|
+
sessionId.value = session_id;
|
|
163
|
+
emit({ type: "login.started", sessionId: session_id, backendKind: "", flow });
|
|
164
|
+
for await (const event of client.streamLogin(id, session_id)) {
|
|
165
|
+
dispatch(event);
|
|
166
|
+
if (status.value !== "running") return;
|
|
167
|
+
}
|
|
168
|
+
if (status.value === "running") {
|
|
169
|
+
status.value = "failed";
|
|
170
|
+
errorCode.value = "stream_ended";
|
|
171
|
+
errorMessage.value = "Login stream ended unexpectedly";
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
status.value = "failed";
|
|
175
|
+
errorCode.value = "network_error";
|
|
176
|
+
errorMessage.value = err instanceof Error ? err.message : String(err);
|
|
177
|
+
emit({
|
|
178
|
+
type: "login.failed",
|
|
179
|
+
sessionId: sessionId.value ?? "",
|
|
180
|
+
code: "network_error",
|
|
181
|
+
message: errorMessage.value
|
|
182
|
+
});
|
|
164
183
|
}
|
|
165
184
|
}
|
|
166
185
|
function dispatch(event) {
|
|
@@ -173,6 +192,10 @@ function useLoginSession() {
|
|
|
173
192
|
textPrompt.value = event;
|
|
174
193
|
emit({ type: "login.prompt", sessionId: sessionId.value, promptKind: "text" });
|
|
175
194
|
break;
|
|
195
|
+
case "menu_prompt":
|
|
196
|
+
menuPrompt.value = event;
|
|
197
|
+
emit({ type: "login.prompt", sessionId: sessionId.value, promptKind: "menu" });
|
|
198
|
+
break;
|
|
176
199
|
case "stdout":
|
|
177
200
|
stdoutLines.value = [...stdoutLines.value, event.text];
|
|
178
201
|
break;
|
|
@@ -201,9 +224,12 @@ function useLoginSession() {
|
|
|
201
224
|
}
|
|
202
225
|
}
|
|
203
226
|
async function respond(answer) {
|
|
204
|
-
if (!sessionId.value || !accountId.value
|
|
205
|
-
const
|
|
227
|
+
if (!sessionId.value || !accountId.value) return;
|
|
228
|
+
const activePrompt = textPrompt.value ?? menuPrompt.value;
|
|
229
|
+
if (!activePrompt) return;
|
|
230
|
+
const promptId = activePrompt.prompt_id;
|
|
206
231
|
textPrompt.value = null;
|
|
232
|
+
menuPrompt.value = null;
|
|
207
233
|
await client.respondLogin(accountId.value, sessionId.value, promptId, answer);
|
|
208
234
|
}
|
|
209
235
|
async function cancel() {
|
|
@@ -217,6 +243,7 @@ function useLoginSession() {
|
|
|
217
243
|
accountId,
|
|
218
244
|
urlPrompt,
|
|
219
245
|
textPrompt,
|
|
246
|
+
menuPrompt,
|
|
220
247
|
stdoutLines,
|
|
221
248
|
errorCode,
|
|
222
249
|
errorMessage,
|
|
@@ -226,6 +253,595 @@ function useLoginSession() {
|
|
|
226
253
|
};
|
|
227
254
|
}
|
|
228
255
|
|
|
256
|
+
// src/composables/useConversation.ts
|
|
257
|
+
import { ref as ref5, shallowRef } from "vue";
|
|
258
|
+
function useConversation(client) {
|
|
259
|
+
const sessionId = ref5(null);
|
|
260
|
+
const messages = shallowRef([]);
|
|
261
|
+
const isStreaming = ref5(false);
|
|
262
|
+
const streamingText = ref5("");
|
|
263
|
+
const error = ref5(null);
|
|
264
|
+
async function create(backendId, model) {
|
|
265
|
+
error.value = null;
|
|
266
|
+
const session = await client.createConversation({ backend_id: backendId, model });
|
|
267
|
+
sessionId.value = session.id;
|
|
268
|
+
messages.value = [];
|
|
269
|
+
}
|
|
270
|
+
async function load(id) {
|
|
271
|
+
error.value = null;
|
|
272
|
+
const detail = await client.getConversation(id);
|
|
273
|
+
sessionId.value = detail.id;
|
|
274
|
+
messages.value = detail.messages;
|
|
275
|
+
}
|
|
276
|
+
async function send(content) {
|
|
277
|
+
if (!sessionId.value) {
|
|
278
|
+
error.value = "No active session";
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
error.value = null;
|
|
282
|
+
isStreaming.value = true;
|
|
283
|
+
streamingText.value = "";
|
|
284
|
+
const userMsg = {
|
|
285
|
+
id: `pending-${Date.now()}`,
|
|
286
|
+
role: "user",
|
|
287
|
+
content,
|
|
288
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
289
|
+
model: null,
|
|
290
|
+
tokens_in: null,
|
|
291
|
+
tokens_out: null
|
|
292
|
+
};
|
|
293
|
+
messages.value = [...messages.value, userMsg];
|
|
294
|
+
try {
|
|
295
|
+
let accumulated = "";
|
|
296
|
+
for await (const delta of client.streamChat(sessionId.value, content)) {
|
|
297
|
+
if (delta.kind === "token" && delta.text) {
|
|
298
|
+
accumulated += delta.text;
|
|
299
|
+
streamingText.value = accumulated;
|
|
300
|
+
} else if (delta.kind === "error") {
|
|
301
|
+
error.value = delta.text ?? "Unknown error";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (accumulated) {
|
|
305
|
+
const assistantMsg = {
|
|
306
|
+
id: `msg-${Date.now()}`,
|
|
307
|
+
role: "assistant",
|
|
308
|
+
content: accumulated,
|
|
309
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
310
|
+
model: null,
|
|
311
|
+
tokens_in: null,
|
|
312
|
+
tokens_out: null
|
|
313
|
+
};
|
|
314
|
+
messages.value = [...messages.value, assistantMsg];
|
|
315
|
+
}
|
|
316
|
+
} catch (e) {
|
|
317
|
+
error.value = e instanceof Error ? e.message : "Stream failed";
|
|
318
|
+
} finally {
|
|
319
|
+
isStreaming.value = false;
|
|
320
|
+
streamingText.value = "";
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return { sessionId, messages, isStreaming, streamingText, error, create, send, load };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/composables/useSmartChat.ts
|
|
327
|
+
import { ref as ref7, shallowRef as shallowRef2 } from "vue";
|
|
328
|
+
|
|
329
|
+
// src/composables/useProcessGroups.ts
|
|
330
|
+
import { ref as ref6 } from "vue";
|
|
331
|
+
var AUTO_COLLAPSE_MS = {
|
|
332
|
+
tool_call: 4e3,
|
|
333
|
+
reasoning: 2e3,
|
|
334
|
+
code_execution: 2e3
|
|
335
|
+
};
|
|
336
|
+
function useProcessGroups() {
|
|
337
|
+
const groups = ref6(/* @__PURE__ */ new Map());
|
|
338
|
+
function _triggerReactivity() {
|
|
339
|
+
groups.value = new Map(groups.value);
|
|
340
|
+
}
|
|
341
|
+
function addGroup(group) {
|
|
342
|
+
groups.value.set(group.id, { ...group, isExpanded: true });
|
|
343
|
+
_triggerReactivity();
|
|
344
|
+
}
|
|
345
|
+
function removeGroup(id) {
|
|
346
|
+
groups.value.delete(id);
|
|
347
|
+
_triggerReactivity();
|
|
348
|
+
}
|
|
349
|
+
function toggleGroup(id) {
|
|
350
|
+
const g = groups.value.get(id);
|
|
351
|
+
if (g) {
|
|
352
|
+
g.isExpanded = !g.isExpanded;
|
|
353
|
+
_triggerReactivity();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function collapseGroup(id) {
|
|
357
|
+
const g = groups.value.get(id);
|
|
358
|
+
if (g) {
|
|
359
|
+
g.isExpanded = false;
|
|
360
|
+
_triggerReactivity();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function expandGroup(id) {
|
|
364
|
+
const g = groups.value.get(id);
|
|
365
|
+
if (g) {
|
|
366
|
+
g.isExpanded = true;
|
|
367
|
+
_triggerReactivity();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function updateGroupContent(id, content) {
|
|
371
|
+
const g = groups.value.get(id);
|
|
372
|
+
if (g) {
|
|
373
|
+
g.content += content;
|
|
374
|
+
_triggerReactivity();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function clearGroups() {
|
|
378
|
+
groups.value.clear();
|
|
379
|
+
_triggerReactivity();
|
|
380
|
+
}
|
|
381
|
+
function processToolCallDelta(delta) {
|
|
382
|
+
const existing = groups.value.get(delta.id);
|
|
383
|
+
if (existing) {
|
|
384
|
+
if (delta.arguments) {
|
|
385
|
+
existing.content += delta.arguments;
|
|
386
|
+
_triggerReactivity();
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const type = delta.group_type ?? "tool_call";
|
|
391
|
+
addGroup({
|
|
392
|
+
id: delta.id,
|
|
393
|
+
type,
|
|
394
|
+
label: delta.name ?? delta.id,
|
|
395
|
+
content: delta.arguments ?? "",
|
|
396
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
397
|
+
autoCollapseMs: AUTO_COLLAPSE_MS[type]
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
groups,
|
|
402
|
+
addGroup,
|
|
403
|
+
removeGroup,
|
|
404
|
+
toggleGroup,
|
|
405
|
+
collapseGroup,
|
|
406
|
+
expandGroup,
|
|
407
|
+
updateGroupContent,
|
|
408
|
+
clearGroups,
|
|
409
|
+
processToolCallDelta
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/composables/useSmartChat.ts
|
|
414
|
+
var HEARTBEAT_TIMEOUT_MS = 9e4;
|
|
415
|
+
function useSmartChat() {
|
|
416
|
+
const { client } = useAiAccounts();
|
|
417
|
+
const sessionId = ref7(null);
|
|
418
|
+
const messages = shallowRef2([]);
|
|
419
|
+
const isStreaming = ref7(false);
|
|
420
|
+
const streamingContent = ref7("");
|
|
421
|
+
const error = ref7(null);
|
|
422
|
+
const chatMode = ref7("single");
|
|
423
|
+
const backendResponses = ref7(/* @__PURE__ */ new Map());
|
|
424
|
+
const synthesisState = ref7(null);
|
|
425
|
+
const selectedBackend = ref7(null);
|
|
426
|
+
const selectedAccount = ref7(null);
|
|
427
|
+
const selectedModel = ref7(null);
|
|
428
|
+
const processGroups = useProcessGroups();
|
|
429
|
+
const canFinalize = ref7(false);
|
|
430
|
+
const isFinalizing = ref7(false);
|
|
431
|
+
const detectedConfig = ref7(null);
|
|
432
|
+
let configParser = null;
|
|
433
|
+
function setConfigParser(parser) {
|
|
434
|
+
configParser = parser;
|
|
435
|
+
}
|
|
436
|
+
async function finalize() {
|
|
437
|
+
if (!canFinalize.value) return null;
|
|
438
|
+
isFinalizing.value = true;
|
|
439
|
+
try {
|
|
440
|
+
return detectedConfig.value;
|
|
441
|
+
} finally {
|
|
442
|
+
isFinalizing.value = false;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
let lastSeq = 0;
|
|
446
|
+
let heartbeatTimer = null;
|
|
447
|
+
let activeAbort = null;
|
|
448
|
+
function clearHeartbeat() {
|
|
449
|
+
if (heartbeatTimer) {
|
|
450
|
+
clearTimeout(heartbeatTimer);
|
|
451
|
+
heartbeatTimer = null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function resetHeartbeat() {
|
|
455
|
+
clearHeartbeat();
|
|
456
|
+
heartbeatTimer = setTimeout(() => {
|
|
457
|
+
error.value = "Connection lost \u2014 no activity from server";
|
|
458
|
+
isStreaming.value = false;
|
|
459
|
+
heartbeatTimer = null;
|
|
460
|
+
if (activeAbort) {
|
|
461
|
+
activeAbort.abort(new DOMException("heartbeat-timeout", "AbortError"));
|
|
462
|
+
}
|
|
463
|
+
}, HEARTBEAT_TIMEOUT_MS);
|
|
464
|
+
}
|
|
465
|
+
async function createSession(backendId, model) {
|
|
466
|
+
error.value = null;
|
|
467
|
+
const session = await client.createChatSession(backendId, model);
|
|
468
|
+
sessionId.value = session.id;
|
|
469
|
+
messages.value = [];
|
|
470
|
+
}
|
|
471
|
+
async function loadSession(id) {
|
|
472
|
+
error.value = null;
|
|
473
|
+
const detail = await client.getConversation(id);
|
|
474
|
+
sessionId.value = detail.id;
|
|
475
|
+
messages.value = detail.messages;
|
|
476
|
+
}
|
|
477
|
+
async function send(content) {
|
|
478
|
+
if (!sessionId.value) {
|
|
479
|
+
error.value = "No active session";
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
error.value = null;
|
|
483
|
+
isStreaming.value = true;
|
|
484
|
+
streamingContent.value = "";
|
|
485
|
+
backendResponses.value = /* @__PURE__ */ new Map();
|
|
486
|
+
synthesisState.value = null;
|
|
487
|
+
canFinalize.value = false;
|
|
488
|
+
detectedConfig.value = null;
|
|
489
|
+
processGroups.clearGroups();
|
|
490
|
+
lastSeq = 0;
|
|
491
|
+
activeAbort = new AbortController();
|
|
492
|
+
resetHeartbeat();
|
|
493
|
+
const userMsg = {
|
|
494
|
+
id: `pending-${Date.now()}`,
|
|
495
|
+
role: "user",
|
|
496
|
+
content,
|
|
497
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
498
|
+
model: null,
|
|
499
|
+
tokens_in: null,
|
|
500
|
+
tokens_out: null
|
|
501
|
+
};
|
|
502
|
+
messages.value = [...messages.value, userMsg];
|
|
503
|
+
try {
|
|
504
|
+
const req = { session_id: sessionId.value, content, mode: chatMode.value };
|
|
505
|
+
if (selectedBackend.value) req.backend_kind = selectedBackend.value;
|
|
506
|
+
if (selectedAccount.value) req.account_id = selectedAccount.value;
|
|
507
|
+
if (selectedModel.value) req.model = selectedModel.value;
|
|
508
|
+
for await (const event of client.sendChat(req, { signal: activeAbort.signal })) {
|
|
509
|
+
dispatch(event);
|
|
510
|
+
}
|
|
511
|
+
if (chatMode.value === "single" && streamingContent.value) {
|
|
512
|
+
const assistantMsg = {
|
|
513
|
+
id: `msg-${Date.now()}`,
|
|
514
|
+
role: "assistant",
|
|
515
|
+
content: streamingContent.value,
|
|
516
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
517
|
+
model: null,
|
|
518
|
+
tokens_in: null,
|
|
519
|
+
tokens_out: null
|
|
520
|
+
};
|
|
521
|
+
messages.value = [...messages.value, assistantMsg];
|
|
522
|
+
}
|
|
523
|
+
if (configParser) {
|
|
524
|
+
const lastMsg = messages.value[messages.value.length - 1];
|
|
525
|
+
if (lastMsg && lastMsg.role === "assistant") {
|
|
526
|
+
try {
|
|
527
|
+
const cfg = configParser(lastMsg.content);
|
|
528
|
+
if (cfg) {
|
|
529
|
+
detectedConfig.value = cfg;
|
|
530
|
+
canFinalize.value = true;
|
|
531
|
+
}
|
|
532
|
+
} catch (e) {
|
|
533
|
+
console.error("[useSmartChat] configParser threw \u2014 canFinalize stays false", e);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
} catch (e) {
|
|
538
|
+
if (e instanceof DOMException && e.name === "AbortError") {
|
|
539
|
+
} else if (e instanceof Error) {
|
|
540
|
+
error.value = `${e.name}: ${e.message}`;
|
|
541
|
+
} else {
|
|
542
|
+
error.value = `Chat failed: ${String(e)}`;
|
|
543
|
+
}
|
|
544
|
+
} finally {
|
|
545
|
+
clearHeartbeat();
|
|
546
|
+
activeAbort = null;
|
|
547
|
+
isStreaming.value = false;
|
|
548
|
+
streamingContent.value = "";
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function dispatch(event) {
|
|
552
|
+
if (typeof event._seq === "number") {
|
|
553
|
+
if (event._seq <= lastSeq) return;
|
|
554
|
+
if (lastSeq > 0 && event._seq > lastSeq + 1) {
|
|
555
|
+
console.warn(
|
|
556
|
+
"[useSmartChat] seq gap detected: lastSeq=%d incoming=%d (lost %d events)",
|
|
557
|
+
lastSeq,
|
|
558
|
+
event._seq,
|
|
559
|
+
event._seq - lastSeq - 1
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
lastSeq = event._seq;
|
|
563
|
+
}
|
|
564
|
+
resetHeartbeat();
|
|
565
|
+
if (event.kind === "done" || event.kind === "error" || event.kind === "synthesis_complete" || event.kind === "synthesis_error") {
|
|
566
|
+
clearHeartbeat();
|
|
567
|
+
}
|
|
568
|
+
switch (event.kind) {
|
|
569
|
+
// Single mode
|
|
570
|
+
case "token":
|
|
571
|
+
streamingContent.value += event.payload;
|
|
572
|
+
break;
|
|
573
|
+
case "done":
|
|
574
|
+
break;
|
|
575
|
+
case "error":
|
|
576
|
+
error.value = event.payload ?? "Unknown error";
|
|
577
|
+
break;
|
|
578
|
+
case "tool_call": {
|
|
579
|
+
const delta = { id: event.id };
|
|
580
|
+
if (event.name !== void 0) delta.name = event.name;
|
|
581
|
+
if (event.arguments !== void 0) delta.arguments = event.arguments;
|
|
582
|
+
if (event.group_type !== void 0) delta.group_type = event.group_type;
|
|
583
|
+
processGroups.processToolCallDelta(delta);
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
// All/compound mode — backend events
|
|
587
|
+
case "backend_delta": {
|
|
588
|
+
const existing = backendResponses.value.get(event.backend);
|
|
589
|
+
const updated = new Map(backendResponses.value);
|
|
590
|
+
updated.set(event.backend, {
|
|
591
|
+
backend: event.backend,
|
|
592
|
+
content: (existing?.content ?? "") + (event.text ?? ""),
|
|
593
|
+
status: "streaming"
|
|
594
|
+
});
|
|
595
|
+
backendResponses.value = updated;
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
case "backend_complete": {
|
|
599
|
+
const existing = backendResponses.value.get(event.backend);
|
|
600
|
+
if (existing) {
|
|
601
|
+
const updated = new Map(backendResponses.value);
|
|
602
|
+
updated.set(event.backend, { ...existing, status: "complete" });
|
|
603
|
+
backendResponses.value = updated;
|
|
604
|
+
}
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
case "backend_error": {
|
|
608
|
+
const existing = backendResponses.value.get(event.backend);
|
|
609
|
+
const updated = new Map(backendResponses.value);
|
|
610
|
+
updated.set(event.backend, {
|
|
611
|
+
backend: event.backend,
|
|
612
|
+
content: existing?.content ?? "",
|
|
613
|
+
status: "error",
|
|
614
|
+
error: event.error
|
|
615
|
+
});
|
|
616
|
+
backendResponses.value = updated;
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
case "backend_timeout": {
|
|
620
|
+
const existing = backendResponses.value.get(event.backend);
|
|
621
|
+
const updated = new Map(backendResponses.value);
|
|
622
|
+
updated.set(event.backend, {
|
|
623
|
+
backend: event.backend,
|
|
624
|
+
content: existing?.content ?? "",
|
|
625
|
+
status: "timeout"
|
|
626
|
+
});
|
|
627
|
+
backendResponses.value = updated;
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
// Compound synthesis
|
|
631
|
+
case "synthesis_start": {
|
|
632
|
+
synthesisState.value = {
|
|
633
|
+
status: "streaming",
|
|
634
|
+
content: "",
|
|
635
|
+
primaryBackend: event.primary_backend,
|
|
636
|
+
backendsCollected: event.backends_collected ?? []
|
|
637
|
+
};
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
case "synthesis_delta": {
|
|
641
|
+
if (synthesisState.value) {
|
|
642
|
+
synthesisState.value = {
|
|
643
|
+
...synthesisState.value,
|
|
644
|
+
content: synthesisState.value.content + (event.text ?? "")
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
case "synthesis_complete":
|
|
650
|
+
if (synthesisState.value) {
|
|
651
|
+
synthesisState.value = { ...synthesisState.value, status: "complete" };
|
|
652
|
+
}
|
|
653
|
+
break;
|
|
654
|
+
case "synthesis_error": {
|
|
655
|
+
if (synthesisState.value) {
|
|
656
|
+
synthesisState.value = { ...synthesisState.value, status: "error", error: event.error };
|
|
657
|
+
} else {
|
|
658
|
+
synthesisState.value = {
|
|
659
|
+
status: "error",
|
|
660
|
+
content: "",
|
|
661
|
+
primaryBackend: "",
|
|
662
|
+
backendsCollected: [],
|
|
663
|
+
error: event.error
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function setMode(mode) {
|
|
671
|
+
chatMode.value = mode;
|
|
672
|
+
}
|
|
673
|
+
function selectBackend(kind) {
|
|
674
|
+
selectedBackend.value = kind;
|
|
675
|
+
selectedAccount.value = null;
|
|
676
|
+
selectedModel.value = null;
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
sessionId,
|
|
680
|
+
messages,
|
|
681
|
+
isStreaming,
|
|
682
|
+
streamingContent,
|
|
683
|
+
error,
|
|
684
|
+
chatMode,
|
|
685
|
+
backendResponses,
|
|
686
|
+
synthesisState,
|
|
687
|
+
selectedBackend,
|
|
688
|
+
selectedAccount,
|
|
689
|
+
selectedModel,
|
|
690
|
+
createSession,
|
|
691
|
+
loadSession,
|
|
692
|
+
send,
|
|
693
|
+
setMode,
|
|
694
|
+
selectBackend,
|
|
695
|
+
processGroups,
|
|
696
|
+
canFinalize,
|
|
697
|
+
isFinalizing,
|
|
698
|
+
detectedConfig,
|
|
699
|
+
finalize,
|
|
700
|
+
setConfigParser
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// src/composables/useSmartScroll.ts
|
|
705
|
+
import { ref as ref8, onMounted, onUnmounted } from "vue";
|
|
706
|
+
var THRESHOLD = 32;
|
|
707
|
+
function useSmartScroll() {
|
|
708
|
+
const containerRef = ref8(null);
|
|
709
|
+
const isNearBottom = ref8(true);
|
|
710
|
+
const showScrollButton = ref8(false);
|
|
711
|
+
function check() {
|
|
712
|
+
const el = containerRef.value;
|
|
713
|
+
if (!el) return;
|
|
714
|
+
const near = el.scrollHeight - el.scrollTop - el.clientHeight < THRESHOLD;
|
|
715
|
+
isNearBottom.value = near;
|
|
716
|
+
showScrollButton.value = !near;
|
|
717
|
+
}
|
|
718
|
+
function scrollToBottom() {
|
|
719
|
+
containerRef.value?.scrollTo({ top: containerRef.value.scrollHeight, behavior: "smooth" });
|
|
720
|
+
}
|
|
721
|
+
let observer = null;
|
|
722
|
+
onMounted(() => {
|
|
723
|
+
const el = containerRef.value;
|
|
724
|
+
if (!el) return;
|
|
725
|
+
el.addEventListener("scroll", check, { passive: true });
|
|
726
|
+
observer = new MutationObserver(() => {
|
|
727
|
+
if (isNearBottom.value) scrollToBottom();
|
|
728
|
+
});
|
|
729
|
+
observer.observe(el, { childList: true, subtree: true });
|
|
730
|
+
});
|
|
731
|
+
onUnmounted(() => {
|
|
732
|
+
containerRef.value?.removeEventListener("scroll", check);
|
|
733
|
+
observer?.disconnect();
|
|
734
|
+
});
|
|
735
|
+
return { containerRef, isNearBottom, showScrollButton, scrollToBottom };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// src/composables/useStreamingParser.ts
|
|
739
|
+
var DEFAULT_MAX_PENDING = 1e6;
|
|
740
|
+
var smdResolutionWarned = false;
|
|
741
|
+
async function resolveSmd() {
|
|
742
|
+
const injected = globalThis.__smd;
|
|
743
|
+
if (injected) return injected;
|
|
744
|
+
try {
|
|
745
|
+
const mod = await import(
|
|
746
|
+
/* @vite-ignore */
|
|
747
|
+
"streaming-markdown"
|
|
748
|
+
);
|
|
749
|
+
return mod;
|
|
750
|
+
} catch (err) {
|
|
751
|
+
if (!smdResolutionWarned) {
|
|
752
|
+
smdResolutionWarned = true;
|
|
753
|
+
console.warn(
|
|
754
|
+
'[useStreamingParser] Could not load "streaming-markdown" peer dep; markdown rendering disabled. Install it or inject globalThis.__smd.',
|
|
755
|
+
err
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function useStreamingParser(options = {}) {
|
|
762
|
+
const maxPending = options.maxPendingChars ?? DEFAULT_MAX_PENDING;
|
|
763
|
+
let smd = null;
|
|
764
|
+
let parser = null;
|
|
765
|
+
let pending = [];
|
|
766
|
+
let pendingChars = 0;
|
|
767
|
+
let rafId = null;
|
|
768
|
+
let initToken = 0;
|
|
769
|
+
function flush() {
|
|
770
|
+
rafId = null;
|
|
771
|
+
if (!smd || !parser || pending.length === 0) return;
|
|
772
|
+
const text = pending.join("");
|
|
773
|
+
pending = [];
|
|
774
|
+
pendingChars = 0;
|
|
775
|
+
try {
|
|
776
|
+
smd.parser_write(parser, text);
|
|
777
|
+
} catch (err) {
|
|
778
|
+
console.error("[useStreamingParser] parser_write threw; chunk discarded", err);
|
|
779
|
+
}
|
|
780
|
+
options.onFlush?.();
|
|
781
|
+
}
|
|
782
|
+
function init(container) {
|
|
783
|
+
destroy();
|
|
784
|
+
container.textContent = "";
|
|
785
|
+
const token = ++initToken;
|
|
786
|
+
resolveSmd().then((resolved) => {
|
|
787
|
+
if (token !== initToken) return;
|
|
788
|
+
if (!resolved) return;
|
|
789
|
+
smd = resolved;
|
|
790
|
+
const renderer = resolved.default_renderer(container);
|
|
791
|
+
parser = resolved.parser(renderer);
|
|
792
|
+
if (pending.length > 0) {
|
|
793
|
+
const text = pending.join("");
|
|
794
|
+
pending = [];
|
|
795
|
+
pendingChars = 0;
|
|
796
|
+
try {
|
|
797
|
+
resolved.parser_write(parser, text);
|
|
798
|
+
} catch (err) {
|
|
799
|
+
console.error("[useStreamingParser] initial parser_write threw; buffered chunk discarded", err);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}).catch((err) => {
|
|
803
|
+
console.error("[useStreamingParser] resolveSmd unexpectedly rejected", err);
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
function write(text) {
|
|
807
|
+
if (initToken === 0) return;
|
|
808
|
+
if (pendingChars + text.length > maxPending) {
|
|
809
|
+
console.warn(
|
|
810
|
+
"[useStreamingParser] dropping write \u2014 pending buffer exceeded maxPendingChars (%d). smd may never have resolved.",
|
|
811
|
+
maxPending
|
|
812
|
+
);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
pending.push(text);
|
|
816
|
+
pendingChars += text.length;
|
|
817
|
+
if (rafId === null && typeof requestAnimationFrame !== "undefined") {
|
|
818
|
+
rafId = requestAnimationFrame(flush);
|
|
819
|
+
} else if (rafId === null) {
|
|
820
|
+
queueMicrotask(flush);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
function finalize() {
|
|
824
|
+
if (rafId !== null) {
|
|
825
|
+
if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(rafId);
|
|
826
|
+
rafId = null;
|
|
827
|
+
}
|
|
828
|
+
if (pending.length > 0) flush();
|
|
829
|
+
if (smd && parser) {
|
|
830
|
+
try {
|
|
831
|
+
smd.parser_end(parser);
|
|
832
|
+
} catch (err) {
|
|
833
|
+
console.error("[useStreamingParser] parser_end threw", err);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
parser = null;
|
|
837
|
+
smd = null;
|
|
838
|
+
}
|
|
839
|
+
function destroy() {
|
|
840
|
+
finalize();
|
|
841
|
+
}
|
|
842
|
+
return { init, write, finalize, destroy };
|
|
843
|
+
}
|
|
844
|
+
|
|
229
845
|
// src/index.ts
|
|
230
846
|
var version = "0.3.0-alpha.1";
|
|
231
847
|
export {
|
|
@@ -234,7 +850,12 @@ export {
|
|
|
234
850
|
useAccountWizard,
|
|
235
851
|
useAiAccounts,
|
|
236
852
|
useBackendRegistry,
|
|
853
|
+
useConversation,
|
|
237
854
|
useLoginSession,
|
|
238
855
|
useOnboarding,
|
|
856
|
+
useProcessGroups,
|
|
857
|
+
useSmartChat,
|
|
858
|
+
useSmartScroll,
|
|
859
|
+
useStreamingParser,
|
|
239
860
|
version
|
|
240
861
|
};
|