@paymanai/payman-typescript-ask-sdk 4.0.9 → 4.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +194 -47
- package/dist/index.d.ts +194 -47
- package/dist/index.js +506 -654
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +494 -654
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +509 -657
- package/dist/index.native.js.map +1 -1
- package/package.json +1 -1
package/dist/index.native.js
CHANGED
|
@@ -4,7 +4,9 @@ var react = require('react');
|
|
|
4
4
|
var reactNativeMmkv = require('react-native-mmkv');
|
|
5
5
|
var expoSpeechRecognition = require('expo-speech-recognition');
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
|
|
8
10
|
|
|
9
11
|
// src/utils/generateId.ts
|
|
10
12
|
function generateId() {
|
|
@@ -14,6 +16,95 @@ function generateId() {
|
|
|
14
16
|
return v.toString(16);
|
|
15
17
|
});
|
|
16
18
|
}
|
|
19
|
+
var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
|
|
20
|
+
var chatStore = {
|
|
21
|
+
get(key) {
|
|
22
|
+
const raw = storage.getString(key);
|
|
23
|
+
if (!raw) return [];
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
} catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
set(key, messages) {
|
|
31
|
+
storage.set(key, JSON.stringify(messages));
|
|
32
|
+
},
|
|
33
|
+
delete(key) {
|
|
34
|
+
storage.delete(key);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/utils/activeStreamStore.ts
|
|
39
|
+
var streams = /* @__PURE__ */ new Map();
|
|
40
|
+
var activeStreamStore = {
|
|
41
|
+
has(key) {
|
|
42
|
+
return streams.has(key);
|
|
43
|
+
},
|
|
44
|
+
get(key) {
|
|
45
|
+
const entry = streams.get(key);
|
|
46
|
+
if (!entry) return null;
|
|
47
|
+
return { messages: entry.messages, isWaiting: entry.isWaiting };
|
|
48
|
+
},
|
|
49
|
+
// Called before startStream — registers the controller and initial messages
|
|
50
|
+
start(key, abortController, initialMessages) {
|
|
51
|
+
const existing = streams.get(key);
|
|
52
|
+
streams.set(key, {
|
|
53
|
+
messages: initialMessages,
|
|
54
|
+
isWaiting: true,
|
|
55
|
+
abortController,
|
|
56
|
+
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
// Called by the stream on every event — applies the same updater pattern React uses
|
|
60
|
+
applyMessages(key, updater) {
|
|
61
|
+
const entry = streams.get(key);
|
|
62
|
+
if (!entry) return;
|
|
63
|
+
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
64
|
+
entry.messages = next;
|
|
65
|
+
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
66
|
+
},
|
|
67
|
+
setWaiting(key, waiting) {
|
|
68
|
+
const entry = streams.get(key);
|
|
69
|
+
if (!entry) return;
|
|
70
|
+
entry.isWaiting = waiting;
|
|
71
|
+
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
72
|
+
},
|
|
73
|
+
// Called when stream completes — persists to chatStore and cleans up
|
|
74
|
+
complete(key) {
|
|
75
|
+
const entry = streams.get(key);
|
|
76
|
+
if (!entry) return;
|
|
77
|
+
entry.isWaiting = false;
|
|
78
|
+
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
79
|
+
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
80
|
+
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
81
|
+
streams.delete(key);
|
|
82
|
+
},
|
|
83
|
+
// Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
|
|
84
|
+
subscribe(key, listener) {
|
|
85
|
+
const entry = streams.get(key);
|
|
86
|
+
if (!entry) return () => {
|
|
87
|
+
};
|
|
88
|
+
entry.listeners.add(listener);
|
|
89
|
+
return () => {
|
|
90
|
+
streams.get(key)?.listeners.delete(listener);
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
// Rename an entry — used when the server assigns a new session ID mid-stream
|
|
94
|
+
rename(oldKey, newKey) {
|
|
95
|
+
const entry = streams.get(oldKey);
|
|
96
|
+
if (!entry) return;
|
|
97
|
+
streams.set(newKey, entry);
|
|
98
|
+
streams.delete(oldKey);
|
|
99
|
+
},
|
|
100
|
+
// Explicit user cancel — aborts the controller and removes the entry
|
|
101
|
+
abort(key) {
|
|
102
|
+
const entry = streams.get(key);
|
|
103
|
+
if (!entry) return;
|
|
104
|
+
entry.abortController.abort();
|
|
105
|
+
streams.delete(key);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
17
108
|
|
|
18
109
|
// src/utils/streamingClient.native.ts
|
|
19
110
|
function parseJSONBuffer(buffer) {
|
|
@@ -275,62 +366,28 @@ function normalizeEvent(event) {
|
|
|
275
366
|
return event;
|
|
276
367
|
}
|
|
277
368
|
}
|
|
278
|
-
function
|
|
279
|
-
|
|
369
|
+
function isVerificationSchema(schema) {
|
|
370
|
+
const props = schema?.properties;
|
|
371
|
+
if (!props) return false;
|
|
372
|
+
const keys = Object.keys(props);
|
|
373
|
+
return keys.length === 1 && keys[0] === "verificationCode";
|
|
280
374
|
}
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
case "
|
|
286
|
-
return
|
|
287
|
-
case "
|
|
288
|
-
return
|
|
289
|
-
case "USER_ACTION_INVALID":
|
|
290
|
-
return safeRaw || "Invalid code. Please try again.";
|
|
291
|
-
case "USER_ACTION_REJECTED":
|
|
292
|
-
return safeRaw || "Verification cancelled";
|
|
293
|
-
case "USER_ACTION_EXPIRED":
|
|
294
|
-
return safeRaw || "Verification expired";
|
|
295
|
-
case "USER_ACTION_RESENT":
|
|
296
|
-
return safeRaw || "Verification code resent";
|
|
297
|
-
case "USER_ACTION_FAILED":
|
|
298
|
-
return safeRaw || "Verification failed";
|
|
375
|
+
function classifyUserActionKind(action, schema) {
|
|
376
|
+
switch ((action || "").toLowerCase()) {
|
|
377
|
+
case "userverificationrequest":
|
|
378
|
+
return "verification";
|
|
379
|
+
case "usernotificationrequest":
|
|
380
|
+
return "notification";
|
|
381
|
+
case "userformrequest":
|
|
382
|
+
return isVerificationSchema(schema) ? "verification" : "form";
|
|
299
383
|
default:
|
|
300
|
-
return
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
function workingPhaseDetailForDisplay(raw) {
|
|
304
|
-
const t = raw.trim();
|
|
305
|
-
if (!t) return "";
|
|
306
|
-
if (/^Identified\s+\d+\s+tasks?\s+to\s+execute\.?$/i.test(t)) {
|
|
307
|
-
return "";
|
|
384
|
+
return isVerificationSchema(schema) ? "verification" : "form";
|
|
308
385
|
}
|
|
309
|
-
return t;
|
|
310
386
|
}
|
|
311
387
|
function getEventText(event, field) {
|
|
312
388
|
const value = event[field];
|
|
313
389
|
return typeof value === "string" ? value.trim() : "";
|
|
314
390
|
}
|
|
315
|
-
function shouldShowIntentHeader(event) {
|
|
316
|
-
const workerName = getEventText(event, "workerName");
|
|
317
|
-
const intentId = getEventText(event, "intentId");
|
|
318
|
-
return Boolean(workerName && intentId && workerName === intentId);
|
|
319
|
-
}
|
|
320
|
-
function addThinkingHeader(state, header) {
|
|
321
|
-
state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + header;
|
|
322
|
-
}
|
|
323
|
-
function addThinkingDetail(state, detail) {
|
|
324
|
-
const trimmed = detail.trim();
|
|
325
|
-
if (!trimmed) return;
|
|
326
|
-
state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + trimmed;
|
|
327
|
-
}
|
|
328
|
-
function addThinkingLine(state, header, detail) {
|
|
329
|
-
state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + header + "\n" + detail;
|
|
330
|
-
}
|
|
331
|
-
function appendThinkingText(state, text) {
|
|
332
|
-
state.formattedThinkingText += text;
|
|
333
|
-
}
|
|
334
391
|
function updateExecutionStageMessage(state, msg) {
|
|
335
392
|
if (!msg) return;
|
|
336
393
|
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
@@ -351,26 +408,38 @@ function completeLastInProgressStep(steps) {
|
|
|
351
408
|
}
|
|
352
409
|
function createInitialV2State() {
|
|
353
410
|
return {
|
|
354
|
-
formattedThinkingText: "",
|
|
355
411
|
finalResponse: "",
|
|
356
|
-
currentWorker: "",
|
|
357
412
|
lastEventType: "",
|
|
358
413
|
sessionId: void 0,
|
|
359
414
|
executionId: void 0,
|
|
360
415
|
hasError: false,
|
|
361
416
|
errorMessage: "",
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
417
|
+
userActions: [],
|
|
418
|
+
notifications: [],
|
|
419
|
+
lastUserAction: void 0,
|
|
420
|
+
lastNotification: void 0,
|
|
365
421
|
finalData: void 0,
|
|
366
422
|
steps: [],
|
|
367
423
|
stepCounter: 0,
|
|
368
424
|
currentExecutingStepId: void 0
|
|
369
425
|
};
|
|
370
426
|
}
|
|
427
|
+
function upsertUserAction(state, req) {
|
|
428
|
+
const active = { ...req, status: "pending" };
|
|
429
|
+
const matchIdx = state.userActions.findIndex(
|
|
430
|
+
(p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
|
|
431
|
+
);
|
|
432
|
+
if (matchIdx >= 0) {
|
|
433
|
+
state.userActions[matchIdx] = active;
|
|
434
|
+
} else {
|
|
435
|
+
state.userActions.push(active);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
371
438
|
function processStreamEventV2(rawEvent, state) {
|
|
372
439
|
const event = normalizeEvent(rawEvent);
|
|
373
440
|
const eventType = event.eventType;
|
|
441
|
+
state.lastUserAction = void 0;
|
|
442
|
+
state.lastNotification = void 0;
|
|
374
443
|
if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
375
444
|
if (event.executionId) state.executionId = event.executionId;
|
|
376
445
|
if (event.sessionId) state.sessionId = event.sessionId;
|
|
@@ -386,10 +455,6 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
386
455
|
return state;
|
|
387
456
|
}
|
|
388
457
|
if (typeof eventType === "string" && eventType.toUpperCase() === "THINKING_DELTA") {
|
|
389
|
-
const text = typeof event.text === "string" ? event.text : "";
|
|
390
|
-
if (text) {
|
|
391
|
-
appendThinkingText(state, text);
|
|
392
|
-
}
|
|
393
458
|
if (event.executionId) state.executionId = event.executionId;
|
|
394
459
|
if (event.sessionId) state.sessionId = event.sessionId;
|
|
395
460
|
state.lastEventType = "THINKING_DELTA";
|
|
@@ -400,7 +465,6 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
400
465
|
const message = getEventMessage(event);
|
|
401
466
|
switch (eventType) {
|
|
402
467
|
case "WORKFLOW_STARTED":
|
|
403
|
-
case "STARTED":
|
|
404
468
|
state.lastEventType = eventType;
|
|
405
469
|
break;
|
|
406
470
|
case "INTENT_PROGRESS": {
|
|
@@ -413,97 +477,7 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
413
477
|
state.lastEventType = eventType;
|
|
414
478
|
break;
|
|
415
479
|
}
|
|
416
|
-
case "INTENT_THINKING": {
|
|
417
|
-
const worker = getEventText(event, "workerName") || "Worker";
|
|
418
|
-
const msg = getEventText(event, "message") || "Thinking...";
|
|
419
|
-
const showHeader = shouldShowIntentHeader(event);
|
|
420
|
-
if (worker !== state.currentWorker) {
|
|
421
|
-
state.currentWorker = worker;
|
|
422
|
-
if (showHeader && msg && msg !== worker) {
|
|
423
|
-
addThinkingLine(state, `**${worker}**`, msg);
|
|
424
|
-
} else if (showHeader) {
|
|
425
|
-
addThinkingHeader(state, `**${worker}**`);
|
|
426
|
-
} else if (msg !== "Thinking...") {
|
|
427
|
-
addThinkingDetail(state, msg);
|
|
428
|
-
}
|
|
429
|
-
} else if ((showHeader || msg !== "Thinking...")) {
|
|
430
|
-
appendThinkingText(state, "\n" + msg);
|
|
431
|
-
}
|
|
432
|
-
const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
|
|
433
|
-
if (lastInProgress) {
|
|
434
|
-
lastInProgress.thinkingText = "";
|
|
435
|
-
lastInProgress.isThinking = true;
|
|
436
|
-
}
|
|
437
|
-
state.lastEventType = "INTENT_THINKING";
|
|
438
|
-
break;
|
|
439
|
-
}
|
|
440
|
-
case "INTENT_THINKING_CONT": {
|
|
441
|
-
const msg = event.message || "";
|
|
442
|
-
if (!msg) break;
|
|
443
|
-
if (state.lastEventType === "INTENT_THINKING") {
|
|
444
|
-
appendThinkingText(state, "\n" + msg);
|
|
445
|
-
} else {
|
|
446
|
-
appendThinkingText(state, msg);
|
|
447
|
-
}
|
|
448
|
-
const thinkingStep = [...state.steps].reverse().find((s) => s.isThinking);
|
|
449
|
-
if (thinkingStep) {
|
|
450
|
-
thinkingStep.thinkingText = (thinkingStep.thinkingText || "") + msg;
|
|
451
|
-
}
|
|
452
|
-
state.lastEventType = "INTENT_THINKING_CONT";
|
|
453
|
-
break;
|
|
454
|
-
}
|
|
455
|
-
case "ORCHESTRATOR_THINKING": {
|
|
456
|
-
addThinkingLine(state, "**Planning**", event.message || "Understanding your request...");
|
|
457
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
458
|
-
state.steps.push({
|
|
459
|
-
id: stepId,
|
|
460
|
-
eventType,
|
|
461
|
-
message,
|
|
462
|
-
status: "in_progress",
|
|
463
|
-
timestamp: Date.now(),
|
|
464
|
-
elapsedMs: event.elapsedMs
|
|
465
|
-
});
|
|
466
|
-
state.currentExecutingStepId = stepId;
|
|
467
|
-
state.lastEventType = eventType;
|
|
468
|
-
break;
|
|
469
|
-
}
|
|
470
|
-
case "ORCHESTRATOR_COMPLETED": {
|
|
471
|
-
const workingDetail = workingPhaseDetailForDisplay(message);
|
|
472
|
-
if (workingDetail) {
|
|
473
|
-
addThinkingLine(state, "**Working**", workingDetail);
|
|
474
|
-
} else {
|
|
475
|
-
addThinkingHeader(state, "**Working**");
|
|
476
|
-
}
|
|
477
|
-
state.steps.push({
|
|
478
|
-
id: `step-${state.stepCounter++}`,
|
|
479
|
-
eventType: "WORKING",
|
|
480
|
-
message: workingDetail,
|
|
481
|
-
status: "completed",
|
|
482
|
-
timestamp: Date.now()
|
|
483
|
-
});
|
|
484
|
-
const step = state.steps.find((s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress");
|
|
485
|
-
if (step) {
|
|
486
|
-
step.status = "completed";
|
|
487
|
-
if (event.elapsedMs) step.elapsedMs = event.elapsedMs;
|
|
488
|
-
if (step.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
|
|
489
|
-
}
|
|
490
|
-
state.lastEventType = eventType;
|
|
491
|
-
break;
|
|
492
|
-
}
|
|
493
480
|
case "INTENT_STARTED": {
|
|
494
|
-
const worker = getEventText(event, "workerName") || "Worker";
|
|
495
|
-
const msg = getEventText(event, "message") || "Starting...";
|
|
496
|
-
const showHeader = shouldShowIntentHeader(event);
|
|
497
|
-
state.currentWorker = worker;
|
|
498
|
-
if (showHeader && msg !== worker) {
|
|
499
|
-
addThinkingLine(state, `**${worker}**`, msg);
|
|
500
|
-
} else if (showHeader) {
|
|
501
|
-
addThinkingHeader(state, `**${worker}**`);
|
|
502
|
-
} else {
|
|
503
|
-
addThinkingDetail(state, msg);
|
|
504
|
-
}
|
|
505
|
-
const thinkingStep = state.steps.find((s) => s.isThinking);
|
|
506
|
-
if (thinkingStep) thinkingStep.isThinking = false;
|
|
507
481
|
const stepId = `step-${state.stepCounter++}`;
|
|
508
482
|
state.steps.push({
|
|
509
483
|
id: stepId,
|
|
@@ -522,55 +496,18 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
522
496
|
const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
|
|
523
497
|
if (intentStep) {
|
|
524
498
|
intentStep.status = "completed";
|
|
525
|
-
intentStep.isThinking = false;
|
|
526
499
|
if (event.elapsedMs) intentStep.elapsedMs = event.elapsedMs;
|
|
527
500
|
if (intentStep.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
|
|
528
501
|
}
|
|
529
502
|
state.lastEventType = eventType;
|
|
530
503
|
break;
|
|
531
504
|
}
|
|
532
|
-
case "
|
|
533
|
-
addThinkingLine(state, "**Finalizing**", event.message || "Preparing response...");
|
|
534
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
535
|
-
state.steps.push({
|
|
536
|
-
id: stepId,
|
|
537
|
-
eventType,
|
|
538
|
-
message,
|
|
539
|
-
status: "in_progress",
|
|
540
|
-
timestamp: Date.now(),
|
|
541
|
-
elapsedMs: event.elapsedMs
|
|
542
|
-
});
|
|
543
|
-
state.currentExecutingStepId = stepId;
|
|
544
|
-
state.lastEventType = eventType;
|
|
545
|
-
break;
|
|
546
|
-
}
|
|
547
|
-
case "AGGREGATOR_COMPLETED": {
|
|
548
|
-
appendThinkingText(state, "\n" + (event.message || "Response ready"));
|
|
549
|
-
const step = state.steps.find((s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress");
|
|
550
|
-
if (step) {
|
|
551
|
-
step.status = "completed";
|
|
552
|
-
if (event.elapsedMs) step.elapsedMs = event.elapsedMs;
|
|
553
|
-
if (step.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
|
|
554
|
-
}
|
|
555
|
-
state.lastEventType = eventType;
|
|
556
|
-
break;
|
|
557
|
-
}
|
|
558
|
-
case "WORKFLOW_COMPLETED":
|
|
559
|
-
case "COMPLETED": {
|
|
505
|
+
case "WORKFLOW_COMPLETED": {
|
|
560
506
|
const totalTime = Number(event.totalTimeMs);
|
|
561
507
|
if (Number.isFinite(totalTime) && totalTime > 0) {
|
|
562
508
|
state.totalElapsedMs = totalTime;
|
|
563
509
|
}
|
|
564
|
-
|
|
565
|
-
const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
|
|
566
|
-
if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
|
|
567
|
-
content = trace.workflowMsg;
|
|
568
|
-
}
|
|
569
|
-
if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
|
|
570
|
-
const agg = trace.aggregator;
|
|
571
|
-
if (typeof agg.response === "string") content = agg.response;
|
|
572
|
-
else content = extractResponseContent(agg.response);
|
|
573
|
-
}
|
|
510
|
+
const content = extractResponseContent(event.response);
|
|
574
511
|
if (content) {
|
|
575
512
|
state.finalResponse = content;
|
|
576
513
|
if (event.trace && typeof event.trace === "object") {
|
|
@@ -585,178 +522,81 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
585
522
|
state.steps.forEach((step) => {
|
|
586
523
|
if (step.status === "in_progress") {
|
|
587
524
|
step.status = "completed";
|
|
588
|
-
step.isThinking = false;
|
|
589
525
|
}
|
|
590
526
|
});
|
|
591
527
|
state.lastEventType = eventType;
|
|
592
528
|
break;
|
|
593
529
|
}
|
|
594
530
|
case "USER_ACTION_REQUIRED": {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
531
|
+
const rawAction = typeof event.action === "string" ? event.action : void 0;
|
|
532
|
+
const schema = event.requestedSchema;
|
|
533
|
+
const kind = classifyUserActionKind(rawAction, schema);
|
|
534
|
+
const promptMessage = typeof event.message === "string" && event.message.trim() || message || "";
|
|
535
|
+
const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
|
|
536
|
+
if (kind === "notification") {
|
|
537
|
+
const notification = {
|
|
538
|
+
id: userActionId || `note-${state.stepCounter++}`,
|
|
539
|
+
message: promptMessage
|
|
603
540
|
};
|
|
541
|
+
state.notifications.push(notification);
|
|
542
|
+
state.lastNotification = notification;
|
|
543
|
+
state.lastEventType = eventType;
|
|
544
|
+
break;
|
|
604
545
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
eventType,
|
|
609
|
-
req?.message || message
|
|
610
|
-
);
|
|
611
|
-
if (req) {
|
|
612
|
-
addThinkingLine(state, "**Verification Required**", displayMessage);
|
|
546
|
+
if (!userActionId) {
|
|
547
|
+
state.lastEventType = eventType;
|
|
548
|
+
break;
|
|
613
549
|
}
|
|
614
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
615
|
-
state.steps.push({
|
|
616
|
-
id: stepId,
|
|
617
|
-
eventType,
|
|
618
|
-
message: displayMessage,
|
|
619
|
-
status: "in_progress",
|
|
620
|
-
timestamp: Date.now(),
|
|
621
|
-
elapsedMs: event.elapsedMs
|
|
622
|
-
});
|
|
623
|
-
state.currentExecutingStepId = stepId;
|
|
624
|
-
state.lastEventType = eventType;
|
|
625
|
-
break;
|
|
626
|
-
}
|
|
627
|
-
case "USER_ACTION_SUCCESS": {
|
|
628
|
-
const displayMessage = getUserActionDisplayMessage(eventType, event.message);
|
|
629
|
-
appendThinkingText(state, "\n\u2713 " + displayMessage);
|
|
630
550
|
completeLastInProgressStep(state.steps);
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
551
|
+
const verificationType = event.verificationType === "ALPHANUMERIC_CODE" || event.verificationType === "NUMERIC_CODE" ? event.verificationType : void 0;
|
|
552
|
+
const request = {
|
|
553
|
+
userActionId,
|
|
554
|
+
kind,
|
|
555
|
+
rawAction,
|
|
556
|
+
subAction: typeof event.subAction === "string" ? event.subAction : void 0,
|
|
557
|
+
verificationType,
|
|
558
|
+
expirySeconds: typeof event.expirySeconds === "number" ? event.expirySeconds : void 0,
|
|
559
|
+
message: promptMessage || void 0,
|
|
560
|
+
requestedSchema: schema,
|
|
561
|
+
metadata: event.metadata,
|
|
562
|
+
toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : void 0,
|
|
563
|
+
executionId: state.executionId,
|
|
564
|
+
sessionId: state.sessionId
|
|
565
|
+
};
|
|
566
|
+
upsertUserAction(state, request);
|
|
567
|
+
state.lastUserAction = request;
|
|
634
568
|
const stepId = `step-${state.stepCounter++}`;
|
|
635
569
|
state.steps.push({
|
|
636
570
|
id: stepId,
|
|
637
571
|
eventType,
|
|
638
|
-
message:
|
|
639
|
-
status: "completed",
|
|
640
|
-
timestamp: Date.now(),
|
|
641
|
-
elapsedMs: event.elapsedMs
|
|
642
|
-
});
|
|
643
|
-
state.lastEventType = eventType;
|
|
644
|
-
break;
|
|
645
|
-
}
|
|
646
|
-
case "USER_ACTION_INVALID": {
|
|
647
|
-
const displayMessage = getUserActionDisplayMessage(eventType, event.message);
|
|
648
|
-
appendThinkingText(state, "\n\u2717 " + displayMessage);
|
|
649
|
-
completeLastInProgressStep(state.steps);
|
|
650
|
-
const errorStepId = `step-${state.stepCounter++}`;
|
|
651
|
-
state.steps.push({
|
|
652
|
-
id: errorStepId,
|
|
653
|
-
eventType,
|
|
654
|
-
message: displayMessage,
|
|
655
|
-
status: "error",
|
|
656
|
-
timestamp: Date.now(),
|
|
657
|
-
elapsedMs: event.elapsedMs
|
|
658
|
-
});
|
|
659
|
-
const retryStepId = `step-${state.stepCounter++}`;
|
|
660
|
-
state.steps.push({
|
|
661
|
-
id: retryStepId,
|
|
662
|
-
eventType: "USER_ACTION_REQUIRED",
|
|
663
|
-
message: "Waiting for verification...",
|
|
572
|
+
message: promptMessage || (kind === "verification" ? "Waiting for verification..." : "Waiting for your input..."),
|
|
664
573
|
status: "in_progress",
|
|
665
|
-
timestamp: Date.now()
|
|
666
|
-
});
|
|
667
|
-
state.currentExecutingStepId = retryStepId;
|
|
668
|
-
state.lastEventType = eventType;
|
|
669
|
-
break;
|
|
670
|
-
}
|
|
671
|
-
case "USER_ACTION_REJECTED": {
|
|
672
|
-
const displayMessage = getUserActionDisplayMessage(eventType, event.message);
|
|
673
|
-
appendThinkingText(state, "\n" + displayMessage);
|
|
674
|
-
completeLastInProgressStep(state.steps);
|
|
675
|
-
state.userActionRequest = void 0;
|
|
676
|
-
state.userActionPending = false;
|
|
677
|
-
state.userActionResult = "rejected";
|
|
678
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
679
|
-
state.steps.push({
|
|
680
|
-
id: stepId,
|
|
681
|
-
eventType,
|
|
682
|
-
message: displayMessage,
|
|
683
|
-
status: "completed",
|
|
684
|
-
timestamp: Date.now(),
|
|
685
|
-
elapsedMs: event.elapsedMs
|
|
686
|
-
});
|
|
687
|
-
state.lastEventType = eventType;
|
|
688
|
-
break;
|
|
689
|
-
}
|
|
690
|
-
case "USER_ACTION_EXPIRED": {
|
|
691
|
-
const displayMessage = getUserActionDisplayMessage(eventType, event.message);
|
|
692
|
-
appendThinkingText(state, "\n\u2717 " + displayMessage);
|
|
693
|
-
completeLastInProgressStep(state.steps);
|
|
694
|
-
state.userActionRequest = void 0;
|
|
695
|
-
state.userActionPending = false;
|
|
696
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
697
|
-
state.steps.push({
|
|
698
|
-
id: stepId,
|
|
699
|
-
eventType,
|
|
700
|
-
message: displayMessage,
|
|
701
|
-
status: "error",
|
|
702
|
-
timestamp: Date.now(),
|
|
703
|
-
elapsedMs: event.elapsedMs
|
|
704
|
-
});
|
|
705
|
-
state.lastEventType = eventType;
|
|
706
|
-
break;
|
|
707
|
-
}
|
|
708
|
-
case "USER_ACTION_RESENT": {
|
|
709
|
-
const displayMessage = getUserActionDisplayMessage(eventType, event.message);
|
|
710
|
-
appendThinkingText(state, "\n" + displayMessage);
|
|
711
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
712
|
-
state.steps.push({
|
|
713
|
-
id: stepId,
|
|
714
|
-
eventType,
|
|
715
|
-
message: displayMessage,
|
|
716
|
-
status: "completed",
|
|
717
574
|
timestamp: Date.now(),
|
|
718
575
|
elapsedMs: event.elapsedMs
|
|
719
576
|
});
|
|
577
|
+
state.currentExecutingStepId = stepId;
|
|
720
578
|
state.lastEventType = eventType;
|
|
721
579
|
break;
|
|
722
580
|
}
|
|
723
|
-
case "
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
id: stepId,
|
|
735
|
-
eventType,
|
|
736
|
-
message: displayMessage,
|
|
737
|
-
status: "error",
|
|
738
|
-
timestamp: Date.now(),
|
|
739
|
-
elapsedMs: event.elapsedMs
|
|
740
|
-
});
|
|
581
|
+
case "USER_NOTIFICATION": {
|
|
582
|
+
const noteMessage = typeof event.message === "string" && event.message.trim() || message || "";
|
|
583
|
+
const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
|
|
584
|
+
if (noteMessage || userActionId) {
|
|
585
|
+
const notification = {
|
|
586
|
+
id: userActionId || `note-${state.stepCounter++}`,
|
|
587
|
+
message: noteMessage
|
|
588
|
+
};
|
|
589
|
+
state.notifications.push(notification);
|
|
590
|
+
state.lastNotification = notification;
|
|
591
|
+
}
|
|
741
592
|
state.lastEventType = eventType;
|
|
742
593
|
break;
|
|
743
594
|
}
|
|
744
595
|
case "WORKFLOW_ERROR":
|
|
745
|
-
case "ERROR":
|
|
746
596
|
state.hasError = true;
|
|
747
597
|
state.errorMessage = event.errorMessage || event.message || "Workflow error";
|
|
748
598
|
state.lastEventType = eventType;
|
|
749
599
|
break;
|
|
750
|
-
case "INTENT_ERROR": {
|
|
751
|
-
state.errorMessage = message || event.errorMessage || "An error occurred";
|
|
752
|
-
const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
|
|
753
|
-
if (intentStep) {
|
|
754
|
-
intentStep.status = "error";
|
|
755
|
-
intentStep.isThinking = false;
|
|
756
|
-
}
|
|
757
|
-
state.lastEventType = eventType;
|
|
758
|
-
break;
|
|
759
|
-
}
|
|
760
600
|
// ---- K2 pipeline stage lifecycle events ----
|
|
761
601
|
//
|
|
762
602
|
// The k2-server playground streaming API emits
|
|
@@ -877,96 +717,6 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
877
717
|
return state;
|
|
878
718
|
}
|
|
879
719
|
|
|
880
|
-
// src/utils/messageStateManager.ts
|
|
881
|
-
function buildFormattedThinking(steps, allThinkingText) {
|
|
882
|
-
const parts = [];
|
|
883
|
-
const safeSteps = steps ?? [];
|
|
884
|
-
const cleanAll = allThinkingText.replace(/^\s+/, "");
|
|
885
|
-
if (cleanAll) {
|
|
886
|
-
const firstStepWithThinking = safeSteps.find(
|
|
887
|
-
(s) => s.thinkingText && s.thinkingText.trim()
|
|
888
|
-
);
|
|
889
|
-
if (!firstStepWithThinking) {
|
|
890
|
-
parts.push("**Preflight**");
|
|
891
|
-
parts.push(cleanAll);
|
|
892
|
-
} else {
|
|
893
|
-
const stepText = firstStepWithThinking.thinkingText.trim();
|
|
894
|
-
const idx = cleanAll.indexOf(stepText);
|
|
895
|
-
if (idx > 0) {
|
|
896
|
-
const orphaned = cleanAll.substring(0, idx).replace(/\s+$/, "");
|
|
897
|
-
if (orphaned) {
|
|
898
|
-
parts.push("**Preflight**");
|
|
899
|
-
parts.push(orphaned);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
for (const step of safeSteps) {
|
|
905
|
-
switch (step.eventType) {
|
|
906
|
-
case "STAGE_STARTED": {
|
|
907
|
-
if (step.message) parts.push(`**${step.message}**`);
|
|
908
|
-
break;
|
|
909
|
-
}
|
|
910
|
-
case "ORCHESTRATOR_THINKING":
|
|
911
|
-
parts.push("**Planning**");
|
|
912
|
-
if (step.message) parts.push(step.message);
|
|
913
|
-
break;
|
|
914
|
-
case "INTENT_STARTED": {
|
|
915
|
-
let label = step.message || "Processing";
|
|
916
|
-
const started = label.match(/^(.+?)\s+started$/i);
|
|
917
|
-
const progress = label.match(/^(.+?)\s+in progress$/i);
|
|
918
|
-
if (started) label = started[1];
|
|
919
|
-
else if (progress) label = progress[1];
|
|
920
|
-
parts.push(`**${label}**`);
|
|
921
|
-
if (step.thinkingText) parts.push(step.thinkingText);
|
|
922
|
-
break;
|
|
923
|
-
}
|
|
924
|
-
case "INTENT_PROGRESS": {
|
|
925
|
-
if (step.thinkingText) parts.push(step.thinkingText);
|
|
926
|
-
else if (step.message) parts.push(step.message);
|
|
927
|
-
break;
|
|
928
|
-
}
|
|
929
|
-
case "AGGREGATOR_THINKING":
|
|
930
|
-
parts.push("**Finalizing**");
|
|
931
|
-
if (step.message) parts.push(step.message);
|
|
932
|
-
break;
|
|
933
|
-
case "USER_ACTION_REQUIRED":
|
|
934
|
-
parts.push("**Verification Required**");
|
|
935
|
-
if (step.message) parts.push(step.message);
|
|
936
|
-
break;
|
|
937
|
-
case "USER_ACTION_SUCCESS":
|
|
938
|
-
parts.push(`\u2713 ${step.message || "Verification successful"}`);
|
|
939
|
-
break;
|
|
940
|
-
case "USER_ACTION_REJECTED":
|
|
941
|
-
parts.push(`\u2717 ${step.message || "Verification rejected"}`);
|
|
942
|
-
break;
|
|
943
|
-
case "USER_ACTION_EXPIRED":
|
|
944
|
-
parts.push(`\u2717 ${step.message || "Verification expired"}`);
|
|
945
|
-
break;
|
|
946
|
-
case "USER_ACTION_FAILED":
|
|
947
|
-
parts.push(`\u2717 ${step.message || "Verification failed"}`);
|
|
948
|
-
break;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
return parts.length > 0 ? parts.join("\n") : allThinkingText;
|
|
952
|
-
}
|
|
953
|
-
function createCancelledMessageUpdate(steps, currentMessage) {
|
|
954
|
-
const updatedSteps = steps.map((step) => {
|
|
955
|
-
if (step.status === "in_progress") {
|
|
956
|
-
return { ...step, status: "pending" };
|
|
957
|
-
}
|
|
958
|
-
return step;
|
|
959
|
-
});
|
|
960
|
-
return {
|
|
961
|
-
isStreaming: false,
|
|
962
|
-
isCancelled: true,
|
|
963
|
-
steps: updatedSteps,
|
|
964
|
-
currentExecutingStepId: void 0,
|
|
965
|
-
// Preserve currentMessage so UI can show it with X icon
|
|
966
|
-
currentMessage: currentMessage || "Thinking..."
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
|
|
970
720
|
// src/utils/requestBuilder.ts
|
|
971
721
|
var DEFAULT_STREAM_ENDPOINT = "/api/playground/ask/stream";
|
|
972
722
|
function buildRequestBody(config, userMessage, sessionId, options) {
|
|
@@ -1040,115 +790,6 @@ function buildRequestHeaders(config) {
|
|
|
1040
790
|
return headers;
|
|
1041
791
|
}
|
|
1042
792
|
|
|
1043
|
-
// src/utils/userActionClient.ts
|
|
1044
|
-
async function sendUserActionRequest(config, userActionId, action, data) {
|
|
1045
|
-
const url = buildUserActionUrl(config, userActionId, action);
|
|
1046
|
-
const baseHeaders = buildRequestHeaders(config);
|
|
1047
|
-
const hasBody = data !== void 0;
|
|
1048
|
-
const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
|
|
1049
|
-
const response = await fetch(url, {
|
|
1050
|
-
method: "POST",
|
|
1051
|
-
headers,
|
|
1052
|
-
body: hasBody ? JSON.stringify(data) : void 0
|
|
1053
|
-
});
|
|
1054
|
-
if (!response.ok) {
|
|
1055
|
-
const errorText = await response.text();
|
|
1056
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1057
|
-
}
|
|
1058
|
-
return await response.json();
|
|
1059
|
-
}
|
|
1060
|
-
async function submitUserAction(config, userActionId, data) {
|
|
1061
|
-
return sendUserActionRequest(config, userActionId, "submit", data);
|
|
1062
|
-
}
|
|
1063
|
-
async function cancelUserAction(config, userActionId) {
|
|
1064
|
-
return sendUserActionRequest(config, userActionId, "cancel");
|
|
1065
|
-
}
|
|
1066
|
-
async function resendUserAction(config, userActionId) {
|
|
1067
|
-
return sendUserActionRequest(config, userActionId, "resend");
|
|
1068
|
-
}
|
|
1069
|
-
var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
|
|
1070
|
-
var chatStore = {
|
|
1071
|
-
get(key) {
|
|
1072
|
-
const raw = storage.getString(key);
|
|
1073
|
-
if (!raw) return [];
|
|
1074
|
-
try {
|
|
1075
|
-
return JSON.parse(raw);
|
|
1076
|
-
} catch {
|
|
1077
|
-
return [];
|
|
1078
|
-
}
|
|
1079
|
-
},
|
|
1080
|
-
set(key, messages) {
|
|
1081
|
-
storage.set(key, JSON.stringify(messages));
|
|
1082
|
-
},
|
|
1083
|
-
delete(key) {
|
|
1084
|
-
storage.delete(key);
|
|
1085
|
-
}
|
|
1086
|
-
};
|
|
1087
|
-
|
|
1088
|
-
// src/utils/activeStreamStore.ts
|
|
1089
|
-
var streams = /* @__PURE__ */ new Map();
|
|
1090
|
-
var activeStreamStore = {
|
|
1091
|
-
has(key) {
|
|
1092
|
-
return streams.has(key);
|
|
1093
|
-
},
|
|
1094
|
-
get(key) {
|
|
1095
|
-
const entry = streams.get(key);
|
|
1096
|
-
if (!entry) return null;
|
|
1097
|
-
return { messages: entry.messages, isWaiting: entry.isWaiting };
|
|
1098
|
-
},
|
|
1099
|
-
// Called before startStream — registers the controller and initial messages
|
|
1100
|
-
start(key, abortController, initialMessages) {
|
|
1101
|
-
const existing = streams.get(key);
|
|
1102
|
-
streams.set(key, {
|
|
1103
|
-
messages: initialMessages,
|
|
1104
|
-
isWaiting: true,
|
|
1105
|
-
abortController,
|
|
1106
|
-
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
1107
|
-
});
|
|
1108
|
-
},
|
|
1109
|
-
// Called by the stream on every event — applies the same updater pattern React uses
|
|
1110
|
-
applyMessages(key, updater) {
|
|
1111
|
-
const entry = streams.get(key);
|
|
1112
|
-
if (!entry) return;
|
|
1113
|
-
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
1114
|
-
entry.messages = next;
|
|
1115
|
-
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
1116
|
-
},
|
|
1117
|
-
setWaiting(key, waiting) {
|
|
1118
|
-
const entry = streams.get(key);
|
|
1119
|
-
if (!entry) return;
|
|
1120
|
-
entry.isWaiting = waiting;
|
|
1121
|
-
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
1122
|
-
},
|
|
1123
|
-
// Called when stream completes — persists to chatStore and cleans up
|
|
1124
|
-
complete(key) {
|
|
1125
|
-
const entry = streams.get(key);
|
|
1126
|
-
if (!entry) return;
|
|
1127
|
-
entry.isWaiting = false;
|
|
1128
|
-
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
1129
|
-
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
1130
|
-
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
1131
|
-
streams.delete(key);
|
|
1132
|
-
},
|
|
1133
|
-
// Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
|
|
1134
|
-
subscribe(key, listener) {
|
|
1135
|
-
const entry = streams.get(key);
|
|
1136
|
-
if (!entry) return () => {
|
|
1137
|
-
};
|
|
1138
|
-
entry.listeners.add(listener);
|
|
1139
|
-
return () => {
|
|
1140
|
-
streams.get(key)?.listeners.delete(listener);
|
|
1141
|
-
};
|
|
1142
|
-
},
|
|
1143
|
-
// Explicit user cancel — aborts the controller and removes the entry
|
|
1144
|
-
abort(key) {
|
|
1145
|
-
const entry = streams.get(key);
|
|
1146
|
-
if (!entry) return;
|
|
1147
|
-
entry.abortController.abort();
|
|
1148
|
-
streams.delete(key);
|
|
1149
|
-
}
|
|
1150
|
-
};
|
|
1151
|
-
|
|
1152
793
|
// src/utils/ragImageResolver.ts
|
|
1153
794
|
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
1154
795
|
function hasRagImages(content) {
|
|
@@ -1236,15 +877,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1236
877
|
onEvent: (event) => {
|
|
1237
878
|
if (abortController.signal.aborted) return;
|
|
1238
879
|
processStreamEventV2(event, state);
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
eventType,
|
|
1245
|
-
event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event)
|
|
1246
|
-
);
|
|
1247
|
-
callbacksRef.current.onUserActionEvent?.(eventType, msg);
|
|
880
|
+
if (state.lastUserAction) {
|
|
881
|
+
callbacksRef.current.onUserActionRequired?.(state.lastUserAction);
|
|
882
|
+
}
|
|
883
|
+
if (state.lastNotification) {
|
|
884
|
+
callbacksRef.current.onUserNotification?.(state.lastNotification);
|
|
1248
885
|
}
|
|
1249
886
|
const activeStep = state.steps.find((s) => s.id === state.currentExecutingStepId);
|
|
1250
887
|
const lastInProgressStep = [...state.steps].reverse().find((s) => s.status === "in_progress");
|
|
@@ -1267,7 +904,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1267
904
|
streamProgress: "error",
|
|
1268
905
|
isError: true,
|
|
1269
906
|
errorDetails: state.errorMessage,
|
|
1270
|
-
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1271
907
|
steps: [...state.steps],
|
|
1272
908
|
currentExecutingStepId: void 0,
|
|
1273
909
|
executionId: state.executionId,
|
|
@@ -1280,12 +916,10 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1280
916
|
currentMessage,
|
|
1281
917
|
streamProgress: "processing",
|
|
1282
918
|
isError: false,
|
|
1283
|
-
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1284
919
|
steps: [...state.steps],
|
|
1285
920
|
currentExecutingStepId: state.currentExecutingStepId,
|
|
1286
921
|
executionId: state.executionId,
|
|
1287
922
|
sessionId: state.sessionId,
|
|
1288
|
-
userActionResult: state.userActionResult,
|
|
1289
923
|
isCancelled: false
|
|
1290
924
|
});
|
|
1291
925
|
}
|
|
@@ -1296,14 +930,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1296
930
|
if (error.name !== "AbortError") {
|
|
1297
931
|
callbacksRef.current.onError?.(error);
|
|
1298
932
|
}
|
|
1299
|
-
if (state.userActionPending) {
|
|
1300
|
-
state.userActionPending = false;
|
|
1301
|
-
state.userActionRequest = void 0;
|
|
1302
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1303
|
-
"USER_ACTION_FAILED",
|
|
1304
|
-
"Connection lost. Please try again."
|
|
1305
|
-
);
|
|
1306
|
-
}
|
|
1307
933
|
const isAborted = error.name === "AbortError";
|
|
1308
934
|
setMessages(
|
|
1309
935
|
(prev) => prev.map(
|
|
@@ -1316,7 +942,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1316
942
|
errorDetails: isAborted ? void 0 : error.message,
|
|
1317
943
|
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1318
944
|
currentMessage: isAborted ? "Thinking..." : void 0,
|
|
1319
|
-
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1320
945
|
steps: [...state.steps].map((step) => {
|
|
1321
946
|
if (step.status === "in_progress" && isAborted) {
|
|
1322
947
|
return { ...step, status: "pending" };
|
|
@@ -1332,14 +957,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1332
957
|
setIsWaitingForResponse(false);
|
|
1333
958
|
callbacksRef.current.onStatusMessage?.(null);
|
|
1334
959
|
callbacksRef.current.onStepsUpdate?.([]);
|
|
1335
|
-
if (state.userActionPending) {
|
|
1336
|
-
state.userActionPending = false;
|
|
1337
|
-
state.userActionRequest = void 0;
|
|
1338
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1339
|
-
"USER_ACTION_FAILED",
|
|
1340
|
-
"Verification could not be completed."
|
|
1341
|
-
);
|
|
1342
|
-
}
|
|
1343
960
|
if (state.sessionId && state.sessionId !== sessionId) {
|
|
1344
961
|
callbacksRef.current.onSessionIdChange?.(state.sessionId);
|
|
1345
962
|
}
|
|
@@ -1363,8 +980,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1363
980
|
steps: state.hasError ? [] : [...state.steps],
|
|
1364
981
|
isCancelled: false,
|
|
1365
982
|
currentExecutingStepId: void 0,
|
|
1366
|
-
userActionResult: state.userActionResult,
|
|
1367
|
-
formattedThinkingText: state.hasError ? void 0 : state.formattedThinkingText || void 0,
|
|
1368
983
|
isResolvingImages: needsImageResolve,
|
|
1369
984
|
totalElapsedMs: state.hasError ? void 0 : state.totalElapsedMs
|
|
1370
985
|
};
|
|
@@ -1406,14 +1021,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1406
1021
|
if (error.name !== "AbortError") {
|
|
1407
1022
|
callbacksRef.current.onError?.(error);
|
|
1408
1023
|
}
|
|
1409
|
-
if (state.userActionPending) {
|
|
1410
|
-
state.userActionPending = false;
|
|
1411
|
-
state.userActionRequest = void 0;
|
|
1412
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1413
|
-
"USER_ACTION_FAILED",
|
|
1414
|
-
"Connection lost. Please try again."
|
|
1415
|
-
);
|
|
1416
|
-
}
|
|
1417
1024
|
const isAborted = error.name === "AbortError";
|
|
1418
1025
|
setMessages(
|
|
1419
1026
|
(prev) => prev.map(
|
|
@@ -1425,7 +1032,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1425
1032
|
isCancelled: isAborted,
|
|
1426
1033
|
errorDetails: isAborted ? void 0 : error.message,
|
|
1427
1034
|
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1428
|
-
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1429
1035
|
steps: [...state.steps].map((step) => {
|
|
1430
1036
|
if (step.status === "in_progress" && isAborted) {
|
|
1431
1037
|
return { ...step, status: "pending" };
|
|
@@ -1451,9 +1057,79 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1451
1057
|
};
|
|
1452
1058
|
}
|
|
1453
1059
|
|
|
1060
|
+
// src/utils/messageStateManager.ts
|
|
1061
|
+
function createCancelledMessageUpdate(steps, currentMessage) {
|
|
1062
|
+
const updatedSteps = steps.map((step) => {
|
|
1063
|
+
if (step.status === "in_progress") {
|
|
1064
|
+
return { ...step, status: "pending" };
|
|
1065
|
+
}
|
|
1066
|
+
return step;
|
|
1067
|
+
});
|
|
1068
|
+
return {
|
|
1069
|
+
isStreaming: false,
|
|
1070
|
+
isCancelled: true,
|
|
1071
|
+
steps: updatedSteps,
|
|
1072
|
+
currentExecutingStepId: void 0,
|
|
1073
|
+
currentMessage: currentMessage || "Thinking..."
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// src/utils/userActionClient.ts
|
|
1078
|
+
var UserActionStaleError = class extends Error {
|
|
1079
|
+
constructor(userActionId, message = "User action is no longer actionable") {
|
|
1080
|
+
super(message);
|
|
1081
|
+
__publicField(this, "userActionId");
|
|
1082
|
+
this.name = "UserActionStaleError";
|
|
1083
|
+
this.userActionId = userActionId;
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
async function sendUserActionRequest(config, userActionId, action, data) {
|
|
1087
|
+
const url = buildUserActionUrl(config, userActionId, action);
|
|
1088
|
+
const baseHeaders = buildRequestHeaders(config);
|
|
1089
|
+
const hasBody = data !== void 0;
|
|
1090
|
+
const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
|
|
1091
|
+
const response = await fetch(url, {
|
|
1092
|
+
method: "POST",
|
|
1093
|
+
headers,
|
|
1094
|
+
body: hasBody ? JSON.stringify(data) : void 0
|
|
1095
|
+
});
|
|
1096
|
+
if (response.status === 404) {
|
|
1097
|
+
throw new UserActionStaleError(userActionId);
|
|
1098
|
+
}
|
|
1099
|
+
if (!response.ok) {
|
|
1100
|
+
const errorText = await response.text();
|
|
1101
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1102
|
+
}
|
|
1103
|
+
return await response.json();
|
|
1104
|
+
}
|
|
1105
|
+
async function submitUserAction(config, userActionId, content) {
|
|
1106
|
+
return sendUserActionRequest(config, userActionId, "submit", content ?? {});
|
|
1107
|
+
}
|
|
1108
|
+
async function cancelUserAction(config, userActionId) {
|
|
1109
|
+
return sendUserActionRequest(config, userActionId, "cancel");
|
|
1110
|
+
}
|
|
1111
|
+
async function resendUserAction(config, userActionId) {
|
|
1112
|
+
return sendUserActionRequest(config, userActionId, "resend");
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1454
1115
|
// src/hooks/useChatV2.ts
|
|
1116
|
+
var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
|
|
1117
|
+
function upsertPrompt(prompts, req) {
|
|
1118
|
+
const active = { ...req, status: "pending" };
|
|
1119
|
+
const idx = prompts.findIndex(
|
|
1120
|
+
(p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
|
|
1121
|
+
);
|
|
1122
|
+
if (idx >= 0) {
|
|
1123
|
+
const next = prompts.slice();
|
|
1124
|
+
next[idx] = active;
|
|
1125
|
+
return next;
|
|
1126
|
+
}
|
|
1127
|
+
return [...prompts, active];
|
|
1128
|
+
}
|
|
1455
1129
|
function getStoredOrInitialMessages(config) {
|
|
1456
1130
|
if (!config.userId) return config.initialMessages ?? [];
|
|
1131
|
+
const activeStream = activeStreamStore.get(config.userId);
|
|
1132
|
+
if (activeStream) return activeStream.messages;
|
|
1457
1133
|
const stored = chatStore.get(config.userId);
|
|
1458
1134
|
if (stored.length > 0) return stored;
|
|
1459
1135
|
if (config.initialMessages?.length) {
|
|
@@ -1467,11 +1143,16 @@ function getSessionIdFromMessages(messages) {
|
|
|
1467
1143
|
}
|
|
1468
1144
|
function useChatV2(config, callbacks = {}) {
|
|
1469
1145
|
const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
|
|
1470
|
-
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(
|
|
1146
|
+
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
|
|
1147
|
+
if (!config.userId) return false;
|
|
1148
|
+
return activeStreamStore.get(config.userId)?.isWaiting ?? false;
|
|
1149
|
+
});
|
|
1471
1150
|
const sessionIdRef = react.useRef(
|
|
1472
1151
|
getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
|
|
1473
1152
|
);
|
|
1474
1153
|
const prevUserIdRef = react.useRef(config.userId);
|
|
1154
|
+
const streamUserIdRef = react.useRef(void 0);
|
|
1155
|
+
const subscriptionPrevUserIdRef = react.useRef(config.userId);
|
|
1475
1156
|
const callbacksRef = react.useRef(callbacks);
|
|
1476
1157
|
callbacksRef.current = callbacks;
|
|
1477
1158
|
const configRef = react.useRef(config);
|
|
@@ -1480,33 +1161,35 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1480
1161
|
messagesRef.current = messages;
|
|
1481
1162
|
const storeAwareSetMessages = react.useCallback(
|
|
1482
1163
|
(updater) => {
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1485
|
-
|
|
1164
|
+
const streamUserId = streamUserIdRef.current;
|
|
1165
|
+
const currentUserId = configRef.current.userId;
|
|
1166
|
+
const storeKey = streamUserId ?? currentUserId;
|
|
1167
|
+
if (storeKey && activeStreamStore.has(storeKey)) {
|
|
1168
|
+
activeStreamStore.applyMessages(storeKey, updater);
|
|
1169
|
+
}
|
|
1170
|
+
if (!streamUserId || streamUserId === currentUserId) {
|
|
1171
|
+
setMessages(updater);
|
|
1486
1172
|
}
|
|
1487
|
-
setMessages(updater);
|
|
1488
1173
|
},
|
|
1489
1174
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1490
1175
|
[]
|
|
1491
1176
|
);
|
|
1492
1177
|
const storeAwareSetIsWaiting = react.useCallback(
|
|
1493
1178
|
(waiting) => {
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
|
|
1179
|
+
const streamUserId = streamUserIdRef.current;
|
|
1180
|
+
const currentUserId = configRef.current.userId;
|
|
1181
|
+
const storeKey = streamUserId ?? currentUserId;
|
|
1182
|
+
if (storeKey && activeStreamStore.has(storeKey)) {
|
|
1183
|
+
activeStreamStore.setWaiting(storeKey, waiting);
|
|
1184
|
+
}
|
|
1185
|
+
if (!streamUserId || streamUserId === currentUserId) {
|
|
1186
|
+
setIsWaitingForResponse(waiting);
|
|
1497
1187
|
}
|
|
1498
|
-
setIsWaitingForResponse(waiting);
|
|
1499
1188
|
},
|
|
1500
1189
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1501
1190
|
[]
|
|
1502
1191
|
);
|
|
1503
|
-
const [userActionState, setUserActionState] = react.useState(
|
|
1504
|
-
request: null,
|
|
1505
|
-
result: null,
|
|
1506
|
-
clearOtpTrigger: 0
|
|
1507
|
-
});
|
|
1508
|
-
const userActionStateRef = react.useRef(userActionState);
|
|
1509
|
-
userActionStateRef.current = userActionState;
|
|
1192
|
+
const [userActionState, setUserActionState] = react.useState(EMPTY_USER_ACTION_STATE);
|
|
1510
1193
|
const wrappedCallbacks = react.useMemo(() => ({
|
|
1511
1194
|
...callbacksRef.current,
|
|
1512
1195
|
onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
|
|
@@ -1516,26 +1199,17 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1516
1199
|
onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
|
|
1517
1200
|
onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
|
|
1518
1201
|
onUserActionRequired: (request) => {
|
|
1519
|
-
setUserActionState((prev) => ({
|
|
1202
|
+
setUserActionState((prev) => ({
|
|
1203
|
+
...prev,
|
|
1204
|
+
prompts: upsertPrompt(prev.prompts, request)
|
|
1205
|
+
}));
|
|
1520
1206
|
callbacksRef.current.onUserActionRequired?.(request);
|
|
1521
1207
|
},
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
case "USER_ACTION_REJECTED":
|
|
1528
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
|
|
1529
|
-
break;
|
|
1530
|
-
case "USER_ACTION_EXPIRED":
|
|
1531
|
-
case "USER_ACTION_FAILED":
|
|
1532
|
-
setUserActionState((prev) => ({ ...prev, request: null }));
|
|
1533
|
-
break;
|
|
1534
|
-
case "USER_ACTION_INVALID":
|
|
1535
|
-
setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
|
|
1536
|
-
break;
|
|
1537
|
-
}
|
|
1538
|
-
callbacksRef.current.onUserActionEvent?.(eventType, message);
|
|
1208
|
+
onUserNotification: (notification) => {
|
|
1209
|
+
setUserActionState(
|
|
1210
|
+
(prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
|
|
1211
|
+
);
|
|
1212
|
+
callbacksRef.current.onUserNotification?.(notification);
|
|
1539
1213
|
}
|
|
1540
1214
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1541
1215
|
}), []);
|
|
@@ -1583,6 +1257,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1583
1257
|
const abortController = new AbortController();
|
|
1584
1258
|
const { userId } = configRef.current;
|
|
1585
1259
|
if (userId) {
|
|
1260
|
+
streamUserIdRef.current = userId;
|
|
1586
1261
|
const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
|
|
1587
1262
|
activeStreamStore.start(userId, abortController, initialMessages);
|
|
1588
1263
|
}
|
|
@@ -1593,9 +1268,11 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1593
1268
|
abortController,
|
|
1594
1269
|
options
|
|
1595
1270
|
);
|
|
1596
|
-
|
|
1597
|
-
|
|
1271
|
+
const finalStreamUserId = streamUserIdRef.current ?? userId;
|
|
1272
|
+
if (finalStreamUserId) {
|
|
1273
|
+
activeStreamStore.complete(finalStreamUserId);
|
|
1598
1274
|
}
|
|
1275
|
+
streamUserIdRef.current = void 0;
|
|
1599
1276
|
if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
|
|
1600
1277
|
sessionIdRef.current = newSessionId;
|
|
1601
1278
|
}
|
|
@@ -1612,12 +1289,14 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1612
1289
|
setMessages((prev) => [...msgs, ...prev]);
|
|
1613
1290
|
}, []);
|
|
1614
1291
|
const cancelStream = react.useCallback(() => {
|
|
1615
|
-
|
|
1616
|
-
|
|
1292
|
+
const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
|
|
1293
|
+
if (streamUserId) {
|
|
1294
|
+
activeStreamStore.abort(streamUserId);
|
|
1617
1295
|
}
|
|
1296
|
+
streamUserIdRef.current = void 0;
|
|
1618
1297
|
cancelStreamManager();
|
|
1619
1298
|
setIsWaitingForResponse(false);
|
|
1620
|
-
setUserActionState((prev) => ({ ...prev,
|
|
1299
|
+
setUserActionState((prev) => ({ ...prev, prompts: [] }));
|
|
1621
1300
|
setMessages(
|
|
1622
1301
|
(prev) => prev.map((msg) => {
|
|
1623
1302
|
if (msg.isStreaming) {
|
|
@@ -1634,15 +1313,19 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1634
1313
|
);
|
|
1635
1314
|
}, [cancelStreamManager]);
|
|
1636
1315
|
const resetSession = react.useCallback(() => {
|
|
1316
|
+
const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
|
|
1317
|
+
if (streamUserId) {
|
|
1318
|
+
activeStreamStore.abort(streamUserId);
|
|
1319
|
+
}
|
|
1637
1320
|
if (configRef.current.userId) {
|
|
1638
|
-
activeStreamStore.abort(configRef.current.userId);
|
|
1639
1321
|
chatStore.delete(configRef.current.userId);
|
|
1640
1322
|
}
|
|
1323
|
+
streamUserIdRef.current = void 0;
|
|
1641
1324
|
setMessages([]);
|
|
1642
1325
|
sessionIdRef.current = void 0;
|
|
1643
1326
|
abortControllerRef.current?.abort();
|
|
1644
1327
|
setIsWaitingForResponse(false);
|
|
1645
|
-
setUserActionState(
|
|
1328
|
+
setUserActionState(EMPTY_USER_ACTION_STATE);
|
|
1646
1329
|
}, []);
|
|
1647
1330
|
const getSessionId = react.useCallback(() => {
|
|
1648
1331
|
return sessionIdRef.current;
|
|
@@ -1650,59 +1333,91 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1650
1333
|
const getMessages = react.useCallback(() => {
|
|
1651
1334
|
return messages;
|
|
1652
1335
|
}, [messages]);
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1336
|
+
const setPromptStatus = react.useCallback(
|
|
1337
|
+
(userActionId, status) => {
|
|
1338
|
+
setUserActionState((prev) => ({
|
|
1339
|
+
...prev,
|
|
1340
|
+
prompts: prev.prompts.map(
|
|
1341
|
+
(p) => p.userActionId === userActionId ? { ...p, status } : p
|
|
1342
|
+
)
|
|
1343
|
+
}));
|
|
1344
|
+
},
|
|
1345
|
+
[]
|
|
1346
|
+
);
|
|
1347
|
+
const removePrompt = react.useCallback((userActionId) => {
|
|
1348
|
+
setUserActionState((prev) => ({
|
|
1349
|
+
...prev,
|
|
1350
|
+
prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
|
|
1351
|
+
}));
|
|
1352
|
+
}, []);
|
|
1353
|
+
const submitUserAction2 = react.useCallback(
|
|
1354
|
+
async (userActionId, content) => {
|
|
1355
|
+
setPromptStatus(userActionId, "submitting");
|
|
1657
1356
|
try {
|
|
1658
|
-
await submitUserAction(configRef.current,
|
|
1357
|
+
await submitUserAction(configRef.current, userActionId, content);
|
|
1358
|
+
removePrompt(userActionId);
|
|
1659
1359
|
} catch (error) {
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
1360
|
+
if (error instanceof UserActionStaleError) {
|
|
1361
|
+
setPromptStatus(userActionId, "stale");
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
setPromptStatus(userActionId, "pending");
|
|
1664
1365
|
callbacksRef.current.onError?.(error);
|
|
1665
1366
|
throw error;
|
|
1666
1367
|
}
|
|
1667
1368
|
},
|
|
1668
|
-
[]
|
|
1369
|
+
[removePrompt, setPromptStatus]
|
|
1669
1370
|
);
|
|
1670
|
-
const
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
}
|
|
1371
|
+
const cancelUserAction2 = react.useCallback(
|
|
1372
|
+
async (userActionId) => {
|
|
1373
|
+
setPromptStatus(userActionId, "submitting");
|
|
1374
|
+
try {
|
|
1375
|
+
await cancelUserAction(configRef.current, userActionId);
|
|
1376
|
+
removePrompt(userActionId);
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
if (error instanceof UserActionStaleError) {
|
|
1379
|
+
removePrompt(userActionId);
|
|
1380
|
+
return;
|
|
1681
1381
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1382
|
+
setPromptStatus(userActionId, "pending");
|
|
1383
|
+
callbacksRef.current.onError?.(error);
|
|
1384
|
+
throw error;
|
|
1385
|
+
}
|
|
1386
|
+
},
|
|
1387
|
+
[removePrompt, setPromptStatus]
|
|
1388
|
+
);
|
|
1389
|
+
const resendUserAction2 = react.useCallback(
|
|
1390
|
+
async (userActionId) => {
|
|
1391
|
+
setPromptStatus(userActionId, "submitting");
|
|
1392
|
+
try {
|
|
1393
|
+
await resendUserAction(configRef.current, userActionId);
|
|
1394
|
+
setPromptStatus(userActionId, "pending");
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
if (error instanceof UserActionStaleError) {
|
|
1397
|
+
setPromptStatus(userActionId, "stale");
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
setPromptStatus(userActionId, "pending");
|
|
1401
|
+
callbacksRef.current.onError?.(error);
|
|
1402
|
+
throw error;
|
|
1403
|
+
}
|
|
1404
|
+
},
|
|
1405
|
+
[setPromptStatus]
|
|
1406
|
+
);
|
|
1407
|
+
const dismissNotification = react.useCallback((id) => {
|
|
1408
|
+
setUserActionState((prev) => ({
|
|
1409
|
+
...prev,
|
|
1410
|
+
notifications: prev.notifications.filter((n) => n.id !== id)
|
|
1411
|
+
}));
|
|
1702
1412
|
}, []);
|
|
1703
1413
|
react.useEffect(() => {
|
|
1414
|
+
const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
|
|
1415
|
+
subscriptionPrevUserIdRef.current = config.userId;
|
|
1704
1416
|
const { userId } = config;
|
|
1705
1417
|
if (!userId) return;
|
|
1418
|
+
if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
|
|
1419
|
+
streamUserIdRef.current = userId;
|
|
1420
|
+
}
|
|
1706
1421
|
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1707
1422
|
setMessages(msgs);
|
|
1708
1423
|
setIsWaitingForResponse(isWaiting);
|
|
@@ -1716,6 +1431,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1716
1431
|
}, [config.userId]);
|
|
1717
1432
|
react.useEffect(() => {
|
|
1718
1433
|
if (!config.userId) return;
|
|
1434
|
+
if (prevUserIdRef.current !== config.userId) return;
|
|
1719
1435
|
const toSave = messages.filter((m) => !m.isStreaming);
|
|
1720
1436
|
if (toSave.length > 0) {
|
|
1721
1437
|
chatStore.set(config.userId, toSave);
|
|
@@ -1737,7 +1453,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1737
1453
|
setMessages([]);
|
|
1738
1454
|
sessionIdRef.current = void 0;
|
|
1739
1455
|
setIsWaitingForResponse(false);
|
|
1740
|
-
setUserActionState(
|
|
1456
|
+
setUserActionState(EMPTY_USER_ACTION_STATE);
|
|
1741
1457
|
} else if (config.userId) {
|
|
1742
1458
|
const active = activeStreamStore.get(config.userId);
|
|
1743
1459
|
if (active) {
|
|
@@ -1764,9 +1480,10 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1764
1480
|
isWaitingForResponse,
|
|
1765
1481
|
sessionId: sessionIdRef.current,
|
|
1766
1482
|
userActionState,
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1483
|
+
submitUserAction: submitUserAction2,
|
|
1484
|
+
cancelUserAction: cancelUserAction2,
|
|
1485
|
+
resendUserAction: resendUserAction2,
|
|
1486
|
+
dismissNotification
|
|
1770
1487
|
};
|
|
1771
1488
|
}
|
|
1772
1489
|
function useVoice() {
|
|
@@ -1885,15 +1602,150 @@ function useVoice() {
|
|
|
1885
1602
|
};
|
|
1886
1603
|
}
|
|
1887
1604
|
|
|
1888
|
-
|
|
1605
|
+
// src/utils/jsonSchemaForm.ts
|
|
1606
|
+
function classifyField(field) {
|
|
1607
|
+
if (!field) return "text";
|
|
1608
|
+
if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
|
|
1609
|
+
switch (field.type) {
|
|
1610
|
+
case "boolean":
|
|
1611
|
+
return "boolean";
|
|
1612
|
+
case "integer":
|
|
1613
|
+
return "integer";
|
|
1614
|
+
case "number":
|
|
1615
|
+
return "decimal";
|
|
1616
|
+
case "string":
|
|
1617
|
+
return "text";
|
|
1618
|
+
default:
|
|
1619
|
+
return "text";
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
function isNestedOrUnsupported(field) {
|
|
1623
|
+
if (!field) return false;
|
|
1624
|
+
if (field.type === "object" || field.type === "array") return true;
|
|
1625
|
+
if ("properties" in field && field.properties != null) return true;
|
|
1626
|
+
if ("items" in field && field.items != null) return true;
|
|
1627
|
+
return false;
|
|
1628
|
+
}
|
|
1629
|
+
function getOptions(field) {
|
|
1630
|
+
if (!field || !Array.isArray(field.oneOf)) return [];
|
|
1631
|
+
return field.oneOf.filter(
|
|
1632
|
+
(o) => !!o && typeof o === "object" && typeof o.const === "string"
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
function isRequired(schema, key) {
|
|
1636
|
+
return Array.isArray(schema?.required) && schema.required.includes(key);
|
|
1637
|
+
}
|
|
1638
|
+
function coerceValue(field, raw) {
|
|
1639
|
+
const widget = classifyField(field);
|
|
1640
|
+
if (widget === "boolean") {
|
|
1641
|
+
if (typeof raw === "boolean") return raw;
|
|
1642
|
+
if (raw === "true") return true;
|
|
1643
|
+
if (raw === "false") return false;
|
|
1644
|
+
return Boolean(raw);
|
|
1645
|
+
}
|
|
1646
|
+
if (widget === "integer" || widget === "decimal") {
|
|
1647
|
+
if (raw === "" || raw == null) return void 0;
|
|
1648
|
+
const num = typeof raw === "number" ? raw : Number(String(raw).trim());
|
|
1649
|
+
if (Number.isNaN(num)) return void 0;
|
|
1650
|
+
return widget === "integer" ? Math.trunc(num) : num;
|
|
1651
|
+
}
|
|
1652
|
+
if (raw == null) return void 0;
|
|
1653
|
+
const str = String(raw);
|
|
1654
|
+
return str === "" ? void 0 : str;
|
|
1655
|
+
}
|
|
1656
|
+
function defaultValueFor(field) {
|
|
1657
|
+
if (!field || field.default === void 0) {
|
|
1658
|
+
return classifyField(field) === "boolean" ? false : "";
|
|
1659
|
+
}
|
|
1660
|
+
return field.default;
|
|
1661
|
+
}
|
|
1662
|
+
function validateField(field, value, required) {
|
|
1663
|
+
const widget = classifyField(field);
|
|
1664
|
+
const label = field?.title || "This field";
|
|
1665
|
+
const isEmpty = value === void 0 || value === null || typeof value === "string" && value.trim() === "";
|
|
1666
|
+
if (isEmpty) {
|
|
1667
|
+
if (required && widget !== "boolean") return `${label} is required.`;
|
|
1668
|
+
return null;
|
|
1669
|
+
}
|
|
1670
|
+
if (widget === "integer" || widget === "decimal") {
|
|
1671
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
1672
|
+
if (Number.isNaN(num)) return `${label} must be a number.`;
|
|
1673
|
+
if (widget === "integer" && !Number.isInteger(num)) {
|
|
1674
|
+
return `${label} must be a whole number.`;
|
|
1675
|
+
}
|
|
1676
|
+
if (typeof field?.minimum === "number" && num < field.minimum) {
|
|
1677
|
+
return `${label} must be at least ${field.minimum}.`;
|
|
1678
|
+
}
|
|
1679
|
+
if (typeof field?.maximum === "number" && num > field.maximum) {
|
|
1680
|
+
return `${label} must be at most ${field.maximum}.`;
|
|
1681
|
+
}
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
if (widget === "select") {
|
|
1685
|
+
const allowed = getOptions(field).map((o) => o.const);
|
|
1686
|
+
if (allowed.length > 0 && !allowed.includes(String(value))) {
|
|
1687
|
+
return `${label} has an invalid selection.`;
|
|
1688
|
+
}
|
|
1689
|
+
return null;
|
|
1690
|
+
}
|
|
1691
|
+
const str = String(value);
|
|
1692
|
+
if (typeof field?.minLength === "number" && str.length < field.minLength) {
|
|
1693
|
+
return `${label} must be at least ${field.minLength} characters.`;
|
|
1694
|
+
}
|
|
1695
|
+
if (typeof field?.maxLength === "number" && str.length > field.maxLength) {
|
|
1696
|
+
return `${label} must be at most ${field.maxLength} characters.`;
|
|
1697
|
+
}
|
|
1698
|
+
return null;
|
|
1699
|
+
}
|
|
1700
|
+
function renderableFields(schema) {
|
|
1701
|
+
const props = schema?.properties;
|
|
1702
|
+
if (!props) return [];
|
|
1703
|
+
return Object.entries(props).filter(([, field]) => !isNestedOrUnsupported(field));
|
|
1704
|
+
}
|
|
1705
|
+
function validateForm(schema, values) {
|
|
1706
|
+
const errors = {};
|
|
1707
|
+
for (const [key, field] of renderableFields(schema)) {
|
|
1708
|
+
const coerced = coerceValue(field, values[key]);
|
|
1709
|
+
const err = validateField(field, coerced, isRequired(schema, key));
|
|
1710
|
+
if (err) errors[key] = err;
|
|
1711
|
+
}
|
|
1712
|
+
return errors;
|
|
1713
|
+
}
|
|
1714
|
+
function buildContent(schema, values) {
|
|
1715
|
+
const content = {};
|
|
1716
|
+
for (const [key, field] of renderableFields(schema)) {
|
|
1717
|
+
const coerced = coerceValue(field, values[key]);
|
|
1718
|
+
if (coerced !== void 0) content[key] = coerced;
|
|
1719
|
+
}
|
|
1720
|
+
return content;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
// src/index.ts
|
|
1724
|
+
function migrateActiveStream(oldUserId, newUserId) {
|
|
1725
|
+
activeStreamStore.rename(oldUserId, newUserId);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
exports.UserActionStaleError = UserActionStaleError;
|
|
1729
|
+
exports.buildContent = buildContent;
|
|
1889
1730
|
exports.cancelUserAction = cancelUserAction;
|
|
1731
|
+
exports.classifyField = classifyField;
|
|
1732
|
+
exports.classifyUserActionKind = classifyUserActionKind;
|
|
1733
|
+
exports.coerceValue = coerceValue;
|
|
1890
1734
|
exports.createInitialV2State = createInitialV2State;
|
|
1735
|
+
exports.defaultValueFor = defaultValueFor;
|
|
1891
1736
|
exports.generateId = generateId;
|
|
1737
|
+
exports.getOptions = getOptions;
|
|
1738
|
+
exports.isNestedOrUnsupported = isNestedOrUnsupported;
|
|
1739
|
+
exports.isRequired = isRequired;
|
|
1740
|
+
exports.migrateActiveStream = migrateActiveStream;
|
|
1892
1741
|
exports.processStreamEventV2 = processStreamEventV2;
|
|
1742
|
+
exports.renderableFields = renderableFields;
|
|
1893
1743
|
exports.resendUserAction = resendUserAction;
|
|
1894
1744
|
exports.streamWorkflowEvents = streamWorkflowEvents;
|
|
1895
1745
|
exports.submitUserAction = submitUserAction;
|
|
1896
1746
|
exports.useChatV2 = useChatV2;
|
|
1897
1747
|
exports.useVoice = useVoice;
|
|
1748
|
+
exports.validateField = validateField;
|
|
1749
|
+
exports.validateForm = validateForm;
|
|
1898
1750
|
//# sourceMappingURL=index.native.js.map
|
|
1899
1751
|
//# sourceMappingURL=index.native.js.map
|