@dudousxd/nestjs-agent-react 0.5.4 → 0.6.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/README.md +296 -8
- package/dist/index.cjs +2189 -758
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +980 -224
- package/dist/index.d.ts +980 -224
- package/dist/index.js +2175 -758
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
4
4
|
// src/agent-chat-transport.ts
|
|
5
5
|
var HEADER_RUN_ID = "x-agent-run-id";
|
|
6
6
|
var HEADER_THREAD_ID = "x-agent-thread-id";
|
|
7
|
+
var ASK_TOOL_NAME = "ask";
|
|
7
8
|
var AgentChatTransport = class {
|
|
8
9
|
static {
|
|
9
10
|
__name(this, "AgentChatTransport");
|
|
@@ -11,6 +12,14 @@ var AgentChatTransport = class {
|
|
|
11
12
|
options;
|
|
12
13
|
currentRunId;
|
|
13
14
|
currentThreadId;
|
|
15
|
+
/**
|
|
16
|
+
* One attempt at a time. The AI SDK keeps a single in-flight response per chat and decides
|
|
17
|
+
* whether its streamed message REPLACES the list's last entry or is appended by comparing that
|
|
18
|
+
* response's message id against the last entry alone. Two attempts writing into one chat
|
|
19
|
+
* therefore append alternating copies of each other's message, and the list ends up holding
|
|
20
|
+
* several entries under each id — which React renders as duplicate keys.
|
|
21
|
+
*/
|
|
22
|
+
attemptLive = false;
|
|
14
23
|
constructor(options = {}) {
|
|
15
24
|
this.options = options;
|
|
16
25
|
}
|
|
@@ -22,59 +31,84 @@ var AgentChatTransport = class {
|
|
|
22
31
|
get threadId() {
|
|
23
32
|
return this.currentThreadId;
|
|
24
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* True from the moment a send or resume attempt starts until its chunk stream terminates. A
|
|
36
|
+
* caller that can refuse a turn BEFORE the SDK commits to one — `useAgentChat` wrapping
|
|
37
|
+
* `sendMessage` — reads this; the transport itself can only refuse a resume, which is the one
|
|
38
|
+
* attempt the SDK lets a transport decline.
|
|
39
|
+
*/
|
|
40
|
+
get isAttemptLive() {
|
|
41
|
+
return this.attemptLive;
|
|
42
|
+
}
|
|
25
43
|
async sendMessages(options) {
|
|
26
44
|
this.options.onAttemptStart?.();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
this.attemptLive = true;
|
|
46
|
+
try {
|
|
47
|
+
const lastMessage = options.messages.at(-1);
|
|
48
|
+
const message = lastMessage ? extractText(lastMessage) : "";
|
|
49
|
+
const body = {
|
|
50
|
+
...this.options.agent !== void 0 ? {
|
|
51
|
+
agent: this.options.agent
|
|
52
|
+
} : {},
|
|
53
|
+
...this.options.getBody?.() ?? {},
|
|
54
|
+
...options.body ?? {},
|
|
55
|
+
message
|
|
56
|
+
};
|
|
57
|
+
const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: {
|
|
60
|
+
"content-type": "application/json",
|
|
61
|
+
accept: "text/event-stream",
|
|
62
|
+
...await this.resolveHeaders(options.headers)
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(body),
|
|
65
|
+
...this.options.credentials !== void 0 ? {
|
|
66
|
+
credentials: this.options.credentials
|
|
67
|
+
} : {},
|
|
68
|
+
...options.abortSignal ? {
|
|
69
|
+
signal: options.abortSignal
|
|
70
|
+
} : {}
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok || !response.body) {
|
|
73
|
+
throw new Error(`Agent chat request failed: ${response.status} ${response.statusText}`);
|
|
74
|
+
}
|
|
75
|
+
this.captureHeaderMeta(response.headers);
|
|
76
|
+
return this.toChunkStream(response.body);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.attemptLive = false;
|
|
79
|
+
throw error;
|
|
54
80
|
}
|
|
55
|
-
this.captureHeaderMeta(response.headers);
|
|
56
|
-
return this.toChunkStream(response.body);
|
|
57
81
|
}
|
|
58
82
|
async reconnectToStream(options) {
|
|
83
|
+
if (this.attemptLive) return null;
|
|
59
84
|
this.options.onAttemptStart?.();
|
|
60
85
|
const runId = this.options.getResumeRunId?.();
|
|
61
86
|
if (runId === void 0) return null;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
this.attemptLive = true;
|
|
88
|
+
try {
|
|
89
|
+
const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat/${encodeURIComponent(runId)}/stream`, {
|
|
90
|
+
method: "GET",
|
|
91
|
+
headers: {
|
|
92
|
+
accept: "text/event-stream",
|
|
93
|
+
...await this.resolveHeaders(options.headers)
|
|
94
|
+
},
|
|
95
|
+
...this.options.credentials !== void 0 ? {
|
|
96
|
+
credentials: this.options.credentials
|
|
97
|
+
} : {}
|
|
98
|
+
});
|
|
99
|
+
if (response.status === 404) {
|
|
100
|
+
this.attemptLive = false;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (!response.ok || !response.body) {
|
|
104
|
+
throw new Error(`Agent stream reconnect failed: ${response.status} ${response.statusText}`);
|
|
105
|
+
}
|
|
106
|
+
this.captureHeaderMeta(response.headers);
|
|
107
|
+
return this.toChunkStream(response.body);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.attemptLive = false;
|
|
110
|
+
throw error;
|
|
75
111
|
}
|
|
76
|
-
this.captureHeaderMeta(response.headers);
|
|
77
|
-
return this.toChunkStream(response.body);
|
|
78
112
|
}
|
|
79
113
|
fetchImpl() {
|
|
80
114
|
return this.options.fetch ?? globalThis.fetch;
|
|
@@ -123,7 +157,11 @@ var AgentChatTransport = class {
|
|
|
123
157
|
let stepIndex = 0;
|
|
124
158
|
let textId = null;
|
|
125
159
|
let reasoningId = null;
|
|
160
|
+
const announced = /* @__PURE__ */ new Set();
|
|
126
161
|
const record = /* @__PURE__ */ __name((meta) => this.recordMeta(meta), "record");
|
|
162
|
+
const endAttempt = /* @__PURE__ */ __name(() => {
|
|
163
|
+
this.attemptLive = false;
|
|
164
|
+
}, "endAttempt");
|
|
127
165
|
return new ReadableStream({
|
|
128
166
|
async pull(controller) {
|
|
129
167
|
function ensureStarted() {
|
|
@@ -220,6 +258,7 @@ var AgentChatTransport = class {
|
|
|
220
258
|
break;
|
|
221
259
|
case "tool-input-start":
|
|
222
260
|
ensureStep();
|
|
261
|
+
announced.add(event.id);
|
|
223
262
|
controller.enqueue({
|
|
224
263
|
type: "tool-input-start",
|
|
225
264
|
toolCallId: event.id,
|
|
@@ -244,6 +283,7 @@ var AgentChatTransport = class {
|
|
|
244
283
|
break;
|
|
245
284
|
case "tool-input-available":
|
|
246
285
|
ensureStep();
|
|
286
|
+
announced.add(event.id);
|
|
247
287
|
controller.enqueue({
|
|
248
288
|
type: "tool-input-available",
|
|
249
289
|
toolCallId: event.id,
|
|
@@ -256,6 +296,23 @@ var AgentChatTransport = class {
|
|
|
256
296
|
} : {}
|
|
257
297
|
});
|
|
258
298
|
break;
|
|
299
|
+
case "elicitation":
|
|
300
|
+
ensureStep();
|
|
301
|
+
if (!announced.has(event.id)) {
|
|
302
|
+
announced.add(event.id);
|
|
303
|
+
controller.enqueue({
|
|
304
|
+
type: "tool-input-available",
|
|
305
|
+
toolCallId: event.id,
|
|
306
|
+
toolName: ASK_TOOL_NAME,
|
|
307
|
+
input: {
|
|
308
|
+
...event.request.preamble !== void 0 ? {
|
|
309
|
+
preamble: event.request.preamble
|
|
310
|
+
} : {},
|
|
311
|
+
questions: event.request.questions
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
259
316
|
case "tool-output":
|
|
260
317
|
ensureStep();
|
|
261
318
|
controller.enqueue({
|
|
@@ -284,6 +341,7 @@ var AgentChatTransport = class {
|
|
|
284
341
|
type: "finish"
|
|
285
342
|
});
|
|
286
343
|
controller.close();
|
|
344
|
+
endAttempt();
|
|
287
345
|
}
|
|
288
346
|
__name(finish, "finish");
|
|
289
347
|
try {
|
|
@@ -312,6 +370,7 @@ var AgentChatTransport = class {
|
|
|
312
370
|
errorText: parseErrorText(frame.data)
|
|
313
371
|
});
|
|
314
372
|
controller.close();
|
|
373
|
+
endAttempt();
|
|
315
374
|
return;
|
|
316
375
|
}
|
|
317
376
|
if (frame.event === "meta") {
|
|
@@ -330,10 +389,12 @@ var AgentChatTransport = class {
|
|
|
330
389
|
errorText: error instanceof Error ? error.message : "Agent stream error"
|
|
331
390
|
});
|
|
332
391
|
controller.close();
|
|
392
|
+
endAttempt();
|
|
333
393
|
}
|
|
334
394
|
},
|
|
335
395
|
cancel() {
|
|
336
396
|
void reader.cancel();
|
|
397
|
+
endAttempt();
|
|
337
398
|
}
|
|
338
399
|
});
|
|
339
400
|
}
|
|
@@ -401,6 +462,53 @@ function extractText(message) {
|
|
|
401
462
|
}
|
|
402
463
|
__name(extractText, "extractText");
|
|
403
464
|
|
|
465
|
+
// src/background-runs.ts
|
|
466
|
+
function asReceipt(output) {
|
|
467
|
+
if (typeof output !== "object" || output === null) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
const candidate = output;
|
|
471
|
+
if (candidate.detached !== true || candidate.status !== "started" || typeof candidate.agent !== "string" || typeof candidate.runId !== "string") {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
detached: true,
|
|
476
|
+
status: "started",
|
|
477
|
+
agent: candidate.agent,
|
|
478
|
+
runId: candidate.runId
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
__name(asReceipt, "asReceipt");
|
|
482
|
+
function backgroundRunsFromThread(messages) {
|
|
483
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
484
|
+
for (const message of messages) {
|
|
485
|
+
for (const result of message.toolResults ?? []) {
|
|
486
|
+
const receipt = asReceipt(result.output);
|
|
487
|
+
if (receipt === null || byRun.has(receipt.runId)) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
byRun.set(receipt.runId, {
|
|
491
|
+
runId: receipt.runId,
|
|
492
|
+
agent: receipt.agent,
|
|
493
|
+
toolCallId: result.id,
|
|
494
|
+
toolName: result.name,
|
|
495
|
+
status: "running"
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
for (const message of messages) {
|
|
500
|
+
const run = message.runId === void 0 ? void 0 : byRun.get(message.runId);
|
|
501
|
+
if (run !== void 0) {
|
|
502
|
+
run.status = "delivered";
|
|
503
|
+
run.message = message;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return [
|
|
507
|
+
...byRun.values()
|
|
508
|
+
];
|
|
509
|
+
}
|
|
510
|
+
__name(backgroundRunsFromThread, "backgroundRunsFromThread");
|
|
511
|
+
|
|
404
512
|
// src/client.ts
|
|
405
513
|
var AgentHttpError = class extends Error {
|
|
406
514
|
static {
|
|
@@ -425,6 +533,16 @@ var AgentClient = class {
|
|
|
425
533
|
listThreads() {
|
|
426
534
|
return this.request("GET", "/agent/threads");
|
|
427
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* The skills this caller can invoke right now, scope-resolved — the same list, built by the same
|
|
538
|
+
* call, that the model is offered, so what a user can type after a `/` and what the agent can
|
|
539
|
+
* reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to
|
|
540
|
+
* one conversation; omitted, the server reads it as a brand-new thread.
|
|
541
|
+
*/
|
|
542
|
+
listSkills(threadId) {
|
|
543
|
+
const query = threadId === void 0 ? "" : `?threadId=${encodeURIComponent(threadId)}`;
|
|
544
|
+
return this.request("GET", `/agent/skills${query}`);
|
|
545
|
+
}
|
|
428
546
|
getThread(id) {
|
|
429
547
|
return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
|
|
430
548
|
}
|
|
@@ -486,6 +604,23 @@ var AgentClient = class {
|
|
|
486
604
|
rejectToolCall(input) {
|
|
487
605
|
return this.request("POST", "/agent/tool-call/reject", input);
|
|
488
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* Settle a parked question set. `answers` is questionId → chosen option values; a question left
|
|
609
|
+
* out takes the pre-picked default the request carried, resolved server-side against the request
|
|
610
|
+
* the run already holds. Omit the whole object and the user has confirmed every pre-picked
|
|
611
|
+
* answer — which is the point of the surface, so it is a valid submission rather than a blank.
|
|
612
|
+
*/
|
|
613
|
+
answerToolCall(input) {
|
|
614
|
+
return this.request("POST", "/agent/tool-call/answer", input);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same
|
|
618
|
+
* values a confirmation would, and persists differently on purpose — only one of them is
|
|
619
|
+
* evidence the user chose them.
|
|
620
|
+
*/
|
|
621
|
+
skipToolCall(input) {
|
|
622
|
+
return this.request("POST", "/agent/tool-call/skip", input);
|
|
623
|
+
}
|
|
489
624
|
async request(method, path, body) {
|
|
490
625
|
const fetchImpl = this.options.fetch ?? globalThis.fetch;
|
|
491
626
|
const baseUrl = (this.options.baseUrl ?? "").replace(/\/$/, "");
|
|
@@ -520,414 +655,76 @@ var AgentClient = class {
|
|
|
520
655
|
}
|
|
521
656
|
};
|
|
522
657
|
|
|
523
|
-
// src/
|
|
524
|
-
|
|
525
|
-
const parts = [];
|
|
526
|
-
if (message.content) {
|
|
527
|
-
parts.push({
|
|
528
|
-
type: "text",
|
|
529
|
-
text: message.content
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
for (const attachment of message.attachments ?? []) {
|
|
533
|
-
parts.push({
|
|
534
|
-
type: "file",
|
|
535
|
-
mediaType: attachment.contentType,
|
|
536
|
-
filename: attachment.name,
|
|
537
|
-
url: attachment.url
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
for (const call of message.toolCalls ?? []) {
|
|
541
|
-
const result = message.toolResults?.find((candidate) => candidate.id === call.id);
|
|
542
|
-
const toolKind = call.kind;
|
|
543
|
-
parts.push({
|
|
544
|
-
type: `tool-${call.name}`,
|
|
545
|
-
toolCallId: call.id,
|
|
546
|
-
...toolKind !== void 0 ? {
|
|
547
|
-
toolMetadata: {
|
|
548
|
-
toolKind
|
|
549
|
-
}
|
|
550
|
-
} : {},
|
|
551
|
-
...result !== void 0 ? {
|
|
552
|
-
state: "output-available",
|
|
553
|
-
input: call.input,
|
|
554
|
-
output: result.output
|
|
555
|
-
} : {
|
|
556
|
-
state: "input-available",
|
|
557
|
-
input: call.input
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
return {
|
|
562
|
-
id: message.id,
|
|
563
|
-
role: message.role,
|
|
564
|
-
parts
|
|
565
|
-
};
|
|
566
|
-
}
|
|
567
|
-
__name(storedMessageToUiMessage, "storedMessageToUiMessage");
|
|
568
|
-
|
|
569
|
-
// src/stored-thread-to-ui-messages.ts
|
|
570
|
-
function storedThreadToUiMessages(messages) {
|
|
571
|
-
const turns = [];
|
|
572
|
-
for (const message of messages) {
|
|
573
|
-
const currentTurn = turns.at(-1);
|
|
574
|
-
const previousRow = currentTurn?.at(-1);
|
|
575
|
-
if (message.role === "assistant" && previousRow?.role === "assistant" && currentTurn) {
|
|
576
|
-
currentTurn.push(message);
|
|
577
|
-
} else {
|
|
578
|
-
turns.push([
|
|
579
|
-
message
|
|
580
|
-
]);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
return turns.map(mergeTurn);
|
|
584
|
-
}
|
|
585
|
-
__name(storedThreadToUiMessages, "storedThreadToUiMessages");
|
|
586
|
-
function mergeTurn(rows) {
|
|
587
|
-
const lastRow = rows.at(-1);
|
|
588
|
-
if (lastRow === void 0) {
|
|
589
|
-
throw new Error("storedThreadToUiMessages: encountered an empty turn");
|
|
590
|
-
}
|
|
591
|
-
if (rows.length === 1) {
|
|
592
|
-
return storedMessageToUiMessage(lastRow);
|
|
593
|
-
}
|
|
594
|
-
const parts = rows.flatMap((row) => storedMessageToUiMessage(row).parts);
|
|
595
|
-
const usage = sumUsage(rows);
|
|
596
|
-
return {
|
|
597
|
-
id: lastRow.id,
|
|
598
|
-
role: lastRow.role,
|
|
599
|
-
parts,
|
|
600
|
-
...usage !== void 0 ? {
|
|
601
|
-
metadata: {
|
|
602
|
-
usage
|
|
603
|
-
}
|
|
604
|
-
} : {}
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
__name(mergeTurn, "mergeTurn");
|
|
608
|
-
function sumUsage(rows) {
|
|
609
|
-
const usages = rows.map((row) => row.usage).filter((usage) => usage !== void 0);
|
|
610
|
-
if (usages.length === 0) {
|
|
611
|
-
return void 0;
|
|
612
|
-
}
|
|
613
|
-
const allCostsUnknown = usages.every((usage) => usage.costUsd === null || usage.costUsd === void 0);
|
|
614
|
-
return {
|
|
615
|
-
inputTokens: usages.reduce((sum, usage) => sum + usage.inputTokens, 0),
|
|
616
|
-
outputTokens: usages.reduce((sum, usage) => sum + usage.outputTokens, 0),
|
|
617
|
-
costUsd: allCostsUnknown ? null : usages.reduce((sum, usage) => sum + (usage.costUsd ?? 0), 0)
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
__name(sumUsage, "sumUsage");
|
|
658
|
+
// src/components/chat-input.tsx
|
|
659
|
+
import React2, { useEffect as useEffect2, useState as useState2 } from "react";
|
|
621
660
|
|
|
622
|
-
// src/use-
|
|
623
|
-
import { useChat } from "@ai-sdk/react";
|
|
661
|
+
// src/components/use-speech-recognition.ts
|
|
624
662
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
625
|
-
function
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const
|
|
633
|
-
const [
|
|
634
|
-
const
|
|
635
|
-
const
|
|
636
|
-
const
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
...options.baseUrl !== void 0 ? {
|
|
641
|
-
baseUrl: options.baseUrl
|
|
642
|
-
} : {},
|
|
643
|
-
...options.credentials !== void 0 ? {
|
|
644
|
-
credentials: options.credentials
|
|
645
|
-
} : {},
|
|
646
|
-
...options.fetch !== void 0 ? {
|
|
647
|
-
fetch: options.fetch
|
|
648
|
-
} : {},
|
|
649
|
-
getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
|
|
650
|
-
});
|
|
651
|
-
}, []);
|
|
663
|
+
function getRecognitionCtor() {
|
|
664
|
+
if (typeof window === "undefined") return null;
|
|
665
|
+
const scope = window;
|
|
666
|
+
return scope.SpeechRecognition ?? scope.webkitSpeechRecognition ?? null;
|
|
667
|
+
}
|
|
668
|
+
__name(getRecognitionCtor, "getRecognitionCtor");
|
|
669
|
+
function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTranscript }) {
|
|
670
|
+
const recognitionRef = useRef(null);
|
|
671
|
+
const [isListening, setIsListening] = useState(false);
|
|
672
|
+
const ctor = useMemo(() => getRecognitionCtor(), []);
|
|
673
|
+
const isSupported = ctor !== null;
|
|
674
|
+
const finalRef = useRef(onFinalTranscript);
|
|
675
|
+
const interimRef = useRef(onInterimTranscript);
|
|
676
|
+
finalRef.current = onFinalTranscript;
|
|
677
|
+
interimRef.current = onInterimTranscript;
|
|
652
678
|
useEffect(() => {
|
|
653
|
-
if (!
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
679
|
+
if (!ctor) return;
|
|
680
|
+
const recognition = new ctor();
|
|
681
|
+
recognition.lang = lang;
|
|
682
|
+
recognition.continuous = false;
|
|
683
|
+
recognition.interimResults = true;
|
|
684
|
+
recognition.onresult = (event) => {
|
|
685
|
+
let interim = "";
|
|
686
|
+
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
687
|
+
const result = event.results[i];
|
|
688
|
+
if (!result) continue;
|
|
689
|
+
const text = result[0]?.transcript ?? "";
|
|
690
|
+
if (result.isFinal) {
|
|
691
|
+
finalRef.current(text);
|
|
692
|
+
} else {
|
|
693
|
+
interim += text;
|
|
694
|
+
}
|
|
662
695
|
}
|
|
663
|
-
|
|
696
|
+
if (interim && interimRef.current) interimRef.current(interim);
|
|
697
|
+
};
|
|
698
|
+
recognition.onerror = (event) => {
|
|
699
|
+
if (event.error !== "no-speech" && event.error !== "aborted") {
|
|
700
|
+
console.warn("SpeechRecognition error", event.error);
|
|
701
|
+
}
|
|
702
|
+
setIsListening(false);
|
|
703
|
+
};
|
|
704
|
+
recognition.onend = () => setIsListening(false);
|
|
705
|
+
recognitionRef.current = recognition;
|
|
664
706
|
return () => {
|
|
665
|
-
|
|
707
|
+
try {
|
|
708
|
+
recognition.stop();
|
|
709
|
+
} catch {
|
|
710
|
+
}
|
|
711
|
+
recognitionRef.current = null;
|
|
666
712
|
};
|
|
667
713
|
}, [
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
options.threadId
|
|
714
|
+
ctor,
|
|
715
|
+
lang
|
|
671
716
|
]);
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
__name(onMeta, "onMeta");
|
|
682
|
-
return new AgentChatTransport({
|
|
683
|
-
...options.baseUrl !== void 0 ? {
|
|
684
|
-
baseUrl: options.baseUrl
|
|
685
|
-
} : {},
|
|
686
|
-
...options.credentials !== void 0 ? {
|
|
687
|
-
credentials: options.credentials
|
|
688
|
-
} : {},
|
|
689
|
-
...options.fetch !== void 0 ? {
|
|
690
|
-
fetch: options.fetch
|
|
691
|
-
} : {},
|
|
692
|
-
...options.agent !== void 0 ? {
|
|
693
|
-
agent: options.agent
|
|
694
|
-
} : {},
|
|
695
|
-
getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders"),
|
|
696
|
-
getBody: /* @__PURE__ */ __name(() => {
|
|
697
|
-
const current = latest.current;
|
|
698
|
-
const pageContext = current.getPageContext?.() ?? null;
|
|
699
|
-
const regenerate2 = regenerateNext.current;
|
|
700
|
-
regenerateNext.current = false;
|
|
701
|
-
const threadId = current.threadId ?? createdThreadId.current;
|
|
702
|
-
return {
|
|
703
|
-
...threadId !== void 0 ? {
|
|
704
|
-
threadId
|
|
705
|
-
} : {},
|
|
706
|
-
...pageContext ? {
|
|
707
|
-
pageContext
|
|
708
|
-
} : {},
|
|
709
|
-
...regenerate2 ? {
|
|
710
|
-
regenerate: true
|
|
711
|
-
} : {}
|
|
712
|
-
};
|
|
713
|
-
}, "getBody"),
|
|
714
|
-
getResumeRunId: /* @__PURE__ */ __name(() => latest.current.resumeRunId ?? autoResumeRunIdRef.current, "getResumeRunId"),
|
|
715
|
-
onMeta,
|
|
716
|
-
onAttemptStart: /* @__PURE__ */ __name(() => {
|
|
717
|
-
settlingRunIdRef.current = void 0;
|
|
718
|
-
}, "onAttemptStart")
|
|
719
|
-
});
|
|
720
|
-
}, []);
|
|
721
|
-
const chat = useChat({
|
|
722
|
-
transport,
|
|
723
|
-
resume: options.resumeRunId !== void 0 || autoResume,
|
|
724
|
-
...options.threadId !== void 0 ? {
|
|
725
|
-
id: options.threadId
|
|
726
|
-
} : {},
|
|
727
|
-
...options.initialMessages !== void 0 ? {
|
|
728
|
-
messages: options.initialMessages
|
|
729
|
-
} : {},
|
|
730
|
-
onFinish: /* @__PURE__ */ __name(({ isError }) => {
|
|
731
|
-
latest.current.onFinish?.();
|
|
732
|
-
const settledRunId = settlingRunIdRef.current;
|
|
733
|
-
if (settledRunId !== void 0) {
|
|
734
|
-
latest.current.onRunSettled?.({
|
|
735
|
-
runId: settledRunId,
|
|
736
|
-
status: isError ? "failed" : "completed"
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
}, "onFinish")
|
|
740
|
-
});
|
|
741
|
-
const chatRef = useRef(chat);
|
|
742
|
-
chatRef.current = chat;
|
|
743
|
-
const addToolResult = useCallback(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
|
|
744
|
-
tool,
|
|
745
|
-
toolCallId,
|
|
746
|
-
output
|
|
747
|
-
}), []);
|
|
748
|
-
const [threads, setThreads] = useState([]);
|
|
749
|
-
const [quota, setQuota] = useState(null);
|
|
750
|
-
const loadThreads = useCallback(async () => {
|
|
751
|
-
const list = await client.listThreads();
|
|
752
|
-
setThreads(list);
|
|
753
|
-
return list;
|
|
754
|
-
}, [
|
|
755
|
-
client
|
|
756
|
-
]);
|
|
757
|
-
const loadThread = useCallback((id) => client.getThread(id), [
|
|
758
|
-
client
|
|
759
|
-
]);
|
|
760
|
-
const deleteThread = useCallback(async (id) => {
|
|
761
|
-
await client.deleteThread(id);
|
|
762
|
-
setThreads((current) => current.filter((thread) => thread.id !== id));
|
|
763
|
-
}, [
|
|
764
|
-
client
|
|
765
|
-
]);
|
|
766
|
-
const forkThread = useCallback((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
|
|
767
|
-
client
|
|
768
|
-
]);
|
|
769
|
-
const renameThread = useCallback(async (id, title) => {
|
|
770
|
-
await client.renameThread(id, title);
|
|
771
|
-
setThreads((current) => current.map((thread) => thread.id === id ? {
|
|
772
|
-
...thread,
|
|
773
|
-
title
|
|
774
|
-
} : thread));
|
|
775
|
-
}, [
|
|
776
|
-
client
|
|
777
|
-
]);
|
|
778
|
-
const promoteThread = useCallback(async (id) => {
|
|
779
|
-
await client.promoteThread(id);
|
|
780
|
-
await loadThreads();
|
|
781
|
-
}, [
|
|
782
|
-
client,
|
|
783
|
-
loadThreads
|
|
784
|
-
]);
|
|
785
|
-
const truncateFromMessage = useCallback((threadId, messageId) => client.truncateFromMessage(threadId, messageId).then(() => void 0), [
|
|
786
|
-
client
|
|
787
|
-
]);
|
|
788
|
-
const loadQuota = useCallback(async () => {
|
|
789
|
-
const today = await client.getQuotaToday();
|
|
790
|
-
setQuota(today);
|
|
791
|
-
return today;
|
|
792
|
-
}, [
|
|
793
|
-
client
|
|
794
|
-
]);
|
|
795
|
-
const cancel = useCallback(async () => {
|
|
796
|
-
chatRef.current.stop();
|
|
797
|
-
const activeRunId2 = runIdRef.current;
|
|
798
|
-
if (activeRunId2) {
|
|
799
|
-
try {
|
|
800
|
-
await client.cancelStream(activeRunId2);
|
|
801
|
-
} catch {
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
}, [
|
|
805
|
-
client
|
|
806
|
-
]);
|
|
807
|
-
const approve = useCallback(async ({ toolCallId }) => {
|
|
808
|
-
await client.approveToolCall({
|
|
809
|
-
toolCallId
|
|
810
|
-
});
|
|
811
|
-
}, [
|
|
812
|
-
client
|
|
813
|
-
]);
|
|
814
|
-
const reject = useCallback(async ({ toolCallId, reason }) => {
|
|
815
|
-
await client.rejectToolCall({
|
|
816
|
-
toolCallId,
|
|
817
|
-
...reason !== void 0 ? {
|
|
818
|
-
reason
|
|
819
|
-
} : {}
|
|
820
|
-
});
|
|
821
|
-
}, [
|
|
822
|
-
client
|
|
823
|
-
]);
|
|
824
|
-
const regenerate = useCallback(() => {
|
|
825
|
-
regenerateNext.current = true;
|
|
826
|
-
void chatRef.current.regenerate();
|
|
827
|
-
}, []);
|
|
828
|
-
return {
|
|
829
|
-
...chat,
|
|
830
|
-
addToolResult,
|
|
831
|
-
runId,
|
|
832
|
-
/** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
|
|
833
|
-
activeRunId,
|
|
834
|
-
client,
|
|
835
|
-
threads,
|
|
836
|
-
loadThreads,
|
|
837
|
-
loadThread,
|
|
838
|
-
deleteThread,
|
|
839
|
-
forkThread,
|
|
840
|
-
renameThread,
|
|
841
|
-
promoteThread,
|
|
842
|
-
truncateFromMessage,
|
|
843
|
-
quota,
|
|
844
|
-
loadQuota,
|
|
845
|
-
cancel,
|
|
846
|
-
approve,
|
|
847
|
-
reject,
|
|
848
|
-
regenerate
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
__name(useAgentChat, "useAgentChat");
|
|
852
|
-
async function mergeHeaders(options) {
|
|
853
|
-
const dynamic = await options.getHeaders?.() ?? {};
|
|
854
|
-
return {
|
|
855
|
-
...options.headers,
|
|
856
|
-
...dynamic
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
__name(mergeHeaders, "mergeHeaders");
|
|
860
|
-
|
|
861
|
-
// src/components/chat-input.tsx
|
|
862
|
-
import React, { useEffect as useEffect3, useState as useState3 } from "react";
|
|
863
|
-
|
|
864
|
-
// src/components/use-speech-recognition.ts
|
|
865
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
866
|
-
function getRecognitionCtor() {
|
|
867
|
-
if (typeof window === "undefined") return null;
|
|
868
|
-
const scope = window;
|
|
869
|
-
return scope.SpeechRecognition ?? scope.webkitSpeechRecognition ?? null;
|
|
870
|
-
}
|
|
871
|
-
__name(getRecognitionCtor, "getRecognitionCtor");
|
|
872
|
-
function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTranscript }) {
|
|
873
|
-
const recognitionRef = useRef2(null);
|
|
874
|
-
const [isListening, setIsListening] = useState2(false);
|
|
875
|
-
const ctor = useMemo2(() => getRecognitionCtor(), []);
|
|
876
|
-
const isSupported = ctor !== null;
|
|
877
|
-
const finalRef = useRef2(onFinalTranscript);
|
|
878
|
-
const interimRef = useRef2(onInterimTranscript);
|
|
879
|
-
finalRef.current = onFinalTranscript;
|
|
880
|
-
interimRef.current = onInterimTranscript;
|
|
881
|
-
useEffect2(() => {
|
|
882
|
-
if (!ctor) return;
|
|
883
|
-
const recognition = new ctor();
|
|
884
|
-
recognition.lang = lang;
|
|
885
|
-
recognition.continuous = false;
|
|
886
|
-
recognition.interimResults = true;
|
|
887
|
-
recognition.onresult = (event) => {
|
|
888
|
-
let interim = "";
|
|
889
|
-
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
890
|
-
const result = event.results[i];
|
|
891
|
-
if (!result) continue;
|
|
892
|
-
const text = result[0]?.transcript ?? "";
|
|
893
|
-
if (result.isFinal) {
|
|
894
|
-
finalRef.current(text);
|
|
895
|
-
} else {
|
|
896
|
-
interim += text;
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
if (interim && interimRef.current) interimRef.current(interim);
|
|
900
|
-
};
|
|
901
|
-
recognition.onerror = (event) => {
|
|
902
|
-
if (event.error !== "no-speech" && event.error !== "aborted") {
|
|
903
|
-
console.warn("SpeechRecognition error", event.error);
|
|
904
|
-
}
|
|
905
|
-
setIsListening(false);
|
|
906
|
-
};
|
|
907
|
-
recognition.onend = () => setIsListening(false);
|
|
908
|
-
recognitionRef.current = recognition;
|
|
909
|
-
return () => {
|
|
910
|
-
try {
|
|
911
|
-
recognition.stop();
|
|
912
|
-
} catch {
|
|
913
|
-
}
|
|
914
|
-
recognitionRef.current = null;
|
|
915
|
-
};
|
|
916
|
-
}, [
|
|
917
|
-
ctor,
|
|
918
|
-
lang
|
|
919
|
-
]);
|
|
920
|
-
const start = useCallback2(() => {
|
|
921
|
-
const recognition = recognitionRef.current;
|
|
922
|
-
if (!recognition) return;
|
|
923
|
-
try {
|
|
924
|
-
recognition.start();
|
|
925
|
-
setIsListening(true);
|
|
926
|
-
} catch (error) {
|
|
927
|
-
console.warn("SpeechRecognition start failed", error);
|
|
717
|
+
const start = useCallback(() => {
|
|
718
|
+
const recognition = recognitionRef.current;
|
|
719
|
+
if (!recognition) return;
|
|
720
|
+
try {
|
|
721
|
+
recognition.start();
|
|
722
|
+
setIsListening(true);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
console.warn("SpeechRecognition start failed", error);
|
|
928
725
|
}
|
|
929
726
|
}, []);
|
|
930
|
-
const stop =
|
|
727
|
+
const stop = useCallback(() => {
|
|
931
728
|
const recognition = recognitionRef.current;
|
|
932
729
|
if (!recognition) return;
|
|
933
730
|
try {
|
|
@@ -945,9 +742,9 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
|
|
|
945
742
|
__name(useSpeechRecognition, "useSpeechRecognition");
|
|
946
743
|
|
|
947
744
|
// src/components/chat-input.tsx
|
|
948
|
-
function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = true, speechLang = "en-US", sendLabel = "Send", className, textareaClassName, sendButtonClassName, micButtonClassName, renderMic }) {
|
|
949
|
-
const [value, setValue] =
|
|
950
|
-
const [interim, setInterim] =
|
|
745
|
+
function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = true, speechLang = "en-US", sendLabel = "Send", onStop, isStreaming = false, stopLabel = "Stop generating", className, textareaClassName, sendButtonClassName, micButtonClassName, stopButtonClassName, renderMic }) {
|
|
746
|
+
const [value, setValue] = useState2("");
|
|
747
|
+
const [interim, setInterim] = useState2("");
|
|
951
748
|
const speech = useSpeechRecognition({
|
|
952
749
|
lang: speechLang,
|
|
953
750
|
onFinalTranscript: /* @__PURE__ */ __name((text) => {
|
|
@@ -958,7 +755,7 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
|
|
|
958
755
|
}, "onFinalTranscript"),
|
|
959
756
|
onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
|
|
960
757
|
});
|
|
961
|
-
|
|
758
|
+
useEffect2(() => {
|
|
962
759
|
if (!speech.isListening) setInterim("");
|
|
963
760
|
}, [
|
|
964
761
|
speech.isListening
|
|
@@ -985,9 +782,10 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
|
|
|
985
782
|
__name(toggleMic, "toggleMic");
|
|
986
783
|
const displayValue = speech.isListening && interim ? `${value}${value && !value.endsWith(" ") ? " " : ""}${interim}` : value;
|
|
987
784
|
const showMic = enableSpeech && speech.isSupported;
|
|
988
|
-
|
|
785
|
+
const showStop = isStreaming && onStop !== void 0;
|
|
786
|
+
return /* @__PURE__ */ React2.createElement("div", {
|
|
989
787
|
className
|
|
990
|
-
}, /* @__PURE__ */
|
|
788
|
+
}, /* @__PURE__ */ React2.createElement("textarea", {
|
|
991
789
|
className: textareaClassName,
|
|
992
790
|
rows,
|
|
993
791
|
value: displayValue,
|
|
@@ -998,12 +796,17 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
|
|
|
998
796
|
}, "onChange"),
|
|
999
797
|
onKeyDown: handleKeyDown,
|
|
1000
798
|
placeholder: placeholder ?? "Ask something (Enter to send, Shift+Enter for newline)"
|
|
1001
|
-
}), /* @__PURE__ */
|
|
799
|
+
}), /* @__PURE__ */ React2.createElement("button", {
|
|
1002
800
|
type: "button",
|
|
1003
801
|
className: sendButtonClassName,
|
|
1004
802
|
onClick: submit,
|
|
1005
803
|
disabled: disabled || !value.trim()
|
|
1006
|
-
}, sendLabel),
|
|
804
|
+
}, sendLabel), showStop ? /* @__PURE__ */ React2.createElement("button", {
|
|
805
|
+
type: "button",
|
|
806
|
+
className: stopButtonClassName,
|
|
807
|
+
onClick: /* @__PURE__ */ __name(() => void onStop(), "onClick"),
|
|
808
|
+
"aria-label": "Stop generating"
|
|
809
|
+
}, stopLabel) : null, showMic ? /* @__PURE__ */ React2.createElement("button", {
|
|
1007
810
|
type: "button",
|
|
1008
811
|
className: micButtonClassName,
|
|
1009
812
|
onClick: toggleMic,
|
|
@@ -1016,223 +819,393 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
|
|
|
1016
819
|
__name(ChatInput, "ChatInput");
|
|
1017
820
|
|
|
1018
821
|
// src/components/message-item.tsx
|
|
1019
|
-
import
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
"data-actions-for": message.role
|
|
1028
|
-
}, copyableText ? /* @__PURE__ */ React2.createElement(CopyButton, {
|
|
1029
|
-
text: copyableText,
|
|
1030
|
-
className: classNames?.actionButton
|
|
1031
|
-
}) : null, onFork ? /* @__PURE__ */ React2.createElement("button", {
|
|
1032
|
-
type: "button",
|
|
1033
|
-
onClick: /* @__PURE__ */ __name(() => void onFork(message.id), "onClick"),
|
|
1034
|
-
className: classNames?.actionButton,
|
|
1035
|
-
title: "Fork thread from this message"
|
|
1036
|
-
}, "Fork") : null, !isUser && regeneratable && onRegenerate ? /* @__PURE__ */ React2.createElement("button", {
|
|
1037
|
-
type: "button",
|
|
1038
|
-
onClick: /* @__PURE__ */ __name(() => void onRegenerate(), "onClick"),
|
|
1039
|
-
className: classNames?.actionButton,
|
|
1040
|
-
title: "Regenerate response"
|
|
1041
|
-
}, "Regenerate") : null, !isUser && usage ? /* @__PURE__ */ React2.createElement(UsageInline, {
|
|
1042
|
-
usage,
|
|
1043
|
-
className: classNames?.usage
|
|
1044
|
-
}) : null, createdAt ? /* @__PURE__ */ React2.createElement(TimestampInline, {
|
|
1045
|
-
createdAt,
|
|
1046
|
-
className: classNames?.timestamp
|
|
1047
|
-
}) : null);
|
|
1048
|
-
if (isUser && editable && onEditSubmit) {
|
|
1049
|
-
return /* @__PURE__ */ React2.createElement(EditableUserBubble, {
|
|
1050
|
-
message,
|
|
1051
|
-
onSubmit: onEditSubmit,
|
|
1052
|
-
renderText,
|
|
1053
|
-
classNames,
|
|
1054
|
-
actions: actionRow
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
const className = [
|
|
1058
|
-
classNames?.root,
|
|
1059
|
-
classNames?.byRole?.[message.role]
|
|
1060
|
-
].filter((entry) => entry).join(" ");
|
|
1061
|
-
return /* @__PURE__ */ React2.createElement("div", {
|
|
1062
|
-
className: className === "" ? void 0 : className,
|
|
1063
|
-
"data-role": message.role,
|
|
1064
|
-
"data-streaming": isStreaming ? "true" : void 0
|
|
1065
|
-
}, /* @__PURE__ */ React2.createElement("div", null, renderParts(message, parts, {
|
|
1066
|
-
renderText,
|
|
1067
|
-
renderToolPart,
|
|
1068
|
-
renderToolGroup,
|
|
1069
|
-
classNames,
|
|
1070
|
-
isStreaming
|
|
1071
|
-
})), actionRow);
|
|
1072
|
-
}
|
|
1073
|
-
__name(MessageItem, "MessageItem");
|
|
1074
|
-
function renderParts(message, parts, ctx) {
|
|
822
|
+
import React3 from "react";
|
|
823
|
+
|
|
824
|
+
// src/transcript/use-chat-transcript.ts
|
|
825
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
|
|
826
|
+
|
|
827
|
+
// src/transcript/model.ts
|
|
828
|
+
import { getToolName, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolUIPart } from "ai";
|
|
829
|
+
function buildTranscriptBlocks(message, options) {
|
|
1075
830
|
const blocks = [];
|
|
1076
831
|
let toolBuffer = [];
|
|
1077
|
-
let
|
|
832
|
+
let retrievalBuffer = [];
|
|
833
|
+
let fileBuffer = [];
|
|
834
|
+
let toolCounter = 0;
|
|
1078
835
|
let textCounter = 0;
|
|
836
|
+
let fileCounter = 0;
|
|
837
|
+
let reasoningCounter = 0;
|
|
838
|
+
let sourcesCounter = 0;
|
|
839
|
+
let elicitationCounter = 0;
|
|
1079
840
|
function flushTools() {
|
|
1080
841
|
if (toolBuffer.length === 0) {
|
|
1081
842
|
return;
|
|
1082
843
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1089
|
-
for (const part of toolBuffer) {
|
|
1090
|
-
const partKey = `${message.id}-${part.toolCallId}`;
|
|
1091
|
-
blocks.push(/* @__PURE__ */ React2.createElement(React2.Fragment, {
|
|
1092
|
-
key: partKey
|
|
1093
|
-
}, ctx.renderToolPart(part, partKey)));
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
844
|
+
blocks.push({
|
|
845
|
+
kind: "tools",
|
|
846
|
+
key: `${message.id}-tools-${toolCounter++}`,
|
|
847
|
+
parts: toolBuffer,
|
|
848
|
+
calls: toolBuffer.map((part) => buildToolCall(part, options.approval))
|
|
849
|
+
});
|
|
1096
850
|
toolBuffer = [];
|
|
1097
851
|
}
|
|
1098
852
|
__name(flushTools, "flushTools");
|
|
1099
|
-
|
|
853
|
+
function flushSources() {
|
|
854
|
+
if (retrievalBuffer.length === 0) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
blocks.push(buildSourcesBlock(`${message.id}-sources-${sourcesCounter++}`, retrievalBuffer));
|
|
858
|
+
retrievalBuffer = [];
|
|
859
|
+
}
|
|
860
|
+
__name(flushSources, "flushSources");
|
|
861
|
+
function flushFiles() {
|
|
862
|
+
if (fileBuffer.length === 0) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
blocks.push({
|
|
866
|
+
kind: "files",
|
|
867
|
+
key: `${message.id}-files-${fileCounter++}`,
|
|
868
|
+
files: fileBuffer.map((part) => ({
|
|
869
|
+
url: part.url,
|
|
870
|
+
mediaType: part.mediaType,
|
|
871
|
+
filename: part.filename ?? null,
|
|
872
|
+
isImage: part.mediaType.startsWith("image/")
|
|
873
|
+
}))
|
|
874
|
+
});
|
|
875
|
+
fileBuffer = [];
|
|
876
|
+
}
|
|
877
|
+
__name(flushFiles, "flushFiles");
|
|
878
|
+
function flushAll() {
|
|
879
|
+
flushTools();
|
|
880
|
+
flushSources();
|
|
881
|
+
flushFiles();
|
|
882
|
+
}
|
|
883
|
+
__name(flushAll, "flushAll");
|
|
884
|
+
for (const part of message.parts ?? []) {
|
|
1100
885
|
if (isToolUIPart(part)) {
|
|
886
|
+
const elicitation = options.elicitation;
|
|
887
|
+
if (elicitation !== void 0) {
|
|
888
|
+
const request = readElicitationRequest(part);
|
|
889
|
+
if (request !== null) {
|
|
890
|
+
flushAll();
|
|
891
|
+
blocks.push(buildElicitationBlock(`${message.id}-elicitation-${elicitationCounter++}`, part, request, elicitation));
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (options.sources === true && readPassages(part) !== null) {
|
|
896
|
+
flushTools();
|
|
897
|
+
retrievalBuffer.push(part);
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
flushSources();
|
|
901
|
+
flushFiles();
|
|
1101
902
|
toolBuffer.push(part);
|
|
1102
903
|
continue;
|
|
1103
904
|
}
|
|
1104
|
-
|
|
905
|
+
if (isFileUIPart(part)) {
|
|
906
|
+
flushTools();
|
|
907
|
+
flushSources();
|
|
908
|
+
fileBuffer.push(part);
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
flushAll();
|
|
1105
912
|
if (isTextUIPart(part)) {
|
|
1106
|
-
|
|
1107
|
-
|
|
913
|
+
blocks.push({
|
|
914
|
+
kind: "text",
|
|
915
|
+
key: `${message.id}-text-${textCounter++}`,
|
|
916
|
+
text: part.text,
|
|
917
|
+
isStreaming: part.state === "streaming"
|
|
918
|
+
});
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
if (isReasoningUIPart(part)) {
|
|
922
|
+
const key = `${message.id}-reasoning-${reasoningCounter++}`;
|
|
923
|
+
const isStreaming = part.state === "streaming";
|
|
924
|
+
blocks.push({
|
|
925
|
+
kind: "reasoning",
|
|
1108
926
|
key,
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
927
|
+
text: part.text,
|
|
928
|
+
isStreaming,
|
|
929
|
+
isOpen: options.isReasoningOpen(key, isStreaming),
|
|
930
|
+
toggle: /* @__PURE__ */ __name((open) => options.toggleReasoning(key, open), "toggle")
|
|
931
|
+
});
|
|
1113
932
|
}
|
|
1114
933
|
}
|
|
1115
|
-
|
|
934
|
+
flushAll();
|
|
1116
935
|
return blocks;
|
|
1117
936
|
}
|
|
1118
|
-
__name(
|
|
1119
|
-
function
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
937
|
+
__name(buildTranscriptBlocks, "buildTranscriptBlocks");
|
|
938
|
+
function buildSourcesBlock(key, parts) {
|
|
939
|
+
const byId = /* @__PURE__ */ new Map();
|
|
940
|
+
let query = null;
|
|
941
|
+
let passageCount = 0;
|
|
942
|
+
for (const part of parts) {
|
|
943
|
+
query ??= readQuery(part);
|
|
944
|
+
for (const passage of readPassages(part) ?? []) {
|
|
945
|
+
passageCount++;
|
|
946
|
+
const id = passage.source ?? passage.id;
|
|
947
|
+
const existing = byId.get(id);
|
|
948
|
+
if (existing) {
|
|
949
|
+
existing.passageCount++;
|
|
950
|
+
existing.topScore = Math.max(existing.topScore, passage.score);
|
|
951
|
+
existing.passages.push(passage);
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
byId.set(id, {
|
|
955
|
+
id,
|
|
956
|
+
label: passage.source ?? passage.id,
|
|
957
|
+
passageCount: 1,
|
|
958
|
+
topScore: passage.score,
|
|
959
|
+
passages: [
|
|
960
|
+
passage
|
|
961
|
+
]
|
|
962
|
+
});
|
|
1137
963
|
}
|
|
1138
|
-
setIsEditing(false);
|
|
1139
|
-
await onSubmit(next);
|
|
1140
|
-
}
|
|
1141
|
-
__name(handleSave, "handleSave");
|
|
1142
|
-
if (!isEditing) {
|
|
1143
|
-
const rootClass = [
|
|
1144
|
-
classNames?.root,
|
|
1145
|
-
classNames?.byRole?.user
|
|
1146
|
-
].filter((entry) => entry).join(" ");
|
|
1147
|
-
return /* @__PURE__ */ React2.createElement("div", {
|
|
1148
|
-
className: rootClass === "" ? void 0 : rootClass,
|
|
1149
|
-
"data-role": "user"
|
|
1150
|
-
}, /* @__PURE__ */ React2.createElement("div", {
|
|
1151
|
-
className: classNames?.text
|
|
1152
|
-
}, renderText ? renderText(initialText, {
|
|
1153
|
-
isStreaming: false
|
|
1154
|
-
}) : initialText), /* @__PURE__ */ React2.createElement("button", {
|
|
1155
|
-
type: "button",
|
|
1156
|
-
onClick: /* @__PURE__ */ __name(() => {
|
|
1157
|
-
setDraft(initialText);
|
|
1158
|
-
setIsEditing(true);
|
|
1159
|
-
}, "onClick"),
|
|
1160
|
-
className: classNames?.actionButton,
|
|
1161
|
-
title: "Edit & resubmit"
|
|
1162
|
-
}, "Edit"), actions);
|
|
1163
964
|
}
|
|
1164
|
-
return
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
965
|
+
return {
|
|
966
|
+
kind: "sources",
|
|
967
|
+
key,
|
|
968
|
+
query,
|
|
969
|
+
sources: [
|
|
970
|
+
...byId.values()
|
|
971
|
+
],
|
|
972
|
+
passageCount
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
__name(buildSourcesBlock, "buildSourcesBlock");
|
|
976
|
+
function isSettled(state) {
|
|
977
|
+
return state === "output-available" || state === "output-error" || state === "output-denied";
|
|
978
|
+
}
|
|
979
|
+
__name(isSettled, "isSettled");
|
|
980
|
+
function toolKind(part) {
|
|
981
|
+
const metadata = part.toolMetadata;
|
|
982
|
+
return isRecord(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
|
|
983
|
+
}
|
|
984
|
+
__name(toolKind, "toolKind");
|
|
985
|
+
function isAwaitingApproval(part) {
|
|
986
|
+
if (part.state === "approval-requested") {
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
if (part.state !== "input-available") {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
return toolKind(part) === "action" && readElicitationRequest(part) === null;
|
|
993
|
+
}
|
|
994
|
+
__name(isAwaitingApproval, "isAwaitingApproval");
|
|
995
|
+
function buildToolCall(part, options) {
|
|
996
|
+
const toolCallId = part.toolCallId;
|
|
997
|
+
const awaiting = isAwaitingApproval(part);
|
|
998
|
+
const isSubmitting = awaiting && options?.isSubmitting(toolCallId) === true;
|
|
999
|
+
return {
|
|
1000
|
+
part,
|
|
1001
|
+
toolCallId,
|
|
1002
|
+
name: getToolName(part),
|
|
1003
|
+
isAwaitingApproval: awaiting,
|
|
1004
|
+
approve: {
|
|
1005
|
+
available: awaiting && options?.canApprove === true,
|
|
1006
|
+
isSubmitting,
|
|
1007
|
+
run: /* @__PURE__ */ __name(() => options?.approve(toolCallId), "run")
|
|
1008
|
+
},
|
|
1009
|
+
reject: {
|
|
1010
|
+
available: awaiting && options?.canReject === true,
|
|
1011
|
+
isSubmitting,
|
|
1012
|
+
run: /* @__PURE__ */ __name(() => options?.reject(toolCallId), "run")
|
|
1013
|
+
},
|
|
1014
|
+
error: options?.errorOf(toolCallId) ?? null
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
__name(buildToolCall, "buildToolCall");
|
|
1018
|
+
function readElicitationRequest(part) {
|
|
1019
|
+
const input = part.input;
|
|
1020
|
+
if (!isRecord(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
const questions = [];
|
|
1024
|
+
for (const candidate of input.questions) {
|
|
1025
|
+
if (!isRecord(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string" || !Array.isArray(candidate.options) || candidate.options.length === 0) {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
const options = [];
|
|
1029
|
+
for (const option of candidate.options) {
|
|
1030
|
+
if (!isRecord(option) || typeof option.value !== "string" || typeof option.label !== "string") {
|
|
1031
|
+
return null;
|
|
1178
1032
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1033
|
+
options.push({
|
|
1034
|
+
value: option.value,
|
|
1035
|
+
label: option.label,
|
|
1036
|
+
hotkey: typeof option.hotkey === "string" ? option.hotkey : null
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
questions.push({
|
|
1040
|
+
id: candidate.id,
|
|
1041
|
+
prompt: candidate.prompt,
|
|
1042
|
+
multiple: candidate.multiple === true,
|
|
1043
|
+
defaults: Array.isArray(candidate.defaults) ? candidate.defaults.filter((value) => typeof value === "string") : [],
|
|
1044
|
+
options
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
return {
|
|
1048
|
+
preamble: typeof input.preamble === "string" ? input.preamble : null,
|
|
1049
|
+
questions
|
|
1050
|
+
};
|
|
1192
1051
|
}
|
|
1193
|
-
__name(
|
|
1194
|
-
function
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1052
|
+
__name(readElicitationRequest, "readElicitationRequest");
|
|
1053
|
+
function readElicitationOutcome(part) {
|
|
1054
|
+
const output = part.output;
|
|
1055
|
+
if (!isRecord(output) || !isRecord(output.answers) || typeof output.skipped !== "boolean") {
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
const answers = {};
|
|
1059
|
+
for (const [id, values] of Object.entries(output.answers)) {
|
|
1060
|
+
answers[id] = Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
answers,
|
|
1064
|
+
skipped: output.skipped,
|
|
1065
|
+
defaulted: Array.isArray(output.defaulted) ? output.defaulted.filter((id) => typeof id === "string") : [],
|
|
1066
|
+
summary: typeof output.summary === "string" ? output.summary : null
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
__name(readElicitationOutcome, "readElicitationOutcome");
|
|
1070
|
+
function buildElicitationBlock(key, part, request, options) {
|
|
1071
|
+
const toolCallId = part.toolCallId;
|
|
1072
|
+
const isPending = !isSettled(part.state);
|
|
1073
|
+
const outcome = isPending ? null : readElicitationOutcome(part);
|
|
1074
|
+
const questions = request.questions.map((question, index) => {
|
|
1075
|
+
const picked = isPending ? options.picked(toolCallId, question.id) : void 0;
|
|
1076
|
+
const selected = picked ?? outcome?.answers[question.id] ?? question.defaults;
|
|
1077
|
+
const pick = /* @__PURE__ */ __name((values) => options.pick(toolCallId, question.id, values), "pick");
|
|
1078
|
+
return {
|
|
1079
|
+
id: question.id,
|
|
1080
|
+
prompt: question.prompt,
|
|
1081
|
+
multiple: question.multiple,
|
|
1082
|
+
position: index + 1,
|
|
1083
|
+
selected,
|
|
1084
|
+
isPristine: isPending ? picked === void 0 : outcome?.defaulted.includes(question.id) ?? true,
|
|
1085
|
+
options: question.options.map((option) => ({
|
|
1086
|
+
value: option.value,
|
|
1087
|
+
label: option.label,
|
|
1088
|
+
hotkey: option.hotkey,
|
|
1089
|
+
isSelected: selected.includes(option.value),
|
|
1090
|
+
isDefault: question.defaults.includes(option.value),
|
|
1091
|
+
select: /* @__PURE__ */ __name(() => {
|
|
1092
|
+
if (!isPending) {
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (!question.multiple) {
|
|
1096
|
+
pick([
|
|
1097
|
+
option.value
|
|
1098
|
+
]);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
pick(selected.includes(option.value) ? selected.filter((value) => value !== option.value) : [
|
|
1102
|
+
...selected,
|
|
1103
|
+
option.value
|
|
1104
|
+
]);
|
|
1105
|
+
}, "select")
|
|
1106
|
+
}))
|
|
1107
|
+
};
|
|
1108
|
+
});
|
|
1109
|
+
const isSubmitting = isPending && options.isSubmitting(toolCallId);
|
|
1110
|
+
return {
|
|
1111
|
+
kind: "elicitation",
|
|
1112
|
+
key,
|
|
1113
|
+
toolCallId,
|
|
1114
|
+
preamble: request.preamble,
|
|
1115
|
+
questions,
|
|
1116
|
+
questionCount: questions.length,
|
|
1117
|
+
isPending,
|
|
1118
|
+
outcome,
|
|
1119
|
+
error: options.errorOf(toolCallId),
|
|
1120
|
+
answer: {
|
|
1121
|
+
available: isPending && options.canAnswer,
|
|
1122
|
+
isSubmitting,
|
|
1123
|
+
run: /* @__PURE__ */ __name(() => options.answer(toolCallId), "run")
|
|
1124
|
+
},
|
|
1125
|
+
skip: {
|
|
1126
|
+
available: isPending && options.canSkip,
|
|
1127
|
+
isSubmitting,
|
|
1128
|
+
run: /* @__PURE__ */ __name(() => options.skip(toolCallId), "run")
|
|
1202
1129
|
}
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
__name(buildElicitationBlock, "buildElicitationBlock");
|
|
1133
|
+
function isRecord(value) {
|
|
1134
|
+
return typeof value === "object" && value !== null;
|
|
1135
|
+
}
|
|
1136
|
+
__name(isRecord, "isRecord");
|
|
1137
|
+
function readQuery(part) {
|
|
1138
|
+
const input = part.input;
|
|
1139
|
+
if (!isRecord(input) || typeof input.query !== "string") {
|
|
1140
|
+
return null;
|
|
1203
1141
|
}
|
|
1204
|
-
|
|
1205
|
-
return /* @__PURE__ */ React2.createElement("button", {
|
|
1206
|
-
type: "button",
|
|
1207
|
-
onClick: /* @__PURE__ */ __name(() => void handleCopy(), "onClick"),
|
|
1208
|
-
className,
|
|
1209
|
-
title: copied ? "Copied!" : "Copy message text"
|
|
1210
|
-
}, copied ? "Copied" : "Copy");
|
|
1142
|
+
return input.query;
|
|
1211
1143
|
}
|
|
1212
|
-
__name(
|
|
1213
|
-
function
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1144
|
+
__name(readQuery, "readQuery");
|
|
1145
|
+
function readPassages(part) {
|
|
1146
|
+
const output = part.output;
|
|
1147
|
+
if (!isRecord(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
const passages = [];
|
|
1151
|
+
for (const candidate of output.passages) {
|
|
1152
|
+
if (!isRecord(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
passages.push({
|
|
1156
|
+
id: candidate.id,
|
|
1157
|
+
text: candidate.text,
|
|
1158
|
+
score: typeof candidate.score === "number" ? candidate.score : 0,
|
|
1159
|
+
...typeof candidate.source === "string" ? {
|
|
1160
|
+
source: candidate.source
|
|
1161
|
+
} : {},
|
|
1162
|
+
...isRecord(candidate.metadata) ? {
|
|
1163
|
+
metadata: candidate.metadata
|
|
1164
|
+
} : {}
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
return passages;
|
|
1219
1168
|
}
|
|
1220
|
-
__name(
|
|
1221
|
-
function
|
|
1222
|
-
const
|
|
1223
|
-
|
|
1169
|
+
__name(readPassages, "readPassages");
|
|
1170
|
+
function extractMessageText(parts) {
|
|
1171
|
+
const out = [];
|
|
1172
|
+
for (const part of parts ?? []) {
|
|
1173
|
+
if (isTextUIPart(part)) {
|
|
1174
|
+
out.push(part.text);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return out.join("\n\n").trim();
|
|
1178
|
+
}
|
|
1179
|
+
__name(extractMessageText, "extractMessageText");
|
|
1180
|
+
function describeUsage(usage) {
|
|
1181
|
+
const totalTokens = usage.inputTokens + usage.outputTokens;
|
|
1182
|
+
return {
|
|
1183
|
+
...usage,
|
|
1184
|
+
totalTokens,
|
|
1185
|
+
costLabel: formatCostUsd(usage.costUsd),
|
|
1186
|
+
tokensLabel: formatTokensShort(totalTokens)
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
__name(describeUsage, "describeUsage");
|
|
1190
|
+
function describeTimestamp(createdAt) {
|
|
1191
|
+
if (createdAt == null) {
|
|
1224
1192
|
return null;
|
|
1225
1193
|
}
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
}
|
|
1230
|
-
return
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1194
|
+
const date = new Date(createdAt);
|
|
1195
|
+
if (Number.isNaN(date.getTime())) {
|
|
1196
|
+
return null;
|
|
1197
|
+
}
|
|
1198
|
+
return {
|
|
1199
|
+
iso: createdAt,
|
|
1200
|
+
date,
|
|
1201
|
+
relative: formatRelativeTime(date),
|
|
1202
|
+
absolute: date.toLocaleString(void 0, {
|
|
1203
|
+
dateStyle: "medium",
|
|
1204
|
+
timeStyle: "short"
|
|
1205
|
+
})
|
|
1206
|
+
};
|
|
1234
1207
|
}
|
|
1235
|
-
__name(
|
|
1208
|
+
__name(describeTimestamp, "describeTimestamp");
|
|
1236
1209
|
var RELATIVE_TIME = new Intl.RelativeTimeFormat(void 0, {
|
|
1237
1210
|
numeric: "auto",
|
|
1238
1211
|
style: "narrow"
|
|
@@ -1271,6 +1244,9 @@ function formatTokensShort(n) {
|
|
|
1271
1244
|
}
|
|
1272
1245
|
__name(formatTokensShort, "formatTokensShort");
|
|
1273
1246
|
function formatCostUsd(cost) {
|
|
1247
|
+
if (cost === null) {
|
|
1248
|
+
return "\u2014";
|
|
1249
|
+
}
|
|
1274
1250
|
if (cost === 0) {
|
|
1275
1251
|
return "$0";
|
|
1276
1252
|
}
|
|
@@ -1283,142 +1259,799 @@ function formatCostUsd(cost) {
|
|
|
1283
1259
|
return `$${cost.toFixed(2)}`;
|
|
1284
1260
|
}
|
|
1285
1261
|
__name(formatCostUsd, "formatCostUsd");
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1262
|
+
|
|
1263
|
+
// src/transcript/use-stick-to-bottom.ts
|
|
1264
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
|
|
1265
|
+
function useStickToBottom({ contentKey, threshold = 32 }) {
|
|
1266
|
+
const containerRef = useRef2(null);
|
|
1267
|
+
const [isAtBottom, setIsAtBottom] = useState3(true);
|
|
1268
|
+
const measure = useCallback2(() => {
|
|
1269
|
+
const element = containerRef.current;
|
|
1270
|
+
if (!element) {
|
|
1271
|
+
return;
|
|
1291
1272
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1273
|
+
const distance = element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
1274
|
+
setIsAtBottom(distance <= threshold);
|
|
1275
|
+
}, [
|
|
1276
|
+
threshold
|
|
1277
|
+
]);
|
|
1278
|
+
const scrollToBottom = useCallback2(() => {
|
|
1279
|
+
const element = containerRef.current;
|
|
1280
|
+
if (!element) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
element.scrollTop = element.scrollHeight;
|
|
1284
|
+
setIsAtBottom(true);
|
|
1285
|
+
}, []);
|
|
1286
|
+
const setContainer = useCallback2((element) => {
|
|
1287
|
+
containerRef.current = element;
|
|
1288
|
+
if (element) {
|
|
1289
|
+
measure();
|
|
1290
|
+
}
|
|
1291
|
+
}, [
|
|
1292
|
+
measure
|
|
1293
|
+
]);
|
|
1294
|
+
useEffect3(() => {
|
|
1295
|
+
if (isAtBottom) {
|
|
1296
|
+
scrollToBottom();
|
|
1297
|
+
}
|
|
1298
|
+
}, [
|
|
1299
|
+
contentKey,
|
|
1300
|
+
isAtBottom,
|
|
1301
|
+
scrollToBottom
|
|
1302
|
+
]);
|
|
1303
|
+
const getContainerProps = useCallback2(() => ({
|
|
1304
|
+
ref: setContainer,
|
|
1305
|
+
onScroll: measure
|
|
1306
|
+
}), [
|
|
1307
|
+
setContainer,
|
|
1308
|
+
measure
|
|
1309
|
+
]);
|
|
1310
|
+
return {
|
|
1311
|
+
isAtBottom,
|
|
1312
|
+
showJumpToLatest: !isAtBottom,
|
|
1313
|
+
scrollToBottom,
|
|
1314
|
+
getContainerProps
|
|
1315
|
+
};
|
|
1294
1316
|
}
|
|
1295
|
-
__name(
|
|
1317
|
+
__name(useStickToBottom, "useStickToBottom");
|
|
1296
1318
|
|
|
1297
|
-
// src/
|
|
1298
|
-
import React3, { useState as useState5 } from "react";
|
|
1319
|
+
// src/transcript/use-chat-transcript.ts
|
|
1299
1320
|
var DEFAULT_VISIBLE_COUNT = 50;
|
|
1300
|
-
var
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1321
|
+
var DEFAULT_LOAD_EARLIER_STEP = 50;
|
|
1322
|
+
var DEFAULT_COPY_RESET_MS = 1500;
|
|
1323
|
+
function useChatTranscript(options) {
|
|
1324
|
+
const { messages, status, initialVisibleCount = DEFAULT_VISIBLE_COUNT, loadEarlierStep = DEFAULT_LOAD_EARLIER_STEP, followUps, stickToBottomThreshold } = options;
|
|
1325
|
+
const latest = useRef3(options);
|
|
1326
|
+
latest.current = options;
|
|
1327
|
+
const [visibleCount, setVisibleCount] = useState4(initialVisibleCount);
|
|
1328
|
+
const [stopRequested, setStopRequested] = useState4(false);
|
|
1329
|
+
const isStreaming = status === "streaming";
|
|
1330
|
+
const isBusy = status === "submitted" || isStreaming;
|
|
1331
|
+
const isEmpty = messages.length === 0;
|
|
1332
|
+
useEffect4(() => {
|
|
1333
|
+
if (!isBusy) {
|
|
1334
|
+
setStopRequested(false);
|
|
1335
|
+
}
|
|
1336
|
+
}, [
|
|
1337
|
+
isBusy
|
|
1338
|
+
]);
|
|
1308
1339
|
const lastMessage = messages.at(-1);
|
|
1309
|
-
const
|
|
1310
|
-
const
|
|
1340
|
+
const lastAssistant = findLastAssistant(messages);
|
|
1341
|
+
const streamingMessageId = isStreaming ? lastAssistant?.id ?? null : null;
|
|
1311
1342
|
const visibleStart = Math.max(0, messages.length - visibleCount);
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1343
|
+
const items = useTranscriptItems({
|
|
1344
|
+
messages,
|
|
1345
|
+
visibleStart,
|
|
1346
|
+
streamingMessageId,
|
|
1347
|
+
options
|
|
1348
|
+
});
|
|
1349
|
+
const stop = useCallback3(() => {
|
|
1350
|
+
const current = latest.current;
|
|
1351
|
+
if (current.status !== "submitted" && current.status !== "streaming") {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
setStopRequested(true);
|
|
1355
|
+
void current.onStop?.();
|
|
1356
|
+
}, []);
|
|
1357
|
+
const loadEarlier = useCallback3(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
|
|
1358
|
+
const showAll = useCallback3(() => setVisibleCount(latest.current.messages.length), []);
|
|
1359
|
+
const scroll = useStickToBottom({
|
|
1360
|
+
contentKey: contentSignature(messages),
|
|
1361
|
+
...stickToBottomThreshold !== void 0 ? {
|
|
1362
|
+
threshold: stickToBottomThreshold
|
|
1363
|
+
} : {}
|
|
1364
|
+
});
|
|
1365
|
+
const showTypingIndicator = isBusy && lastMessage?.role !== "assistant";
|
|
1366
|
+
return {
|
|
1367
|
+
items,
|
|
1368
|
+
status,
|
|
1369
|
+
isEmpty,
|
|
1370
|
+
isBusy,
|
|
1371
|
+
isStreaming,
|
|
1372
|
+
showEmptyState: isEmpty && status === "ready",
|
|
1373
|
+
showTypingIndicator,
|
|
1374
|
+
showFollowUps: status === "ready" && !showTypingIndicator && lastMessage?.role === "assistant" && !!followUps && followUps.length > 0,
|
|
1375
|
+
lastAssistantId: lastAssistant?.id ?? null,
|
|
1376
|
+
window: {
|
|
1377
|
+
visibleCount,
|
|
1378
|
+
hiddenCount: visibleStart,
|
|
1379
|
+
canLoadEarlier: visibleStart > 0,
|
|
1380
|
+
loadEarlier,
|
|
1381
|
+
showAll
|
|
1382
|
+
},
|
|
1383
|
+
scroll,
|
|
1384
|
+
stop: {
|
|
1385
|
+
available: isBusy && options.onStop !== void 0,
|
|
1386
|
+
isStopping: stopRequested,
|
|
1387
|
+
stop
|
|
1319
1388
|
}
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
__name(useChatTranscript, "useChatTranscript");
|
|
1392
|
+
function useTranscriptItem(options) {
|
|
1393
|
+
const { message, isStreaming = false } = options;
|
|
1394
|
+
const messagesRef = useRef3([
|
|
1395
|
+
message
|
|
1396
|
+
]);
|
|
1397
|
+
if (messagesRef.current[0] !== message) {
|
|
1398
|
+
messagesRef.current = [
|
|
1399
|
+
message
|
|
1400
|
+
];
|
|
1320
1401
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1402
|
+
const items = useTranscriptItems({
|
|
1403
|
+
messages: messagesRef.current,
|
|
1404
|
+
visibleStart: 0,
|
|
1405
|
+
streamingMessageId: isStreaming ? message.id : null,
|
|
1406
|
+
options
|
|
1407
|
+
});
|
|
1408
|
+
return items[0];
|
|
1409
|
+
}
|
|
1410
|
+
__name(useTranscriptItem, "useTranscriptItem");
|
|
1411
|
+
function useTranscriptItems({ messages, visibleStart, streamingMessageId, options }) {
|
|
1412
|
+
const [drafts, setDrafts] = useState4(() => /* @__PURE__ */ new Map());
|
|
1413
|
+
const [copied, setCopied] = useState4(() => /* @__PURE__ */ new Set());
|
|
1414
|
+
const [openReasoning, setOpenReasoning] = useState4(() => /* @__PURE__ */ new Map());
|
|
1415
|
+
const [picks, setPicks] = useState4(() => /* @__PURE__ */ new Map());
|
|
1416
|
+
const [settling, setSettling] = useState4(() => /* @__PURE__ */ new Set());
|
|
1417
|
+
const [settleErrors, setSettleErrors] = useState4(() => /* @__PURE__ */ new Map());
|
|
1418
|
+
const latest = useRef3({
|
|
1419
|
+
options,
|
|
1420
|
+
drafts,
|
|
1421
|
+
picks,
|
|
1422
|
+
messages
|
|
1423
|
+
});
|
|
1424
|
+
latest.current = {
|
|
1425
|
+
options,
|
|
1426
|
+
drafts,
|
|
1427
|
+
picks,
|
|
1428
|
+
messages
|
|
1429
|
+
};
|
|
1430
|
+
const copyTimers = useRef3(/* @__PURE__ */ new Map());
|
|
1431
|
+
useEffect4(() => () => {
|
|
1432
|
+
for (const timer of copyTimers.current.values()) {
|
|
1433
|
+
clearTimeout(timer);
|
|
1434
|
+
}
|
|
1435
|
+
copyTimers.current.clear();
|
|
1436
|
+
}, []);
|
|
1437
|
+
const callbackCache = useRef3(/* @__PURE__ */ new Map());
|
|
1438
|
+
const reasoningToggleCache = useRef3(/* @__PURE__ */ new Map());
|
|
1439
|
+
const toggleReasoning = useCallback3((key, open) => {
|
|
1440
|
+
setOpenReasoning((current) => {
|
|
1441
|
+
const next = new Map(current);
|
|
1442
|
+
next.set(key, open ?? !(current.get(key) ?? false));
|
|
1443
|
+
return next;
|
|
1444
|
+
});
|
|
1445
|
+
}, []);
|
|
1446
|
+
const stableToggle = useCallback3((key) => {
|
|
1447
|
+
let cached = reasoningToggleCache.current.get(key);
|
|
1448
|
+
if (!cached) {
|
|
1449
|
+
cached = /* @__PURE__ */ __name((open) => toggleReasoning(key, open), "cached");
|
|
1450
|
+
reasoningToggleCache.current.set(key, cached);
|
|
1451
|
+
}
|
|
1452
|
+
return cached;
|
|
1453
|
+
}, [
|
|
1454
|
+
toggleReasoning
|
|
1455
|
+
]);
|
|
1456
|
+
const pick = useCallback3((toolCallId, questionId, values) => {
|
|
1457
|
+
setPicks((current) => new Map(current).set(pickKey(toolCallId, questionId), values));
|
|
1458
|
+
}, []);
|
|
1459
|
+
const settle = useCallback3((toolCallId, send) => {
|
|
1460
|
+
setSettleErrors((current) => {
|
|
1461
|
+
if (!current.has(toolCallId)) {
|
|
1462
|
+
return current;
|
|
1463
|
+
}
|
|
1464
|
+
const next = new Map(current);
|
|
1465
|
+
next.delete(toolCallId);
|
|
1466
|
+
return next;
|
|
1467
|
+
});
|
|
1468
|
+
setSettling((current) => new Set(current).add(toolCallId));
|
|
1469
|
+
const failed = /* @__PURE__ */ __name((error) => {
|
|
1470
|
+
setSettling((current) => {
|
|
1471
|
+
const next = new Set(current);
|
|
1472
|
+
next.delete(toolCallId);
|
|
1473
|
+
return next;
|
|
1474
|
+
});
|
|
1475
|
+
setSettleErrors((current) => new Map(current).set(toolCallId, error instanceof Error ? error.message : "Could not settle this"));
|
|
1476
|
+
}, "failed");
|
|
1477
|
+
try {
|
|
1478
|
+
void Promise.resolve(send()).catch(failed);
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
failed(error);
|
|
1481
|
+
}
|
|
1482
|
+
}, []);
|
|
1483
|
+
const answer = useCallback3((toolCallId) => {
|
|
1484
|
+
const current = latest.current;
|
|
1485
|
+
const onAnswer = current.options.onAnswer;
|
|
1486
|
+
if (!onAnswer) {
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
settle(toolCallId, () => onAnswer(toolCallId, answersFor(current.picks, toolCallId)));
|
|
1490
|
+
}, [
|
|
1491
|
+
settle
|
|
1492
|
+
]);
|
|
1493
|
+
const skip = useCallback3((toolCallId) => {
|
|
1494
|
+
const onSkip = latest.current.options.onSkip;
|
|
1495
|
+
if (onSkip) {
|
|
1496
|
+
settle(toolCallId, () => onSkip(toolCallId));
|
|
1497
|
+
}
|
|
1498
|
+
}, [
|
|
1499
|
+
settle
|
|
1500
|
+
]);
|
|
1501
|
+
const approve = useCallback3((toolCallId) => {
|
|
1502
|
+
const onApprove = latest.current.options.onApprove;
|
|
1503
|
+
if (onApprove) {
|
|
1504
|
+
settle(toolCallId, () => onApprove(toolCallId));
|
|
1505
|
+
}
|
|
1506
|
+
}, [
|
|
1507
|
+
settle
|
|
1508
|
+
]);
|
|
1509
|
+
const reject = useCallback3((toolCallId) => {
|
|
1510
|
+
const onReject = latest.current.options.onReject;
|
|
1511
|
+
if (onReject) {
|
|
1512
|
+
settle(toolCallId, () => onReject(toolCallId));
|
|
1513
|
+
}
|
|
1514
|
+
}, [
|
|
1515
|
+
settle
|
|
1516
|
+
]);
|
|
1517
|
+
const callbacksFor = useCallback3((id) => {
|
|
1518
|
+
const cached = callbackCache.current.get(id);
|
|
1519
|
+
if (cached) {
|
|
1520
|
+
return cached;
|
|
1521
|
+
}
|
|
1522
|
+
const textFor = /* @__PURE__ */ __name(() => extractMessageText(latest.current.messages.find((message) => message.id === id)?.parts), "textFor");
|
|
1523
|
+
const created = {
|
|
1524
|
+
copy: /* @__PURE__ */ __name(() => {
|
|
1525
|
+
const text = textFor();
|
|
1526
|
+
if (!text) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
const write = latest.current.options.writeClipboard ?? ((value) => navigator.clipboard.writeText(value));
|
|
1530
|
+
void Promise.resolve().then(() => write(text)).then(() => {
|
|
1531
|
+
setCopied((current) => new Set(current).add(id));
|
|
1532
|
+
const existing = copyTimers.current.get(id);
|
|
1533
|
+
if (existing) {
|
|
1534
|
+
clearTimeout(existing);
|
|
1535
|
+
}
|
|
1536
|
+
copyTimers.current.set(id, setTimeout(() => {
|
|
1537
|
+
copyTimers.current.delete(id);
|
|
1538
|
+
setCopied((current) => {
|
|
1539
|
+
const next = new Set(current);
|
|
1540
|
+
next.delete(id);
|
|
1541
|
+
return next;
|
|
1542
|
+
});
|
|
1543
|
+
}, latest.current.options.copyResetMs ?? DEFAULT_COPY_RESET_MS));
|
|
1544
|
+
}).catch(() => {
|
|
1545
|
+
});
|
|
1546
|
+
}, "copy"),
|
|
1547
|
+
startEdit: /* @__PURE__ */ __name(() => {
|
|
1548
|
+
const text = textFor();
|
|
1549
|
+
setDrafts((current) => new Map(current).set(id, text));
|
|
1550
|
+
}, "startEdit"),
|
|
1551
|
+
cancelEdit: /* @__PURE__ */ __name(() => {
|
|
1552
|
+
setDrafts((current) => {
|
|
1553
|
+
const next = new Map(current);
|
|
1554
|
+
next.delete(id);
|
|
1555
|
+
return next;
|
|
1556
|
+
});
|
|
1557
|
+
}, "cancelEdit"),
|
|
1558
|
+
setDraft: /* @__PURE__ */ __name((value) => {
|
|
1559
|
+
setDrafts((current) => new Map(current).set(id, value));
|
|
1560
|
+
}, "setDraft"),
|
|
1561
|
+
save: /* @__PURE__ */ __name(() => {
|
|
1562
|
+
const next = (latest.current.drafts.get(id) ?? "").trim();
|
|
1563
|
+
if (!next) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
created.cancelEdit();
|
|
1567
|
+
void latest.current.options.onEditSubmit?.(id, next);
|
|
1568
|
+
}, "save"),
|
|
1569
|
+
fork: /* @__PURE__ */ __name(() => {
|
|
1570
|
+
void latest.current.options.onFork?.(id);
|
|
1571
|
+
}, "fork"),
|
|
1572
|
+
regenerate: /* @__PURE__ */ __name(() => {
|
|
1573
|
+
void latest.current.options.onRegenerate?.(id);
|
|
1574
|
+
}, "regenerate"),
|
|
1575
|
+
textareaRef: /* @__PURE__ */ __name((element) => {
|
|
1576
|
+
if (!element) {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
element.focus();
|
|
1580
|
+
element.setSelectionRange(element.value.length, element.value.length);
|
|
1581
|
+
}, "textareaRef"),
|
|
1582
|
+
onTextareaChange: /* @__PURE__ */ __name((event) => {
|
|
1583
|
+
created.setDraft(event.target.value);
|
|
1584
|
+
}, "onTextareaChange"),
|
|
1585
|
+
onTextareaKeyDown: /* @__PURE__ */ __name((event) => {
|
|
1586
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
1587
|
+
event.preventDefault();
|
|
1588
|
+
created.save();
|
|
1589
|
+
}
|
|
1590
|
+
if (event.key === "Escape") {
|
|
1591
|
+
event.preventDefault();
|
|
1592
|
+
created.cancelEdit();
|
|
1593
|
+
}
|
|
1594
|
+
}, "onTextareaKeyDown")
|
|
1595
|
+
};
|
|
1596
|
+
callbackCache.current.set(id, created);
|
|
1597
|
+
return created;
|
|
1598
|
+
}, []);
|
|
1599
|
+
const lastAssistantId = findLastAssistant(messages)?.id ?? null;
|
|
1600
|
+
const items = [];
|
|
1601
|
+
for (let index = visibleStart; index < messages.length; index++) {
|
|
1602
|
+
const message = messages[index];
|
|
1603
|
+
if (!message) {
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
const callbacks = callbacksFor(message.id);
|
|
1607
|
+
const draft = drafts.get(message.id);
|
|
1608
|
+
const isEditing = draft !== void 0;
|
|
1609
|
+
const text = extractMessageText(message.parts);
|
|
1610
|
+
const usage = options.getUsage?.(message) ?? null;
|
|
1611
|
+
const isUser = message.role === "user";
|
|
1612
|
+
const isAssistant = message.role === "assistant";
|
|
1613
|
+
const isLastAssistant = isAssistant && message.id === lastAssistantId;
|
|
1614
|
+
items.push({
|
|
1615
|
+
id: message.id,
|
|
1334
1616
|
message,
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1617
|
+
role: message.role,
|
|
1618
|
+
index,
|
|
1619
|
+
isUser,
|
|
1620
|
+
isAssistant,
|
|
1621
|
+
isLastAssistant,
|
|
1622
|
+
isStreaming: message.id === streamingMessageId,
|
|
1623
|
+
blocks: buildTranscriptBlocks(message, {
|
|
1624
|
+
isReasoningOpen: /* @__PURE__ */ __name((key, isStreamingRun) => openReasoning.get(key) ?? isStreamingRun, "isReasoningOpen"),
|
|
1625
|
+
toggleReasoning: /* @__PURE__ */ __name((key, open) => stableToggle(key)(open), "toggleReasoning"),
|
|
1626
|
+
...options.sources !== void 0 ? {
|
|
1627
|
+
sources: options.sources
|
|
1628
|
+
} : {},
|
|
1629
|
+
...options.onAnswer !== void 0 ? {
|
|
1630
|
+
elicitation: {
|
|
1631
|
+
picked: /* @__PURE__ */ __name((toolCallId, questionId) => picks.get(pickKey(toolCallId, questionId)), "picked"),
|
|
1632
|
+
pick,
|
|
1633
|
+
canAnswer: true,
|
|
1634
|
+
canSkip: options.onSkip !== void 0,
|
|
1635
|
+
answer,
|
|
1636
|
+
skip,
|
|
1637
|
+
isSubmitting: /* @__PURE__ */ __name((toolCallId) => settling.has(toolCallId), "isSubmitting"),
|
|
1638
|
+
errorOf: /* @__PURE__ */ __name((toolCallId) => settleErrors.get(toolCallId) ?? null, "errorOf")
|
|
1639
|
+
}
|
|
1640
|
+
} : {},
|
|
1641
|
+
...options.onApprove !== void 0 || options.onReject !== void 0 ? {
|
|
1642
|
+
approval: {
|
|
1643
|
+
canApprove: options.onApprove !== void 0,
|
|
1644
|
+
canReject: options.onReject !== void 0,
|
|
1645
|
+
approve,
|
|
1646
|
+
reject,
|
|
1647
|
+
isSubmitting: /* @__PURE__ */ __name((toolCallId) => settling.has(toolCallId), "isSubmitting"),
|
|
1648
|
+
errorOf: /* @__PURE__ */ __name((toolCallId) => settleErrors.get(toolCallId) ?? null, "errorOf")
|
|
1649
|
+
}
|
|
1650
|
+
} : {}
|
|
1651
|
+
}),
|
|
1652
|
+
text,
|
|
1653
|
+
usage: usage ? describeUsage(usage) : null,
|
|
1654
|
+
timestamp: describeTimestamp(options.getCreatedAt?.(message)),
|
|
1655
|
+
copy: {
|
|
1656
|
+
available: text.length > 0,
|
|
1657
|
+
copied: copied.has(message.id),
|
|
1658
|
+
copy: callbacks.copy
|
|
1659
|
+
},
|
|
1660
|
+
edit: {
|
|
1661
|
+
available: isUser && options.editable === true && options.onEditSubmit !== void 0,
|
|
1662
|
+
isEditing,
|
|
1663
|
+
draft: draft ?? text,
|
|
1664
|
+
canSave: (draft ?? "").trim().length > 0,
|
|
1665
|
+
start: callbacks.startEdit,
|
|
1666
|
+
cancel: callbacks.cancelEdit,
|
|
1667
|
+
setDraft: callbacks.setDraft,
|
|
1668
|
+
save: callbacks.save,
|
|
1669
|
+
getTextareaProps: /* @__PURE__ */ __name(() => ({
|
|
1670
|
+
ref: callbacks.textareaRef,
|
|
1671
|
+
value: draft ?? text,
|
|
1672
|
+
onChange: callbacks.onTextareaChange,
|
|
1673
|
+
onKeyDown: callbacks.onTextareaKeyDown
|
|
1674
|
+
}), "getTextareaProps")
|
|
1675
|
+
},
|
|
1676
|
+
fork: {
|
|
1677
|
+
available: options.onFork !== void 0,
|
|
1678
|
+
run: callbacks.fork
|
|
1679
|
+
},
|
|
1680
|
+
regenerate: {
|
|
1681
|
+
available: isLastAssistant && options.regeneratable === true && options.onRegenerate !== void 0,
|
|
1682
|
+
run: callbacks.regenerate
|
|
1683
|
+
}
|
|
1369
1684
|
});
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
}, typingIndicator ?? "Thinking\u2026") : null, error && onRetry ? /* @__PURE__ */ React3.createElement("div", {
|
|
1373
|
-
className: classNames?.error
|
|
1374
|
-
}, /* @__PURE__ */ React3.createElement("span", null, error.message || "The assistant didn't respond."), /* @__PURE__ */ React3.createElement("button", {
|
|
1375
|
-
type: "button",
|
|
1376
|
-
className: classNames?.errorRetry,
|
|
1377
|
-
onClick: /* @__PURE__ */ __name(() => void onRetry(), "onClick")
|
|
1378
|
-
}, "Retry")) : null, showFollowUps ? /* @__PURE__ */ React3.createElement("div", {
|
|
1379
|
-
className: classNames?.followUps
|
|
1380
|
-
}, followUps.map((text) => /* @__PURE__ */ React3.createElement("button", {
|
|
1381
|
-
key: text,
|
|
1382
|
-
type: "button",
|
|
1383
|
-
className: classNames?.followUpChip,
|
|
1384
|
-
onClick: /* @__PURE__ */ __name(() => onFollowUpClick(text), "onClick")
|
|
1385
|
-
}, text))) : null);
|
|
1685
|
+
}
|
|
1686
|
+
return items;
|
|
1386
1687
|
}
|
|
1387
|
-
__name(
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
},
|
|
1400
|
-
{
|
|
1401
|
-
tag: "pt-BR",
|
|
1402
|
-
label: "Portuguese (Brazil)"
|
|
1403
|
-
},
|
|
1404
|
-
{
|
|
1405
|
-
tag: "es-ES",
|
|
1406
|
-
label: "Spanish (Spain)"
|
|
1688
|
+
__name(useTranscriptItems, "useTranscriptItems");
|
|
1689
|
+
function pickKey(toolCallId, questionId) {
|
|
1690
|
+
return `${toolCallId}|${questionId}`;
|
|
1691
|
+
}
|
|
1692
|
+
__name(pickKey, "pickKey");
|
|
1693
|
+
function answersFor(picks, toolCallId) {
|
|
1694
|
+
const prefix = `${toolCallId}|`;
|
|
1695
|
+
const answers = {};
|
|
1696
|
+
for (const [key, values] of picks) {
|
|
1697
|
+
if (key.startsWith(prefix)) {
|
|
1698
|
+
answers[key.slice(prefix.length)] = values;
|
|
1699
|
+
}
|
|
1407
1700
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
const
|
|
1414
|
-
if (
|
|
1415
|
-
return
|
|
1701
|
+
return answers;
|
|
1702
|
+
}
|
|
1703
|
+
__name(answersFor, "answersFor");
|
|
1704
|
+
function findLastAssistant(messages) {
|
|
1705
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
1706
|
+
const message = messages[index];
|
|
1707
|
+
if (message?.role === "assistant") {
|
|
1708
|
+
return message;
|
|
1416
1709
|
}
|
|
1417
|
-
} catch {
|
|
1418
1710
|
}
|
|
1419
|
-
return
|
|
1711
|
+
return void 0;
|
|
1420
1712
|
}
|
|
1421
|
-
__name(
|
|
1713
|
+
__name(findLastAssistant, "findLastAssistant");
|
|
1714
|
+
function contentSignature(messages) {
|
|
1715
|
+
const last = messages.at(-1);
|
|
1716
|
+
if (!last) {
|
|
1717
|
+
return "0";
|
|
1718
|
+
}
|
|
1719
|
+
let length = 0;
|
|
1720
|
+
for (const part of last.parts ?? []) {
|
|
1721
|
+
if ("text" in part && typeof part.text === "string") {
|
|
1722
|
+
length += part.text.length;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return `${messages.length}|${last.id}|${last.parts?.length ?? 0}|${length}`;
|
|
1726
|
+
}
|
|
1727
|
+
__name(contentSignature, "contentSignature");
|
|
1728
|
+
|
|
1729
|
+
// src/components/message-item.tsx
|
|
1730
|
+
function MessageItem({ message, isStreaming = false, editable, onEditSubmit, regeneratable, onRegenerate, onFork, usage, createdAt, ...slots }) {
|
|
1731
|
+
const item = useTranscriptItem({
|
|
1732
|
+
message,
|
|
1733
|
+
isStreaming,
|
|
1734
|
+
...editable !== void 0 ? {
|
|
1735
|
+
editable
|
|
1736
|
+
} : {},
|
|
1737
|
+
...onEditSubmit ? {
|
|
1738
|
+
onEditSubmit: /* @__PURE__ */ __name((_id, next) => onEditSubmit(next), "onEditSubmit")
|
|
1739
|
+
} : {},
|
|
1740
|
+
...onFork ? {
|
|
1741
|
+
onFork
|
|
1742
|
+
} : {},
|
|
1743
|
+
...regeneratable !== void 0 ? {
|
|
1744
|
+
regeneratable
|
|
1745
|
+
} : {},
|
|
1746
|
+
...onRegenerate ? {
|
|
1747
|
+
onRegenerate: /* @__PURE__ */ __name(() => onRegenerate(), "onRegenerate")
|
|
1748
|
+
} : {},
|
|
1749
|
+
...usage != null ? {
|
|
1750
|
+
getUsage: /* @__PURE__ */ __name(() => usage, "getUsage")
|
|
1751
|
+
} : {},
|
|
1752
|
+
...createdAt != null ? {
|
|
1753
|
+
getCreatedAt: /* @__PURE__ */ __name(() => createdAt, "getCreatedAt")
|
|
1754
|
+
} : {}
|
|
1755
|
+
});
|
|
1756
|
+
return /* @__PURE__ */ React3.createElement(MessageItemView, {
|
|
1757
|
+
item,
|
|
1758
|
+
...slots
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
__name(MessageItem, "MessageItem");
|
|
1762
|
+
function MessageItemView({ item, ...slots }) {
|
|
1763
|
+
const { classNames } = slots;
|
|
1764
|
+
const actionRow = /* @__PURE__ */ React3.createElement("div", {
|
|
1765
|
+
className: classNames?.actions,
|
|
1766
|
+
"data-actions-for": item.role
|
|
1767
|
+
}, item.copy.available ? /* @__PURE__ */ React3.createElement("button", {
|
|
1768
|
+
type: "button",
|
|
1769
|
+
onClick: item.copy.copy,
|
|
1770
|
+
className: classNames?.actionButton,
|
|
1771
|
+
title: item.copy.copied ? "Copied!" : "Copy message text"
|
|
1772
|
+
}, item.copy.copied ? "Copied" : "Copy") : null, item.fork.available ? /* @__PURE__ */ React3.createElement("button", {
|
|
1773
|
+
type: "button",
|
|
1774
|
+
onClick: item.fork.run,
|
|
1775
|
+
className: classNames?.actionButton,
|
|
1776
|
+
title: "Fork thread from this message"
|
|
1777
|
+
}, "Fork") : null, item.regenerate.available ? /* @__PURE__ */ React3.createElement("button", {
|
|
1778
|
+
type: "button",
|
|
1779
|
+
onClick: item.regenerate.run,
|
|
1780
|
+
className: classNames?.actionButton,
|
|
1781
|
+
title: "Regenerate response"
|
|
1782
|
+
}, "Regenerate") : null, slots.meta, !item.isUser && item.usage ? /* @__PURE__ */ React3.createElement("span", {
|
|
1783
|
+
className: classNames?.usage,
|
|
1784
|
+
title: `Input ${item.usage.inputTokens.toLocaleString()} \xB7 Output ${item.usage.outputTokens.toLocaleString()} tokens`
|
|
1785
|
+
}, item.usage.costLabel, " \xB7 ", item.usage.tokensLabel) : null, item.timestamp ? /* @__PURE__ */ React3.createElement("span", {
|
|
1786
|
+
className: classNames?.timestamp,
|
|
1787
|
+
title: item.timestamp.absolute
|
|
1788
|
+
}, item.timestamp.relative) : null);
|
|
1789
|
+
if (item.edit.available) {
|
|
1790
|
+
return /* @__PURE__ */ React3.createElement(EditableUserBubble, {
|
|
1791
|
+
item,
|
|
1792
|
+
slots,
|
|
1793
|
+
actions: actionRow
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
const className = joinClasses(classNames?.root, classNames?.byRole?.[item.role]);
|
|
1797
|
+
return /* @__PURE__ */ React3.createElement("div", {
|
|
1798
|
+
className,
|
|
1799
|
+
"data-role": item.role,
|
|
1800
|
+
"data-streaming": item.isStreaming ? "true" : void 0
|
|
1801
|
+
}, /* @__PURE__ */ React3.createElement("div", null, renderBlocks(item, slots)), actionRow);
|
|
1802
|
+
}
|
|
1803
|
+
__name(MessageItemView, "MessageItemView");
|
|
1804
|
+
function renderBlocks(item, slots) {
|
|
1805
|
+
const nodes = [];
|
|
1806
|
+
for (const block of item.blocks) {
|
|
1807
|
+
if (block.kind === "text") {
|
|
1808
|
+
nodes.push(/* @__PURE__ */ React3.createElement("div", {
|
|
1809
|
+
key: block.key,
|
|
1810
|
+
className: slots.classNames?.text
|
|
1811
|
+
}, slots.renderText ? slots.renderText(block.text, {
|
|
1812
|
+
isStreaming: item.isStreaming
|
|
1813
|
+
}) : block.text));
|
|
1814
|
+
continue;
|
|
1815
|
+
}
|
|
1816
|
+
if (block.kind === "reasoning") {
|
|
1817
|
+
nodes.push(renderReasoningBlock(block, slots));
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
if (block.kind === "files") {
|
|
1821
|
+
nodes.push(renderFilesBlock(block, slots));
|
|
1822
|
+
continue;
|
|
1823
|
+
}
|
|
1824
|
+
if (block.kind === "tools") {
|
|
1825
|
+
nodes.push(...renderToolBlock(item, block, slots));
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
return nodes;
|
|
1829
|
+
}
|
|
1830
|
+
__name(renderBlocks, "renderBlocks");
|
|
1831
|
+
function renderFilesBlock(block, slots) {
|
|
1832
|
+
return /* @__PURE__ */ React3.createElement("div", {
|
|
1833
|
+
key: block.key,
|
|
1834
|
+
className: slots.classNames?.files,
|
|
1835
|
+
"data-files": "true"
|
|
1836
|
+
}, slots.renderFiles ? slots.renderFiles(block.files) : block.files.map((file) => /* @__PURE__ */ React3.createElement("a", {
|
|
1837
|
+
key: file.url,
|
|
1838
|
+
href: file.url,
|
|
1839
|
+
target: "_blank",
|
|
1840
|
+
rel: "noreferrer",
|
|
1841
|
+
className: slots.classNames?.file,
|
|
1842
|
+
title: file.filename ?? file.mediaType
|
|
1843
|
+
}, file.isImage ? /* @__PURE__ */ React3.createElement("img", {
|
|
1844
|
+
src: file.url,
|
|
1845
|
+
alt: file.filename ?? "image",
|
|
1846
|
+
className: slots.classNames?.fileImage
|
|
1847
|
+
}) : file.filename ?? file.mediaType)));
|
|
1848
|
+
}
|
|
1849
|
+
__name(renderFilesBlock, "renderFilesBlock");
|
|
1850
|
+
function renderReasoningBlock(block, slots) {
|
|
1851
|
+
return /* @__PURE__ */ React3.createElement("div", {
|
|
1852
|
+
key: block.key,
|
|
1853
|
+
className: slots.classNames?.reasoning,
|
|
1854
|
+
"data-reasoning": "true",
|
|
1855
|
+
"data-streaming": block.isStreaming ? "true" : void 0
|
|
1856
|
+
}, /* @__PURE__ */ React3.createElement("button", {
|
|
1857
|
+
type: "button",
|
|
1858
|
+
className: slots.classNames?.reasoningToggle,
|
|
1859
|
+
onClick: /* @__PURE__ */ __name(() => block.toggle(), "onClick"),
|
|
1860
|
+
"aria-expanded": block.isOpen
|
|
1861
|
+
}, slots.reasoningLabel ?? "Reasoning"), block.isOpen ? /* @__PURE__ */ React3.createElement("div", {
|
|
1862
|
+
className: slots.classNames?.reasoningText
|
|
1863
|
+
}, slots.renderReasoning ? slots.renderReasoning(block.text, {
|
|
1864
|
+
isStreaming: block.isStreaming,
|
|
1865
|
+
isOpen: block.isOpen
|
|
1866
|
+
}) : block.text) : null);
|
|
1867
|
+
}
|
|
1868
|
+
__name(renderReasoningBlock, "renderReasoningBlock");
|
|
1869
|
+
function renderToolBlock(item, block, slots) {
|
|
1870
|
+
if (slots.renderToolGroup) {
|
|
1871
|
+
return [
|
|
1872
|
+
/* @__PURE__ */ React3.createElement(React3.Fragment, {
|
|
1873
|
+
key: block.key
|
|
1874
|
+
}, slots.renderToolGroup(block.parts, block.key))
|
|
1875
|
+
];
|
|
1876
|
+
}
|
|
1877
|
+
if (!slots.renderToolPart) {
|
|
1878
|
+
return [];
|
|
1879
|
+
}
|
|
1880
|
+
const renderToolPart = slots.renderToolPart;
|
|
1881
|
+
return block.parts.map((part) => {
|
|
1882
|
+
const partKey = `${item.id}-${part.toolCallId}`;
|
|
1883
|
+
return /* @__PURE__ */ React3.createElement(React3.Fragment, {
|
|
1884
|
+
key: partKey
|
|
1885
|
+
}, renderToolPart(part, partKey));
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
__name(renderToolBlock, "renderToolBlock");
|
|
1889
|
+
function EditableUserBubble({ item, slots, actions }) {
|
|
1890
|
+
const { classNames } = slots;
|
|
1891
|
+
const { edit } = item;
|
|
1892
|
+
if (!edit.isEditing) {
|
|
1893
|
+
return /* @__PURE__ */ React3.createElement("div", {
|
|
1894
|
+
className: joinClasses(classNames?.root, classNames?.byRole?.user),
|
|
1895
|
+
"data-role": "user"
|
|
1896
|
+
}, /* @__PURE__ */ React3.createElement("div", {
|
|
1897
|
+
className: classNames?.text
|
|
1898
|
+
}, slots.renderText ? slots.renderText(item.text, {
|
|
1899
|
+
isStreaming: false
|
|
1900
|
+
}) : item.text), /* @__PURE__ */ React3.createElement("button", {
|
|
1901
|
+
type: "button",
|
|
1902
|
+
onClick: edit.start,
|
|
1903
|
+
className: classNames?.actionButton,
|
|
1904
|
+
title: "Edit & resubmit"
|
|
1905
|
+
}, "Edit"), actions);
|
|
1906
|
+
}
|
|
1907
|
+
const textareaProps = edit.getTextareaProps();
|
|
1908
|
+
return /* @__PURE__ */ React3.createElement("div", {
|
|
1909
|
+
"data-editing": "true"
|
|
1910
|
+
}, /* @__PURE__ */ React3.createElement("textarea", {
|
|
1911
|
+
...textareaProps,
|
|
1912
|
+
rows: Math.min(8, edit.draft.split("\n").length + 1),
|
|
1913
|
+
className: classNames?.editTextarea
|
|
1914
|
+
}), /* @__PURE__ */ React3.createElement("div", {
|
|
1915
|
+
className: classNames?.editActions
|
|
1916
|
+
}, /* @__PURE__ */ React3.createElement("button", {
|
|
1917
|
+
type: "button",
|
|
1918
|
+
onClick: edit.cancel
|
|
1919
|
+
}, "Cancel"), /* @__PURE__ */ React3.createElement("button", {
|
|
1920
|
+
type: "button",
|
|
1921
|
+
onClick: edit.save,
|
|
1922
|
+
disabled: !edit.canSave
|
|
1923
|
+
}, "Resend")));
|
|
1924
|
+
}
|
|
1925
|
+
__name(EditableUserBubble, "EditableUserBubble");
|
|
1926
|
+
function joinClasses(...entries) {
|
|
1927
|
+
const joined = entries.filter((entry) => entry).join(" ");
|
|
1928
|
+
return joined === "" ? void 0 : joined;
|
|
1929
|
+
}
|
|
1930
|
+
__name(joinClasses, "joinClasses");
|
|
1931
|
+
|
|
1932
|
+
// src/components/message-list.tsx
|
|
1933
|
+
function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, reasoningLabel, getMeta, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
|
|
1934
|
+
const transcript = useChatTranscript({
|
|
1935
|
+
messages,
|
|
1936
|
+
status,
|
|
1937
|
+
...onFork ? {
|
|
1938
|
+
onFork
|
|
1939
|
+
} : {},
|
|
1940
|
+
...editable !== void 0 ? {
|
|
1941
|
+
editable
|
|
1942
|
+
} : {},
|
|
1943
|
+
...onEditSubmit ? {
|
|
1944
|
+
onEditSubmit
|
|
1945
|
+
} : {},
|
|
1946
|
+
...regeneratable !== void 0 ? {
|
|
1947
|
+
regeneratable
|
|
1948
|
+
} : {},
|
|
1949
|
+
...onRegenerate ? {
|
|
1950
|
+
onRegenerate
|
|
1951
|
+
} : {},
|
|
1952
|
+
...getUsage ? {
|
|
1953
|
+
getUsage
|
|
1954
|
+
} : {},
|
|
1955
|
+
...getCreatedAt ? {
|
|
1956
|
+
getCreatedAt
|
|
1957
|
+
} : {},
|
|
1958
|
+
...followUps !== void 0 ? {
|
|
1959
|
+
followUps
|
|
1960
|
+
} : {}
|
|
1961
|
+
});
|
|
1962
|
+
if (transcript.showEmptyState) {
|
|
1963
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1964
|
+
className: classNames?.empty
|
|
1965
|
+
}, emptyState);
|
|
1966
|
+
}
|
|
1967
|
+
const slots = {
|
|
1968
|
+
...renderToolPart ? {
|
|
1969
|
+
renderToolPart
|
|
1970
|
+
} : {},
|
|
1971
|
+
...renderToolGroup ? {
|
|
1972
|
+
renderToolGroup
|
|
1973
|
+
} : {},
|
|
1974
|
+
...renderText ? {
|
|
1975
|
+
renderText
|
|
1976
|
+
} : {},
|
|
1977
|
+
...renderReasoning ? {
|
|
1978
|
+
renderReasoning
|
|
1979
|
+
} : {},
|
|
1980
|
+
...renderFiles ? {
|
|
1981
|
+
renderFiles
|
|
1982
|
+
} : {},
|
|
1983
|
+
...reasoningLabel !== void 0 ? {
|
|
1984
|
+
reasoningLabel
|
|
1985
|
+
} : {},
|
|
1986
|
+
...classNames?.message ? {
|
|
1987
|
+
classNames: classNames.message
|
|
1988
|
+
} : {}
|
|
1989
|
+
};
|
|
1990
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1991
|
+
className: classNames?.root
|
|
1992
|
+
}, transcript.window.canLoadEarlier ? /* @__PURE__ */ React.createElement("button", {
|
|
1993
|
+
type: "button",
|
|
1994
|
+
className: classNames?.loadEarlier,
|
|
1995
|
+
onClick: transcript.window.loadEarlier
|
|
1996
|
+
}, "Load earlier (", transcript.window.hiddenCount, " more)") : null, transcript.items.map((item) => /* @__PURE__ */ React.createElement(MessageItemView, {
|
|
1997
|
+
key: item.id,
|
|
1998
|
+
item,
|
|
1999
|
+
...slots,
|
|
2000
|
+
...getMeta ? {
|
|
2001
|
+
meta: getMeta(item.message)
|
|
2002
|
+
} : {}
|
|
2003
|
+
})), transcript.showTypingIndicator ? /* @__PURE__ */ React.createElement("div", {
|
|
2004
|
+
className: classNames?.typing
|
|
2005
|
+
}, typingIndicator ?? "Thinking\u2026") : null, error && onRetry ? /* @__PURE__ */ React.createElement("div", {
|
|
2006
|
+
className: classNames?.error
|
|
2007
|
+
}, /* @__PURE__ */ React.createElement("span", null, error.message || "The assistant didn't respond."), /* @__PURE__ */ React.createElement("button", {
|
|
2008
|
+
type: "button",
|
|
2009
|
+
className: classNames?.errorRetry,
|
|
2010
|
+
onClick: /* @__PURE__ */ __name(() => void onRetry(), "onClick")
|
|
2011
|
+
}, "Retry")) : null, transcript.showFollowUps && onFollowUpClick && followUps ? /* @__PURE__ */ React.createElement("div", {
|
|
2012
|
+
className: classNames?.followUps
|
|
2013
|
+
}, followUps.map((text) => /* @__PURE__ */ React.createElement("button", {
|
|
2014
|
+
key: text,
|
|
2015
|
+
type: "button",
|
|
2016
|
+
className: classNames?.followUpChip,
|
|
2017
|
+
onClick: /* @__PURE__ */ __name(() => onFollowUpClick(text), "onClick")
|
|
2018
|
+
}, text))) : null);
|
|
2019
|
+
}
|
|
2020
|
+
__name(MessageList, "MessageList");
|
|
2021
|
+
|
|
2022
|
+
// src/components/speech-languages.ts
|
|
2023
|
+
var DEFAULT_SPEECH_LANG = "en-US";
|
|
2024
|
+
var SPEECH_LANGUAGES = [
|
|
2025
|
+
{
|
|
2026
|
+
tag: "en-US",
|
|
2027
|
+
label: "English (US)"
|
|
2028
|
+
},
|
|
2029
|
+
{
|
|
2030
|
+
tag: "en-GB",
|
|
2031
|
+
label: "English (UK)"
|
|
2032
|
+
},
|
|
2033
|
+
{
|
|
2034
|
+
tag: "pt-BR",
|
|
2035
|
+
label: "Portuguese (Brazil)"
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
tag: "es-ES",
|
|
2039
|
+
label: "Spanish (Spain)"
|
|
2040
|
+
}
|
|
2041
|
+
];
|
|
2042
|
+
var STORAGE_KEY = "nestjs-agent-chat-speech-lang";
|
|
2043
|
+
function loadStoredSpeechLang() {
|
|
2044
|
+
if (typeof window === "undefined") return DEFAULT_SPEECH_LANG;
|
|
2045
|
+
try {
|
|
2046
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
2047
|
+
if (raw && SPEECH_LANGUAGES.some((language) => language.tag === raw)) {
|
|
2048
|
+
return raw;
|
|
2049
|
+
}
|
|
2050
|
+
} catch {
|
|
2051
|
+
}
|
|
2052
|
+
return DEFAULT_SPEECH_LANG;
|
|
2053
|
+
}
|
|
2054
|
+
__name(loadStoredSpeechLang, "loadStoredSpeechLang");
|
|
1422
2055
|
function persistSpeechLang(tag) {
|
|
1423
2056
|
if (typeof window === "undefined") return;
|
|
1424
2057
|
try {
|
|
@@ -1427,6 +2060,776 @@ function persistSpeechLang(tag) {
|
|
|
1427
2060
|
}
|
|
1428
2061
|
}
|
|
1429
2062
|
__name(persistSpeechLang, "persistSpeechLang");
|
|
2063
|
+
|
|
2064
|
+
// src/composer/model.ts
|
|
2065
|
+
var WHITESPACE = /\s/;
|
|
2066
|
+
function findActiveTrigger(text, caret, sources) {
|
|
2067
|
+
const end = Math.max(0, Math.min(caret, text.length));
|
|
2068
|
+
for (let index = end - 1; index >= 0; index--) {
|
|
2069
|
+
const char = text[index];
|
|
2070
|
+
if (char === void 0 || WHITESPACE.test(char)) {
|
|
2071
|
+
return null;
|
|
2072
|
+
}
|
|
2073
|
+
const source = sources.find((candidate) => candidate.trigger === char);
|
|
2074
|
+
if (source && isAllowedAt(text, index, source.position)) {
|
|
2075
|
+
return {
|
|
2076
|
+
source,
|
|
2077
|
+
index,
|
|
2078
|
+
query: text.slice(index + 1, end)
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
return null;
|
|
2083
|
+
}
|
|
2084
|
+
__name(findActiveTrigger, "findActiveTrigger");
|
|
2085
|
+
function isAllowedAt(text, index, position) {
|
|
2086
|
+
if (index === 0) {
|
|
2087
|
+
return true;
|
|
2088
|
+
}
|
|
2089
|
+
if (position === "start") {
|
|
2090
|
+
return false;
|
|
2091
|
+
}
|
|
2092
|
+
const previous = text[index - 1];
|
|
2093
|
+
return previous !== void 0 && WHITESPACE.test(previous);
|
|
2094
|
+
}
|
|
2095
|
+
__name(isAllowedAt, "isAllowedAt");
|
|
2096
|
+
function filterAutocompleteItems(items, query) {
|
|
2097
|
+
const needle = query.trim().toLowerCase();
|
|
2098
|
+
return items.filter((item) => item.label.toLowerCase().includes(needle) || (item.description?.toLowerCase().includes(needle) ?? false));
|
|
2099
|
+
}
|
|
2100
|
+
__name(filterAutocompleteItems, "filterAutocompleteItems");
|
|
2101
|
+
function applyCompletion(text, caret, match, item) {
|
|
2102
|
+
const end = Math.max(match.index, Math.min(caret, text.length));
|
|
2103
|
+
const inserted = `${match.source.trigger}${item.value ?? item.label}${match.source.insertSuffix ?? " "}`;
|
|
2104
|
+
return {
|
|
2105
|
+
text: text.slice(0, match.index) + inserted + text.slice(end),
|
|
2106
|
+
caret: match.index + inserted.length
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
__name(applyCompletion, "applyCompletion");
|
|
2110
|
+
|
|
2111
|
+
// src/composer/skills-source.ts
|
|
2112
|
+
function createSkillsSource(options) {
|
|
2113
|
+
const byThread = /* @__PURE__ */ new Map();
|
|
2114
|
+
return {
|
|
2115
|
+
id: "skills",
|
|
2116
|
+
label: options.label ?? "Skills",
|
|
2117
|
+
trigger: options.trigger ?? "/",
|
|
2118
|
+
position: "start",
|
|
2119
|
+
getItems: /* @__PURE__ */ __name(() => {
|
|
2120
|
+
const threadId = options.getThreadId?.();
|
|
2121
|
+
const key = threadId ?? "";
|
|
2122
|
+
const cached = byThread.get(key);
|
|
2123
|
+
if (cached) {
|
|
2124
|
+
return cached;
|
|
2125
|
+
}
|
|
2126
|
+
const pending = options.client.listSkills(threadId).then((entries) => entries.map(toItem)).catch((error) => {
|
|
2127
|
+
byThread.delete(key);
|
|
2128
|
+
throw error;
|
|
2129
|
+
});
|
|
2130
|
+
byThread.set(key, pending);
|
|
2131
|
+
return pending;
|
|
2132
|
+
}, "getItems")
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
__name(createSkillsSource, "createSkillsSource");
|
|
2136
|
+
function toItem(entry) {
|
|
2137
|
+
const data = {
|
|
2138
|
+
scope: entry.scope,
|
|
2139
|
+
...entry.shadows !== void 0 ? {
|
|
2140
|
+
shadows: entry.shadows
|
|
2141
|
+
} : {}
|
|
2142
|
+
};
|
|
2143
|
+
return {
|
|
2144
|
+
id: entry.name,
|
|
2145
|
+
label: entry.name,
|
|
2146
|
+
description: entry.description,
|
|
2147
|
+
// `shadows` is present only on a conflict, so saying "overrides" is never noise: without it a
|
|
2148
|
+
// user cannot tell "there is no org default" from "there is one and mine wins".
|
|
2149
|
+
hint: entry.shadows === void 0 ? entry.scope : `${entry.scope} \xB7 overrides ${entry.shadows.join(", ")}`,
|
|
2150
|
+
data
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
__name(toItem, "toItem");
|
|
2154
|
+
|
|
2155
|
+
// src/composer/use-composer-autocomplete.ts
|
|
2156
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useId, useLayoutEffect, useMemo as useMemo2, useRef as useRef4, useState as useState5 } from "react";
|
|
2157
|
+
function useComposerAutocomplete(options) {
|
|
2158
|
+
const { value, sources, acceptOnTab = true } = options;
|
|
2159
|
+
const latest = useRef4(options);
|
|
2160
|
+
latest.current = options;
|
|
2161
|
+
const inputRef = useRef4(null);
|
|
2162
|
+
const [caret, setCaret] = useState5(value.length);
|
|
2163
|
+
const [items, setItems] = useState5([]);
|
|
2164
|
+
const [isLoading, setLoading] = useState5(false);
|
|
2165
|
+
const [error, setError] = useState5(null);
|
|
2166
|
+
const [activeIndex, setActiveIndex] = useState5(0);
|
|
2167
|
+
const [dismissedIndex, setDismissedIndex] = useState5(null);
|
|
2168
|
+
const safeCaret = Math.min(caret, value.length);
|
|
2169
|
+
const match = useMemo2(() => findActiveTrigger(value, safeCaret, sources), [
|
|
2170
|
+
value,
|
|
2171
|
+
safeCaret,
|
|
2172
|
+
sources
|
|
2173
|
+
]);
|
|
2174
|
+
const isActive = match !== null && dismissedIndex !== match.index;
|
|
2175
|
+
const matchRef = useRef4(match);
|
|
2176
|
+
matchRef.current = match;
|
|
2177
|
+
const caretRef = useRef4(safeCaret);
|
|
2178
|
+
caretRef.current = safeCaret;
|
|
2179
|
+
const itemsRef = useRef4(items);
|
|
2180
|
+
itemsRef.current = items;
|
|
2181
|
+
const activeIndexRef = useRef4(activeIndex);
|
|
2182
|
+
activeIndexRef.current = activeIndex;
|
|
2183
|
+
const requestKey = isActive && match ? `${match.source.id}\0${match.query}` : null;
|
|
2184
|
+
useEffect5(() => {
|
|
2185
|
+
const current = matchRef.current;
|
|
2186
|
+
if (requestKey === null || current === null) {
|
|
2187
|
+
setItems([]);
|
|
2188
|
+
setLoading(false);
|
|
2189
|
+
setError(null);
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
const { source, query } = current;
|
|
2193
|
+
const controller = new AbortController();
|
|
2194
|
+
let superseded = false;
|
|
2195
|
+
const settle = /* @__PURE__ */ __name((next) => {
|
|
2196
|
+
if (superseded) return;
|
|
2197
|
+
setItems((source.filter ?? filterAutocompleteItems)(next, query));
|
|
2198
|
+
setActiveIndex(0);
|
|
2199
|
+
setLoading(false);
|
|
2200
|
+
setError(null);
|
|
2201
|
+
}, "settle");
|
|
2202
|
+
const failed = /* @__PURE__ */ __name((cause) => {
|
|
2203
|
+
if (superseded) return;
|
|
2204
|
+
setItems([]);
|
|
2205
|
+
setLoading(false);
|
|
2206
|
+
setError(cause instanceof Error ? cause.message : "Could not load suggestions");
|
|
2207
|
+
latest.current.onError?.(cause, source);
|
|
2208
|
+
}, "failed");
|
|
2209
|
+
let result;
|
|
2210
|
+
try {
|
|
2211
|
+
result = source.getItems(query, controller.signal);
|
|
2212
|
+
} catch (cause) {
|
|
2213
|
+
failed(cause);
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
if (!isPromise(result)) {
|
|
2217
|
+
settle(result);
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
setLoading(true);
|
|
2221
|
+
setError(null);
|
|
2222
|
+
result.then(settle, failed);
|
|
2223
|
+
return () => {
|
|
2224
|
+
superseded = true;
|
|
2225
|
+
controller.abort();
|
|
2226
|
+
};
|
|
2227
|
+
}, [
|
|
2228
|
+
requestKey
|
|
2229
|
+
]);
|
|
2230
|
+
const isOpen = isActive;
|
|
2231
|
+
const activeItem = isOpen ? items[activeIndex] ?? null : null;
|
|
2232
|
+
const reactId = useId();
|
|
2233
|
+
const listboxId = options.id ?? `agent-autocomplete-${reactId}`;
|
|
2234
|
+
const optionId = useCallback4((index) => `${listboxId}-option-${index}`, [
|
|
2235
|
+
listboxId
|
|
2236
|
+
]);
|
|
2237
|
+
const highlight = useCallback4((index) => {
|
|
2238
|
+
const total = itemsRef.current.length;
|
|
2239
|
+
if (total === 0) return;
|
|
2240
|
+
setActiveIndex((index % total + total) % total);
|
|
2241
|
+
}, []);
|
|
2242
|
+
const moveHighlight = useCallback4((delta) => highlight(activeIndexRef.current + delta), [
|
|
2243
|
+
highlight
|
|
2244
|
+
]);
|
|
2245
|
+
const dismiss = useCallback4(() => {
|
|
2246
|
+
const current = matchRef.current;
|
|
2247
|
+
if (current) setDismissedIndex(current.index);
|
|
2248
|
+
}, []);
|
|
2249
|
+
const pendingCaret = useRef4(null);
|
|
2250
|
+
const accept = useCallback4((item) => {
|
|
2251
|
+
const current = matchRef.current;
|
|
2252
|
+
if (!current) return;
|
|
2253
|
+
const chosen = item ?? itemsRef.current[activeIndexRef.current];
|
|
2254
|
+
if (!chosen) return;
|
|
2255
|
+
const edit = applyCompletion(latest.current.value, caretRef.current, current, chosen);
|
|
2256
|
+
latest.current.onValueChange(edit.text);
|
|
2257
|
+
setCaret(edit.caret);
|
|
2258
|
+
setDismissedIndex(null);
|
|
2259
|
+
pendingCaret.current = edit.caret;
|
|
2260
|
+
}, []);
|
|
2261
|
+
useLayoutEffect(() => {
|
|
2262
|
+
const position = pendingCaret.current;
|
|
2263
|
+
if (position === null) return;
|
|
2264
|
+
pendingCaret.current = null;
|
|
2265
|
+
inputRef.current?.setSelectionRange(position, position);
|
|
2266
|
+
});
|
|
2267
|
+
const readCaret = useCallback4((element) => {
|
|
2268
|
+
setCaret(element.selectionStart ?? element.value.length);
|
|
2269
|
+
}, []);
|
|
2270
|
+
const onKeyDown = useCallback4((event) => {
|
|
2271
|
+
if (!isOpen) return;
|
|
2272
|
+
const hasItems = itemsRef.current.length > 0;
|
|
2273
|
+
switch (event.key) {
|
|
2274
|
+
case "ArrowDown":
|
|
2275
|
+
if (!hasItems) return;
|
|
2276
|
+
event.preventDefault();
|
|
2277
|
+
moveHighlight(1);
|
|
2278
|
+
return;
|
|
2279
|
+
case "ArrowUp":
|
|
2280
|
+
if (!hasItems) return;
|
|
2281
|
+
event.preventDefault();
|
|
2282
|
+
moveHighlight(-1);
|
|
2283
|
+
return;
|
|
2284
|
+
case "Enter":
|
|
2285
|
+
if (!hasItems) return;
|
|
2286
|
+
event.preventDefault();
|
|
2287
|
+
accept();
|
|
2288
|
+
return;
|
|
2289
|
+
case "Tab":
|
|
2290
|
+
if (!acceptOnTab || event.shiftKey || !hasItems) return;
|
|
2291
|
+
event.preventDefault();
|
|
2292
|
+
accept();
|
|
2293
|
+
return;
|
|
2294
|
+
case "Escape":
|
|
2295
|
+
event.preventDefault();
|
|
2296
|
+
dismiss();
|
|
2297
|
+
return;
|
|
2298
|
+
default:
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
}, [
|
|
2302
|
+
isOpen,
|
|
2303
|
+
acceptOnTab,
|
|
2304
|
+
accept,
|
|
2305
|
+
dismiss,
|
|
2306
|
+
moveHighlight
|
|
2307
|
+
]);
|
|
2308
|
+
const onChange = useCallback4((event) => {
|
|
2309
|
+
readCaret(event.target);
|
|
2310
|
+
const next = findActiveTrigger(event.target.value, event.target.selectionStart ?? event.target.value.length, latest.current.sources);
|
|
2311
|
+
setDismissedIndex((current) => current !== null && next?.index === current ? current : null);
|
|
2312
|
+
}, [
|
|
2313
|
+
readCaret
|
|
2314
|
+
]);
|
|
2315
|
+
const onSelect = useCallback4((event) => readCaret(event.currentTarget), [
|
|
2316
|
+
readCaret
|
|
2317
|
+
]);
|
|
2318
|
+
const onClick = useCallback4((event) => readCaret(event.currentTarget), [
|
|
2319
|
+
readCaret
|
|
2320
|
+
]);
|
|
2321
|
+
const setInput = useCallback4((element) => {
|
|
2322
|
+
inputRef.current = element;
|
|
2323
|
+
}, []);
|
|
2324
|
+
const getInputProps = useCallback4(() => ({
|
|
2325
|
+
ref: setInput,
|
|
2326
|
+
onChange,
|
|
2327
|
+
onKeyDown,
|
|
2328
|
+
onSelect,
|
|
2329
|
+
onClick,
|
|
2330
|
+
role: "combobox",
|
|
2331
|
+
"aria-expanded": isOpen,
|
|
2332
|
+
"aria-autocomplete": "list",
|
|
2333
|
+
"aria-haspopup": "listbox",
|
|
2334
|
+
...isOpen ? {
|
|
2335
|
+
"aria-controls": listboxId
|
|
2336
|
+
} : {},
|
|
2337
|
+
...activeItem ? {
|
|
2338
|
+
"aria-activedescendant": optionId(activeIndex)
|
|
2339
|
+
} : {}
|
|
2340
|
+
}), [
|
|
2341
|
+
setInput,
|
|
2342
|
+
onChange,
|
|
2343
|
+
onKeyDown,
|
|
2344
|
+
onSelect,
|
|
2345
|
+
onClick,
|
|
2346
|
+
isOpen,
|
|
2347
|
+
listboxId,
|
|
2348
|
+
activeItem,
|
|
2349
|
+
activeIndex,
|
|
2350
|
+
optionId
|
|
2351
|
+
]);
|
|
2352
|
+
const getListboxProps = useCallback4(() => ({
|
|
2353
|
+
id: listboxId,
|
|
2354
|
+
role: "listbox",
|
|
2355
|
+
"aria-label": match?.source.label ?? match?.source.id ?? "Suggestions"
|
|
2356
|
+
}), [
|
|
2357
|
+
listboxId,
|
|
2358
|
+
match
|
|
2359
|
+
]);
|
|
2360
|
+
const getOptionProps = useCallback4((index) => ({
|
|
2361
|
+
id: optionId(index),
|
|
2362
|
+
role: "option",
|
|
2363
|
+
"aria-selected": index === activeIndex
|
|
2364
|
+
}), [
|
|
2365
|
+
optionId,
|
|
2366
|
+
activeIndex
|
|
2367
|
+
]);
|
|
2368
|
+
return {
|
|
2369
|
+
isOpen,
|
|
2370
|
+
source: isOpen && match ? match.source : null,
|
|
2371
|
+
trigger: isOpen && match ? match.source.trigger : null,
|
|
2372
|
+
query: match?.query ?? "",
|
|
2373
|
+
items,
|
|
2374
|
+
activeIndex,
|
|
2375
|
+
activeItem,
|
|
2376
|
+
isLoading,
|
|
2377
|
+
error,
|
|
2378
|
+
highlight,
|
|
2379
|
+
moveHighlight,
|
|
2380
|
+
accept,
|
|
2381
|
+
dismiss,
|
|
2382
|
+
getInputProps,
|
|
2383
|
+
getListboxProps,
|
|
2384
|
+
getOptionProps
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
__name(useComposerAutocomplete, "useComposerAutocomplete");
|
|
2388
|
+
function isPromise(value) {
|
|
2389
|
+
return typeof value.then === "function";
|
|
2390
|
+
}
|
|
2391
|
+
__name(isPromise, "isPromise");
|
|
2392
|
+
|
|
2393
|
+
// src/stored-message-to-ui-message.ts
|
|
2394
|
+
function storedMessageToUiMessage(message) {
|
|
2395
|
+
const parts = [];
|
|
2396
|
+
if (message.content) {
|
|
2397
|
+
parts.push({
|
|
2398
|
+
type: "text",
|
|
2399
|
+
text: message.content
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
for (const attachment of message.attachments ?? []) {
|
|
2403
|
+
parts.push({
|
|
2404
|
+
type: "file",
|
|
2405
|
+
mediaType: attachment.contentType,
|
|
2406
|
+
filename: attachment.name,
|
|
2407
|
+
url: attachment.url
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
for (const call of message.toolCalls ?? []) {
|
|
2411
|
+
const result = message.toolResults?.find((candidate) => candidate.id === call.id);
|
|
2412
|
+
const toolKind2 = call.kind;
|
|
2413
|
+
parts.push({
|
|
2414
|
+
type: `tool-${call.name}`,
|
|
2415
|
+
toolCallId: call.id,
|
|
2416
|
+
...toolKind2 !== void 0 ? {
|
|
2417
|
+
toolMetadata: {
|
|
2418
|
+
toolKind: toolKind2
|
|
2419
|
+
}
|
|
2420
|
+
} : {},
|
|
2421
|
+
...result !== void 0 ? {
|
|
2422
|
+
state: "output-available",
|
|
2423
|
+
input: call.input,
|
|
2424
|
+
output: result.output
|
|
2425
|
+
} : {
|
|
2426
|
+
state: "input-available",
|
|
2427
|
+
input: call.input
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
return {
|
|
2432
|
+
id: message.id,
|
|
2433
|
+
role: message.role,
|
|
2434
|
+
parts
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
__name(storedMessageToUiMessage, "storedMessageToUiMessage");
|
|
2438
|
+
|
|
2439
|
+
// src/stored-thread-to-ui-messages.ts
|
|
2440
|
+
function storedThreadToUiMessages(messages) {
|
|
2441
|
+
const turns = [];
|
|
2442
|
+
for (const message of messages) {
|
|
2443
|
+
const currentTurn = turns.at(-1);
|
|
2444
|
+
const previousRow = currentTurn?.at(-1);
|
|
2445
|
+
if (message.role === "assistant" && previousRow?.role === "assistant" && currentTurn && sameRun(previousRow, message)) {
|
|
2446
|
+
currentTurn.push(message);
|
|
2447
|
+
} else {
|
|
2448
|
+
turns.push([
|
|
2449
|
+
message
|
|
2450
|
+
]);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
return turns.map(mergeTurn);
|
|
2454
|
+
}
|
|
2455
|
+
__name(storedThreadToUiMessages, "storedThreadToUiMessages");
|
|
2456
|
+
function sameRun(previous, next) {
|
|
2457
|
+
return previous.runId === void 0 || next.runId === void 0 || previous.runId === next.runId;
|
|
2458
|
+
}
|
|
2459
|
+
__name(sameRun, "sameRun");
|
|
2460
|
+
function mergeTurn(rows) {
|
|
2461
|
+
const lastRow = rows.at(-1);
|
|
2462
|
+
if (lastRow === void 0) {
|
|
2463
|
+
throw new Error("storedThreadToUiMessages: encountered an empty turn");
|
|
2464
|
+
}
|
|
2465
|
+
if (rows.length === 1) {
|
|
2466
|
+
return storedMessageToUiMessage(lastRow);
|
|
2467
|
+
}
|
|
2468
|
+
const parts = rows.flatMap((row) => storedMessageToUiMessage(row).parts);
|
|
2469
|
+
const usage = sumUsage(rows);
|
|
2470
|
+
return {
|
|
2471
|
+
id: lastRow.id,
|
|
2472
|
+
role: lastRow.role,
|
|
2473
|
+
parts,
|
|
2474
|
+
...usage !== void 0 ? {
|
|
2475
|
+
metadata: {
|
|
2476
|
+
usage
|
|
2477
|
+
}
|
|
2478
|
+
} : {}
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
__name(mergeTurn, "mergeTurn");
|
|
2482
|
+
function sumUsage(rows) {
|
|
2483
|
+
const usages = rows.map((row) => row.usage).filter((usage) => usage !== void 0);
|
|
2484
|
+
if (usages.length === 0) {
|
|
2485
|
+
return void 0;
|
|
2486
|
+
}
|
|
2487
|
+
const allCostsUnknown = usages.every((usage) => usage.costUsd === null || usage.costUsd === void 0);
|
|
2488
|
+
return {
|
|
2489
|
+
inputTokens: usages.reduce((sum, usage) => sum + usage.inputTokens, 0),
|
|
2490
|
+
outputTokens: usages.reduce((sum, usage) => sum + usage.outputTokens, 0),
|
|
2491
|
+
costUsd: allCostsUnknown ? null : usages.reduce((sum, usage) => sum + (usage.costUsd ?? 0), 0)
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
__name(sumUsage, "sumUsage");
|
|
2495
|
+
|
|
2496
|
+
// src/use-agent-chat.ts
|
|
2497
|
+
import { useChat } from "@ai-sdk/react";
|
|
2498
|
+
import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef5, useState as useState6 } from "react";
|
|
2499
|
+
function useAgentChat(options) {
|
|
2500
|
+
const latest = useRef5(options);
|
|
2501
|
+
latest.current = options;
|
|
2502
|
+
const [runId, setRunId] = useState6(options.resumeRunId);
|
|
2503
|
+
const runIdRef = useRef5(runId);
|
|
2504
|
+
runIdRef.current = runId;
|
|
2505
|
+
const settlingRunIdRef = useRef5(void 0);
|
|
2506
|
+
const autoResumeRunIdRef = useRef5(void 0);
|
|
2507
|
+
const [autoResume, setAutoResume] = useState6(false);
|
|
2508
|
+
const [activeRunId, setActiveRunId] = useState6(null);
|
|
2509
|
+
const regenerateNext = useRef5(false);
|
|
2510
|
+
const createdThreadId = useRef5(void 0);
|
|
2511
|
+
const client = useMemo3(() => {
|
|
2512
|
+
if (options.client) return options.client;
|
|
2513
|
+
return new AgentClient({
|
|
2514
|
+
...options.baseUrl !== void 0 ? {
|
|
2515
|
+
baseUrl: options.baseUrl
|
|
2516
|
+
} : {},
|
|
2517
|
+
...options.credentials !== void 0 ? {
|
|
2518
|
+
credentials: options.credentials
|
|
2519
|
+
} : {},
|
|
2520
|
+
...options.fetch !== void 0 ? {
|
|
2521
|
+
fetch: options.fetch
|
|
2522
|
+
} : {},
|
|
2523
|
+
getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
|
|
2524
|
+
});
|
|
2525
|
+
}, []);
|
|
2526
|
+
useEffect6(() => {
|
|
2527
|
+
if (!options.resume || options.threadId === void 0) return;
|
|
2528
|
+
let cancelled = false;
|
|
2529
|
+
const threadId = options.threadId;
|
|
2530
|
+
void client.getThread(threadId).then((thread) => {
|
|
2531
|
+
if (cancelled) return;
|
|
2532
|
+
setActiveRunId(thread.activeRunId ?? null);
|
|
2533
|
+
if (thread.activeRunId != null) {
|
|
2534
|
+
autoResumeRunIdRef.current = thread.activeRunId;
|
|
2535
|
+
setAutoResume(true);
|
|
2536
|
+
}
|
|
2537
|
+
});
|
|
2538
|
+
return () => {
|
|
2539
|
+
cancelled = true;
|
|
2540
|
+
};
|
|
2541
|
+
}, [
|
|
2542
|
+
client,
|
|
2543
|
+
options.resume,
|
|
2544
|
+
options.threadId
|
|
2545
|
+
]);
|
|
2546
|
+
const transport = useMemo3(() => {
|
|
2547
|
+
function onMeta(meta) {
|
|
2548
|
+
settlingRunIdRef.current = meta.runId;
|
|
2549
|
+
setRunId(meta.runId);
|
|
2550
|
+
if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
|
|
2551
|
+
createdThreadId.current = meta.threadId;
|
|
2552
|
+
latest.current.onThreadCreated?.(meta.threadId);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
__name(onMeta, "onMeta");
|
|
2556
|
+
return new AgentChatTransport({
|
|
2557
|
+
...options.baseUrl !== void 0 ? {
|
|
2558
|
+
baseUrl: options.baseUrl
|
|
2559
|
+
} : {},
|
|
2560
|
+
...options.credentials !== void 0 ? {
|
|
2561
|
+
credentials: options.credentials
|
|
2562
|
+
} : {},
|
|
2563
|
+
...options.fetch !== void 0 ? {
|
|
2564
|
+
fetch: options.fetch
|
|
2565
|
+
} : {},
|
|
2566
|
+
...options.agent !== void 0 ? {
|
|
2567
|
+
agent: options.agent
|
|
2568
|
+
} : {},
|
|
2569
|
+
getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders"),
|
|
2570
|
+
getBody: /* @__PURE__ */ __name(() => {
|
|
2571
|
+
const current = latest.current;
|
|
2572
|
+
const pageContext = current.getPageContext?.() ?? null;
|
|
2573
|
+
const regenerate2 = regenerateNext.current;
|
|
2574
|
+
regenerateNext.current = false;
|
|
2575
|
+
const threadId = current.threadId ?? createdThreadId.current;
|
|
2576
|
+
return {
|
|
2577
|
+
...threadId !== void 0 ? {
|
|
2578
|
+
threadId
|
|
2579
|
+
} : {},
|
|
2580
|
+
...pageContext ? {
|
|
2581
|
+
pageContext
|
|
2582
|
+
} : {},
|
|
2583
|
+
...regenerate2 ? {
|
|
2584
|
+
regenerate: true
|
|
2585
|
+
} : {}
|
|
2586
|
+
};
|
|
2587
|
+
}, "getBody"),
|
|
2588
|
+
getResumeRunId: /* @__PURE__ */ __name(() => latest.current.resumeRunId ?? autoResumeRunIdRef.current, "getResumeRunId"),
|
|
2589
|
+
onMeta,
|
|
2590
|
+
onAttemptStart: /* @__PURE__ */ __name(() => {
|
|
2591
|
+
settlingRunIdRef.current = void 0;
|
|
2592
|
+
}, "onAttemptStart")
|
|
2593
|
+
});
|
|
2594
|
+
}, []);
|
|
2595
|
+
const backgroundRef = useRef5(() => Promise.resolve());
|
|
2596
|
+
const chat = useChat({
|
|
2597
|
+
transport,
|
|
2598
|
+
resume: options.resumeRunId !== void 0 || autoResume,
|
|
2599
|
+
...options.threadId !== void 0 ? {
|
|
2600
|
+
id: options.threadId
|
|
2601
|
+
} : {},
|
|
2602
|
+
...options.initialMessages !== void 0 ? {
|
|
2603
|
+
messages: options.initialMessages
|
|
2604
|
+
} : {},
|
|
2605
|
+
onFinish: /* @__PURE__ */ __name(({ isError }) => {
|
|
2606
|
+
latest.current.onFinish?.();
|
|
2607
|
+
if (latest.current.background === true) {
|
|
2608
|
+
void backgroundRef.current();
|
|
2609
|
+
}
|
|
2610
|
+
const settledRunId = settlingRunIdRef.current;
|
|
2611
|
+
if (settledRunId !== void 0) {
|
|
2612
|
+
latest.current.onRunSettled?.({
|
|
2613
|
+
runId: settledRunId,
|
|
2614
|
+
status: isError ? "failed" : "completed"
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
}, "onFinish")
|
|
2618
|
+
});
|
|
2619
|
+
const chatRef = useRef5(chat);
|
|
2620
|
+
chatRef.current = chat;
|
|
2621
|
+
const turnInFlight = useRef5(false);
|
|
2622
|
+
const isTurnInFlight = useCallback5(() => turnInFlight.current || transport.isAttemptLive, [
|
|
2623
|
+
transport
|
|
2624
|
+
]);
|
|
2625
|
+
const sendMessage = useCallback5(async (...args) => {
|
|
2626
|
+
if (isTurnInFlight()) return;
|
|
2627
|
+
turnInFlight.current = true;
|
|
2628
|
+
try {
|
|
2629
|
+
await chatRef.current.sendMessage(...args);
|
|
2630
|
+
} finally {
|
|
2631
|
+
turnInFlight.current = false;
|
|
2632
|
+
}
|
|
2633
|
+
}, [
|
|
2634
|
+
isTurnInFlight
|
|
2635
|
+
]);
|
|
2636
|
+
const addToolResult = useCallback5(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
|
|
2637
|
+
tool,
|
|
2638
|
+
toolCallId,
|
|
2639
|
+
output
|
|
2640
|
+
}), []);
|
|
2641
|
+
const [threads, setThreads] = useState6([]);
|
|
2642
|
+
const [quota, setQuota] = useState6(null);
|
|
2643
|
+
const [backgroundRuns, setBackgroundRuns] = useState6([]);
|
|
2644
|
+
const refreshBackground = useCallback5(async () => {
|
|
2645
|
+
const threadId = latest.current.threadId ?? createdThreadId.current;
|
|
2646
|
+
if (threadId === void 0) {
|
|
2647
|
+
return;
|
|
2648
|
+
}
|
|
2649
|
+
const thread = await client.getThread(threadId);
|
|
2650
|
+
const runs = backgroundRunsFromThread(thread.messages);
|
|
2651
|
+
setBackgroundRuns(runs);
|
|
2652
|
+
const landed = runs.map((run) => run.message).filter((message) => message !== void 0);
|
|
2653
|
+
if (landed.length === 0) {
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
chatRef.current.setMessages((current) => {
|
|
2657
|
+
const known = new Set(current.map((message) => message.id));
|
|
2658
|
+
const missing = landed.filter((message) => !known.has(message.id));
|
|
2659
|
+
return missing.length === 0 ? current : [
|
|
2660
|
+
...current,
|
|
2661
|
+
...missing.map((message) => storedMessageToUiMessage(message))
|
|
2662
|
+
];
|
|
2663
|
+
});
|
|
2664
|
+
}, [
|
|
2665
|
+
client
|
|
2666
|
+
]);
|
|
2667
|
+
backgroundRef.current = refreshBackground;
|
|
2668
|
+
const isWorking = backgroundRuns.some((run) => run.status === "running");
|
|
2669
|
+
useEffect6(() => {
|
|
2670
|
+
if (options.background !== true || options.threadId === void 0) {
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
void refreshBackground();
|
|
2674
|
+
}, [
|
|
2675
|
+
options.background,
|
|
2676
|
+
options.threadId,
|
|
2677
|
+
refreshBackground
|
|
2678
|
+
]);
|
|
2679
|
+
useEffect6(() => {
|
|
2680
|
+
const every = latest.current.backgroundPollMs ?? 5e3;
|
|
2681
|
+
if (latest.current.background !== true || !isWorking || every <= 0) {
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
const timer = setInterval(() => void refreshBackground(), every);
|
|
2685
|
+
return () => clearInterval(timer);
|
|
2686
|
+
}, [
|
|
2687
|
+
isWorking,
|
|
2688
|
+
refreshBackground
|
|
2689
|
+
]);
|
|
2690
|
+
const loadThreads = useCallback5(async () => {
|
|
2691
|
+
const list = await client.listThreads();
|
|
2692
|
+
setThreads(list);
|
|
2693
|
+
return list;
|
|
2694
|
+
}, [
|
|
2695
|
+
client
|
|
2696
|
+
]);
|
|
2697
|
+
const loadThread = useCallback5((id) => client.getThread(id), [
|
|
2698
|
+
client
|
|
2699
|
+
]);
|
|
2700
|
+
const deleteThread = useCallback5(async (id) => {
|
|
2701
|
+
await client.deleteThread(id);
|
|
2702
|
+
setThreads((current) => current.filter((thread) => thread.id !== id));
|
|
2703
|
+
}, [
|
|
2704
|
+
client
|
|
2705
|
+
]);
|
|
2706
|
+
const forkThread = useCallback5((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
|
|
2707
|
+
client
|
|
2708
|
+
]);
|
|
2709
|
+
const renameThread = useCallback5(async (id, title) => {
|
|
2710
|
+
await client.renameThread(id, title);
|
|
2711
|
+
setThreads((current) => current.map((thread) => thread.id === id ? {
|
|
2712
|
+
...thread,
|
|
2713
|
+
title
|
|
2714
|
+
} : thread));
|
|
2715
|
+
}, [
|
|
2716
|
+
client
|
|
2717
|
+
]);
|
|
2718
|
+
const promoteThread = useCallback5(async (id) => {
|
|
2719
|
+
await client.promoteThread(id);
|
|
2720
|
+
await loadThreads();
|
|
2721
|
+
}, [
|
|
2722
|
+
client,
|
|
2723
|
+
loadThreads
|
|
2724
|
+
]);
|
|
2725
|
+
const truncateFromMessage = useCallback5((threadId, messageId) => client.truncateFromMessage(threadId, messageId).then(() => void 0), [
|
|
2726
|
+
client
|
|
2727
|
+
]);
|
|
2728
|
+
const loadQuota = useCallback5(async () => {
|
|
2729
|
+
const today = await client.getQuotaToday();
|
|
2730
|
+
setQuota(today);
|
|
2731
|
+
return today;
|
|
2732
|
+
}, [
|
|
2733
|
+
client
|
|
2734
|
+
]);
|
|
2735
|
+
const cancel = useCallback5(async () => {
|
|
2736
|
+
chatRef.current.stop();
|
|
2737
|
+
const activeRunId2 = runIdRef.current;
|
|
2738
|
+
if (activeRunId2) {
|
|
2739
|
+
try {
|
|
2740
|
+
await client.cancelStream(activeRunId2);
|
|
2741
|
+
} catch {
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}, [
|
|
2745
|
+
client
|
|
2746
|
+
]);
|
|
2747
|
+
const approve = useCallback5(async ({ toolCallId }) => {
|
|
2748
|
+
await client.approveToolCall({
|
|
2749
|
+
toolCallId
|
|
2750
|
+
});
|
|
2751
|
+
}, [
|
|
2752
|
+
client
|
|
2753
|
+
]);
|
|
2754
|
+
const reject = useCallback5(async ({ toolCallId, reason }) => {
|
|
2755
|
+
await client.rejectToolCall({
|
|
2756
|
+
toolCallId,
|
|
2757
|
+
...reason !== void 0 ? {
|
|
2758
|
+
reason
|
|
2759
|
+
} : {}
|
|
2760
|
+
});
|
|
2761
|
+
}, [
|
|
2762
|
+
client
|
|
2763
|
+
]);
|
|
2764
|
+
const answer = useCallback5(async ({ toolCallId, answers }) => {
|
|
2765
|
+
await client.answerToolCall({
|
|
2766
|
+
toolCallId,
|
|
2767
|
+
...answers !== void 0 ? {
|
|
2768
|
+
answers
|
|
2769
|
+
} : {}
|
|
2770
|
+
});
|
|
2771
|
+
}, [
|
|
2772
|
+
client
|
|
2773
|
+
]);
|
|
2774
|
+
const skip = useCallback5(async ({ toolCallId }) => {
|
|
2775
|
+
await client.skipToolCall({
|
|
2776
|
+
toolCallId
|
|
2777
|
+
});
|
|
2778
|
+
}, [
|
|
2779
|
+
client
|
|
2780
|
+
]);
|
|
2781
|
+
const regenerate = useCallback5(() => {
|
|
2782
|
+
if (isTurnInFlight()) return;
|
|
2783
|
+
regenerateNext.current = true;
|
|
2784
|
+
turnInFlight.current = true;
|
|
2785
|
+
void chatRef.current.regenerate().finally(() => {
|
|
2786
|
+
turnInFlight.current = false;
|
|
2787
|
+
});
|
|
2788
|
+
}, [
|
|
2789
|
+
isTurnInFlight
|
|
2790
|
+
]);
|
|
2791
|
+
const background = {
|
|
2792
|
+
runs: backgroundRuns,
|
|
2793
|
+
isWorking,
|
|
2794
|
+
refresh: refreshBackground
|
|
2795
|
+
};
|
|
2796
|
+
return {
|
|
2797
|
+
...chat,
|
|
2798
|
+
sendMessage,
|
|
2799
|
+
addToolResult,
|
|
2800
|
+
/** Sub-agents this conversation started and did not wait for. */
|
|
2801
|
+
background,
|
|
2802
|
+
runId,
|
|
2803
|
+
/** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
|
|
2804
|
+
activeRunId,
|
|
2805
|
+
client,
|
|
2806
|
+
threads,
|
|
2807
|
+
loadThreads,
|
|
2808
|
+
loadThread,
|
|
2809
|
+
deleteThread,
|
|
2810
|
+
forkThread,
|
|
2811
|
+
renameThread,
|
|
2812
|
+
promoteThread,
|
|
2813
|
+
truncateFromMessage,
|
|
2814
|
+
quota,
|
|
2815
|
+
loadQuota,
|
|
2816
|
+
cancel,
|
|
2817
|
+
approve,
|
|
2818
|
+
reject,
|
|
2819
|
+
answer,
|
|
2820
|
+
skip,
|
|
2821
|
+
regenerate
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
__name(useAgentChat, "useAgentChat");
|
|
2825
|
+
async function mergeHeaders(options) {
|
|
2826
|
+
const dynamic = await options.getHeaders?.() ?? {};
|
|
2827
|
+
return {
|
|
2828
|
+
...options.headers,
|
|
2829
|
+
...dynamic
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
__name(mergeHeaders, "mergeHeaders");
|
|
1430
2833
|
export {
|
|
1431
2834
|
AgentChatTransport,
|
|
1432
2835
|
AgentClient,
|
|
@@ -1434,14 +2837,28 @@ export {
|
|
|
1434
2837
|
ChatInput,
|
|
1435
2838
|
DEFAULT_SPEECH_LANG,
|
|
1436
2839
|
MessageItem,
|
|
2840
|
+
MessageItemView,
|
|
1437
2841
|
MessageList,
|
|
1438
2842
|
SPEECH_LANGUAGES,
|
|
2843
|
+
applyCompletion,
|
|
2844
|
+
backgroundRunsFromThread,
|
|
2845
|
+
buildTranscriptBlocks,
|
|
2846
|
+
createSkillsSource,
|
|
2847
|
+
describeTimestamp,
|
|
2848
|
+
describeUsage,
|
|
2849
|
+
extractMessageText,
|
|
2850
|
+
filterAutocompleteItems,
|
|
2851
|
+
findActiveTrigger,
|
|
1439
2852
|
formatRelativeTime,
|
|
1440
2853
|
loadStoredSpeechLang,
|
|
1441
2854
|
persistSpeechLang,
|
|
1442
2855
|
storedMessageToUiMessage,
|
|
1443
2856
|
storedThreadToUiMessages,
|
|
1444
2857
|
useAgentChat,
|
|
1445
|
-
|
|
2858
|
+
useChatTranscript,
|
|
2859
|
+
useComposerAutocomplete,
|
|
2860
|
+
useSpeechRecognition,
|
|
2861
|
+
useStickToBottom,
|
|
2862
|
+
useTranscriptItem
|
|
1446
2863
|
};
|
|
1447
2864
|
//# sourceMappingURL=index.js.map
|