@dudousxd/nestjs-agent-react 0.1.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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/index.cjs +1168 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +419 -0
- package/dist/index.d.ts +419 -0
- package/dist/index.js +1121 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.cjs +75 -0
- package/dist/markdown.cjs.map +1 -0
- package/dist/markdown.d.cts +39 -0
- package/dist/markdown.d.ts +39 -0
- package/dist/markdown.js +40 -0
- package/dist/markdown.js.map +1 -0
- package/package.json +128 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var src_exports = {};
|
|
33
|
+
__export(src_exports, {
|
|
34
|
+
AgentChatTransport: () => AgentChatTransport,
|
|
35
|
+
AgentClient: () => AgentClient,
|
|
36
|
+
AgentHttpError: () => AgentHttpError,
|
|
37
|
+
ChatInput: () => ChatInput,
|
|
38
|
+
DEFAULT_SPEECH_LANG: () => DEFAULT_SPEECH_LANG,
|
|
39
|
+
MessageItem: () => MessageItem,
|
|
40
|
+
MessageList: () => MessageList,
|
|
41
|
+
SPEECH_LANGUAGES: () => SPEECH_LANGUAGES,
|
|
42
|
+
formatRelativeTime: () => formatRelativeTime,
|
|
43
|
+
loadStoredSpeechLang: () => loadStoredSpeechLang,
|
|
44
|
+
persistSpeechLang: () => persistSpeechLang,
|
|
45
|
+
useAgentChat: () => useAgentChat,
|
|
46
|
+
useSpeechRecognition: () => useSpeechRecognition
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(src_exports);
|
|
49
|
+
|
|
50
|
+
// src/agent-chat-transport.ts
|
|
51
|
+
var HEADER_RUN_ID = "x-agent-run-id";
|
|
52
|
+
var HEADER_THREAD_ID = "x-agent-thread-id";
|
|
53
|
+
var AgentChatTransport = class {
|
|
54
|
+
static {
|
|
55
|
+
__name(this, "AgentChatTransport");
|
|
56
|
+
}
|
|
57
|
+
options;
|
|
58
|
+
currentRunId;
|
|
59
|
+
currentThreadId;
|
|
60
|
+
constructor(options = {}) {
|
|
61
|
+
this.options = options;
|
|
62
|
+
}
|
|
63
|
+
/** Run id of the most recent stream — HITL approve/reject target this. */
|
|
64
|
+
get runId() {
|
|
65
|
+
return this.currentRunId;
|
|
66
|
+
}
|
|
67
|
+
/** Thread id of the most recent stream. */
|
|
68
|
+
get threadId() {
|
|
69
|
+
return this.currentThreadId;
|
|
70
|
+
}
|
|
71
|
+
async sendMessages(options) {
|
|
72
|
+
const lastMessage = options.messages.at(-1);
|
|
73
|
+
const message = lastMessage ? extractText(lastMessage) : "";
|
|
74
|
+
const body = {
|
|
75
|
+
...this.options.agent !== void 0 ? {
|
|
76
|
+
agent: this.options.agent
|
|
77
|
+
} : {},
|
|
78
|
+
...this.options.getBody?.() ?? {},
|
|
79
|
+
...options.body ?? {},
|
|
80
|
+
message
|
|
81
|
+
};
|
|
82
|
+
const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"content-type": "application/json",
|
|
86
|
+
accept: "text/event-stream",
|
|
87
|
+
...await this.resolveHeaders(options.headers)
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify(body),
|
|
90
|
+
...this.options.credentials !== void 0 ? {
|
|
91
|
+
credentials: this.options.credentials
|
|
92
|
+
} : {},
|
|
93
|
+
...options.abortSignal ? {
|
|
94
|
+
signal: options.abortSignal
|
|
95
|
+
} : {}
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok || !response.body) {
|
|
98
|
+
throw new Error(`Agent chat request failed: ${response.status} ${response.statusText}`);
|
|
99
|
+
}
|
|
100
|
+
this.captureHeaderMeta(response.headers);
|
|
101
|
+
return this.toChunkStream(response.body);
|
|
102
|
+
}
|
|
103
|
+
async reconnectToStream(options) {
|
|
104
|
+
const runId = this.options.getResumeRunId?.();
|
|
105
|
+
if (runId === void 0) return null;
|
|
106
|
+
const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat/${encodeURIComponent(runId)}/stream`, {
|
|
107
|
+
method: "GET",
|
|
108
|
+
headers: {
|
|
109
|
+
accept: "text/event-stream",
|
|
110
|
+
...await this.resolveHeaders(options.headers)
|
|
111
|
+
},
|
|
112
|
+
...this.options.credentials !== void 0 ? {
|
|
113
|
+
credentials: this.options.credentials
|
|
114
|
+
} : {}
|
|
115
|
+
});
|
|
116
|
+
if (response.status === 404) return null;
|
|
117
|
+
if (!response.ok || !response.body) {
|
|
118
|
+
throw new Error(`Agent stream reconnect failed: ${response.status} ${response.statusText}`);
|
|
119
|
+
}
|
|
120
|
+
this.captureHeaderMeta(response.headers);
|
|
121
|
+
return this.toChunkStream(response.body);
|
|
122
|
+
}
|
|
123
|
+
fetchImpl() {
|
|
124
|
+
return this.options.fetch ?? globalThis.fetch;
|
|
125
|
+
}
|
|
126
|
+
baseUrl() {
|
|
127
|
+
return (this.options.baseUrl ?? "").replace(/\/$/, "");
|
|
128
|
+
}
|
|
129
|
+
async resolveHeaders(perRequest) {
|
|
130
|
+
const dynamic = await this.options.getHeaders?.() ?? {};
|
|
131
|
+
const extra = perRequest instanceof Headers ? Object.fromEntries(perRequest.entries()) : perRequest ?? {};
|
|
132
|
+
return {
|
|
133
|
+
...this.options.headers,
|
|
134
|
+
...dynamic,
|
|
135
|
+
...extra
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
captureHeaderMeta(headers) {
|
|
139
|
+
const runId = headers.get(HEADER_RUN_ID);
|
|
140
|
+
const threadId = headers.get(HEADER_THREAD_ID);
|
|
141
|
+
if (runId) this.currentRunId = runId;
|
|
142
|
+
if (threadId) this.currentThreadId = threadId;
|
|
143
|
+
}
|
|
144
|
+
recordMeta(meta) {
|
|
145
|
+
this.currentRunId = meta.runId;
|
|
146
|
+
this.currentThreadId = meta.threadId;
|
|
147
|
+
this.options.onMeta?.(meta);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Parse the backend's SSE byte stream and synthesize a valid v7
|
|
151
|
+
* UI-message chunk stream. Recognized backend frames:
|
|
152
|
+
* - `event: meta` `data: {"runId","threadId"}` → records identity
|
|
153
|
+
* - `data: {"delta":"..."}` → `text-delta`
|
|
154
|
+
* - `event: done` `data: {}` → terminates
|
|
155
|
+
*/
|
|
156
|
+
toChunkStream(source) {
|
|
157
|
+
const reader = source.getReader();
|
|
158
|
+
const decoder = new TextDecoder();
|
|
159
|
+
const textId = `txt-${Math.random().toString(36).slice(2)}`;
|
|
160
|
+
let buffer = "";
|
|
161
|
+
let started = false;
|
|
162
|
+
const record = /* @__PURE__ */ __name((meta) => this.recordMeta(meta), "record");
|
|
163
|
+
return new ReadableStream({
|
|
164
|
+
async pull(controller) {
|
|
165
|
+
function ensureStarted() {
|
|
166
|
+
if (started) return;
|
|
167
|
+
started = true;
|
|
168
|
+
controller.enqueue({
|
|
169
|
+
type: "start"
|
|
170
|
+
});
|
|
171
|
+
controller.enqueue({
|
|
172
|
+
type: "start-step"
|
|
173
|
+
});
|
|
174
|
+
controller.enqueue({
|
|
175
|
+
type: "text-start",
|
|
176
|
+
id: textId
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
__name(ensureStarted, "ensureStarted");
|
|
180
|
+
function finish() {
|
|
181
|
+
ensureStarted();
|
|
182
|
+
controller.enqueue({
|
|
183
|
+
type: "text-end",
|
|
184
|
+
id: textId
|
|
185
|
+
});
|
|
186
|
+
controller.enqueue({
|
|
187
|
+
type: "finish-step"
|
|
188
|
+
});
|
|
189
|
+
controller.enqueue({
|
|
190
|
+
type: "finish"
|
|
191
|
+
});
|
|
192
|
+
controller.close();
|
|
193
|
+
}
|
|
194
|
+
__name(finish, "finish");
|
|
195
|
+
try {
|
|
196
|
+
while (true) {
|
|
197
|
+
const { done, value } = await reader.read();
|
|
198
|
+
if (done) {
|
|
199
|
+
finish();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
buffer += decoder.decode(value, {
|
|
203
|
+
stream: true
|
|
204
|
+
});
|
|
205
|
+
let separator = buffer.indexOf("\n\n");
|
|
206
|
+
while (separator !== -1) {
|
|
207
|
+
const rawEvent = buffer.slice(0, separator);
|
|
208
|
+
buffer = buffer.slice(separator + 2);
|
|
209
|
+
const frame = parseSseFrame(rawEvent);
|
|
210
|
+
if (frame.event === "done") {
|
|
211
|
+
finish();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (frame.event === "error") {
|
|
215
|
+
ensureStarted();
|
|
216
|
+
controller.enqueue({
|
|
217
|
+
type: "error",
|
|
218
|
+
errorText: parseErrorText(frame.data)
|
|
219
|
+
});
|
|
220
|
+
controller.close();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (frame.event === "meta") {
|
|
224
|
+
const meta = parseMeta(frame.data);
|
|
225
|
+
if (meta) record(meta);
|
|
226
|
+
} else if (frame.data) {
|
|
227
|
+
const delta = parseDelta(frame.data);
|
|
228
|
+
if (delta) {
|
|
229
|
+
ensureStarted();
|
|
230
|
+
controller.enqueue({
|
|
231
|
+
type: "text-delta",
|
|
232
|
+
id: textId,
|
|
233
|
+
delta
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
separator = buffer.indexOf("\n\n");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
controller.enqueue({
|
|
242
|
+
type: "error",
|
|
243
|
+
errorText: error instanceof Error ? error.message : "Agent stream error"
|
|
244
|
+
});
|
|
245
|
+
controller.close();
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
cancel() {
|
|
249
|
+
void reader.cancel();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
function parseSseFrame(raw) {
|
|
255
|
+
let event;
|
|
256
|
+
const dataLines = [];
|
|
257
|
+
for (const line of raw.split("\n")) {
|
|
258
|
+
if (line.startsWith("event:")) {
|
|
259
|
+
event = line.slice("event:".length).trim();
|
|
260
|
+
} else if (line.startsWith("data:")) {
|
|
261
|
+
dataLines.push(line.slice("data:".length).trimStart());
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
event,
|
|
266
|
+
data: dataLines.length > 0 ? dataLines.join("\n") : void 0
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
__name(parseSseFrame, "parseSseFrame");
|
|
270
|
+
function parseMeta(data) {
|
|
271
|
+
if (!data) return null;
|
|
272
|
+
try {
|
|
273
|
+
const parsed = JSON.parse(data);
|
|
274
|
+
if (parsed !== null && typeof parsed === "object" && "runId" in parsed && "threadId" in parsed && typeof parsed.runId === "string" && typeof parsed.threadId === "string") {
|
|
275
|
+
return {
|
|
276
|
+
runId: parsed.runId,
|
|
277
|
+
threadId: parsed.threadId
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
__name(parseMeta, "parseMeta");
|
|
285
|
+
function parseErrorText(data) {
|
|
286
|
+
if (!data) return "Agent run failed";
|
|
287
|
+
try {
|
|
288
|
+
const parsed = JSON.parse(data);
|
|
289
|
+
if (parsed !== null && typeof parsed === "object" && "message" in parsed && typeof parsed.message === "string") {
|
|
290
|
+
return parsed.message;
|
|
291
|
+
}
|
|
292
|
+
} catch {
|
|
293
|
+
}
|
|
294
|
+
return "Agent run failed";
|
|
295
|
+
}
|
|
296
|
+
__name(parseErrorText, "parseErrorText");
|
|
297
|
+
function parseDelta(data) {
|
|
298
|
+
try {
|
|
299
|
+
const parsed = JSON.parse(data);
|
|
300
|
+
if (parsed !== null && typeof parsed === "object" && "delta" in parsed && typeof parsed.delta === "string") {
|
|
301
|
+
return parsed.delta;
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
__name(parseDelta, "parseDelta");
|
|
308
|
+
function extractText(message) {
|
|
309
|
+
let text = "";
|
|
310
|
+
for (const part of message.parts ?? []) {
|
|
311
|
+
if (part.type === "text") text += part.text;
|
|
312
|
+
}
|
|
313
|
+
return text;
|
|
314
|
+
}
|
|
315
|
+
__name(extractText, "extractText");
|
|
316
|
+
|
|
317
|
+
// src/client.ts
|
|
318
|
+
var AgentHttpError = class extends Error {
|
|
319
|
+
static {
|
|
320
|
+
__name(this, "AgentHttpError");
|
|
321
|
+
}
|
|
322
|
+
status;
|
|
323
|
+
method;
|
|
324
|
+
path;
|
|
325
|
+
constructor(status, method, path, statusText) {
|
|
326
|
+
super(`Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
|
|
327
|
+
this.name = "AgentHttpError";
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var AgentClient = class {
|
|
331
|
+
static {
|
|
332
|
+
__name(this, "AgentClient");
|
|
333
|
+
}
|
|
334
|
+
options;
|
|
335
|
+
constructor(options = {}) {
|
|
336
|
+
this.options = options;
|
|
337
|
+
}
|
|
338
|
+
listThreads() {
|
|
339
|
+
return this.request("GET", "/agent/threads");
|
|
340
|
+
}
|
|
341
|
+
getThread(id) {
|
|
342
|
+
return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
|
|
343
|
+
}
|
|
344
|
+
deleteThread(id) {
|
|
345
|
+
return this.request("DELETE", `/agent/threads/${encodeURIComponent(id)}`);
|
|
346
|
+
}
|
|
347
|
+
forkFromMessage(threadId, messageId) {
|
|
348
|
+
return this.request("POST", `/agent/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
|
|
349
|
+
}
|
|
350
|
+
getPersonaCatalog() {
|
|
351
|
+
return this.request("GET", "/agent/threads/personas/catalog");
|
|
352
|
+
}
|
|
353
|
+
getQuotaToday() {
|
|
354
|
+
return this.request("GET", "/agent/quota/today");
|
|
355
|
+
}
|
|
356
|
+
cancelStream(runId) {
|
|
357
|
+
return this.request("POST", `/agent/chat/${encodeURIComponent(runId)}/cancel`);
|
|
358
|
+
}
|
|
359
|
+
approveToolCall(input) {
|
|
360
|
+
return this.request("POST", "/agent/tool-call/approve", input);
|
|
361
|
+
}
|
|
362
|
+
rejectToolCall(input) {
|
|
363
|
+
return this.request("POST", "/agent/tool-call/reject", input);
|
|
364
|
+
}
|
|
365
|
+
async request(method, path, body) {
|
|
366
|
+
const fetchImpl = this.options.fetch ?? globalThis.fetch;
|
|
367
|
+
const baseUrl = (this.options.baseUrl ?? "").replace(/\/$/, "");
|
|
368
|
+
const dynamic = await this.options.getHeaders?.() ?? {};
|
|
369
|
+
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
370
|
+
method,
|
|
371
|
+
headers: {
|
|
372
|
+
accept: "application/json",
|
|
373
|
+
...body !== void 0 ? {
|
|
374
|
+
"content-type": "application/json"
|
|
375
|
+
} : {},
|
|
376
|
+
...this.options.headers,
|
|
377
|
+
...dynamic
|
|
378
|
+
},
|
|
379
|
+
...body !== void 0 ? {
|
|
380
|
+
body: JSON.stringify(body)
|
|
381
|
+
} : {},
|
|
382
|
+
...this.options.credentials !== void 0 ? {
|
|
383
|
+
credentials: this.options.credentials
|
|
384
|
+
} : {}
|
|
385
|
+
});
|
|
386
|
+
if (!response.ok) {
|
|
387
|
+
throw new AgentHttpError(response.status, method, path, response.statusText);
|
|
388
|
+
}
|
|
389
|
+
if (response.status === 204) return void 0;
|
|
390
|
+
const text = await response.text();
|
|
391
|
+
if (!text) return void 0;
|
|
392
|
+
return JSON.parse(text);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/use-agent-chat.ts
|
|
397
|
+
var import_react = require("@ai-sdk/react");
|
|
398
|
+
var import_react2 = require("react");
|
|
399
|
+
function useAgentChat(options) {
|
|
400
|
+
const latest = (0, import_react2.useRef)(options);
|
|
401
|
+
latest.current = options;
|
|
402
|
+
const [runId, setRunId] = (0, import_react2.useState)(options.resumeRunId);
|
|
403
|
+
const runIdRef = (0, import_react2.useRef)(runId);
|
|
404
|
+
runIdRef.current = runId;
|
|
405
|
+
const regenerateNext = (0, import_react2.useRef)(false);
|
|
406
|
+
const client = (0, import_react2.useMemo)(() => {
|
|
407
|
+
if (options.client) return options.client;
|
|
408
|
+
return new AgentClient({
|
|
409
|
+
...options.baseUrl !== void 0 ? {
|
|
410
|
+
baseUrl: options.baseUrl
|
|
411
|
+
} : {},
|
|
412
|
+
...options.credentials !== void 0 ? {
|
|
413
|
+
credentials: options.credentials
|
|
414
|
+
} : {},
|
|
415
|
+
...options.fetch !== void 0 ? {
|
|
416
|
+
fetch: options.fetch
|
|
417
|
+
} : {},
|
|
418
|
+
getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
|
|
419
|
+
});
|
|
420
|
+
}, []);
|
|
421
|
+
const transport = (0, import_react2.useMemo)(() => {
|
|
422
|
+
function onMeta(meta) {
|
|
423
|
+
setRunId(meta.runId);
|
|
424
|
+
}
|
|
425
|
+
__name(onMeta, "onMeta");
|
|
426
|
+
return new AgentChatTransport({
|
|
427
|
+
...options.baseUrl !== void 0 ? {
|
|
428
|
+
baseUrl: options.baseUrl
|
|
429
|
+
} : {},
|
|
430
|
+
...options.credentials !== void 0 ? {
|
|
431
|
+
credentials: options.credentials
|
|
432
|
+
} : {},
|
|
433
|
+
...options.fetch !== void 0 ? {
|
|
434
|
+
fetch: options.fetch
|
|
435
|
+
} : {},
|
|
436
|
+
...options.agent !== void 0 ? {
|
|
437
|
+
agent: options.agent
|
|
438
|
+
} : {},
|
|
439
|
+
getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders"),
|
|
440
|
+
getBody: /* @__PURE__ */ __name(() => {
|
|
441
|
+
const current = latest.current;
|
|
442
|
+
const pageContext = current.getPageContext?.() ?? null;
|
|
443
|
+
const regenerate2 = regenerateNext.current;
|
|
444
|
+
regenerateNext.current = false;
|
|
445
|
+
return {
|
|
446
|
+
...current.threadId !== void 0 ? {
|
|
447
|
+
threadId: current.threadId
|
|
448
|
+
} : {},
|
|
449
|
+
...current.persona !== void 0 ? {
|
|
450
|
+
persona: current.persona
|
|
451
|
+
} : {},
|
|
452
|
+
...pageContext ? {
|
|
453
|
+
pageContext
|
|
454
|
+
} : {},
|
|
455
|
+
...regenerate2 ? {
|
|
456
|
+
regenerate: true
|
|
457
|
+
} : {}
|
|
458
|
+
};
|
|
459
|
+
}, "getBody"),
|
|
460
|
+
getResumeRunId: /* @__PURE__ */ __name(() => latest.current.resumeRunId, "getResumeRunId"),
|
|
461
|
+
onMeta
|
|
462
|
+
});
|
|
463
|
+
}, []);
|
|
464
|
+
const chat = (0, import_react.useChat)({
|
|
465
|
+
transport,
|
|
466
|
+
resume: options.resumeRunId !== void 0,
|
|
467
|
+
...options.threadId !== void 0 ? {
|
|
468
|
+
id: options.threadId
|
|
469
|
+
} : {},
|
|
470
|
+
...options.initialMessages !== void 0 ? {
|
|
471
|
+
messages: options.initialMessages
|
|
472
|
+
} : {},
|
|
473
|
+
onFinish: /* @__PURE__ */ __name(() => {
|
|
474
|
+
latest.current.onFinish?.();
|
|
475
|
+
}, "onFinish")
|
|
476
|
+
});
|
|
477
|
+
const chatRef = (0, import_react2.useRef)(chat);
|
|
478
|
+
chatRef.current = chat;
|
|
479
|
+
const addToolResult = (0, import_react2.useCallback)(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
|
|
480
|
+
tool,
|
|
481
|
+
toolCallId,
|
|
482
|
+
output
|
|
483
|
+
}), []);
|
|
484
|
+
const [threads, setThreads] = (0, import_react2.useState)([]);
|
|
485
|
+
const [personaCatalog, setPersonaCatalog] = (0, import_react2.useState)([]);
|
|
486
|
+
const [quota, setQuota] = (0, import_react2.useState)(null);
|
|
487
|
+
const loadThreads = (0, import_react2.useCallback)(async () => {
|
|
488
|
+
const list = await client.listThreads();
|
|
489
|
+
setThreads(list);
|
|
490
|
+
return list;
|
|
491
|
+
}, [
|
|
492
|
+
client
|
|
493
|
+
]);
|
|
494
|
+
const loadThread = (0, import_react2.useCallback)((id) => client.getThread(id), [
|
|
495
|
+
client
|
|
496
|
+
]);
|
|
497
|
+
const deleteThread = (0, import_react2.useCallback)(async (id) => {
|
|
498
|
+
await client.deleteThread(id);
|
|
499
|
+
setThreads((current) => current.filter((thread) => thread.id !== id));
|
|
500
|
+
}, [
|
|
501
|
+
client
|
|
502
|
+
]);
|
|
503
|
+
const forkThread = (0, import_react2.useCallback)((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
|
|
504
|
+
client
|
|
505
|
+
]);
|
|
506
|
+
const loadPersonaCatalog = (0, import_react2.useCallback)(async () => {
|
|
507
|
+
const catalog = await client.getPersonaCatalog();
|
|
508
|
+
setPersonaCatalog(catalog);
|
|
509
|
+
return catalog;
|
|
510
|
+
}, [
|
|
511
|
+
client
|
|
512
|
+
]);
|
|
513
|
+
const loadQuota = (0, import_react2.useCallback)(async () => {
|
|
514
|
+
const today = await client.getQuotaToday();
|
|
515
|
+
setQuota(today);
|
|
516
|
+
return today;
|
|
517
|
+
}, [
|
|
518
|
+
client
|
|
519
|
+
]);
|
|
520
|
+
const cancel = (0, import_react2.useCallback)(async () => {
|
|
521
|
+
chatRef.current.stop();
|
|
522
|
+
const activeRunId = runIdRef.current;
|
|
523
|
+
if (activeRunId) {
|
|
524
|
+
try {
|
|
525
|
+
await client.cancelStream(activeRunId);
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}, [
|
|
530
|
+
client
|
|
531
|
+
]);
|
|
532
|
+
const approve = (0, import_react2.useCallback)(async ({ toolCallId }) => {
|
|
533
|
+
await client.approveToolCall({
|
|
534
|
+
toolCallId
|
|
535
|
+
});
|
|
536
|
+
}, [
|
|
537
|
+
client
|
|
538
|
+
]);
|
|
539
|
+
const reject = (0, import_react2.useCallback)(async ({ toolCallId, reason }) => {
|
|
540
|
+
await client.rejectToolCall({
|
|
541
|
+
toolCallId,
|
|
542
|
+
...reason !== void 0 ? {
|
|
543
|
+
reason
|
|
544
|
+
} : {}
|
|
545
|
+
});
|
|
546
|
+
}, [
|
|
547
|
+
client
|
|
548
|
+
]);
|
|
549
|
+
const regenerate = (0, import_react2.useCallback)(() => {
|
|
550
|
+
regenerateNext.current = true;
|
|
551
|
+
void chatRef.current.regenerate();
|
|
552
|
+
}, []);
|
|
553
|
+
return {
|
|
554
|
+
...chat,
|
|
555
|
+
addToolResult,
|
|
556
|
+
runId,
|
|
557
|
+
client,
|
|
558
|
+
threads,
|
|
559
|
+
loadThreads,
|
|
560
|
+
loadThread,
|
|
561
|
+
deleteThread,
|
|
562
|
+
forkThread,
|
|
563
|
+
personaCatalog,
|
|
564
|
+
loadPersonaCatalog,
|
|
565
|
+
quota,
|
|
566
|
+
loadQuota,
|
|
567
|
+
cancel,
|
|
568
|
+
approve,
|
|
569
|
+
reject,
|
|
570
|
+
regenerate
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
__name(useAgentChat, "useAgentChat");
|
|
574
|
+
async function mergeHeaders(options) {
|
|
575
|
+
const dynamic = await options.getHeaders?.() ?? {};
|
|
576
|
+
return {
|
|
577
|
+
...options.headers,
|
|
578
|
+
...dynamic
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
__name(mergeHeaders, "mergeHeaders");
|
|
582
|
+
|
|
583
|
+
// src/components/chat-input.tsx
|
|
584
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
585
|
+
|
|
586
|
+
// src/components/use-speech-recognition.ts
|
|
587
|
+
var import_react3 = require("react");
|
|
588
|
+
function getRecognitionCtor() {
|
|
589
|
+
if (typeof window === "undefined") return null;
|
|
590
|
+
const scope = window;
|
|
591
|
+
return scope.SpeechRecognition ?? scope.webkitSpeechRecognition ?? null;
|
|
592
|
+
}
|
|
593
|
+
__name(getRecognitionCtor, "getRecognitionCtor");
|
|
594
|
+
function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTranscript }) {
|
|
595
|
+
const recognitionRef = (0, import_react3.useRef)(null);
|
|
596
|
+
const [isListening, setIsListening] = (0, import_react3.useState)(false);
|
|
597
|
+
const ctor = (0, import_react3.useMemo)(() => getRecognitionCtor(), []);
|
|
598
|
+
const isSupported = ctor !== null;
|
|
599
|
+
const finalRef = (0, import_react3.useRef)(onFinalTranscript);
|
|
600
|
+
const interimRef = (0, import_react3.useRef)(onInterimTranscript);
|
|
601
|
+
finalRef.current = onFinalTranscript;
|
|
602
|
+
interimRef.current = onInterimTranscript;
|
|
603
|
+
(0, import_react3.useEffect)(() => {
|
|
604
|
+
if (!ctor) return;
|
|
605
|
+
const recognition = new ctor();
|
|
606
|
+
recognition.lang = lang;
|
|
607
|
+
recognition.continuous = false;
|
|
608
|
+
recognition.interimResults = true;
|
|
609
|
+
recognition.onresult = (event) => {
|
|
610
|
+
let interim = "";
|
|
611
|
+
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
612
|
+
const result = event.results[i];
|
|
613
|
+
if (!result) continue;
|
|
614
|
+
const text = result[0]?.transcript ?? "";
|
|
615
|
+
if (result.isFinal) {
|
|
616
|
+
finalRef.current(text);
|
|
617
|
+
} else {
|
|
618
|
+
interim += text;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (interim && interimRef.current) interimRef.current(interim);
|
|
622
|
+
};
|
|
623
|
+
recognition.onerror = (event) => {
|
|
624
|
+
if (event.error !== "no-speech" && event.error !== "aborted") {
|
|
625
|
+
console.warn("SpeechRecognition error", event.error);
|
|
626
|
+
}
|
|
627
|
+
setIsListening(false);
|
|
628
|
+
};
|
|
629
|
+
recognition.onend = () => setIsListening(false);
|
|
630
|
+
recognitionRef.current = recognition;
|
|
631
|
+
return () => {
|
|
632
|
+
try {
|
|
633
|
+
recognition.stop();
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
recognitionRef.current = null;
|
|
637
|
+
};
|
|
638
|
+
}, [
|
|
639
|
+
ctor,
|
|
640
|
+
lang
|
|
641
|
+
]);
|
|
642
|
+
const start = (0, import_react3.useCallback)(() => {
|
|
643
|
+
const recognition = recognitionRef.current;
|
|
644
|
+
if (!recognition) return;
|
|
645
|
+
try {
|
|
646
|
+
recognition.start();
|
|
647
|
+
setIsListening(true);
|
|
648
|
+
} catch (error) {
|
|
649
|
+
console.warn("SpeechRecognition start failed", error);
|
|
650
|
+
}
|
|
651
|
+
}, []);
|
|
652
|
+
const stop = (0, import_react3.useCallback)(() => {
|
|
653
|
+
const recognition = recognitionRef.current;
|
|
654
|
+
if (!recognition) return;
|
|
655
|
+
try {
|
|
656
|
+
recognition.stop();
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
}, []);
|
|
660
|
+
return {
|
|
661
|
+
isSupported,
|
|
662
|
+
isListening,
|
|
663
|
+
start,
|
|
664
|
+
stop
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
__name(useSpeechRecognition, "useSpeechRecognition");
|
|
668
|
+
|
|
669
|
+
// src/components/chat-input.tsx
|
|
670
|
+
function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = true, speechLang = "en-US", sendLabel = "Send", className, textareaClassName, sendButtonClassName, micButtonClassName, renderMic }) {
|
|
671
|
+
const [value, setValue] = (0, import_react4.useState)("");
|
|
672
|
+
const [interim, setInterim] = (0, import_react4.useState)("");
|
|
673
|
+
const speech = useSpeechRecognition({
|
|
674
|
+
lang: speechLang,
|
|
675
|
+
onFinalTranscript: /* @__PURE__ */ __name((text) => {
|
|
676
|
+
const trimmed = text.trim();
|
|
677
|
+
if (!trimmed) return;
|
|
678
|
+
setValue((prev) => prev.length === 0 ? trimmed : `${prev.trimEnd()} ${trimmed}`);
|
|
679
|
+
setInterim("");
|
|
680
|
+
}, "onFinalTranscript"),
|
|
681
|
+
onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
|
|
682
|
+
});
|
|
683
|
+
(0, import_react4.useEffect)(() => {
|
|
684
|
+
if (!speech.isListening) setInterim("");
|
|
685
|
+
}, [
|
|
686
|
+
speech.isListening
|
|
687
|
+
]);
|
|
688
|
+
function submit() {
|
|
689
|
+
const trimmed = value.trim();
|
|
690
|
+
if (!trimmed) return;
|
|
691
|
+
onSubmit(trimmed);
|
|
692
|
+
setValue("");
|
|
693
|
+
setInterim("");
|
|
694
|
+
}
|
|
695
|
+
__name(submit, "submit");
|
|
696
|
+
function handleKeyDown(event) {
|
|
697
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
698
|
+
event.preventDefault();
|
|
699
|
+
submit();
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
__name(handleKeyDown, "handleKeyDown");
|
|
703
|
+
function toggleMic() {
|
|
704
|
+
if (speech.isListening) speech.stop();
|
|
705
|
+
else speech.start();
|
|
706
|
+
}
|
|
707
|
+
__name(toggleMic, "toggleMic");
|
|
708
|
+
const displayValue = speech.isListening && interim ? `${value}${value && !value.endsWith(" ") ? " " : ""}${interim}` : value;
|
|
709
|
+
const showMic = enableSpeech && speech.isSupported;
|
|
710
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", {
|
|
711
|
+
className
|
|
712
|
+
}, /* @__PURE__ */ import_react4.default.createElement("textarea", {
|
|
713
|
+
className: textareaClassName,
|
|
714
|
+
rows,
|
|
715
|
+
value: displayValue,
|
|
716
|
+
disabled,
|
|
717
|
+
onChange: /* @__PURE__ */ __name((event) => {
|
|
718
|
+
if (speech.isListening && interim) return;
|
|
719
|
+
setValue(event.target.value);
|
|
720
|
+
}, "onChange"),
|
|
721
|
+
onKeyDown: handleKeyDown,
|
|
722
|
+
placeholder: placeholder ?? "Ask something (Enter to send, Shift+Enter for newline)"
|
|
723
|
+
}), /* @__PURE__ */ import_react4.default.createElement("button", {
|
|
724
|
+
type: "button",
|
|
725
|
+
className: sendButtonClassName,
|
|
726
|
+
onClick: submit,
|
|
727
|
+
disabled: disabled || !value.trim()
|
|
728
|
+
}, sendLabel), showMic ? /* @__PURE__ */ import_react4.default.createElement("button", {
|
|
729
|
+
type: "button",
|
|
730
|
+
className: micButtonClassName,
|
|
731
|
+
onClick: toggleMic,
|
|
732
|
+
disabled,
|
|
733
|
+
"aria-label": speech.isListening ? "Stop dictating" : "Start dictating"
|
|
734
|
+
}, renderMic ? renderMic({
|
|
735
|
+
isListening: speech.isListening
|
|
736
|
+
}) : speech.isListening ? "Stop" : "Speak") : null);
|
|
737
|
+
}
|
|
738
|
+
__name(ChatInput, "ChatInput");
|
|
739
|
+
|
|
740
|
+
// src/components/message-item.tsx
|
|
741
|
+
var import_ai = require("ai");
|
|
742
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
743
|
+
function MessageItem({ message, renderToolPart, renderToolGroup, renderText, classNames, isStreaming = false, editable, onEditSubmit, regeneratable, onRegenerate, onFork, usage, createdAt }) {
|
|
744
|
+
const isUser = message.role === "user";
|
|
745
|
+
const parts = message.parts ?? [];
|
|
746
|
+
const copyableText = extractText2(parts);
|
|
747
|
+
const actionRow = /* @__PURE__ */ import_react5.default.createElement("div", {
|
|
748
|
+
className: classNames?.actions,
|
|
749
|
+
"data-actions-for": message.role
|
|
750
|
+
}, copyableText ? /* @__PURE__ */ import_react5.default.createElement(CopyButton, {
|
|
751
|
+
text: copyableText,
|
|
752
|
+
className: classNames?.actionButton
|
|
753
|
+
}) : null, onFork ? /* @__PURE__ */ import_react5.default.createElement("button", {
|
|
754
|
+
type: "button",
|
|
755
|
+
onClick: /* @__PURE__ */ __name(() => void onFork(message.id), "onClick"),
|
|
756
|
+
className: classNames?.actionButton,
|
|
757
|
+
title: "Fork thread from this message"
|
|
758
|
+
}, "Fork") : null, !isUser && regeneratable && onRegenerate ? /* @__PURE__ */ import_react5.default.createElement("button", {
|
|
759
|
+
type: "button",
|
|
760
|
+
onClick: /* @__PURE__ */ __name(() => void onRegenerate(), "onClick"),
|
|
761
|
+
className: classNames?.actionButton,
|
|
762
|
+
title: "Regenerate response"
|
|
763
|
+
}, "Regenerate") : null, !isUser && usage ? /* @__PURE__ */ import_react5.default.createElement(UsageInline, {
|
|
764
|
+
usage,
|
|
765
|
+
className: classNames?.usage
|
|
766
|
+
}) : null, createdAt ? /* @__PURE__ */ import_react5.default.createElement(TimestampInline, {
|
|
767
|
+
createdAt,
|
|
768
|
+
className: classNames?.timestamp
|
|
769
|
+
}) : null);
|
|
770
|
+
if (isUser && editable && onEditSubmit) {
|
|
771
|
+
return /* @__PURE__ */ import_react5.default.createElement(EditableUserBubble, {
|
|
772
|
+
message,
|
|
773
|
+
onSubmit: onEditSubmit,
|
|
774
|
+
renderText,
|
|
775
|
+
classNames,
|
|
776
|
+
actions: actionRow
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
const className = [
|
|
780
|
+
classNames?.root,
|
|
781
|
+
classNames?.byRole?.[message.role]
|
|
782
|
+
].filter((entry) => entry).join(" ");
|
|
783
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", {
|
|
784
|
+
className: className === "" ? void 0 : className,
|
|
785
|
+
"data-role": message.role,
|
|
786
|
+
"data-streaming": isStreaming ? "true" : void 0
|
|
787
|
+
}, /* @__PURE__ */ import_react5.default.createElement("div", null, renderParts(message, parts, {
|
|
788
|
+
renderText,
|
|
789
|
+
renderToolPart,
|
|
790
|
+
renderToolGroup,
|
|
791
|
+
classNames,
|
|
792
|
+
isStreaming
|
|
793
|
+
})), actionRow);
|
|
794
|
+
}
|
|
795
|
+
__name(MessageItem, "MessageItem");
|
|
796
|
+
function renderParts(message, parts, ctx) {
|
|
797
|
+
const blocks = [];
|
|
798
|
+
let toolBuffer = [];
|
|
799
|
+
let toolGroupCounter = 0;
|
|
800
|
+
let textCounter = 0;
|
|
801
|
+
function flushTools() {
|
|
802
|
+
if (toolBuffer.length === 0) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
const key = `${message.id}-tools-${toolGroupCounter++}`;
|
|
806
|
+
if (ctx.renderToolGroup) {
|
|
807
|
+
blocks.push(/* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, {
|
|
808
|
+
key
|
|
809
|
+
}, ctx.renderToolGroup(toolBuffer, key)));
|
|
810
|
+
} else if (ctx.renderToolPart) {
|
|
811
|
+
for (const part of toolBuffer) {
|
|
812
|
+
const partKey = `${message.id}-${part.toolCallId}`;
|
|
813
|
+
blocks.push(/* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, {
|
|
814
|
+
key: partKey
|
|
815
|
+
}, ctx.renderToolPart(part, partKey)));
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
toolBuffer = [];
|
|
819
|
+
}
|
|
820
|
+
__name(flushTools, "flushTools");
|
|
821
|
+
for (const part of parts) {
|
|
822
|
+
if ((0, import_ai.isToolUIPart)(part)) {
|
|
823
|
+
toolBuffer.push(part);
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
flushTools();
|
|
827
|
+
if ((0, import_ai.isTextUIPart)(part)) {
|
|
828
|
+
const key = `${message.id}-text-${textCounter++}`;
|
|
829
|
+
blocks.push(/* @__PURE__ */ import_react5.default.createElement("div", {
|
|
830
|
+
key,
|
|
831
|
+
className: ctx.classNames?.text
|
|
832
|
+
}, ctx.renderText ? ctx.renderText(part.text, {
|
|
833
|
+
isStreaming: ctx.isStreaming
|
|
834
|
+
}) : part.text));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
flushTools();
|
|
838
|
+
return blocks;
|
|
839
|
+
}
|
|
840
|
+
__name(renderParts, "renderParts");
|
|
841
|
+
function EditableUserBubble({ message, onSubmit, renderText, classNames, actions }) {
|
|
842
|
+
const initialText = extractText2(message.parts ?? []);
|
|
843
|
+
const [isEditing, setIsEditing] = (0, import_react5.useState)(false);
|
|
844
|
+
const [draft, setDraft] = (0, import_react5.useState)(initialText);
|
|
845
|
+
const textareaRef = (0, import_react5.useRef)(null);
|
|
846
|
+
(0, import_react5.useEffect)(() => {
|
|
847
|
+
if (isEditing && textareaRef.current) {
|
|
848
|
+
const el = textareaRef.current;
|
|
849
|
+
el.focus();
|
|
850
|
+
el.setSelectionRange(el.value.length, el.value.length);
|
|
851
|
+
}
|
|
852
|
+
}, [
|
|
853
|
+
isEditing
|
|
854
|
+
]);
|
|
855
|
+
async function handleSave() {
|
|
856
|
+
const next = draft.trim();
|
|
857
|
+
if (!next) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
setIsEditing(false);
|
|
861
|
+
await onSubmit(next);
|
|
862
|
+
}
|
|
863
|
+
__name(handleSave, "handleSave");
|
|
864
|
+
if (!isEditing) {
|
|
865
|
+
const rootClass = [
|
|
866
|
+
classNames?.root,
|
|
867
|
+
classNames?.byRole?.user
|
|
868
|
+
].filter((entry) => entry).join(" ");
|
|
869
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", {
|
|
870
|
+
className: rootClass === "" ? void 0 : rootClass,
|
|
871
|
+
"data-role": "user"
|
|
872
|
+
}, /* @__PURE__ */ import_react5.default.createElement("div", {
|
|
873
|
+
className: classNames?.text
|
|
874
|
+
}, renderText ? renderText(initialText, {
|
|
875
|
+
isStreaming: false
|
|
876
|
+
}) : initialText), /* @__PURE__ */ import_react5.default.createElement("button", {
|
|
877
|
+
type: "button",
|
|
878
|
+
onClick: /* @__PURE__ */ __name(() => {
|
|
879
|
+
setDraft(initialText);
|
|
880
|
+
setIsEditing(true);
|
|
881
|
+
}, "onClick"),
|
|
882
|
+
className: classNames?.actionButton,
|
|
883
|
+
title: "Edit & resubmit"
|
|
884
|
+
}, "Edit"), actions);
|
|
885
|
+
}
|
|
886
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", {
|
|
887
|
+
"data-editing": "true"
|
|
888
|
+
}, /* @__PURE__ */ import_react5.default.createElement("textarea", {
|
|
889
|
+
ref: textareaRef,
|
|
890
|
+
value: draft,
|
|
891
|
+
onChange: /* @__PURE__ */ __name((event) => setDraft(event.target.value), "onChange"),
|
|
892
|
+
onKeyDown: /* @__PURE__ */ __name((event) => {
|
|
893
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
894
|
+
event.preventDefault();
|
|
895
|
+
void handleSave();
|
|
896
|
+
}
|
|
897
|
+
if (event.key === "Escape") {
|
|
898
|
+
event.preventDefault();
|
|
899
|
+
setIsEditing(false);
|
|
900
|
+
}
|
|
901
|
+
}, "onKeyDown"),
|
|
902
|
+
rows: Math.min(8, draft.split("\n").length + 1),
|
|
903
|
+
className: classNames?.editTextarea
|
|
904
|
+
}), /* @__PURE__ */ import_react5.default.createElement("div", {
|
|
905
|
+
className: classNames?.editActions
|
|
906
|
+
}, /* @__PURE__ */ import_react5.default.createElement("button", {
|
|
907
|
+
type: "button",
|
|
908
|
+
onClick: /* @__PURE__ */ __name(() => setIsEditing(false), "onClick")
|
|
909
|
+
}, "Cancel"), /* @__PURE__ */ import_react5.default.createElement("button", {
|
|
910
|
+
type: "button",
|
|
911
|
+
onClick: /* @__PURE__ */ __name(() => void handleSave(), "onClick"),
|
|
912
|
+
disabled: !draft.trim()
|
|
913
|
+
}, "Resend")));
|
|
914
|
+
}
|
|
915
|
+
__name(EditableUserBubble, "EditableUserBubble");
|
|
916
|
+
function CopyButton({ text, className }) {
|
|
917
|
+
const [copied, setCopied] = (0, import_react5.useState)(false);
|
|
918
|
+
async function handleCopy() {
|
|
919
|
+
try {
|
|
920
|
+
await navigator.clipboard.writeText(text);
|
|
921
|
+
setCopied(true);
|
|
922
|
+
setTimeout(() => setCopied(false), 1500);
|
|
923
|
+
} catch {
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
__name(handleCopy, "handleCopy");
|
|
927
|
+
return /* @__PURE__ */ import_react5.default.createElement("button", {
|
|
928
|
+
type: "button",
|
|
929
|
+
onClick: /* @__PURE__ */ __name(() => void handleCopy(), "onClick"),
|
|
930
|
+
className,
|
|
931
|
+
title: copied ? "Copied!" : "Copy message text"
|
|
932
|
+
}, copied ? "Copied" : "Copy");
|
|
933
|
+
}
|
|
934
|
+
__name(CopyButton, "CopyButton");
|
|
935
|
+
function UsageInline({ usage, className }) {
|
|
936
|
+
const total = usage.inputTokens + usage.outputTokens;
|
|
937
|
+
return /* @__PURE__ */ import_react5.default.createElement("span", {
|
|
938
|
+
className,
|
|
939
|
+
title: `Input ${usage.inputTokens.toLocaleString()} \xB7 Output ${usage.outputTokens.toLocaleString()} tokens`
|
|
940
|
+
}, formatCostUsd(usage.costUsd), " \xB7 ", formatTokensShort(total));
|
|
941
|
+
}
|
|
942
|
+
__name(UsageInline, "UsageInline");
|
|
943
|
+
function TimestampInline({ createdAt, className }) {
|
|
944
|
+
const parsed = new Date(createdAt);
|
|
945
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
const absolute = parsed.toLocaleString(void 0, {
|
|
949
|
+
dateStyle: "medium",
|
|
950
|
+
timeStyle: "short"
|
|
951
|
+
});
|
|
952
|
+
return /* @__PURE__ */ import_react5.default.createElement("span", {
|
|
953
|
+
className,
|
|
954
|
+
title: absolute
|
|
955
|
+
}, formatRelativeTime(parsed));
|
|
956
|
+
}
|
|
957
|
+
__name(TimestampInline, "TimestampInline");
|
|
958
|
+
var RELATIVE_TIME = new Intl.RelativeTimeFormat(void 0, {
|
|
959
|
+
numeric: "auto",
|
|
960
|
+
style: "narrow"
|
|
961
|
+
});
|
|
962
|
+
var ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
963
|
+
function formatRelativeTime(date) {
|
|
964
|
+
const diffMs = Date.now() - date.getTime();
|
|
965
|
+
if (Math.abs(diffMs) < 1e4) {
|
|
966
|
+
return "just now";
|
|
967
|
+
}
|
|
968
|
+
if (diffMs > ONE_WEEK_MS) {
|
|
969
|
+
return date.toLocaleDateString(void 0, {
|
|
970
|
+
month: "short",
|
|
971
|
+
day: "numeric"
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
const minutes = Math.round(-diffMs / 6e4);
|
|
975
|
+
if (Math.abs(minutes) < 60) {
|
|
976
|
+
return RELATIVE_TIME.format(minutes, "minute");
|
|
977
|
+
}
|
|
978
|
+
const hours = Math.round(-diffMs / 36e5);
|
|
979
|
+
if (Math.abs(hours) < 24) {
|
|
980
|
+
return RELATIVE_TIME.format(hours, "hour");
|
|
981
|
+
}
|
|
982
|
+
return RELATIVE_TIME.format(Math.round(-diffMs / 864e5), "day");
|
|
983
|
+
}
|
|
984
|
+
__name(formatRelativeTime, "formatRelativeTime");
|
|
985
|
+
function formatTokensShort(n) {
|
|
986
|
+
if (n < 1e3) {
|
|
987
|
+
return `${n} tokens`;
|
|
988
|
+
}
|
|
989
|
+
if (n < 1e4) {
|
|
990
|
+
return `${(n / 1e3).toFixed(1)}k tokens`;
|
|
991
|
+
}
|
|
992
|
+
return `${Math.round(n / 1e3)}k tokens`;
|
|
993
|
+
}
|
|
994
|
+
__name(formatTokensShort, "formatTokensShort");
|
|
995
|
+
function formatCostUsd(cost) {
|
|
996
|
+
if (cost === 0) {
|
|
997
|
+
return "$0";
|
|
998
|
+
}
|
|
999
|
+
if (cost < 0.01) {
|
|
1000
|
+
return `$${cost.toFixed(4)}`;
|
|
1001
|
+
}
|
|
1002
|
+
if (cost < 1) {
|
|
1003
|
+
return `$${cost.toFixed(3)}`;
|
|
1004
|
+
}
|
|
1005
|
+
return `$${cost.toFixed(2)}`;
|
|
1006
|
+
}
|
|
1007
|
+
__name(formatCostUsd, "formatCostUsd");
|
|
1008
|
+
function extractText2(parts) {
|
|
1009
|
+
const out = [];
|
|
1010
|
+
for (const part of parts) {
|
|
1011
|
+
if ((0, import_ai.isTextUIPart)(part)) {
|
|
1012
|
+
out.push(part.text);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return out.join("\n\n").trim();
|
|
1016
|
+
}
|
|
1017
|
+
__name(extractText2, "extractText");
|
|
1018
|
+
|
|
1019
|
+
// src/components/message-list.tsx
|
|
1020
|
+
var import_react6 = __toESM(require("react"), 1);
|
|
1021
|
+
var DEFAULT_VISIBLE_COUNT = 50;
|
|
1022
|
+
var LOAD_EARLIER_STEP = 50;
|
|
1023
|
+
function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
|
|
1024
|
+
const [visibleCount, setVisibleCount] = (0, import_react6.useState)(DEFAULT_VISIBLE_COUNT);
|
|
1025
|
+
if (messages.length === 0 && status === "ready") {
|
|
1026
|
+
return /* @__PURE__ */ import_react6.default.createElement("div", {
|
|
1027
|
+
className: classNames?.empty
|
|
1028
|
+
}, emptyState);
|
|
1029
|
+
}
|
|
1030
|
+
const lastMessage = messages.at(-1);
|
|
1031
|
+
const showTypingBubble = (status === "submitted" || status === "streaming") && lastMessage?.role !== "assistant";
|
|
1032
|
+
const showFollowUps = status === "ready" && !showTypingBubble && lastMessage?.role === "assistant" && onFollowUpClick !== void 0 && !!followUps && followUps.length > 0;
|
|
1033
|
+
const visibleStart = Math.max(0, messages.length - visibleCount);
|
|
1034
|
+
const visibleMessages = messages.slice(visibleStart);
|
|
1035
|
+
const hiddenCount = visibleStart;
|
|
1036
|
+
let lastAssistantIndex = -1;
|
|
1037
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1038
|
+
if (messages[i]?.role === "assistant") {
|
|
1039
|
+
lastAssistantIndex = i;
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return /* @__PURE__ */ import_react6.default.createElement("div", {
|
|
1044
|
+
className: classNames?.root
|
|
1045
|
+
}, hiddenCount > 0 ? /* @__PURE__ */ import_react6.default.createElement("button", {
|
|
1046
|
+
type: "button",
|
|
1047
|
+
className: classNames?.loadEarlier,
|
|
1048
|
+
onClick: /* @__PURE__ */ __name(() => setVisibleCount((count) => count + LOAD_EARLIER_STEP), "onClick")
|
|
1049
|
+
}, "Load earlier (", hiddenCount, " more)") : null, visibleMessages.map((message, index) => {
|
|
1050
|
+
const absoluteIndex = visibleStart + index;
|
|
1051
|
+
const isLastAssistant = absoluteIndex === lastAssistantIndex;
|
|
1052
|
+
const usage = getUsage?.(message) ?? null;
|
|
1053
|
+
const createdAt = getCreatedAt?.(message) ?? null;
|
|
1054
|
+
return /* @__PURE__ */ import_react6.default.createElement(MessageItem, {
|
|
1055
|
+
key: message.id,
|
|
1056
|
+
message,
|
|
1057
|
+
...renderToolPart ? {
|
|
1058
|
+
renderToolPart
|
|
1059
|
+
} : {},
|
|
1060
|
+
...renderToolGroup ? {
|
|
1061
|
+
renderToolGroup
|
|
1062
|
+
} : {},
|
|
1063
|
+
...renderText ? {
|
|
1064
|
+
renderText
|
|
1065
|
+
} : {},
|
|
1066
|
+
...classNames?.message ? {
|
|
1067
|
+
classNames: classNames.message
|
|
1068
|
+
} : {},
|
|
1069
|
+
...onFork ? {
|
|
1070
|
+
onFork
|
|
1071
|
+
} : {},
|
|
1072
|
+
...editable ? {
|
|
1073
|
+
editable
|
|
1074
|
+
} : {},
|
|
1075
|
+
...onEditSubmit ? {
|
|
1076
|
+
onEditSubmit: /* @__PURE__ */ __name((text) => onEditSubmit(message.id, text), "onEditSubmit")
|
|
1077
|
+
} : {},
|
|
1078
|
+
...regeneratable && isLastAssistant ? {
|
|
1079
|
+
regeneratable
|
|
1080
|
+
} : {},
|
|
1081
|
+
...onRegenerate ? {
|
|
1082
|
+
onRegenerate: /* @__PURE__ */ __name(() => onRegenerate(message.id), "onRegenerate")
|
|
1083
|
+
} : {},
|
|
1084
|
+
...usage !== null ? {
|
|
1085
|
+
usage
|
|
1086
|
+
} : {},
|
|
1087
|
+
...createdAt !== null ? {
|
|
1088
|
+
createdAt
|
|
1089
|
+
} : {},
|
|
1090
|
+
isStreaming: status === "streaming" && isLastAssistant
|
|
1091
|
+
});
|
|
1092
|
+
}), showTypingBubble ? /* @__PURE__ */ import_react6.default.createElement("div", {
|
|
1093
|
+
className: classNames?.typing
|
|
1094
|
+
}, typingIndicator ?? "Thinking\u2026") : null, error && onRetry ? /* @__PURE__ */ import_react6.default.createElement("div", {
|
|
1095
|
+
className: classNames?.error
|
|
1096
|
+
}, /* @__PURE__ */ import_react6.default.createElement("span", null, error.message || "The assistant didn't respond."), /* @__PURE__ */ import_react6.default.createElement("button", {
|
|
1097
|
+
type: "button",
|
|
1098
|
+
className: classNames?.errorRetry,
|
|
1099
|
+
onClick: /* @__PURE__ */ __name(() => void onRetry(), "onClick")
|
|
1100
|
+
}, "Retry")) : null, showFollowUps ? /* @__PURE__ */ import_react6.default.createElement("div", {
|
|
1101
|
+
className: classNames?.followUps
|
|
1102
|
+
}, followUps.map((text) => /* @__PURE__ */ import_react6.default.createElement("button", {
|
|
1103
|
+
key: text,
|
|
1104
|
+
type: "button",
|
|
1105
|
+
className: classNames?.followUpChip,
|
|
1106
|
+
onClick: /* @__PURE__ */ __name(() => onFollowUpClick(text), "onClick")
|
|
1107
|
+
}, text))) : null);
|
|
1108
|
+
}
|
|
1109
|
+
__name(MessageList, "MessageList");
|
|
1110
|
+
|
|
1111
|
+
// src/components/speech-languages.ts
|
|
1112
|
+
var DEFAULT_SPEECH_LANG = "en-US";
|
|
1113
|
+
var SPEECH_LANGUAGES = [
|
|
1114
|
+
{
|
|
1115
|
+
tag: "en-US",
|
|
1116
|
+
label: "English (US)"
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
tag: "en-GB",
|
|
1120
|
+
label: "English (UK)"
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
tag: "pt-BR",
|
|
1124
|
+
label: "Portuguese (Brazil)"
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
tag: "es-ES",
|
|
1128
|
+
label: "Spanish (Spain)"
|
|
1129
|
+
}
|
|
1130
|
+
];
|
|
1131
|
+
var STORAGE_KEY = "nestjs-agent-chat-speech-lang";
|
|
1132
|
+
function loadStoredSpeechLang() {
|
|
1133
|
+
if (typeof window === "undefined") return DEFAULT_SPEECH_LANG;
|
|
1134
|
+
try {
|
|
1135
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
1136
|
+
if (raw && SPEECH_LANGUAGES.some((language) => language.tag === raw)) {
|
|
1137
|
+
return raw;
|
|
1138
|
+
}
|
|
1139
|
+
} catch {
|
|
1140
|
+
}
|
|
1141
|
+
return DEFAULT_SPEECH_LANG;
|
|
1142
|
+
}
|
|
1143
|
+
__name(loadStoredSpeechLang, "loadStoredSpeechLang");
|
|
1144
|
+
function persistSpeechLang(tag) {
|
|
1145
|
+
if (typeof window === "undefined") return;
|
|
1146
|
+
try {
|
|
1147
|
+
window.localStorage.setItem(STORAGE_KEY, tag);
|
|
1148
|
+
} catch {
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
__name(persistSpeechLang, "persistSpeechLang");
|
|
1152
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1153
|
+
0 && (module.exports = {
|
|
1154
|
+
AgentChatTransport,
|
|
1155
|
+
AgentClient,
|
|
1156
|
+
AgentHttpError,
|
|
1157
|
+
ChatInput,
|
|
1158
|
+
DEFAULT_SPEECH_LANG,
|
|
1159
|
+
MessageItem,
|
|
1160
|
+
MessageList,
|
|
1161
|
+
SPEECH_LANGUAGES,
|
|
1162
|
+
formatRelativeTime,
|
|
1163
|
+
loadStoredSpeechLang,
|
|
1164
|
+
persistSpeechLang,
|
|
1165
|
+
useAgentChat,
|
|
1166
|
+
useSpeechRecognition
|
|
1167
|
+
});
|
|
1168
|
+
//# sourceMappingURL=index.cjs.map
|