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