@kortyx/react 0.0.1
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/CHANGELOG.md +7 -0
- package/LICENSE +201 -0
- package/README.md +17 -0
- package/dist/index.d.ts +435 -0
- package/dist/index.js +679 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
buildAssistantMessage: () => buildAssistantMessage,
|
|
23
|
+
createBrowserChatStorage: () => createBrowserChatStorage,
|
|
24
|
+
createChatTransport: () => createChatTransport,
|
|
25
|
+
createLiveChatPieces: () => createLiveChatPieces,
|
|
26
|
+
createRouteChatTransport: () => createRouteChatTransport,
|
|
27
|
+
toHumanInputPiece: () => toHumanInputPiece,
|
|
28
|
+
useChat: () => useChat,
|
|
29
|
+
useChatStreamDebug: () => useChatStreamDebug,
|
|
30
|
+
useStructuredStreams: () => useStructuredStreams
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/build-assistant-message.ts
|
|
35
|
+
function buildAssistantMessage(args) {
|
|
36
|
+
const plainTextContent = args.pieces.filter(
|
|
37
|
+
(piece) => piece.type === "text"
|
|
38
|
+
).map((piece) => piece.content).join("");
|
|
39
|
+
const base = {
|
|
40
|
+
id: args.createId(),
|
|
41
|
+
role: "assistant",
|
|
42
|
+
content: plainTextContent,
|
|
43
|
+
debug: args.debug
|
|
44
|
+
};
|
|
45
|
+
return args.pieces.length > 0 ? { ...base, contentPieces: args.pieces } : base;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/chat-storage.ts
|
|
49
|
+
var DEFAULT_STORAGE_KEYS = {
|
|
50
|
+
messages: "chat.messages.v1",
|
|
51
|
+
includeHistory: "chat.includeHistory.v1",
|
|
52
|
+
sessionId: "chat.sessionId",
|
|
53
|
+
workflowId: "chat.workflowId"
|
|
54
|
+
};
|
|
55
|
+
var getLocalStorage = () => {
|
|
56
|
+
const candidate = globalThis;
|
|
57
|
+
return candidate.localStorage;
|
|
58
|
+
};
|
|
59
|
+
var defaultParseMessage = (value) => value;
|
|
60
|
+
var defaultSerializeMessage = (message) => message;
|
|
61
|
+
function createBrowserChatStorage(options) {
|
|
62
|
+
const keys = {
|
|
63
|
+
...DEFAULT_STORAGE_KEYS,
|
|
64
|
+
...options?.keys ?? {}
|
|
65
|
+
};
|
|
66
|
+
const maxMessages = options?.maxMessages ?? 60;
|
|
67
|
+
const parseMessage = options?.parseMessage ?? defaultParseMessage;
|
|
68
|
+
const serializeMessage = options?.serializeMessage ?? defaultSerializeMessage;
|
|
69
|
+
return {
|
|
70
|
+
load() {
|
|
71
|
+
try {
|
|
72
|
+
const storage = getLocalStorage();
|
|
73
|
+
if (!storage) {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
const sessionId = storage.getItem(keys.sessionId);
|
|
77
|
+
const workflowId = storage.getItem(keys.workflowId) ?? "";
|
|
78
|
+
const includeHistoryRaw = storage.getItem(keys.includeHistory);
|
|
79
|
+
const rawMessages = storage.getItem(keys.messages);
|
|
80
|
+
const parsedMessages = rawMessages ? JSON.parse(rawMessages) : [];
|
|
81
|
+
const messages = Array.isArray(parsedMessages) ? parsedMessages.map((item) => parseMessage(item)).filter((message) => message !== null) : [];
|
|
82
|
+
return {
|
|
83
|
+
sessionId,
|
|
84
|
+
workflowId,
|
|
85
|
+
includeHistory: includeHistoryRaw !== "0",
|
|
86
|
+
messages
|
|
87
|
+
};
|
|
88
|
+
} catch {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
save(state) {
|
|
93
|
+
try {
|
|
94
|
+
const storage = getLocalStorage();
|
|
95
|
+
if (!storage) return;
|
|
96
|
+
if (state.sessionId) {
|
|
97
|
+
storage.setItem(keys.sessionId, state.sessionId);
|
|
98
|
+
} else {
|
|
99
|
+
storage.removeItem(keys.sessionId);
|
|
100
|
+
}
|
|
101
|
+
storage.setItem(keys.workflowId, state.workflowId);
|
|
102
|
+
storage.setItem(keys.includeHistory, state.includeHistory ? "1" : "0");
|
|
103
|
+
const trimmedMessages = state.messages.slice(-maxMessages).map((message) => serializeMessage(message));
|
|
104
|
+
storage.setItem(keys.messages, JSON.stringify(trimmedMessages));
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
clearMessages() {
|
|
109
|
+
try {
|
|
110
|
+
const storage = getLocalStorage();
|
|
111
|
+
if (!storage) return;
|
|
112
|
+
storage.removeItem(keys.messages);
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/chat-transport.ts
|
|
120
|
+
var import_browser = require("@kortyx/stream/browser");
|
|
121
|
+
function createChatTransport(args) {
|
|
122
|
+
return {
|
|
123
|
+
async stream({ onChunk, ...context }) {
|
|
124
|
+
for await (const chunk of await args.stream(context)) {
|
|
125
|
+
const shouldContinue = await onChunk(chunk);
|
|
126
|
+
if (chunk.type === "done" || shouldContinue === false) {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function createRouteChatTransport(args) {
|
|
134
|
+
return createChatTransport({
|
|
135
|
+
stream: (context) => {
|
|
136
|
+
const routeArgs = {
|
|
137
|
+
endpoint: args.endpoint,
|
|
138
|
+
body: args.getBody(context),
|
|
139
|
+
...args.method ? { method: args.method } : {},
|
|
140
|
+
...args.headers ? { headers: args.headers } : {},
|
|
141
|
+
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
|
|
142
|
+
};
|
|
143
|
+
return (0, import_browser.streamFromRoute)({
|
|
144
|
+
...routeArgs
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/create-live-chat-pieces.ts
|
|
151
|
+
var isNonEmptyTextPiece = (piece) => piece.type !== "text" || "content" in piece && typeof piece.content === "string" && piece.content.length > 0;
|
|
152
|
+
function createLiveChatPieces(args) {
|
|
153
|
+
const keyedPieces = [];
|
|
154
|
+
let sawTextDelta = false;
|
|
155
|
+
const emit = () => {
|
|
156
|
+
args.onChange(
|
|
157
|
+
keyedPieces.map((entry) => entry.piece).filter((piece) => isNonEmptyTextPiece(piece))
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
const findPieceIndex = (key) => keyedPieces.findIndex((entry) => entry.key === key);
|
|
161
|
+
const upsertPiece = (key, piece) => {
|
|
162
|
+
const existingIndex = findPieceIndex(key);
|
|
163
|
+
if (existingIndex >= 0) {
|
|
164
|
+
keyedPieces[existingIndex] = { key, piece };
|
|
165
|
+
} else {
|
|
166
|
+
keyedPieces.push({ key, piece });
|
|
167
|
+
}
|
|
168
|
+
emit();
|
|
169
|
+
};
|
|
170
|
+
const pushPiece = (piece) => {
|
|
171
|
+
keyedPieces.push({
|
|
172
|
+
key: `${piece.type}:${piece.id}`,
|
|
173
|
+
piece
|
|
174
|
+
});
|
|
175
|
+
emit();
|
|
176
|
+
};
|
|
177
|
+
const resolveTextStreamKey = (chunk, fallbackNode) => {
|
|
178
|
+
if ("segmentId" in chunk && typeof chunk.segmentId === "string") {
|
|
179
|
+
const seg = chunk.segmentId.trim();
|
|
180
|
+
if (seg.length > 0) {
|
|
181
|
+
const op = "opId" in chunk && typeof chunk.opId === "string" ? chunk.opId.trim() : "";
|
|
182
|
+
return op.length > 0 ? `${op}:${seg}` : seg;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if ("opId" in chunk && typeof chunk.opId === "string") {
|
|
186
|
+
const op = chunk.opId.trim();
|
|
187
|
+
if (op.length > 0) {
|
|
188
|
+
const node = ("node" in chunk && typeof chunk.node === "string" ? chunk.node : fallbackNode) ?? "__unknown__";
|
|
189
|
+
return `${op}:${node}`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return fallbackNode ?? "__unknown__";
|
|
193
|
+
};
|
|
194
|
+
const ensureTextPiece = (key) => {
|
|
195
|
+
const pieceKey = `text:${key || "__unknown__"}`;
|
|
196
|
+
const existingIndex = findPieceIndex(pieceKey);
|
|
197
|
+
if (existingIndex >= 0) {
|
|
198
|
+
return keyedPieces[existingIndex]?.piece;
|
|
199
|
+
}
|
|
200
|
+
const nextPiece = {
|
|
201
|
+
id: args.createId(),
|
|
202
|
+
type: "text",
|
|
203
|
+
content: ""
|
|
204
|
+
};
|
|
205
|
+
keyedPieces.push({ key: pieceKey, piece: nextPiece });
|
|
206
|
+
return nextPiece;
|
|
207
|
+
};
|
|
208
|
+
const processChunk = (chunk, _options) => {
|
|
209
|
+
if (chunk.type === "text-start") {
|
|
210
|
+
ensureTextPiece(resolveTextStreamKey(chunk, chunk.node));
|
|
211
|
+
emit();
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (chunk.type === "text-delta") {
|
|
215
|
+
sawTextDelta = true;
|
|
216
|
+
const streamKey = resolveTextStreamKey(chunk, chunk.node);
|
|
217
|
+
const current = ensureTextPiece(streamKey);
|
|
218
|
+
upsertPiece(`text:${streamKey}`, {
|
|
219
|
+
...current,
|
|
220
|
+
content: current.content + chunk.delta
|
|
221
|
+
});
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (chunk.type === "text-end") {
|
|
225
|
+
emit();
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
if (chunk.type === "structured-data") {
|
|
229
|
+
const nextItem = args.structuredStreams.applyStreamChunk(chunk);
|
|
230
|
+
if (nextItem) {
|
|
231
|
+
upsertPiece(`structured:${nextItem.streamId}`, {
|
|
232
|
+
id: nextItem.id,
|
|
233
|
+
type: "structured",
|
|
234
|
+
data: nextItem.state
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
if (chunk.type === "interrupt") {
|
|
240
|
+
pushPiece(args.toHumanInputPiece(chunk));
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
if (chunk.type === "message") {
|
|
244
|
+
if (!sawTextDelta) {
|
|
245
|
+
pushPiece({
|
|
246
|
+
id: args.createId(),
|
|
247
|
+
type: "text",
|
|
248
|
+
content: chunk.content ?? ""
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (chunk.type === "error") {
|
|
254
|
+
pushPiece({
|
|
255
|
+
id: args.createId(),
|
|
256
|
+
type: "error",
|
|
257
|
+
content: chunk.message ?? "An error occurred"
|
|
258
|
+
});
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (chunk.type === "done") {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
};
|
|
266
|
+
return {
|
|
267
|
+
processChunk,
|
|
268
|
+
getPieces: () => keyedPieces.map((entry) => entry.piece).filter((piece) => isNonEmptyTextPiece(piece))
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/to-human-input-piece.ts
|
|
273
|
+
function toHumanInputPiece(args) {
|
|
274
|
+
const hi = args.chunk;
|
|
275
|
+
const input = hi.input ?? {};
|
|
276
|
+
const kind = input.kind || (input.multiple ? "multi-choice" : "choice");
|
|
277
|
+
const isText = kind === "text";
|
|
278
|
+
const question = isText ? input.question : typeof input.question === "string" ? input.question : "Please choose";
|
|
279
|
+
const optionsSrc = Array.isArray(input.options) ? input.options : [];
|
|
280
|
+
const optionsArr = optionsSrc.map((option) => ({
|
|
281
|
+
id: String(option.id ?? ""),
|
|
282
|
+
label: String(option.label ?? ""),
|
|
283
|
+
...typeof option.description === "string" && option.description ? { description: option.description } : {}
|
|
284
|
+
})).filter((option) => option.id && option.label);
|
|
285
|
+
return {
|
|
286
|
+
id: args.createId(),
|
|
287
|
+
type: "interrupt",
|
|
288
|
+
resumeToken: String(hi.resumeToken ?? ""),
|
|
289
|
+
requestId: String(hi.requestId ?? ""),
|
|
290
|
+
kind,
|
|
291
|
+
...question !== void 0 ? { question } : {},
|
|
292
|
+
multiple: Boolean(input.multiple),
|
|
293
|
+
options: optionsArr
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/use-chat.ts
|
|
298
|
+
var import_react3 = require("react");
|
|
299
|
+
|
|
300
|
+
// src/use-chat-stream-debug.ts
|
|
301
|
+
var import_react = require("react");
|
|
302
|
+
function useChatStreamDebug() {
|
|
303
|
+
const [streamDebug, setStreamDebug] = (0, import_react.useState)([]);
|
|
304
|
+
const clearStreamDebug = (0, import_react.useCallback)(() => {
|
|
305
|
+
setStreamDebug([]);
|
|
306
|
+
}, []);
|
|
307
|
+
const createRecorder = (0, import_react.useCallback)((initialMessage) => {
|
|
308
|
+
let seq = 0;
|
|
309
|
+
let lastTs = 0;
|
|
310
|
+
const accDebug = [];
|
|
311
|
+
const push = (chunk) => {
|
|
312
|
+
const ts = Date.now();
|
|
313
|
+
const withMeta = {
|
|
314
|
+
...chunk,
|
|
315
|
+
_ts: ts,
|
|
316
|
+
_dt: lastTs ? ts - lastTs : 0,
|
|
317
|
+
_seq: seq++
|
|
318
|
+
};
|
|
319
|
+
lastTs = ts;
|
|
320
|
+
accDebug.push(withMeta);
|
|
321
|
+
setStreamDebug(
|
|
322
|
+
(current) => current.length > 1e3 ? [...current.slice(-1e3), withMeta] : [...current, withMeta]
|
|
323
|
+
);
|
|
324
|
+
};
|
|
325
|
+
push({
|
|
326
|
+
type: "status",
|
|
327
|
+
message: initialMessage
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
push,
|
|
331
|
+
getAll: () => accDebug
|
|
332
|
+
};
|
|
333
|
+
}, []);
|
|
334
|
+
return {
|
|
335
|
+
streamDebug,
|
|
336
|
+
clearStreamDebug,
|
|
337
|
+
createRecorder
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/use-structured-streams.ts
|
|
342
|
+
var import_browser2 = require("@kortyx/stream/browser");
|
|
343
|
+
var import_react2 = require("react");
|
|
344
|
+
var defaultCreateId = () => {
|
|
345
|
+
try {
|
|
346
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
347
|
+
return crypto.randomUUID();
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
352
|
+
};
|
|
353
|
+
function useStructuredStreams(options) {
|
|
354
|
+
const createId = (0, import_react2.useRef)(options?.createId ?? defaultCreateId).current;
|
|
355
|
+
const initialChunks = (0, import_react2.useRef)(options?.initialChunks ?? []).current;
|
|
356
|
+
const accumulatorRef = (0, import_react2.useRef)(
|
|
357
|
+
(0, import_browser2.createStructuredStreamAccumulator)(initialChunks)
|
|
358
|
+
);
|
|
359
|
+
const initialItemsRef = (0, import_react2.useRef)(
|
|
360
|
+
void 0
|
|
361
|
+
);
|
|
362
|
+
if (initialItemsRef.current === void 0) {
|
|
363
|
+
initialItemsRef.current = accumulatorRef.current.entries().map(([streamId, state]) => ({
|
|
364
|
+
id: createId(),
|
|
365
|
+
streamId,
|
|
366
|
+
state
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
const itemsRef = (0, import_react2.useRef)(initialItemsRef.current);
|
|
370
|
+
const [items, setItems] = (0, import_react2.useState)(initialItemsRef.current);
|
|
371
|
+
const setNextItems = (0, import_react2.useCallback)(
|
|
372
|
+
(nextItems) => {
|
|
373
|
+
itemsRef.current = nextItems;
|
|
374
|
+
setItems(nextItems);
|
|
375
|
+
},
|
|
376
|
+
[]
|
|
377
|
+
);
|
|
378
|
+
const applyStreamChunk = (0, import_react2.useCallback)(
|
|
379
|
+
(chunk) => {
|
|
380
|
+
const nextState = accumulatorRef.current.applyStreamChunk(chunk);
|
|
381
|
+
if (!nextState) return void 0;
|
|
382
|
+
const currentItems = itemsRef.current;
|
|
383
|
+
const existingIndex = currentItems.findIndex(
|
|
384
|
+
(item) => item.streamId === nextState.streamId
|
|
385
|
+
);
|
|
386
|
+
const existingItem = existingIndex >= 0 ? currentItems[existingIndex] : void 0;
|
|
387
|
+
const nextItem = existingItem ? {
|
|
388
|
+
...existingItem,
|
|
389
|
+
state: nextState
|
|
390
|
+
} : {
|
|
391
|
+
id: createId(),
|
|
392
|
+
streamId: nextState.streamId,
|
|
393
|
+
state: nextState
|
|
394
|
+
};
|
|
395
|
+
const nextItems = existingIndex >= 0 ? currentItems.map(
|
|
396
|
+
(item, index) => index === existingIndex ? nextItem : item
|
|
397
|
+
) : [...currentItems, nextItem];
|
|
398
|
+
setNextItems(nextItems);
|
|
399
|
+
return nextItem;
|
|
400
|
+
},
|
|
401
|
+
[createId, setNextItems]
|
|
402
|
+
);
|
|
403
|
+
const clear = (0, import_react2.useCallback)(() => {
|
|
404
|
+
accumulatorRef.current = (0, import_browser2.createStructuredStreamAccumulator)();
|
|
405
|
+
setNextItems([]);
|
|
406
|
+
}, [setNextItems]);
|
|
407
|
+
const get = (0, import_react2.useCallback)((streamId) => {
|
|
408
|
+
return itemsRef.current.find((item) => item.streamId === streamId);
|
|
409
|
+
}, []);
|
|
410
|
+
const byStreamId = (0, import_react2.useMemo)(
|
|
411
|
+
() => Object.fromEntries(
|
|
412
|
+
items.map((item) => [item.streamId, item.state])
|
|
413
|
+
),
|
|
414
|
+
[items]
|
|
415
|
+
);
|
|
416
|
+
return {
|
|
417
|
+
items,
|
|
418
|
+
byStreamId,
|
|
419
|
+
applyStreamChunk,
|
|
420
|
+
clear,
|
|
421
|
+
get
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/use-chat.ts
|
|
426
|
+
var defaultCreateId2 = () => {
|
|
427
|
+
try {
|
|
428
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
429
|
+
return crypto.randomUUID();
|
|
430
|
+
}
|
|
431
|
+
} catch {
|
|
432
|
+
}
|
|
433
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
434
|
+
};
|
|
435
|
+
var defaultStorage = createBrowserChatStorage();
|
|
436
|
+
function useInitialValue(value) {
|
|
437
|
+
const ref = (0, import_react3.useRef)(void 0);
|
|
438
|
+
if (ref.current === void 0) {
|
|
439
|
+
ref.current = value;
|
|
440
|
+
}
|
|
441
|
+
return ref.current;
|
|
442
|
+
}
|
|
443
|
+
function findActiveTextInterrupt(args) {
|
|
444
|
+
const liveInterrupt = args.streamContentPieces.find(
|
|
445
|
+
(piece) => piece.type === "interrupt" && piece.kind === "text"
|
|
446
|
+
);
|
|
447
|
+
if (liveInterrupt) return liveInterrupt;
|
|
448
|
+
for (let i = args.messages.length - 1; i >= 0; i -= 1) {
|
|
449
|
+
const message = args.messages[i];
|
|
450
|
+
if (!message) continue;
|
|
451
|
+
if (message.role !== "assistant" || !message.contentPieces) continue;
|
|
452
|
+
const messageInterrupt = [...message.contentPieces].reverse().find(
|
|
453
|
+
(piece) => piece.type === "interrupt" && piece.kind === "text"
|
|
454
|
+
);
|
|
455
|
+
if (messageInterrupt) return messageInterrupt;
|
|
456
|
+
}
|
|
457
|
+
return void 0;
|
|
458
|
+
}
|
|
459
|
+
function useChat(options) {
|
|
460
|
+
const transport = useInitialValue(options.transport);
|
|
461
|
+
const storage = useInitialValue(options.storage ?? defaultStorage);
|
|
462
|
+
const createId = useInitialValue(options.createId ?? defaultCreateId2);
|
|
463
|
+
const [messages, setMessages] = (0, import_react3.useState)([]);
|
|
464
|
+
const [isStreaming, setIsStreaming] = (0, import_react3.useState)(false);
|
|
465
|
+
const [streamContentPieces, setStreamContentPieces] = (0, import_react3.useState)([]);
|
|
466
|
+
const [lastAssistantId, setLastAssistantId] = (0, import_react3.useState)(null);
|
|
467
|
+
const [includeHistory, setIncludeHistory] = (0, import_react3.useState)(true);
|
|
468
|
+
const [workflowId, setWorkflowId] = (0, import_react3.useState)("");
|
|
469
|
+
const [sessionId, setSessionId] = (0, import_react3.useState)(null);
|
|
470
|
+
const [didHydrateStorage, setDidHydrateStorage] = (0, import_react3.useState)(false);
|
|
471
|
+
const { streamDebug, clearStreamDebug, createRecorder } = useChatStreamDebug();
|
|
472
|
+
const { applyStreamChunk, clear: clearStructuredStreams } = useStructuredStreams({
|
|
473
|
+
createId
|
|
474
|
+
});
|
|
475
|
+
(0, import_react3.useEffect)(() => {
|
|
476
|
+
let cancelled = false;
|
|
477
|
+
void Promise.resolve(storage.load()).then((storedState) => {
|
|
478
|
+
if (cancelled) return;
|
|
479
|
+
if (typeof storedState.sessionId === "string") {
|
|
480
|
+
setSessionId(storedState.sessionId);
|
|
481
|
+
}
|
|
482
|
+
if (typeof storedState.workflowId === "string") {
|
|
483
|
+
setWorkflowId(storedState.workflowId);
|
|
484
|
+
}
|
|
485
|
+
if (typeof storedState.includeHistory === "boolean") {
|
|
486
|
+
setIncludeHistory(storedState.includeHistory);
|
|
487
|
+
}
|
|
488
|
+
if (Array.isArray(storedState.messages) && storedState.messages.length > 0) {
|
|
489
|
+
setMessages(storedState.messages);
|
|
490
|
+
}
|
|
491
|
+
setDidHydrateStorage(true);
|
|
492
|
+
});
|
|
493
|
+
return () => {
|
|
494
|
+
cancelled = true;
|
|
495
|
+
};
|
|
496
|
+
}, [storage]);
|
|
497
|
+
(0, import_react3.useEffect)(() => {
|
|
498
|
+
if (!didHydrateStorage) return;
|
|
499
|
+
void storage.save({
|
|
500
|
+
sessionId,
|
|
501
|
+
workflowId,
|
|
502
|
+
includeHistory,
|
|
503
|
+
messages
|
|
504
|
+
});
|
|
505
|
+
}, [
|
|
506
|
+
didHydrateStorage,
|
|
507
|
+
includeHistory,
|
|
508
|
+
messages,
|
|
509
|
+
sessionId,
|
|
510
|
+
storage,
|
|
511
|
+
workflowId
|
|
512
|
+
]);
|
|
513
|
+
const clearChat = () => {
|
|
514
|
+
setMessages([]);
|
|
515
|
+
setStreamContentPieces([]);
|
|
516
|
+
clearStreamDebug();
|
|
517
|
+
clearStructuredStreams();
|
|
518
|
+
setLastAssistantId(null);
|
|
519
|
+
void storage.clearMessages();
|
|
520
|
+
};
|
|
521
|
+
const persistSessionId = (sid) => {
|
|
522
|
+
setSessionId(sid);
|
|
523
|
+
};
|
|
524
|
+
const resolveSessionId = () => {
|
|
525
|
+
const sid = sessionId ?? createId();
|
|
526
|
+
if (!sessionId) persistSessionId(sid);
|
|
527
|
+
return sid;
|
|
528
|
+
};
|
|
529
|
+
const streamAssistantResponse = async (args) => {
|
|
530
|
+
clearStructuredStreams();
|
|
531
|
+
setStreamContentPieces([]);
|
|
532
|
+
const debug = createRecorder(`${args.debugLabel} sessionId=${args.sid}`);
|
|
533
|
+
const pieces = createLiveChatPieces({
|
|
534
|
+
createId,
|
|
535
|
+
onChange: setStreamContentPieces,
|
|
536
|
+
structuredStreams: {
|
|
537
|
+
applyStreamChunk
|
|
538
|
+
},
|
|
539
|
+
toHumanInputPiece: (chunk) => toHumanInputPiece({
|
|
540
|
+
chunk,
|
|
541
|
+
createId
|
|
542
|
+
})
|
|
543
|
+
});
|
|
544
|
+
await transport.stream({
|
|
545
|
+
sessionId: args.sid,
|
|
546
|
+
workflowId,
|
|
547
|
+
messages: args.messagesToSend,
|
|
548
|
+
onChunk: (chunk) => {
|
|
549
|
+
debug.push(chunk);
|
|
550
|
+
if (chunk.type === "session") {
|
|
551
|
+
if (typeof chunk.sessionId === "string") {
|
|
552
|
+
persistSessionId(chunk.sessionId);
|
|
553
|
+
}
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
return pieces.processChunk(chunk, {
|
|
557
|
+
openDebugOnInterrupt: args.openDebugOnInterrupt
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
const assistant = buildAssistantMessage({
|
|
562
|
+
createId,
|
|
563
|
+
pieces: pieces.getPieces(),
|
|
564
|
+
debug: debug.getAll()
|
|
565
|
+
});
|
|
566
|
+
setMessages((prev) => [...prev, assistant]);
|
|
567
|
+
setLastAssistantId(assistant.id);
|
|
568
|
+
clearStructuredStreams();
|
|
569
|
+
setStreamContentPieces([]);
|
|
570
|
+
clearStreamDebug();
|
|
571
|
+
};
|
|
572
|
+
const respondToHumanInput = async ({
|
|
573
|
+
resumeToken,
|
|
574
|
+
requestId,
|
|
575
|
+
selected,
|
|
576
|
+
text
|
|
577
|
+
}) => {
|
|
578
|
+
if (isStreaming) return;
|
|
579
|
+
const label = typeof text === "string" && text.length > 0 ? text : selected.length > 0 ? selected.length === 1 ? selected[0] : selected.join(", ") : "(selection)";
|
|
580
|
+
const userId = createId();
|
|
581
|
+
const userMsg = { id: userId, role: "user", content: label };
|
|
582
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
583
|
+
setIsStreaming(true);
|
|
584
|
+
setStreamContentPieces([]);
|
|
585
|
+
clearStreamDebug();
|
|
586
|
+
clearStructuredStreams();
|
|
587
|
+
try {
|
|
588
|
+
const resumePayload = {
|
|
589
|
+
resume: { token: resumeToken, requestId, selected }
|
|
590
|
+
};
|
|
591
|
+
const outgoing = {
|
|
592
|
+
role: "user",
|
|
593
|
+
content: label,
|
|
594
|
+
metadata: resumePayload
|
|
595
|
+
};
|
|
596
|
+
const sid = resolveSessionId();
|
|
597
|
+
const history = includeHistory ? messages.map((message) => ({
|
|
598
|
+
role: message.role,
|
|
599
|
+
content: message.content
|
|
600
|
+
})) : [];
|
|
601
|
+
const messagesToSend = [...history, outgoing];
|
|
602
|
+
await streamAssistantResponse({
|
|
603
|
+
sid,
|
|
604
|
+
messagesToSend,
|
|
605
|
+
debugLabel: "runChat (resume)"
|
|
606
|
+
});
|
|
607
|
+
} finally {
|
|
608
|
+
setIsStreaming(false);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
const send = async (text) => {
|
|
612
|
+
const content = text.trim();
|
|
613
|
+
if (!content || isStreaming) return;
|
|
614
|
+
const activeTextInterrupt = findActiveTextInterrupt({
|
|
615
|
+
messages,
|
|
616
|
+
streamContentPieces
|
|
617
|
+
});
|
|
618
|
+
if (activeTextInterrupt) {
|
|
619
|
+
await respondToHumanInput({
|
|
620
|
+
resumeToken: activeTextInterrupt.resumeToken,
|
|
621
|
+
requestId: activeTextInterrupt.requestId,
|
|
622
|
+
selected: [content],
|
|
623
|
+
text: content
|
|
624
|
+
});
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const userId = createId();
|
|
628
|
+
const userMsg = { id: userId, role: "user", content };
|
|
629
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
630
|
+
setIsStreaming(true);
|
|
631
|
+
setStreamContentPieces([]);
|
|
632
|
+
clearStreamDebug();
|
|
633
|
+
clearStructuredStreams();
|
|
634
|
+
try {
|
|
635
|
+
const sid = resolveSessionId();
|
|
636
|
+
const history = includeHistory ? messages.map((message) => ({
|
|
637
|
+
role: message.role,
|
|
638
|
+
content: message.content
|
|
639
|
+
})) : [];
|
|
640
|
+
const messagesToSend = [...history, { role: "user", content }];
|
|
641
|
+
await streamAssistantResponse({
|
|
642
|
+
sid,
|
|
643
|
+
messagesToSend,
|
|
644
|
+
debugLabel: "runChat (send)",
|
|
645
|
+
openDebugOnInterrupt: true
|
|
646
|
+
});
|
|
647
|
+
} finally {
|
|
648
|
+
setIsStreaming(false);
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
return {
|
|
652
|
+
messages,
|
|
653
|
+
isStreaming,
|
|
654
|
+
streamContentPieces,
|
|
655
|
+
streamDebug,
|
|
656
|
+
lastAssistantId,
|
|
657
|
+
workflowId,
|
|
658
|
+
setWorkflowId,
|
|
659
|
+
send,
|
|
660
|
+
respondToHumanInput,
|
|
661
|
+
setMessages,
|
|
662
|
+
clearChat,
|
|
663
|
+
includeHistory,
|
|
664
|
+
setIncludeHistory
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
668
|
+
0 && (module.exports = {
|
|
669
|
+
buildAssistantMessage,
|
|
670
|
+
createBrowserChatStorage,
|
|
671
|
+
createChatTransport,
|
|
672
|
+
createLiveChatPieces,
|
|
673
|
+
createRouteChatTransport,
|
|
674
|
+
toHumanInputPiece,
|
|
675
|
+
useChat,
|
|
676
|
+
useChatStreamDebug,
|
|
677
|
+
useStructuredStreams
|
|
678
|
+
});
|
|
679
|
+
//# sourceMappingURL=index.js.map
|