@paymanai/payman-typescript-ask-sdk 1.2.10 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +141 -108
- package/dist/index.d.ts +141 -108
- package/dist/index.js +551 -1189
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +549 -1190
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +553 -1191
- package/dist/index.native.js.map +1 -1
- package/package.json +1 -1
package/dist/index.native.js
CHANGED
|
@@ -4,7 +4,7 @@ var react = require('react');
|
|
|
4
4
|
var reactNativeMmkv = require('react-native-mmkv');
|
|
5
5
|
var expoSpeechRecognition = require('expo-speech-recognition');
|
|
6
6
|
|
|
7
|
-
// src/hooks/
|
|
7
|
+
// src/hooks/useChatV2.ts
|
|
8
8
|
|
|
9
9
|
// src/utils/generateId.ts
|
|
10
10
|
function generateId() {
|
|
@@ -194,6 +194,14 @@ function getEventMessage(event) {
|
|
|
194
194
|
return eventType;
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
+
function workingPhaseDetailForDisplay(raw) {
|
|
198
|
+
const t = raw.trim();
|
|
199
|
+
if (!t) return "";
|
|
200
|
+
if (/^Identified\s+\d+\s+tasks?\s+to\s+execute\.?$/i.test(t)) {
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
return t;
|
|
204
|
+
}
|
|
197
205
|
function extractResponseContent(response) {
|
|
198
206
|
if (typeof response === "string") {
|
|
199
207
|
return response;
|
|
@@ -216,290 +224,8 @@ function extractResponseContent(response) {
|
|
|
216
224
|
}
|
|
217
225
|
return "";
|
|
218
226
|
}
|
|
219
|
-
function completeLastInProgressStep(steps) {
|
|
220
|
-
for (let i = steps.length - 1; i >= 0; i--) {
|
|
221
|
-
if (steps[i].status === "in_progress") {
|
|
222
|
-
steps[i].status = "completed";
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
function processStreamEvent(event, state) {
|
|
228
|
-
const eventType = event.eventType;
|
|
229
|
-
if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
230
|
-
return state;
|
|
231
|
-
}
|
|
232
|
-
const message = getEventMessage(event);
|
|
233
|
-
if (eventType !== "INTENT_THINKING" && eventType !== "INTENT_THINKING_CONT") {
|
|
234
|
-
if (state.currentThinkingStepId) {
|
|
235
|
-
const thinkingStep = state.steps.find((s) => s.id === state.currentThinkingStepId);
|
|
236
|
-
if (thinkingStep) {
|
|
237
|
-
thinkingStep.isThinking = false;
|
|
238
|
-
}
|
|
239
|
-
state.currentThinkingStepId = void 0;
|
|
240
|
-
}
|
|
241
|
-
state.activeThinkingText = void 0;
|
|
242
|
-
}
|
|
243
|
-
if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
|
|
244
|
-
let content = extractResponseContent(event.response);
|
|
245
|
-
const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
|
|
246
|
-
if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
|
|
247
|
-
content = trace.workflowMsg;
|
|
248
|
-
}
|
|
249
|
-
if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
|
|
250
|
-
const agg = trace.aggregator;
|
|
251
|
-
if (typeof agg.response === "string") content = agg.response;
|
|
252
|
-
else content = extractResponseContent(agg.response);
|
|
253
|
-
}
|
|
254
|
-
if (content) {
|
|
255
|
-
state.accumulatedContent = content;
|
|
256
|
-
state.finalData = event.response ?? event.trace;
|
|
257
|
-
state.hasError = false;
|
|
258
|
-
state.errorMessage = "";
|
|
259
|
-
} else {
|
|
260
|
-
state.hasError = true;
|
|
261
|
-
state.errorMessage = "WORKFLOW_FAILED";
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (eventType === "STARTED" || eventType === "WORKFLOW_STARTED") ; else if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
|
|
265
|
-
state.steps.forEach((step) => {
|
|
266
|
-
if (step.status === "in_progress") {
|
|
267
|
-
step.status = "completed";
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
} else if (eventType === "INTENT_ERROR") {
|
|
271
|
-
state.errorMessage = message || event.errorMessage || "An error occurred";
|
|
272
|
-
const intentStep = state.steps.find(
|
|
273
|
-
(s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
|
|
274
|
-
);
|
|
275
|
-
if (intentStep) {
|
|
276
|
-
intentStep.status = "error";
|
|
277
|
-
}
|
|
278
|
-
} else if (eventType === "ERROR" || eventType === "WORKFLOW_ERROR") {
|
|
279
|
-
state.hasError = true;
|
|
280
|
-
state.errorMessage = message || event.errorMessage || "An error occurred";
|
|
281
|
-
} else if (eventType === "ORCHESTRATOR_COMPLETED") {
|
|
282
|
-
state.inOrchestratorPhase = false;
|
|
283
|
-
const orchestratorStep = state.steps.find(
|
|
284
|
-
(s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress"
|
|
285
|
-
);
|
|
286
|
-
if (orchestratorStep) {
|
|
287
|
-
orchestratorStep.status = "completed";
|
|
288
|
-
if (event.elapsedMs) {
|
|
289
|
-
orchestratorStep.elapsedMs = event.elapsedMs;
|
|
290
|
-
}
|
|
291
|
-
if (orchestratorStep.id === state.currentExecutingStepId) {
|
|
292
|
-
state.currentExecutingStepId = void 0;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
} else if (eventType === "INTENT_COMPLETED") {
|
|
296
|
-
const intentStep = state.steps.find(
|
|
297
|
-
(s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
|
|
298
|
-
);
|
|
299
|
-
if (intentStep) {
|
|
300
|
-
intentStep.status = "completed";
|
|
301
|
-
if (event.elapsedMs) {
|
|
302
|
-
intentStep.elapsedMs = event.elapsedMs;
|
|
303
|
-
}
|
|
304
|
-
if (intentStep.id === state.currentExecutingStepId) {
|
|
305
|
-
state.currentExecutingStepId = void 0;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
} else if (eventType === "AGGREGATOR_COMPLETED") {
|
|
309
|
-
state.inAggregatorPhase = false;
|
|
310
|
-
const aggregatorStep = state.steps.find(
|
|
311
|
-
(s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress"
|
|
312
|
-
);
|
|
313
|
-
if (aggregatorStep) {
|
|
314
|
-
aggregatorStep.status = "completed";
|
|
315
|
-
if (event.elapsedMs) {
|
|
316
|
-
aggregatorStep.elapsedMs = event.elapsedMs;
|
|
317
|
-
}
|
|
318
|
-
if (aggregatorStep.id === state.currentExecutingStepId) {
|
|
319
|
-
state.currentExecutingStepId = void 0;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
} else if (eventType === "ORCHESTRATOR_THINKING" || eventType === "INTENT_STARTED" || eventType === "INTENT_PROGRESS" || eventType === "AGGREGATOR_THINKING") {
|
|
323
|
-
if (eventType === "ORCHESTRATOR_THINKING") {
|
|
324
|
-
state.inOrchestratorPhase = true;
|
|
325
|
-
}
|
|
326
|
-
if (eventType === "AGGREGATOR_THINKING") {
|
|
327
|
-
state.inAggregatorPhase = true;
|
|
328
|
-
}
|
|
329
|
-
if (eventType === "INTENT_PROGRESS") {
|
|
330
|
-
const intentStep = state.steps.find(
|
|
331
|
-
(s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
|
|
332
|
-
);
|
|
333
|
-
if (intentStep) {
|
|
334
|
-
intentStep.message = message;
|
|
335
|
-
state.currentExecutingStepId = intentStep.id;
|
|
336
|
-
} else {
|
|
337
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
338
|
-
state.steps.push({
|
|
339
|
-
id: stepId,
|
|
340
|
-
eventType: "INTENT_STARTED",
|
|
341
|
-
message,
|
|
342
|
-
status: "in_progress",
|
|
343
|
-
timestamp: Date.now(),
|
|
344
|
-
elapsedMs: event.elapsedMs
|
|
345
|
-
});
|
|
346
|
-
state.currentExecutingStepId = stepId;
|
|
347
|
-
}
|
|
348
|
-
} else {
|
|
349
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
350
|
-
state.steps.push({
|
|
351
|
-
id: stepId,
|
|
352
|
-
eventType,
|
|
353
|
-
message,
|
|
354
|
-
status: "in_progress",
|
|
355
|
-
timestamp: Date.now(),
|
|
356
|
-
elapsedMs: event.elapsedMs
|
|
357
|
-
});
|
|
358
|
-
state.currentExecutingStepId = stepId;
|
|
359
|
-
}
|
|
360
|
-
} else if (eventType === "USER_ACTION_REQUIRED") {
|
|
361
|
-
completeLastInProgressStep(state.steps);
|
|
362
|
-
if (event.userActionRequest) {
|
|
363
|
-
state.userActionRequest = {
|
|
364
|
-
userActionId: event.userActionRequest.userActionId,
|
|
365
|
-
userActionType: event.userActionRequest.userActionType,
|
|
366
|
-
message: event.userActionRequest.message,
|
|
367
|
-
requestedSchema: event.userActionRequest.requestedSchema,
|
|
368
|
-
metadata: event.userActionRequest.metadata
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
state.userActionPending = true;
|
|
372
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
373
|
-
state.steps.push({
|
|
374
|
-
id: stepId,
|
|
375
|
-
eventType,
|
|
376
|
-
message,
|
|
377
|
-
status: "in_progress",
|
|
378
|
-
timestamp: Date.now(),
|
|
379
|
-
elapsedMs: event.elapsedMs
|
|
380
|
-
});
|
|
381
|
-
state.currentExecutingStepId = stepId;
|
|
382
|
-
} else if (eventType === "USER_ACTION_SUCCESS") {
|
|
383
|
-
completeLastInProgressStep(state.steps);
|
|
384
|
-
state.userActionRequest = void 0;
|
|
385
|
-
state.userActionPending = false;
|
|
386
|
-
state.userActionResult = "approved";
|
|
387
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
388
|
-
state.steps.push({
|
|
389
|
-
id: stepId,
|
|
390
|
-
eventType,
|
|
391
|
-
message,
|
|
392
|
-
status: "completed",
|
|
393
|
-
timestamp: Date.now(),
|
|
394
|
-
elapsedMs: event.elapsedMs
|
|
395
|
-
});
|
|
396
|
-
} else if (eventType === "USER_ACTION_INVALID") {
|
|
397
|
-
completeLastInProgressStep(state.steps);
|
|
398
|
-
const errorStepId = `step-${state.stepCounter++}`;
|
|
399
|
-
state.steps.push({
|
|
400
|
-
id: errorStepId,
|
|
401
|
-
eventType,
|
|
402
|
-
message,
|
|
403
|
-
status: "error",
|
|
404
|
-
timestamp: Date.now(),
|
|
405
|
-
elapsedMs: event.elapsedMs
|
|
406
|
-
});
|
|
407
|
-
const retryStepId = `step-${state.stepCounter++}`;
|
|
408
|
-
state.steps.push({
|
|
409
|
-
id: retryStepId,
|
|
410
|
-
eventType: "USER_ACTION_REQUIRED",
|
|
411
|
-
message: "Waiting for verification...",
|
|
412
|
-
status: "in_progress",
|
|
413
|
-
timestamp: Date.now()
|
|
414
|
-
});
|
|
415
|
-
state.currentExecutingStepId = retryStepId;
|
|
416
|
-
} else if (eventType === "USER_ACTION_EXPIRED") {
|
|
417
|
-
completeLastInProgressStep(state.steps);
|
|
418
|
-
state.userActionRequest = void 0;
|
|
419
|
-
state.userActionPending = false;
|
|
420
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
421
|
-
state.steps.push({
|
|
422
|
-
id: stepId,
|
|
423
|
-
eventType,
|
|
424
|
-
message,
|
|
425
|
-
status: "error",
|
|
426
|
-
timestamp: Date.now(),
|
|
427
|
-
elapsedMs: event.elapsedMs
|
|
428
|
-
});
|
|
429
|
-
} else if (eventType === "USER_ACTION_REJECTED") {
|
|
430
|
-
completeLastInProgressStep(state.steps);
|
|
431
|
-
state.userActionRequest = void 0;
|
|
432
|
-
state.userActionPending = false;
|
|
433
|
-
state.userActionResult = "rejected";
|
|
434
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
435
|
-
state.steps.push({
|
|
436
|
-
id: stepId,
|
|
437
|
-
eventType,
|
|
438
|
-
message,
|
|
439
|
-
status: "completed",
|
|
440
|
-
timestamp: Date.now(),
|
|
441
|
-
elapsedMs: event.elapsedMs
|
|
442
|
-
});
|
|
443
|
-
} else if (eventType === "USER_ACTION_RESENT") {
|
|
444
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
445
|
-
state.steps.push({
|
|
446
|
-
id: stepId,
|
|
447
|
-
eventType,
|
|
448
|
-
message,
|
|
449
|
-
status: "completed",
|
|
450
|
-
timestamp: Date.now(),
|
|
451
|
-
elapsedMs: event.elapsedMs
|
|
452
|
-
});
|
|
453
|
-
} else if (eventType === "USER_ACTION_FAILED") {
|
|
454
|
-
completeLastInProgressStep(state.steps);
|
|
455
|
-
state.userActionRequest = void 0;
|
|
456
|
-
state.userActionPending = false;
|
|
457
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
458
|
-
state.steps.push({
|
|
459
|
-
id: stepId,
|
|
460
|
-
eventType,
|
|
461
|
-
message,
|
|
462
|
-
status: "error",
|
|
463
|
-
timestamp: Date.now(),
|
|
464
|
-
elapsedMs: event.elapsedMs
|
|
465
|
-
});
|
|
466
|
-
} else if (eventType === "INTENT_THINKING") {
|
|
467
|
-
if (state.currentThinkingStepId) {
|
|
468
|
-
const prev = state.steps.find((s) => s.id === state.currentThinkingStepId);
|
|
469
|
-
if (prev) prev.isThinking = false;
|
|
470
|
-
}
|
|
471
|
-
const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
|
|
472
|
-
if (lastInProgress) {
|
|
473
|
-
lastInProgress.thinkingText = "";
|
|
474
|
-
lastInProgress.isThinking = true;
|
|
475
|
-
state.currentThinkingStepId = lastInProgress.id;
|
|
476
|
-
} else {
|
|
477
|
-
state.currentThinkingStepId = void 0;
|
|
478
|
-
}
|
|
479
|
-
if (state.allThinkingText) state.allThinkingText += "\n\n";
|
|
480
|
-
if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
|
|
481
|
-
state.activeThinkingText = "";
|
|
482
|
-
}
|
|
483
|
-
} else if (eventType === "INTENT_THINKING_CONT") {
|
|
484
|
-
const delta = event.message || "";
|
|
485
|
-
if (!delta) return state;
|
|
486
|
-
if (state.currentThinkingStepId) {
|
|
487
|
-
const step = state.steps.find((s) => s.id === state.currentThinkingStepId);
|
|
488
|
-
if (step) {
|
|
489
|
-
step.thinkingText = (step.thinkingText || "") + delta;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
state.allThinkingText += delta;
|
|
493
|
-
if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
|
|
494
|
-
if (state.activeThinkingText == null) state.activeThinkingText = "";
|
|
495
|
-
state.activeThinkingText += delta;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return state;
|
|
499
|
-
}
|
|
500
227
|
|
|
501
228
|
// src/utils/messageStateManager.ts
|
|
502
|
-
var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
|
|
503
229
|
function buildFormattedThinking(steps, allThinkingText) {
|
|
504
230
|
const parts = [];
|
|
505
231
|
const safeSteps = steps ?? [];
|
|
@@ -529,6 +255,12 @@ function buildFormattedThinking(steps, allThinkingText) {
|
|
|
529
255
|
parts.push("**Planning**");
|
|
530
256
|
if (step.message) parts.push(step.message);
|
|
531
257
|
break;
|
|
258
|
+
case "WORKING": {
|
|
259
|
+
const detail = workingPhaseDetailForDisplay(step.message || "");
|
|
260
|
+
parts.push("**Working**");
|
|
261
|
+
if (detail) parts.push(detail);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
532
264
|
case "INTENT_STARTED": {
|
|
533
265
|
let label = step.message || "Processing";
|
|
534
266
|
const started = label.match(/^(.+?)\s+started$/i);
|
|
@@ -568,73 +300,6 @@ function buildFormattedThinking(steps, allThinkingText) {
|
|
|
568
300
|
}
|
|
569
301
|
return parts.length > 0 ? parts.join("\n") : allThinkingText;
|
|
570
302
|
}
|
|
571
|
-
function createStreamingMessageUpdate(state) {
|
|
572
|
-
const hasCompletedContent = state.accumulatedContent && state.finalData !== void 0;
|
|
573
|
-
const steps = state.hasError ? [] : [...state.steps];
|
|
574
|
-
const allThinking = state.hasError ? void 0 : state.allThinkingText;
|
|
575
|
-
return {
|
|
576
|
-
streamingContent: state.hasError ? FRIENDLY_ERROR_MESSAGE : hasCompletedContent ? state.accumulatedContent : "",
|
|
577
|
-
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : "",
|
|
578
|
-
currentMessage: state.hasError ? void 0 : state.currentMessage,
|
|
579
|
-
streamProgress: state.hasError ? "error" : "processing",
|
|
580
|
-
isError: state.hasError,
|
|
581
|
-
errorDetails: state.hasError ? state.errorMessage : void 0,
|
|
582
|
-
executionId: state.executionId,
|
|
583
|
-
sessionId: state.sessionId,
|
|
584
|
-
steps,
|
|
585
|
-
currentExecutingStepId: state.hasError ? void 0 : state.currentExecutingStepId,
|
|
586
|
-
isCancelled: false,
|
|
587
|
-
userActionResult: state.userActionResult,
|
|
588
|
-
activeThinkingText: state.hasError ? void 0 : state.activeThinkingText,
|
|
589
|
-
allThinkingText: allThinking,
|
|
590
|
-
formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || "")
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
function createErrorMessageUpdate(error, state) {
|
|
594
|
-
const isAborted = error.name === "AbortError";
|
|
595
|
-
return {
|
|
596
|
-
isStreaming: false,
|
|
597
|
-
streamProgress: isAborted ? "processing" : "error",
|
|
598
|
-
isError: !isAborted,
|
|
599
|
-
isCancelled: isAborted,
|
|
600
|
-
errorDetails: isAborted ? void 0 : error.message,
|
|
601
|
-
content: isAborted ? state.accumulatedContent || "" : state.accumulatedContent || FRIENDLY_ERROR_MESSAGE,
|
|
602
|
-
// Preserve currentMessage when cancelled so UI can show it
|
|
603
|
-
currentMessage: isAborted ? state.currentMessage || "Thinking..." : void 0,
|
|
604
|
-
steps: [...state.steps].map((step) => {
|
|
605
|
-
if (step.status === "in_progress" && isAborted) {
|
|
606
|
-
return { ...step, status: "pending" };
|
|
607
|
-
}
|
|
608
|
-
return step;
|
|
609
|
-
}),
|
|
610
|
-
currentExecutingStepId: void 0
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
function createFinalMessage(streamingId, state) {
|
|
614
|
-
const steps = state.hasError ? [] : [...state.steps];
|
|
615
|
-
const allThinking = state.hasError ? void 0 : state.allThinkingText;
|
|
616
|
-
return {
|
|
617
|
-
id: streamingId,
|
|
618
|
-
sessionId: state.sessionId,
|
|
619
|
-
role: "assistant",
|
|
620
|
-
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.accumulatedContent || "",
|
|
621
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
622
|
-
isStreaming: false,
|
|
623
|
-
streamProgress: state.hasError ? "error" : "completed",
|
|
624
|
-
isError: state.hasError,
|
|
625
|
-
errorDetails: state.hasError ? state.errorMessage : void 0,
|
|
626
|
-
executionId: state.executionId,
|
|
627
|
-
tracingData: state.finalData,
|
|
628
|
-
steps,
|
|
629
|
-
isCancelled: false,
|
|
630
|
-
currentExecutingStepId: void 0,
|
|
631
|
-
userActionResult: state.userActionResult,
|
|
632
|
-
activeThinkingText: void 0,
|
|
633
|
-
allThinkingText: allThinking,
|
|
634
|
-
formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || ""),
|
|
635
|
-
isResolvingImages: state.hasError ? void 0 : state.isResolvingImages
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
303
|
function createCancelledMessageUpdate(steps, currentMessage) {
|
|
639
304
|
const updatedSteps = steps.map((step) => {
|
|
640
305
|
if (step.status === "in_progress") {
|
|
@@ -654,35 +319,44 @@ function createCancelledMessageUpdate(steps, currentMessage) {
|
|
|
654
319
|
|
|
655
320
|
// src/utils/requestBuilder.ts
|
|
656
321
|
function buildRequestBody(config, userMessage, sessionId) {
|
|
657
|
-
const
|
|
658
|
-
const sessionAttributes =
|
|
322
|
+
const owner = config.session?.owner;
|
|
323
|
+
const sessionAttributes = owner?.attributes && Object.keys(owner.attributes).length > 0 ? owner.attributes : void 0;
|
|
659
324
|
return {
|
|
660
|
-
workflowName: config.
|
|
325
|
+
workflowName: config.workflow.name,
|
|
661
326
|
userInput: userMessage,
|
|
662
327
|
sessionId,
|
|
663
|
-
sessionOwnerId:
|
|
664
|
-
sessionOwnerLabel:
|
|
328
|
+
sessionOwnerId: owner?.id || "",
|
|
329
|
+
sessionOwnerLabel: owner?.name || "",
|
|
665
330
|
sessionAttributes,
|
|
666
331
|
options: {
|
|
667
332
|
clientTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
668
333
|
}
|
|
669
334
|
};
|
|
670
335
|
}
|
|
336
|
+
function getStageParamName(config) {
|
|
337
|
+
return config.api.stageQueryParam ?? "stage";
|
|
338
|
+
}
|
|
339
|
+
function getStage(config) {
|
|
340
|
+
return config.workflow.stage ?? "DEV";
|
|
341
|
+
}
|
|
671
342
|
function buildStreamingUrl(config) {
|
|
672
343
|
const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
if (config.
|
|
677
|
-
queryParams.append("workflowVersion", String(config.
|
|
344
|
+
const queryParams = new URLSearchParams({
|
|
345
|
+
[getStageParamName(config)]: getStage(config)
|
|
346
|
+
});
|
|
347
|
+
if (config.workflow.version !== void 0) {
|
|
348
|
+
queryParams.append("workflowVersion", String(config.workflow.version));
|
|
678
349
|
}
|
|
679
350
|
return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
|
|
680
351
|
}
|
|
681
|
-
function
|
|
352
|
+
function deriveBasePath(config) {
|
|
682
353
|
const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
|
|
683
354
|
const [endpointPath] = endpoint.split("?");
|
|
684
|
-
const
|
|
685
|
-
|
|
355
|
+
const normalized = endpointPath.replace(/\/+$/, "");
|
|
356
|
+
return normalized.endsWith("/stream") ? normalized.slice(0, -"/stream".length) : normalized;
|
|
357
|
+
}
|
|
358
|
+
function buildUserActionUrl(config, userActionId, action) {
|
|
359
|
+
const basePath = deriveBasePath(config);
|
|
686
360
|
const encodedUserActionId = encodeURIComponent(userActionId);
|
|
687
361
|
return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
|
|
688
362
|
}
|
|
@@ -690,21 +364,23 @@ function buildResolveImagesUrl(config) {
|
|
|
690
364
|
if (config.api.resolveImagesEndpoint) {
|
|
691
365
|
return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
|
|
692
366
|
}
|
|
693
|
-
|
|
694
|
-
const [endpointPath] = streamEndpoint.split("?");
|
|
695
|
-
const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
|
|
696
|
-
const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
|
|
697
|
-
return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
|
|
367
|
+
return `${config.api.baseUrl}${deriveBasePath(config)}/resolve-image-urls`;
|
|
698
368
|
}
|
|
699
369
|
function buildRequestHeaders(config) {
|
|
700
|
-
const headers = {
|
|
701
|
-
...config.api.headers
|
|
702
|
-
};
|
|
370
|
+
const headers = { ...config.api.headers };
|
|
703
371
|
if (config.api.authToken) {
|
|
704
372
|
headers.Authorization = `Bearer ${config.api.authToken}`;
|
|
705
373
|
}
|
|
706
374
|
return headers;
|
|
707
375
|
}
|
|
376
|
+
function buildScopeKey(config) {
|
|
377
|
+
return [
|
|
378
|
+
config.session?.userId ?? "",
|
|
379
|
+
config.workflow.id ?? "",
|
|
380
|
+
config.workflow.version ?? "",
|
|
381
|
+
config.workflow.stage ?? ""
|
|
382
|
+
].join("|");
|
|
383
|
+
}
|
|
708
384
|
|
|
709
385
|
// src/utils/userActionClient.ts
|
|
710
386
|
async function sendUserActionRequest(config, userActionId, action, data) {
|
|
@@ -716,691 +392,104 @@ async function sendUserActionRequest(config, userActionId, action, data) {
|
|
|
716
392
|
method: "POST",
|
|
717
393
|
headers,
|
|
718
394
|
body: hasBody ? JSON.stringify(data) : void 0
|
|
719
|
-
});
|
|
720
|
-
if (!response.ok) {
|
|
721
|
-
const errorText = await response.text();
|
|
722
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
723
|
-
}
|
|
724
|
-
return await response.json();
|
|
725
|
-
}
|
|
726
|
-
async function submitUserAction(config, userActionId, data) {
|
|
727
|
-
return sendUserActionRequest(config, userActionId, "submit", data);
|
|
728
|
-
}
|
|
729
|
-
async function cancelUserAction(config, userActionId) {
|
|
730
|
-
return sendUserActionRequest(config, userActionId, "cancel");
|
|
731
|
-
}
|
|
732
|
-
async function resendUserAction(config, userActionId) {
|
|
733
|
-
return sendUserActionRequest(config, userActionId, "resend");
|
|
734
|
-
}
|
|
735
|
-
var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
|
|
736
|
-
var chatStore = {
|
|
737
|
-
get(key) {
|
|
738
|
-
const raw = storage.getString(key);
|
|
739
|
-
if (!raw) return [];
|
|
740
|
-
try {
|
|
741
|
-
return JSON.parse(raw);
|
|
742
|
-
} catch {
|
|
743
|
-
return [];
|
|
744
|
-
}
|
|
745
|
-
},
|
|
746
|
-
set(key, messages) {
|
|
747
|
-
storage.set(key, JSON.stringify(messages));
|
|
748
|
-
},
|
|
749
|
-
delete(key) {
|
|
750
|
-
storage.delete(key);
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
|
|
754
|
-
// src/utils/activeStreamStore.ts
|
|
755
|
-
var streams = /* @__PURE__ */ new Map();
|
|
756
|
-
var activeStreamStore = {
|
|
757
|
-
has(key) {
|
|
758
|
-
return streams.has(key);
|
|
759
|
-
},
|
|
760
|
-
get(key) {
|
|
761
|
-
const entry = streams.get(key);
|
|
762
|
-
if (!entry) return null;
|
|
763
|
-
return { messages: entry.messages, isWaiting: entry.isWaiting };
|
|
764
|
-
},
|
|
765
|
-
// Called before startStream — registers the controller and initial messages
|
|
766
|
-
start(key, abortController, initialMessages) {
|
|
767
|
-
const existing = streams.get(key);
|
|
768
|
-
streams.set(key, {
|
|
769
|
-
messages: initialMessages,
|
|
770
|
-
isWaiting: true,
|
|
771
|
-
abortController,
|
|
772
|
-
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
773
|
-
});
|
|
774
|
-
},
|
|
775
|
-
// Called by the stream on every event — applies the same updater pattern React uses
|
|
776
|
-
applyMessages(key, updater) {
|
|
777
|
-
const entry = streams.get(key);
|
|
778
|
-
if (!entry) return;
|
|
779
|
-
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
780
|
-
entry.messages = next;
|
|
781
|
-
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
782
|
-
},
|
|
783
|
-
setWaiting(key, waiting) {
|
|
784
|
-
const entry = streams.get(key);
|
|
785
|
-
if (!entry) return;
|
|
786
|
-
entry.isWaiting = waiting;
|
|
787
|
-
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
788
|
-
},
|
|
789
|
-
// Called when stream completes — persists to chatStore and cleans up
|
|
790
|
-
complete(key) {
|
|
791
|
-
const entry = streams.get(key);
|
|
792
|
-
if (!entry) return;
|
|
793
|
-
entry.isWaiting = false;
|
|
794
|
-
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
795
|
-
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
796
|
-
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
797
|
-
streams.delete(key);
|
|
798
|
-
},
|
|
799
|
-
// Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
|
|
800
|
-
subscribe(key, listener) {
|
|
801
|
-
const entry = streams.get(key);
|
|
802
|
-
if (!entry) return () => {
|
|
803
|
-
};
|
|
804
|
-
entry.listeners.add(listener);
|
|
805
|
-
return () => {
|
|
806
|
-
streams.get(key)?.listeners.delete(listener);
|
|
807
|
-
};
|
|
808
|
-
},
|
|
809
|
-
// Explicit user cancel — aborts the controller and removes the entry
|
|
810
|
-
abort(key) {
|
|
811
|
-
const entry = streams.get(key);
|
|
812
|
-
if (!entry) return;
|
|
813
|
-
entry.abortController.abort();
|
|
814
|
-
streams.delete(key);
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
|
|
818
|
-
// src/utils/ragImageResolver.ts
|
|
819
|
-
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
820
|
-
function hasRagImages(content) {
|
|
821
|
-
return RAG_IMAGE_REGEX.test(content);
|
|
822
|
-
}
|
|
823
|
-
async function waitForNextPaint(signal) {
|
|
824
|
-
if (signal?.aborted) return;
|
|
825
|
-
await new Promise((resolve) => {
|
|
826
|
-
let isSettled = false;
|
|
827
|
-
const finish = () => {
|
|
828
|
-
if (isSettled) return;
|
|
829
|
-
isSettled = true;
|
|
830
|
-
signal?.removeEventListener("abort", finish);
|
|
831
|
-
resolve();
|
|
832
|
-
};
|
|
833
|
-
signal?.addEventListener("abort", finish, { once: true });
|
|
834
|
-
if (typeof requestAnimationFrame === "function") {
|
|
835
|
-
requestAnimationFrame(() => {
|
|
836
|
-
setTimeout(finish, 0);
|
|
837
|
-
});
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
setTimeout(finish, 0);
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
async function resolveRagImageUrls(config, content, signal) {
|
|
844
|
-
const url = buildResolveImagesUrl(config);
|
|
845
|
-
const baseHeaders = buildRequestHeaders(config);
|
|
846
|
-
const headers = { "Content-Type": "application/json", ...baseHeaders };
|
|
847
|
-
const response = await fetch(url, {
|
|
848
|
-
method: "POST",
|
|
849
|
-
headers,
|
|
850
|
-
body: JSON.stringify({ input: content }),
|
|
851
|
-
signal
|
|
852
|
-
});
|
|
853
|
-
if (!response.ok) {
|
|
854
|
-
const errorText = await response.text();
|
|
855
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
856
|
-
}
|
|
857
|
-
const text = await response.text();
|
|
858
|
-
try {
|
|
859
|
-
const parsed = JSON.parse(text);
|
|
860
|
-
if (typeof parsed === "string") return parsed;
|
|
861
|
-
if (typeof parsed.output === "string") return parsed.output;
|
|
862
|
-
if (typeof parsed.result === "string") return parsed.result;
|
|
863
|
-
} catch {
|
|
864
|
-
}
|
|
865
|
-
return text;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// src/hooks/useStreamManager.ts
|
|
869
|
-
function useStreamManager(config, callbacks, setMessages, setIsWaitingForResponse) {
|
|
870
|
-
const abortControllerRef = react.useRef(null);
|
|
871
|
-
const configRef = react.useRef(config);
|
|
872
|
-
configRef.current = config;
|
|
873
|
-
const callbacksRef = react.useRef(callbacks);
|
|
874
|
-
callbacksRef.current = callbacks;
|
|
875
|
-
const startStream = react.useCallback(
|
|
876
|
-
async (userMessage, streamingId, sessionId, externalAbortController) => {
|
|
877
|
-
abortControllerRef.current?.abort();
|
|
878
|
-
const abortController = externalAbortController ?? new AbortController();
|
|
879
|
-
abortControllerRef.current = abortController;
|
|
880
|
-
const state = {
|
|
881
|
-
accumulatedContent: "",
|
|
882
|
-
executionId: void 0,
|
|
883
|
-
currentSessionId: void 0,
|
|
884
|
-
finalData: void 0,
|
|
885
|
-
steps: [],
|
|
886
|
-
stepCounter: 0,
|
|
887
|
-
currentExecutingStepId: void 0,
|
|
888
|
-
currentThinkingStepId: void 0,
|
|
889
|
-
hasError: false,
|
|
890
|
-
errorMessage: "",
|
|
891
|
-
userActionRequest: void 0,
|
|
892
|
-
userActionPending: false,
|
|
893
|
-
userActionResult: void 0,
|
|
894
|
-
allThinkingText: "",
|
|
895
|
-
inOrchestratorPhase: false,
|
|
896
|
-
inAggregatorPhase: false
|
|
897
|
-
};
|
|
898
|
-
const THROTTLE_MS = 120;
|
|
899
|
-
const CHARS_PER_TICK = 10;
|
|
900
|
-
const displayedLengthRef = { current: 0 };
|
|
901
|
-
let throttleIntervalId = null;
|
|
902
|
-
const getActiveStepMessage = () => state.steps.find((s) => s.id === state.currentExecutingStepId)?.message || [...state.steps].reverse().find((s) => s.status === "in_progress")?.message;
|
|
903
|
-
const advanceDisplayLength = (full) => {
|
|
904
|
-
let newLen = Math.min(displayedLengthRef.current + CHARS_PER_TICK, full.length);
|
|
905
|
-
if (newLen > 0 && newLen < full.length) {
|
|
906
|
-
const code = full.charCodeAt(newLen - 1);
|
|
907
|
-
if (code >= 55296 && code <= 56319) {
|
|
908
|
-
newLen = Math.min(newLen + 1, full.length);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
displayedLengthRef.current = newLen;
|
|
912
|
-
};
|
|
913
|
-
const clearThrottle = () => {
|
|
914
|
-
if (throttleIntervalId != null) {
|
|
915
|
-
clearInterval(throttleIntervalId);
|
|
916
|
-
throttleIntervalId = null;
|
|
917
|
-
}
|
|
918
|
-
};
|
|
919
|
-
abortController.signal.addEventListener("abort", clearThrottle, { once: true });
|
|
920
|
-
const ensureThrottle = () => {
|
|
921
|
-
if (throttleIntervalId != null) return;
|
|
922
|
-
throttleIntervalId = setInterval(() => {
|
|
923
|
-
if (abortController.signal.aborted) {
|
|
924
|
-
clearThrottle();
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
const full = state.activeThinkingText ?? "";
|
|
928
|
-
if (displayedLengthRef.current < full.length) {
|
|
929
|
-
advanceDisplayLength(full);
|
|
930
|
-
const displayText = full.slice(0, displayedLengthRef.current);
|
|
931
|
-
setMessages(
|
|
932
|
-
(prev) => prev.map(
|
|
933
|
-
(msg) => msg.id === streamingId ? {
|
|
934
|
-
...msg,
|
|
935
|
-
...createStreamingMessageUpdate({
|
|
936
|
-
...state,
|
|
937
|
-
activeThinkingText: displayText,
|
|
938
|
-
currentMessage: getActiveStepMessage() || "Thinking..."
|
|
939
|
-
})
|
|
940
|
-
} : msg
|
|
941
|
-
)
|
|
942
|
-
);
|
|
943
|
-
}
|
|
944
|
-
}, THROTTLE_MS);
|
|
945
|
-
};
|
|
946
|
-
const currentConfig = configRef.current;
|
|
947
|
-
const requestBody = buildRequestBody(currentConfig, userMessage, sessionId);
|
|
948
|
-
const url = buildStreamingUrl(currentConfig);
|
|
949
|
-
const headers = buildRequestHeaders(currentConfig);
|
|
950
|
-
try {
|
|
951
|
-
await streamWorkflowEvents(url, requestBody, headers, {
|
|
952
|
-
signal: abortController.signal,
|
|
953
|
-
onEvent: (event) => {
|
|
954
|
-
if (abortController.signal.aborted) {
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
|
-
if (typeof event.eventType === "string" && event.eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
958
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
959
|
-
if (event.sessionId) state.currentSessionId = event.sessionId;
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
963
|
-
if (event.sessionId) state.currentSessionId = event.sessionId;
|
|
964
|
-
const activeThinkingLengthBeforeProcess = state.activeThinkingText?.length ?? 0;
|
|
965
|
-
processStreamEvent(event, state);
|
|
966
|
-
const eventType = event.eventType;
|
|
967
|
-
if (eventType === "INTENT_THINKING") {
|
|
968
|
-
displayedLengthRef.current = 0;
|
|
969
|
-
ensureThrottle();
|
|
970
|
-
} else if (eventType !== "INTENT_THINKING_CONT") {
|
|
971
|
-
displayedLengthRef.current = activeThinkingLengthBeforeProcess;
|
|
972
|
-
clearThrottle();
|
|
973
|
-
}
|
|
974
|
-
if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
|
|
975
|
-
callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
|
|
976
|
-
} else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
|
|
977
|
-
const msg = event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event);
|
|
978
|
-
callbacksRef.current.onUserActionEvent?.(eventType, msg);
|
|
979
|
-
}
|
|
980
|
-
const isIntentThinkingEvent = eventType === "INTENT_THINKING" || eventType === "INTENT_THINKING_CONT";
|
|
981
|
-
const rawMessage = event.message?.trim() || event.errorMessage?.trim();
|
|
982
|
-
const currentMessage = isIntentThinkingEvent ? getActiveStepMessage() || "Thinking..." : rawMessage || (event.eventType?.startsWith("USER_ACTION_") ? getEventMessage(event) : getActiveStepMessage() || getEventMessage(event));
|
|
983
|
-
const displayThinking = state.activeThinkingText != null ? state.activeThinkingText.slice(0, displayedLengthRef.current) : state.activeThinkingText;
|
|
984
|
-
setMessages(
|
|
985
|
-
(prev) => prev.map(
|
|
986
|
-
(msg) => msg.id === streamingId ? {
|
|
987
|
-
...msg,
|
|
988
|
-
...createStreamingMessageUpdate({
|
|
989
|
-
...state,
|
|
990
|
-
activeThinkingText: displayThinking,
|
|
991
|
-
currentMessage
|
|
992
|
-
})
|
|
993
|
-
} : msg
|
|
994
|
-
)
|
|
995
|
-
);
|
|
996
|
-
},
|
|
997
|
-
onError: (error) => {
|
|
998
|
-
clearThrottle();
|
|
999
|
-
setIsWaitingForResponse(false);
|
|
1000
|
-
if (error.name !== "AbortError") {
|
|
1001
|
-
callbacksRef.current.onError?.(error);
|
|
1002
|
-
}
|
|
1003
|
-
if (state.userActionPending) {
|
|
1004
|
-
state.userActionPending = false;
|
|
1005
|
-
state.userActionRequest = void 0;
|
|
1006
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1007
|
-
"USER_ACTION_FAILED",
|
|
1008
|
-
"Connection lost. Please try again."
|
|
1009
|
-
);
|
|
1010
|
-
}
|
|
1011
|
-
setMessages(
|
|
1012
|
-
(prev) => prev.map(
|
|
1013
|
-
(msg) => msg.id === streamingId ? {
|
|
1014
|
-
...msg,
|
|
1015
|
-
...createErrorMessageUpdate(error, state)
|
|
1016
|
-
} : msg
|
|
1017
|
-
)
|
|
1018
|
-
);
|
|
1019
|
-
},
|
|
1020
|
-
onComplete: () => {
|
|
1021
|
-
clearThrottle();
|
|
1022
|
-
setIsWaitingForResponse(false);
|
|
1023
|
-
if (state.userActionPending) {
|
|
1024
|
-
state.userActionPending = false;
|
|
1025
|
-
state.userActionRequest = void 0;
|
|
1026
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1027
|
-
"USER_ACTION_FAILED",
|
|
1028
|
-
"Verification could not be completed."
|
|
1029
|
-
);
|
|
1030
|
-
}
|
|
1031
|
-
if (state.currentSessionId && state.currentSessionId !== sessionId) {
|
|
1032
|
-
callbacksRef.current.onSessionIdChange?.(state.currentSessionId);
|
|
1033
|
-
}
|
|
1034
|
-
const needsImageResolve = !state.hasError && !abortController.signal.aborted && hasRagImages(state.accumulatedContent);
|
|
1035
|
-
const finalMessage = createFinalMessage(streamingId, {
|
|
1036
|
-
...state,
|
|
1037
|
-
sessionId: state.currentSessionId || sessionId,
|
|
1038
|
-
isResolvingImages: needsImageResolve
|
|
1039
|
-
});
|
|
1040
|
-
setMessages(
|
|
1041
|
-
(prev) => prev.map(
|
|
1042
|
-
(msg) => msg.id === streamingId ? finalMessage : msg
|
|
1043
|
-
)
|
|
1044
|
-
);
|
|
1045
|
-
callbacksRef.current.onStreamComplete?.(finalMessage);
|
|
1046
|
-
}
|
|
1047
|
-
});
|
|
1048
|
-
clearThrottle();
|
|
1049
|
-
const shouldResolveImages = !abortController.signal.aborted && !state.hasError && hasRagImages(state.accumulatedContent);
|
|
1050
|
-
if (shouldResolveImages) {
|
|
1051
|
-
await waitForNextPaint(abortController.signal);
|
|
1052
|
-
}
|
|
1053
|
-
if (shouldResolveImages && !abortController.signal.aborted) {
|
|
1054
|
-
try {
|
|
1055
|
-
const resolvedContent = await resolveRagImageUrls(
|
|
1056
|
-
currentConfig,
|
|
1057
|
-
state.accumulatedContent,
|
|
1058
|
-
abortController.signal
|
|
1059
|
-
);
|
|
1060
|
-
setMessages(
|
|
1061
|
-
(prev) => prev.map(
|
|
1062
|
-
(msg) => msg.id === streamingId ? { ...msg, content: resolvedContent, isResolvingImages: false } : msg
|
|
1063
|
-
)
|
|
1064
|
-
);
|
|
1065
|
-
} catch {
|
|
1066
|
-
setMessages(
|
|
1067
|
-
(prev) => prev.map(
|
|
1068
|
-
(msg) => msg.id === streamingId ? { ...msg, isResolvingImages: false } : msg
|
|
1069
|
-
)
|
|
1070
|
-
);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
return state.currentSessionId;
|
|
1074
|
-
} catch (error) {
|
|
1075
|
-
clearThrottle();
|
|
1076
|
-
setIsWaitingForResponse(false);
|
|
1077
|
-
if (error.name !== "AbortError") {
|
|
1078
|
-
callbacksRef.current.onError?.(error);
|
|
1079
|
-
}
|
|
1080
|
-
if (state.userActionPending) {
|
|
1081
|
-
state.userActionPending = false;
|
|
1082
|
-
state.userActionRequest = void 0;
|
|
1083
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1084
|
-
"USER_ACTION_FAILED",
|
|
1085
|
-
"Connection lost. Please try again."
|
|
1086
|
-
);
|
|
1087
|
-
}
|
|
1088
|
-
setMessages(
|
|
1089
|
-
(prev) => prev.map(
|
|
1090
|
-
(msg) => msg.id === streamingId ? {
|
|
1091
|
-
...msg,
|
|
1092
|
-
...createErrorMessageUpdate(error, state)
|
|
1093
|
-
} : msg
|
|
1094
|
-
)
|
|
1095
|
-
);
|
|
1096
|
-
return state.currentSessionId;
|
|
1097
|
-
}
|
|
1098
|
-
},
|
|
1099
|
-
[setMessages, setIsWaitingForResponse]
|
|
1100
|
-
);
|
|
1101
|
-
const cancelStream = react.useCallback(() => {
|
|
1102
|
-
abortControllerRef.current?.abort();
|
|
1103
|
-
}, []);
|
|
1104
|
-
return {
|
|
1105
|
-
startStream,
|
|
1106
|
-
cancelStream,
|
|
1107
|
-
abortControllerRef
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// src/hooks/useChat.ts
|
|
1112
|
-
function useChat(config, callbacks = {}) {
|
|
1113
|
-
const [messages, setMessages] = react.useState(() => {
|
|
1114
|
-
if (config.userId) return chatStore.get(config.userId);
|
|
1115
|
-
return config.initialMessages ?? [];
|
|
1116
|
-
});
|
|
1117
|
-
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
|
|
1118
|
-
const sessionIdRef = react.useRef(
|
|
1119
|
-
config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
|
|
1120
|
-
);
|
|
1121
|
-
const prevUserIdRef = react.useRef(config.userId);
|
|
1122
|
-
const callbacksRef = react.useRef(callbacks);
|
|
1123
|
-
callbacksRef.current = callbacks;
|
|
1124
|
-
const configRef = react.useRef(config);
|
|
1125
|
-
configRef.current = config;
|
|
1126
|
-
const messagesRef = react.useRef(messages);
|
|
1127
|
-
messagesRef.current = messages;
|
|
1128
|
-
const storeAwareSetMessages = react.useCallback(
|
|
1129
|
-
(updater) => {
|
|
1130
|
-
const { userId } = configRef.current;
|
|
1131
|
-
if (userId && activeStreamStore.has(userId)) {
|
|
1132
|
-
activeStreamStore.applyMessages(userId, updater);
|
|
1133
|
-
}
|
|
1134
|
-
setMessages(updater);
|
|
1135
|
-
},
|
|
1136
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1137
|
-
[]
|
|
1138
|
-
);
|
|
1139
|
-
const storeAwareSetIsWaiting = react.useCallback(
|
|
1140
|
-
(waiting) => {
|
|
1141
|
-
const { userId } = configRef.current;
|
|
1142
|
-
if (userId && activeStreamStore.has(userId)) {
|
|
1143
|
-
activeStreamStore.setWaiting(userId, waiting);
|
|
1144
|
-
}
|
|
1145
|
-
setIsWaitingForResponse(waiting);
|
|
1146
|
-
},
|
|
1147
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1148
|
-
[]
|
|
1149
|
-
);
|
|
1150
|
-
const [userActionState, setUserActionState] = react.useState({
|
|
1151
|
-
request: null,
|
|
1152
|
-
result: null,
|
|
1153
|
-
clearOtpTrigger: 0
|
|
1154
|
-
});
|
|
1155
|
-
const userActionStateRef = react.useRef(userActionState);
|
|
1156
|
-
userActionStateRef.current = userActionState;
|
|
1157
|
-
const wrappedCallbacks = react.useMemo(() => ({
|
|
1158
|
-
...callbacksRef.current,
|
|
1159
|
-
onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
|
|
1160
|
-
onStreamStart: () => callbacksRef.current.onStreamStart?.(),
|
|
1161
|
-
onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
|
|
1162
|
-
onError: (error) => callbacksRef.current.onError?.(error),
|
|
1163
|
-
onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
|
|
1164
|
-
onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
|
|
1165
|
-
onUserActionRequired: (request) => {
|
|
1166
|
-
setUserActionState((prev) => ({ ...prev, request, result: null }));
|
|
1167
|
-
callbacksRef.current.onUserActionRequired?.(request);
|
|
1168
|
-
},
|
|
1169
|
-
onUserActionEvent: (eventType, message) => {
|
|
1170
|
-
switch (eventType) {
|
|
1171
|
-
case "USER_ACTION_SUCCESS":
|
|
1172
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
|
|
1173
|
-
break;
|
|
1174
|
-
case "USER_ACTION_REJECTED":
|
|
1175
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
|
|
1176
|
-
break;
|
|
1177
|
-
case "USER_ACTION_EXPIRED":
|
|
1178
|
-
case "USER_ACTION_FAILED":
|
|
1179
|
-
setUserActionState((prev) => ({ ...prev, request: null }));
|
|
1180
|
-
break;
|
|
1181
|
-
case "USER_ACTION_INVALID":
|
|
1182
|
-
setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
|
|
1183
|
-
break;
|
|
1184
|
-
}
|
|
1185
|
-
callbacksRef.current.onUserActionEvent?.(eventType, message);
|
|
1186
|
-
}
|
|
1187
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1188
|
-
}), []);
|
|
1189
|
-
const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManager(
|
|
1190
|
-
config,
|
|
1191
|
-
wrappedCallbacks,
|
|
1192
|
-
storeAwareSetMessages,
|
|
1193
|
-
storeAwareSetIsWaiting
|
|
1194
|
-
);
|
|
1195
|
-
const sendMessage = react.useCallback(
|
|
1196
|
-
async (userMessage) => {
|
|
1197
|
-
if (!userMessage.trim()) return;
|
|
1198
|
-
if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
|
|
1199
|
-
sessionIdRef.current = generateId();
|
|
1200
|
-
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
1201
|
-
}
|
|
1202
|
-
const userMessageId = `user-${Date.now()}`;
|
|
1203
|
-
const userMsg = {
|
|
1204
|
-
id: userMessageId,
|
|
1205
|
-
sessionId: sessionIdRef.current,
|
|
1206
|
-
role: "user",
|
|
1207
|
-
content: userMessage,
|
|
1208
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1209
|
-
};
|
|
1210
|
-
setMessages((prev) => [...prev, userMsg]);
|
|
1211
|
-
callbacksRef.current.onMessageSent?.(userMessage);
|
|
1212
|
-
setIsWaitingForResponse(true);
|
|
1213
|
-
callbacksRef.current.onStreamStart?.();
|
|
1214
|
-
const streamingId = `assistant-${Date.now()}`;
|
|
1215
|
-
const streamingMsg = {
|
|
1216
|
-
id: streamingId,
|
|
1217
|
-
sessionId: sessionIdRef.current,
|
|
1218
|
-
role: "assistant",
|
|
1219
|
-
content: "",
|
|
1220
|
-
streamingContent: "",
|
|
1221
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1222
|
-
isStreaming: true,
|
|
1223
|
-
streamProgress: "started",
|
|
1224
|
-
steps: [],
|
|
1225
|
-
currentExecutingStepId: void 0,
|
|
1226
|
-
isCancelled: false,
|
|
1227
|
-
currentMessage: void 0
|
|
1228
|
-
};
|
|
1229
|
-
setMessages((prev) => [...prev, streamingMsg]);
|
|
1230
|
-
const abortController = new AbortController();
|
|
1231
|
-
const { userId } = configRef.current;
|
|
1232
|
-
if (userId) {
|
|
1233
|
-
const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
|
|
1234
|
-
activeStreamStore.start(userId, abortController, initialMessages);
|
|
1235
|
-
}
|
|
1236
|
-
const newSessionId = await startStream(
|
|
1237
|
-
userMessage,
|
|
1238
|
-
streamingId,
|
|
1239
|
-
sessionIdRef.current,
|
|
1240
|
-
abortController
|
|
1241
|
-
);
|
|
1242
|
-
if (userId) {
|
|
1243
|
-
activeStreamStore.complete(userId);
|
|
1244
|
-
}
|
|
1245
|
-
if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
|
|
1246
|
-
sessionIdRef.current = newSessionId;
|
|
1247
|
-
}
|
|
1248
|
-
},
|
|
1249
|
-
[startStream]
|
|
1250
|
-
);
|
|
1251
|
-
const clearMessages = react.useCallback(() => {
|
|
1252
|
-
if (configRef.current.userId) {
|
|
1253
|
-
chatStore.delete(configRef.current.userId);
|
|
1254
|
-
}
|
|
1255
|
-
setMessages([]);
|
|
1256
|
-
}, []);
|
|
1257
|
-
const prependMessages = react.useCallback((msgs) => {
|
|
1258
|
-
setMessages((prev) => [...msgs, ...prev]);
|
|
1259
|
-
}, []);
|
|
1260
|
-
const cancelStream = react.useCallback(() => {
|
|
1261
|
-
if (configRef.current.userId) {
|
|
1262
|
-
activeStreamStore.abort(configRef.current.userId);
|
|
1263
|
-
}
|
|
1264
|
-
cancelStreamManager();
|
|
1265
|
-
setIsWaitingForResponse(false);
|
|
1266
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: null }));
|
|
1267
|
-
setMessages(
|
|
1268
|
-
(prev) => prev.map((msg) => {
|
|
1269
|
-
if (msg.isStreaming) {
|
|
1270
|
-
return {
|
|
1271
|
-
...msg,
|
|
1272
|
-
...createCancelledMessageUpdate(
|
|
1273
|
-
msg.steps || [],
|
|
1274
|
-
msg.currentMessage
|
|
1275
|
-
)
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
return msg;
|
|
1279
|
-
})
|
|
1280
|
-
);
|
|
1281
|
-
}, [cancelStreamManager]);
|
|
1282
|
-
const resetSession = react.useCallback(() => {
|
|
1283
|
-
if (configRef.current.userId) {
|
|
1284
|
-
activeStreamStore.abort(configRef.current.userId);
|
|
1285
|
-
chatStore.delete(configRef.current.userId);
|
|
1286
|
-
}
|
|
1287
|
-
setMessages([]);
|
|
1288
|
-
sessionIdRef.current = void 0;
|
|
1289
|
-
abortControllerRef.current?.abort();
|
|
1290
|
-
setIsWaitingForResponse(false);
|
|
1291
|
-
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1292
|
-
}, []);
|
|
1293
|
-
const getSessionId = react.useCallback(() => {
|
|
1294
|
-
return sessionIdRef.current;
|
|
1295
|
-
}, []);
|
|
1296
|
-
const getMessages = react.useCallback(() => {
|
|
1297
|
-
return messages;
|
|
1298
|
-
}, [messages]);
|
|
1299
|
-
const approveUserAction = react.useCallback(
|
|
1300
|
-
async (otp) => {
|
|
1301
|
-
const request = userActionStateRef.current.request;
|
|
1302
|
-
if (!request) return;
|
|
1303
|
-
try {
|
|
1304
|
-
await submitUserAction(configRef.current, request.userActionId, { otp });
|
|
1305
|
-
} catch (error) {
|
|
1306
|
-
setUserActionState((prev) => ({
|
|
1307
|
-
...prev,
|
|
1308
|
-
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
1309
|
-
}));
|
|
1310
|
-
callbacksRef.current.onError?.(error);
|
|
1311
|
-
throw error;
|
|
1312
|
-
}
|
|
1313
|
-
},
|
|
1314
|
-
[]
|
|
1315
|
-
);
|
|
1316
|
-
const rejectUserAction = react.useCallback(async () => {
|
|
1317
|
-
const request = userActionStateRef.current.request;
|
|
1318
|
-
if (!request) return;
|
|
1319
|
-
try {
|
|
1320
|
-
setMessages((prev) => {
|
|
1321
|
-
let lastStreamingIdx = -1;
|
|
1322
|
-
for (let i = prev.length - 1; i >= 0; i--) {
|
|
1323
|
-
if (prev[i].role === "assistant" && prev[i].isStreaming) {
|
|
1324
|
-
lastStreamingIdx = i;
|
|
1325
|
-
break;
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
if (lastStreamingIdx === -1) return prev;
|
|
1329
|
-
return prev.map(
|
|
1330
|
-
(msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
|
|
1331
|
-
);
|
|
1332
|
-
});
|
|
1333
|
-
await cancelUserAction(configRef.current, request.userActionId);
|
|
1334
|
-
} catch (error) {
|
|
1335
|
-
callbacksRef.current.onError?.(error);
|
|
1336
|
-
throw error;
|
|
1337
|
-
}
|
|
1338
|
-
}, []);
|
|
1339
|
-
const resendOtp = react.useCallback(async () => {
|
|
1340
|
-
const request = userActionStateRef.current.request;
|
|
1341
|
-
if (!request) return;
|
|
1342
|
-
try {
|
|
1343
|
-
await resendUserAction(configRef.current, request.userActionId);
|
|
1344
|
-
} catch (error) {
|
|
1345
|
-
callbacksRef.current.onError?.(error);
|
|
1346
|
-
throw error;
|
|
1347
|
-
}
|
|
1348
|
-
}, []);
|
|
1349
|
-
react.useEffect(() => {
|
|
1350
|
-
const { userId } = config;
|
|
1351
|
-
if (!userId) return;
|
|
1352
|
-
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1353
|
-
setMessages(msgs);
|
|
1354
|
-
setIsWaitingForResponse(isWaiting);
|
|
1355
|
-
});
|
|
1356
|
-
const active = activeStreamStore.get(userId);
|
|
1357
|
-
if (active) {
|
|
1358
|
-
setMessages(active.messages);
|
|
1359
|
-
setIsWaitingForResponse(active.isWaiting);
|
|
1360
|
-
}
|
|
1361
|
-
return unsubscribe;
|
|
1362
|
-
}, []);
|
|
1363
|
-
react.useEffect(() => {
|
|
1364
|
-
if (!config.userId) return;
|
|
1365
|
-
const toSave = messages.filter((m) => !m.isStreaming);
|
|
1366
|
-
if (toSave.length > 0) {
|
|
1367
|
-
chatStore.set(config.userId, toSave);
|
|
1368
|
-
}
|
|
1369
|
-
}, [messages, config.userId]);
|
|
1370
|
-
react.useEffect(() => {
|
|
1371
|
-
const prevUserId = prevUserIdRef.current;
|
|
1372
|
-
prevUserIdRef.current = config.userId;
|
|
1373
|
-
if (prevUserId === config.userId) return;
|
|
1374
|
-
if (prevUserId && !config.userId) {
|
|
1375
|
-
chatStore.delete(prevUserId);
|
|
1376
|
-
setMessages([]);
|
|
1377
|
-
sessionIdRef.current = void 0;
|
|
1378
|
-
setIsWaitingForResponse(false);
|
|
1379
|
-
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1380
|
-
} else if (config.userId) {
|
|
1381
|
-
const stored = chatStore.get(config.userId);
|
|
1382
|
-
setMessages(stored);
|
|
1383
|
-
sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
|
|
1384
|
-
}
|
|
1385
|
-
}, [config.userId]);
|
|
1386
|
-
return {
|
|
1387
|
-
messages,
|
|
1388
|
-
sendMessage,
|
|
1389
|
-
clearMessages,
|
|
1390
|
-
prependMessages,
|
|
1391
|
-
cancelStream,
|
|
1392
|
-
resetSession,
|
|
1393
|
-
getSessionId,
|
|
1394
|
-
getMessages,
|
|
1395
|
-
isWaitingForResponse,
|
|
1396
|
-
sessionId: sessionIdRef.current,
|
|
1397
|
-
// User action (OTP) state and methods
|
|
1398
|
-
userActionState,
|
|
1399
|
-
approveUserAction,
|
|
1400
|
-
rejectUserAction,
|
|
1401
|
-
resendOtp
|
|
1402
|
-
};
|
|
395
|
+
});
|
|
396
|
+
if (!response.ok) {
|
|
397
|
+
const errorText = await response.text();
|
|
398
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
399
|
+
}
|
|
400
|
+
return await response.json();
|
|
401
|
+
}
|
|
402
|
+
async function submitUserAction(config, userActionId, data) {
|
|
403
|
+
return sendUserActionRequest(config, userActionId, "submit", data);
|
|
404
|
+
}
|
|
405
|
+
async function cancelUserAction(config, userActionId) {
|
|
406
|
+
return sendUserActionRequest(config, userActionId, "cancel");
|
|
1403
407
|
}
|
|
408
|
+
async function resendUserAction(config, userActionId) {
|
|
409
|
+
return sendUserActionRequest(config, userActionId, "resend");
|
|
410
|
+
}
|
|
411
|
+
var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
|
|
412
|
+
var chatStore = {
|
|
413
|
+
get(key) {
|
|
414
|
+
const raw = storage.getString(key);
|
|
415
|
+
if (!raw) return [];
|
|
416
|
+
try {
|
|
417
|
+
return JSON.parse(raw);
|
|
418
|
+
} catch {
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
set(key, messages) {
|
|
423
|
+
storage.set(key, JSON.stringify(messages));
|
|
424
|
+
},
|
|
425
|
+
delete(key) {
|
|
426
|
+
storage.delete(key);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// src/utils/activeStreamStore.ts
|
|
431
|
+
var streams = /* @__PURE__ */ new Map();
|
|
432
|
+
var activeStreamStore = {
|
|
433
|
+
has(key) {
|
|
434
|
+
return streams.has(key);
|
|
435
|
+
},
|
|
436
|
+
get(key) {
|
|
437
|
+
const entry = streams.get(key);
|
|
438
|
+
if (!entry) return null;
|
|
439
|
+
return { messages: entry.messages, isWaiting: entry.isWaiting };
|
|
440
|
+
},
|
|
441
|
+
// Called before startStream — registers the controller and initial messages
|
|
442
|
+
start(key, abortController, initialMessages) {
|
|
443
|
+
const existing = streams.get(key);
|
|
444
|
+
streams.set(key, {
|
|
445
|
+
messages: initialMessages,
|
|
446
|
+
isWaiting: true,
|
|
447
|
+
abortController,
|
|
448
|
+
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
449
|
+
});
|
|
450
|
+
},
|
|
451
|
+
// Called by the stream on every event — applies the same updater pattern React uses
|
|
452
|
+
applyMessages(key, updater) {
|
|
453
|
+
const entry = streams.get(key);
|
|
454
|
+
if (!entry) return;
|
|
455
|
+
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
456
|
+
entry.messages = next;
|
|
457
|
+
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
458
|
+
},
|
|
459
|
+
setWaiting(key, waiting) {
|
|
460
|
+
const entry = streams.get(key);
|
|
461
|
+
if (!entry) return;
|
|
462
|
+
entry.isWaiting = waiting;
|
|
463
|
+
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
464
|
+
},
|
|
465
|
+
// Called when stream completes — persists to chatStore and cleans up
|
|
466
|
+
complete(key) {
|
|
467
|
+
const entry = streams.get(key);
|
|
468
|
+
if (!entry) return;
|
|
469
|
+
entry.isWaiting = false;
|
|
470
|
+
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
471
|
+
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
472
|
+
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
473
|
+
streams.delete(key);
|
|
474
|
+
},
|
|
475
|
+
// Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
|
|
476
|
+
subscribe(key, listener) {
|
|
477
|
+
const entry = streams.get(key);
|
|
478
|
+
if (!entry) return () => {
|
|
479
|
+
};
|
|
480
|
+
entry.listeners.add(listener);
|
|
481
|
+
return () => {
|
|
482
|
+
streams.get(key)?.listeners.delete(listener);
|
|
483
|
+
};
|
|
484
|
+
},
|
|
485
|
+
// Explicit user cancel — aborts the controller and removes the entry
|
|
486
|
+
abort(key) {
|
|
487
|
+
const entry = streams.get(key);
|
|
488
|
+
if (!entry) return;
|
|
489
|
+
entry.abortController.abort();
|
|
490
|
+
streams.delete(key);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
1404
493
|
|
|
1405
494
|
// src/utils/v2EventProcessor.ts
|
|
1406
495
|
function getEventText(event, field) {
|
|
@@ -1426,7 +515,7 @@ function addThinkingLine(state, header, detail) {
|
|
|
1426
515
|
function appendThinkingText(state, text) {
|
|
1427
516
|
state.formattedThinkingText += text;
|
|
1428
517
|
}
|
|
1429
|
-
function
|
|
518
|
+
function completeLastInProgressStep(steps) {
|
|
1430
519
|
for (let i = steps.length - 1; i >= 0; i--) {
|
|
1431
520
|
if (steps[i].status === "in_progress") {
|
|
1432
521
|
steps[i].status = "completed";
|
|
@@ -1523,7 +612,19 @@ function processStreamEventV2(event, state) {
|
|
|
1523
612
|
break;
|
|
1524
613
|
}
|
|
1525
614
|
case "ORCHESTRATOR_COMPLETED": {
|
|
1526
|
-
|
|
615
|
+
const workingDetail = workingPhaseDetailForDisplay(message);
|
|
616
|
+
if (workingDetail) {
|
|
617
|
+
addThinkingLine(state, "**Working**", workingDetail);
|
|
618
|
+
} else {
|
|
619
|
+
addThinkingHeader(state, "**Working**");
|
|
620
|
+
}
|
|
621
|
+
state.steps.push({
|
|
622
|
+
id: `step-${state.stepCounter++}`,
|
|
623
|
+
eventType: "WORKING",
|
|
624
|
+
message: workingDetail,
|
|
625
|
+
status: "completed",
|
|
626
|
+
timestamp: Date.now()
|
|
627
|
+
});
|
|
1527
628
|
const step = state.steps.find((s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress");
|
|
1528
629
|
if (step) {
|
|
1529
630
|
step.status = "completed";
|
|
@@ -1628,7 +729,7 @@ function processStreamEventV2(event, state) {
|
|
|
1628
729
|
break;
|
|
1629
730
|
}
|
|
1630
731
|
case "USER_ACTION_REQUIRED": {
|
|
1631
|
-
|
|
732
|
+
completeLastInProgressStep(state.steps);
|
|
1632
733
|
if (event.userActionRequest) {
|
|
1633
734
|
state.userActionRequest = {
|
|
1634
735
|
userActionId: event.userActionRequest.userActionId,
|
|
@@ -1658,7 +759,7 @@ function processStreamEventV2(event, state) {
|
|
|
1658
759
|
}
|
|
1659
760
|
case "USER_ACTION_SUCCESS": {
|
|
1660
761
|
appendThinkingText(state, "\n\u2713 " + (event.message || "Verification successful"));
|
|
1661
|
-
|
|
762
|
+
completeLastInProgressStep(state.steps);
|
|
1662
763
|
state.userActionRequest = void 0;
|
|
1663
764
|
state.userActionPending = false;
|
|
1664
765
|
state.userActionResult = "approved";
|
|
@@ -1675,7 +776,7 @@ function processStreamEventV2(event, state) {
|
|
|
1675
776
|
break;
|
|
1676
777
|
}
|
|
1677
778
|
case "USER_ACTION_INVALID": {
|
|
1678
|
-
|
|
779
|
+
completeLastInProgressStep(state.steps);
|
|
1679
780
|
const errorStepId = `step-${state.stepCounter++}`;
|
|
1680
781
|
state.steps.push({
|
|
1681
782
|
id: errorStepId,
|
|
@@ -1699,7 +800,7 @@ function processStreamEventV2(event, state) {
|
|
|
1699
800
|
}
|
|
1700
801
|
case "USER_ACTION_REJECTED": {
|
|
1701
802
|
appendThinkingText(state, "\n\u2717 " + (event.message || "Verification rejected"));
|
|
1702
|
-
|
|
803
|
+
completeLastInProgressStep(state.steps);
|
|
1703
804
|
state.userActionRequest = void 0;
|
|
1704
805
|
state.userActionPending = false;
|
|
1705
806
|
state.userActionResult = "rejected";
|
|
@@ -1717,7 +818,7 @@ function processStreamEventV2(event, state) {
|
|
|
1717
818
|
}
|
|
1718
819
|
case "USER_ACTION_EXPIRED": {
|
|
1719
820
|
appendThinkingText(state, "\n\u2717 " + (event.message || "Verification expired"));
|
|
1720
|
-
|
|
821
|
+
completeLastInProgressStep(state.steps);
|
|
1721
822
|
state.userActionRequest = void 0;
|
|
1722
823
|
state.userActionPending = false;
|
|
1723
824
|
const stepId = `step-${state.stepCounter++}`;
|
|
@@ -1747,7 +848,7 @@ function processStreamEventV2(event, state) {
|
|
|
1747
848
|
}
|
|
1748
849
|
case "USER_ACTION_FAILED": {
|
|
1749
850
|
appendThinkingText(state, "\n\u2717 " + (event.message || "Verification failed"));
|
|
1750
|
-
|
|
851
|
+
completeLastInProgressStep(state.steps);
|
|
1751
852
|
state.userActionRequest = void 0;
|
|
1752
853
|
state.userActionPending = false;
|
|
1753
854
|
const stepId = `step-${state.stepCounter++}`;
|
|
@@ -1785,8 +886,58 @@ function processStreamEventV2(event, state) {
|
|
|
1785
886
|
return state;
|
|
1786
887
|
}
|
|
1787
888
|
|
|
889
|
+
// src/utils/ragImageResolver.ts
|
|
890
|
+
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
891
|
+
function hasRagImages(content) {
|
|
892
|
+
return RAG_IMAGE_REGEX.test(content);
|
|
893
|
+
}
|
|
894
|
+
async function waitForNextPaint(signal) {
|
|
895
|
+
if (signal?.aborted) return;
|
|
896
|
+
await new Promise((resolve) => {
|
|
897
|
+
let isSettled = false;
|
|
898
|
+
const finish = () => {
|
|
899
|
+
if (isSettled) return;
|
|
900
|
+
isSettled = true;
|
|
901
|
+
signal?.removeEventListener("abort", finish);
|
|
902
|
+
resolve();
|
|
903
|
+
};
|
|
904
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
905
|
+
if (typeof requestAnimationFrame === "function") {
|
|
906
|
+
requestAnimationFrame(() => {
|
|
907
|
+
setTimeout(finish, 0);
|
|
908
|
+
});
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
setTimeout(finish, 0);
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
async function resolveRagImageUrls(config, content, signal) {
|
|
915
|
+
const url = buildResolveImagesUrl(config);
|
|
916
|
+
const baseHeaders = buildRequestHeaders(config);
|
|
917
|
+
const headers = { "Content-Type": "application/json", ...baseHeaders };
|
|
918
|
+
const response = await fetch(url, {
|
|
919
|
+
method: "POST",
|
|
920
|
+
headers,
|
|
921
|
+
body: JSON.stringify({ input: content }),
|
|
922
|
+
signal
|
|
923
|
+
});
|
|
924
|
+
if (!response.ok) {
|
|
925
|
+
const errorText = await response.text();
|
|
926
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
927
|
+
}
|
|
928
|
+
const text = await response.text();
|
|
929
|
+
try {
|
|
930
|
+
const parsed = JSON.parse(text);
|
|
931
|
+
if (typeof parsed === "string") return parsed;
|
|
932
|
+
if (typeof parsed.output === "string") return parsed.output;
|
|
933
|
+
if (typeof parsed.result === "string") return parsed.result;
|
|
934
|
+
} catch {
|
|
935
|
+
}
|
|
936
|
+
return text;
|
|
937
|
+
}
|
|
938
|
+
|
|
1788
939
|
// src/hooks/useStreamManagerV2.ts
|
|
1789
|
-
var
|
|
940
|
+
var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
|
|
1790
941
|
function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForResponse) {
|
|
1791
942
|
const abortControllerRef = react.useRef(null);
|
|
1792
943
|
const configRef = react.useRef(config);
|
|
@@ -1834,8 +985,8 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1834
985
|
const currentMessage = activeStep?.message || lastInProgressStep?.message || getEventMessage(event);
|
|
1835
986
|
if (state.hasError) {
|
|
1836
987
|
updateMessage({
|
|
1837
|
-
streamingContent:
|
|
1838
|
-
content:
|
|
988
|
+
streamingContent: FRIENDLY_ERROR_MESSAGE,
|
|
989
|
+
content: FRIENDLY_ERROR_MESSAGE,
|
|
1839
990
|
streamProgress: "error",
|
|
1840
991
|
isError: true,
|
|
1841
992
|
errorDetails: state.errorMessage,
|
|
@@ -1885,7 +1036,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1885
1036
|
isError: !isAborted,
|
|
1886
1037
|
isCancelled: isAborted,
|
|
1887
1038
|
errorDetails: isAborted ? void 0 : error.message,
|
|
1888
|
-
content: isAborted ? state.finalResponse || "" : state.finalResponse ||
|
|
1039
|
+
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1889
1040
|
currentMessage: isAborted ? "Thinking..." : void 0,
|
|
1890
1041
|
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1891
1042
|
steps: [...state.steps].map((step) => {
|
|
@@ -1917,7 +1068,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1917
1068
|
id: streamingId,
|
|
1918
1069
|
sessionId: state.sessionId || sessionId,
|
|
1919
1070
|
role: "assistant",
|
|
1920
|
-
content: state.hasError ?
|
|
1071
|
+
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.finalResponse || "",
|
|
1921
1072
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1922
1073
|
isStreaming: false,
|
|
1923
1074
|
streamProgress: state.hasError ? "error" : "completed",
|
|
@@ -1988,7 +1139,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1988
1139
|
isError: !isAborted,
|
|
1989
1140
|
isCancelled: isAborted,
|
|
1990
1141
|
errorDetails: isAborted ? void 0 : error.message,
|
|
1991
|
-
content: isAborted ? state.finalResponse || "" : state.finalResponse ||
|
|
1142
|
+
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1992
1143
|
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1993
1144
|
steps: [...state.steps].map((step) => {
|
|
1994
1145
|
if (step.status === "in_progress" && isAborted) {
|
|
@@ -2015,45 +1166,199 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
2015
1166
|
};
|
|
2016
1167
|
}
|
|
2017
1168
|
|
|
1169
|
+
// src/utils/workflowUsersClient.ts
|
|
1170
|
+
var DEFAULT_SESSION_PAGE_SIZE = 20;
|
|
1171
|
+
var DEFAULT_CONVERSATION_PAGE_SIZE = 50;
|
|
1172
|
+
function getStageParamName2(config) {
|
|
1173
|
+
return config.api.stageQueryParam ?? "stage";
|
|
1174
|
+
}
|
|
1175
|
+
function requireOwnerId(config) {
|
|
1176
|
+
const ownerId = config.session?.owner?.id;
|
|
1177
|
+
if (!ownerId) {
|
|
1178
|
+
throw new Error(
|
|
1179
|
+
"workflowUsersClient: session.owner.id is required to call this endpoint."
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
return ownerId;
|
|
1183
|
+
}
|
|
1184
|
+
function requireWorkflowName(config) {
|
|
1185
|
+
const workflowName = config.workflow.name;
|
|
1186
|
+
if (!workflowName) {
|
|
1187
|
+
throw new Error(
|
|
1188
|
+
"workflowUsersClient: workflow.name is required to call this endpoint."
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
return workflowName;
|
|
1192
|
+
}
|
|
1193
|
+
function requireWorkflowId(config) {
|
|
1194
|
+
const workflowId = config.workflow.id;
|
|
1195
|
+
if (!workflowId) {
|
|
1196
|
+
throw new Error(
|
|
1197
|
+
"workflowUsersClient: workflow.id is required to call this endpoint."
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
return workflowId;
|
|
1201
|
+
}
|
|
1202
|
+
function isPlaygroundYaakAuth(config) {
|
|
1203
|
+
if (config.api.authToken) return false;
|
|
1204
|
+
const headers = config.api.headers;
|
|
1205
|
+
if (!headers) return false;
|
|
1206
|
+
for (const key of Object.keys(headers)) {
|
|
1207
|
+
const lower = key.toLowerCase();
|
|
1208
|
+
if (lower === "yaak-api-key" || lower === "x-yaak-api-key") return true;
|
|
1209
|
+
}
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
async function fetchJson(url, headers, signal) {
|
|
1213
|
+
const response = await fetch(url, {
|
|
1214
|
+
method: "GET",
|
|
1215
|
+
headers,
|
|
1216
|
+
signal
|
|
1217
|
+
});
|
|
1218
|
+
if (!response.ok) {
|
|
1219
|
+
const errorText = await response.text();
|
|
1220
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1221
|
+
}
|
|
1222
|
+
return await response.json();
|
|
1223
|
+
}
|
|
1224
|
+
async function listSessions(config, opts = {}) {
|
|
1225
|
+
const ownerId = requireOwnerId(config);
|
|
1226
|
+
const useYaakEndpoint = isPlaygroundYaakAuth(config);
|
|
1227
|
+
const params = new URLSearchParams({
|
|
1228
|
+
page: String(opts.page ?? 0),
|
|
1229
|
+
size: String(opts.size ?? DEFAULT_SESSION_PAGE_SIZE)
|
|
1230
|
+
});
|
|
1231
|
+
if (useYaakEndpoint) {
|
|
1232
|
+
params.set("workflowUserId", ownerId);
|
|
1233
|
+
params.set("workflowName", requireWorkflowName(config));
|
|
1234
|
+
} else {
|
|
1235
|
+
params.set("workflowId", requireWorkflowId(config));
|
|
1236
|
+
}
|
|
1237
|
+
if (config.workflow.stage) {
|
|
1238
|
+
params.set(getStageParamName2(config), config.workflow.stage);
|
|
1239
|
+
}
|
|
1240
|
+
const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/sessions?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
|
|
1241
|
+
ownerId
|
|
1242
|
+
)}/sessions?${params.toString()}`;
|
|
1243
|
+
return fetchJson(
|
|
1244
|
+
url,
|
|
1245
|
+
buildRequestHeaders(config),
|
|
1246
|
+
opts.signal
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
async function listConversations(config, opts) {
|
|
1250
|
+
const ownerId = requireOwnerId(config);
|
|
1251
|
+
const useYaakEndpoint = isPlaygroundYaakAuth(config);
|
|
1252
|
+
const params = new URLSearchParams({
|
|
1253
|
+
sessionId: opts.sessionId,
|
|
1254
|
+
page: String(opts.page ?? 0),
|
|
1255
|
+
size: String(opts.size ?? DEFAULT_CONVERSATION_PAGE_SIZE)
|
|
1256
|
+
});
|
|
1257
|
+
if (useYaakEndpoint) {
|
|
1258
|
+
params.set("workflowUserId", ownerId);
|
|
1259
|
+
params.set("workflowName", requireWorkflowName(config));
|
|
1260
|
+
}
|
|
1261
|
+
if (config.workflow.stage) {
|
|
1262
|
+
params.set(getStageParamName2(config), config.workflow.stage);
|
|
1263
|
+
}
|
|
1264
|
+
const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/conversations?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
|
|
1265
|
+
ownerId
|
|
1266
|
+
)}/conversations?${params.toString()}`;
|
|
1267
|
+
return fetchJson(
|
|
1268
|
+
url,
|
|
1269
|
+
buildRequestHeaders(config),
|
|
1270
|
+
opts.signal
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
2018
1274
|
// src/hooks/useChatV2.ts
|
|
1275
|
+
function conversationEntryToMessages(entry, sessionId) {
|
|
1276
|
+
return [
|
|
1277
|
+
{
|
|
1278
|
+
id: `history-user-${entry.executionId}`,
|
|
1279
|
+
sessionId,
|
|
1280
|
+
role: "user",
|
|
1281
|
+
content: entry.query,
|
|
1282
|
+
timestamp: entry.createdAt,
|
|
1283
|
+
isHistorical: true
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
id: `history-assistant-${entry.executionId}`,
|
|
1287
|
+
sessionId,
|
|
1288
|
+
role: "assistant",
|
|
1289
|
+
content: entry.response,
|
|
1290
|
+
timestamp: entry.createdAt,
|
|
1291
|
+
executionId: entry.executionId,
|
|
1292
|
+
isHistorical: true
|
|
1293
|
+
}
|
|
1294
|
+
];
|
|
1295
|
+
}
|
|
1296
|
+
function streamKeyFor(scopeKey, sessionId) {
|
|
1297
|
+
return `${scopeKey}|sid:${sessionId ?? ""}`;
|
|
1298
|
+
}
|
|
2019
1299
|
function useChatV2(config, callbacks = {}) {
|
|
1300
|
+
const scopeKey = react.useMemo(() => buildScopeKey(config), [
|
|
1301
|
+
config.session?.userId,
|
|
1302
|
+
config.workflow.id,
|
|
1303
|
+
config.workflow.version,
|
|
1304
|
+
config.workflow.stage
|
|
1305
|
+
]);
|
|
1306
|
+
const initialSessionId = chatStore.get(scopeKey).find((m) => m.sessionId)?.sessionId ?? config.session?.initialId ?? void 0;
|
|
2020
1307
|
const [messages, setMessages] = react.useState(() => {
|
|
2021
|
-
|
|
2022
|
-
|
|
1308
|
+
const stored = chatStore.get(scopeKey);
|
|
1309
|
+
if (stored.length > 0) return stored;
|
|
1310
|
+
return config.session?.initialMessages ?? [];
|
|
2023
1311
|
});
|
|
2024
1312
|
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
|
|
2025
|
-
const
|
|
2026
|
-
|
|
2027
|
-
);
|
|
2028
|
-
const
|
|
1313
|
+
const [loadingSessionId, setLoadingSessionId] = react.useState(void 0);
|
|
1314
|
+
const [currentSessionId, setCurrentSessionId] = react.useState(initialSessionId);
|
|
1315
|
+
const sessionIdRef = react.useRef(initialSessionId);
|
|
1316
|
+
const prevScopeKeyRef = react.useRef(scopeKey);
|
|
1317
|
+
const activeStreamSessionRef = react.useRef(void 0);
|
|
2029
1318
|
const callbacksRef = react.useRef(callbacks);
|
|
2030
1319
|
callbacksRef.current = callbacks;
|
|
2031
1320
|
const configRef = react.useRef(config);
|
|
2032
1321
|
configRef.current = config;
|
|
1322
|
+
const scopeKeyRef = react.useRef(scopeKey);
|
|
1323
|
+
scopeKeyRef.current = scopeKey;
|
|
2033
1324
|
const messagesRef = react.useRef(messages);
|
|
2034
1325
|
messagesRef.current = messages;
|
|
2035
1326
|
const storeAwareSetMessages = react.useCallback(
|
|
2036
1327
|
(updater) => {
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
|
|
1328
|
+
const scope = scopeKeyRef.current;
|
|
1329
|
+
const streamSid = activeStreamSessionRef.current;
|
|
1330
|
+
if (streamSid !== void 0) {
|
|
1331
|
+
const streamKey = streamKeyFor(scope, streamSid);
|
|
1332
|
+
if (activeStreamStore.has(streamKey)) {
|
|
1333
|
+
activeStreamStore.applyMessages(
|
|
1334
|
+
streamKey,
|
|
1335
|
+
updater
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
if (sessionIdRef.current === streamSid) {
|
|
1339
|
+
setMessages(updater);
|
|
1340
|
+
}
|
|
1341
|
+
return;
|
|
2040
1342
|
}
|
|
2041
1343
|
setMessages(updater);
|
|
2042
1344
|
},
|
|
2043
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2044
1345
|
[]
|
|
2045
1346
|
);
|
|
2046
|
-
const storeAwareSetIsWaiting = react.useCallback(
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
1347
|
+
const storeAwareSetIsWaiting = react.useCallback((waiting) => {
|
|
1348
|
+
const scope = scopeKeyRef.current;
|
|
1349
|
+
const streamSid = activeStreamSessionRef.current;
|
|
1350
|
+
if (streamSid !== void 0) {
|
|
1351
|
+
const streamKey = streamKeyFor(scope, streamSid);
|
|
1352
|
+
if (activeStreamStore.has(streamKey)) {
|
|
1353
|
+
activeStreamStore.setWaiting(streamKey, waiting);
|
|
2051
1354
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
1355
|
+
if (sessionIdRef.current === streamSid) {
|
|
1356
|
+
setIsWaitingForResponse(waiting);
|
|
1357
|
+
}
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
setIsWaitingForResponse(waiting);
|
|
1361
|
+
}, []);
|
|
2057
1362
|
const [userActionState, setUserActionState] = react.useState({
|
|
2058
1363
|
request: null,
|
|
2059
1364
|
result: null,
|
|
@@ -2061,38 +1366,43 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2061
1366
|
});
|
|
2062
1367
|
const userActionStateRef = react.useRef(userActionState);
|
|
2063
1368
|
userActionStateRef.current = userActionState;
|
|
2064
|
-
const wrappedCallbacks = react.useMemo(
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
1369
|
+
const wrappedCallbacks = react.useMemo(
|
|
1370
|
+
() => ({
|
|
1371
|
+
...callbacksRef.current,
|
|
1372
|
+
onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
|
|
1373
|
+
onStreamStart: () => callbacksRef.current.onStreamStart?.(),
|
|
1374
|
+
onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
|
|
1375
|
+
onError: (error) => callbacksRef.current.onError?.(error),
|
|
1376
|
+
onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
|
|
1377
|
+
onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
|
|
1378
|
+
onUserActionRequired: (request) => {
|
|
1379
|
+
setUserActionState((prev) => ({ ...prev, request, result: null }));
|
|
1380
|
+
callbacksRef.current.onUserActionRequired?.(request);
|
|
1381
|
+
},
|
|
1382
|
+
onUserActionEvent: (eventType, message) => {
|
|
1383
|
+
switch (eventType) {
|
|
1384
|
+
case "USER_ACTION_SUCCESS":
|
|
1385
|
+
setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
|
|
1386
|
+
break;
|
|
1387
|
+
case "USER_ACTION_REJECTED":
|
|
1388
|
+
setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
|
|
1389
|
+
break;
|
|
1390
|
+
case "USER_ACTION_EXPIRED":
|
|
1391
|
+
case "USER_ACTION_FAILED":
|
|
1392
|
+
setUserActionState((prev) => ({ ...prev, request: null }));
|
|
1393
|
+
break;
|
|
1394
|
+
case "USER_ACTION_INVALID":
|
|
1395
|
+
setUserActionState((prev) => ({
|
|
1396
|
+
...prev,
|
|
1397
|
+
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
1398
|
+
}));
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
callbacksRef.current.onUserActionEvent?.(eventType, message);
|
|
2091
1402
|
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
}), []);
|
|
1403
|
+
}),
|
|
1404
|
+
[]
|
|
1405
|
+
);
|
|
2096
1406
|
const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
|
|
2097
1407
|
config,
|
|
2098
1408
|
wrappedCallbacks,
|
|
@@ -2102,8 +1412,10 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2102
1412
|
const sendMessage = react.useCallback(
|
|
2103
1413
|
async (userMessage) => {
|
|
2104
1414
|
if (!userMessage.trim()) return;
|
|
2105
|
-
|
|
1415
|
+
const autoGen = configRef.current.session?.autoGenerateId;
|
|
1416
|
+
if (!sessionIdRef.current && autoGen !== false) {
|
|
2106
1417
|
sessionIdRef.current = generateId();
|
|
1418
|
+
setCurrentSessionId(sessionIdRef.current);
|
|
2107
1419
|
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
2108
1420
|
}
|
|
2109
1421
|
const userMessageId = `user-${Date.now()}`;
|
|
@@ -2135,38 +1447,40 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2135
1447
|
};
|
|
2136
1448
|
setMessages((prev) => [...prev, streamingMsg]);
|
|
2137
1449
|
const abortController = new AbortController();
|
|
2138
|
-
const
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
1450
|
+
const scope = scopeKeyRef.current;
|
|
1451
|
+
const streamSessionId = sessionIdRef.current;
|
|
1452
|
+
const streamKey = streamKeyFor(scope, streamSessionId);
|
|
1453
|
+
activeStreamSessionRef.current = streamSessionId;
|
|
1454
|
+
const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
|
|
1455
|
+
activeStreamStore.start(streamKey, abortController, initialMessages);
|
|
2143
1456
|
const newSessionId = await startStream(
|
|
2144
1457
|
userMessage,
|
|
2145
1458
|
streamingId,
|
|
2146
|
-
|
|
1459
|
+
streamSessionId,
|
|
2147
1460
|
abortController
|
|
2148
1461
|
);
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
|
|
1462
|
+
activeStreamStore.complete(streamKey);
|
|
1463
|
+
activeStreamSessionRef.current = void 0;
|
|
1464
|
+
if (!abortController.signal.aborted && newSessionId && newSessionId !== streamSessionId && sessionIdRef.current === streamSessionId) {
|
|
2153
1465
|
sessionIdRef.current = newSessionId;
|
|
1466
|
+
setCurrentSessionId(newSessionId);
|
|
2154
1467
|
}
|
|
2155
1468
|
},
|
|
2156
1469
|
[startStream]
|
|
2157
1470
|
);
|
|
2158
1471
|
const clearMessages = react.useCallback(() => {
|
|
2159
|
-
|
|
2160
|
-
chatStore.delete(configRef.current.userId);
|
|
2161
|
-
}
|
|
1472
|
+
chatStore.delete(scopeKeyRef.current);
|
|
2162
1473
|
setMessages([]);
|
|
2163
1474
|
}, []);
|
|
2164
1475
|
const prependMessages = react.useCallback((msgs) => {
|
|
2165
1476
|
setMessages((prev) => [...msgs, ...prev]);
|
|
2166
1477
|
}, []);
|
|
2167
1478
|
const cancelStream = react.useCallback(() => {
|
|
2168
|
-
|
|
2169
|
-
|
|
1479
|
+
const scope = scopeKeyRef.current;
|
|
1480
|
+
const viewSid = sessionIdRef.current;
|
|
1481
|
+
const viewStreamKey = streamKeyFor(scope, viewSid);
|
|
1482
|
+
if (activeStreamStore.has(viewStreamKey)) {
|
|
1483
|
+
activeStreamStore.abort(viewStreamKey);
|
|
2170
1484
|
}
|
|
2171
1485
|
cancelStreamManager();
|
|
2172
1486
|
setIsWaitingForResponse(false);
|
|
@@ -2176,10 +1490,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2176
1490
|
if (msg.isStreaming) {
|
|
2177
1491
|
return {
|
|
2178
1492
|
...msg,
|
|
2179
|
-
...createCancelledMessageUpdate(
|
|
2180
|
-
msg.steps || [],
|
|
2181
|
-
msg.currentMessage
|
|
2182
|
-
)
|
|
1493
|
+
...createCancelledMessageUpdate(msg.steps || [], msg.currentMessage)
|
|
2183
1494
|
};
|
|
2184
1495
|
}
|
|
2185
1496
|
return msg;
|
|
@@ -2187,39 +1498,37 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2187
1498
|
);
|
|
2188
1499
|
}, [cancelStreamManager]);
|
|
2189
1500
|
const resetSession = react.useCallback(() => {
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
1501
|
+
const scope = scopeKeyRef.current;
|
|
1502
|
+
const viewSid = sessionIdRef.current;
|
|
1503
|
+
const viewStreamKey = streamKeyFor(scope, viewSid);
|
|
1504
|
+
if (activeStreamStore.has(viewStreamKey)) {
|
|
1505
|
+
activeStreamStore.abort(viewStreamKey);
|
|
2193
1506
|
}
|
|
1507
|
+
chatStore.delete(scope);
|
|
2194
1508
|
setMessages([]);
|
|
2195
1509
|
sessionIdRef.current = void 0;
|
|
1510
|
+
setCurrentSessionId(void 0);
|
|
1511
|
+
activeStreamSessionRef.current = void 0;
|
|
2196
1512
|
abortControllerRef.current?.abort();
|
|
2197
1513
|
setIsWaitingForResponse(false);
|
|
2198
1514
|
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
2199
1515
|
}, []);
|
|
2200
|
-
const getSessionId = react.useCallback(() =>
|
|
2201
|
-
|
|
1516
|
+
const getSessionId = react.useCallback(() => sessionIdRef.current, []);
|
|
1517
|
+
const getMessages = react.useCallback(() => messages, [messages]);
|
|
1518
|
+
const approveUserAction = react.useCallback(async (otp) => {
|
|
1519
|
+
const request = userActionStateRef.current.request;
|
|
1520
|
+
if (!request) return;
|
|
1521
|
+
try {
|
|
1522
|
+
await submitUserAction(configRef.current, request.userActionId, { otp });
|
|
1523
|
+
} catch (error) {
|
|
1524
|
+
setUserActionState((prev) => ({
|
|
1525
|
+
...prev,
|
|
1526
|
+
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
1527
|
+
}));
|
|
1528
|
+
callbacksRef.current.onError?.(error);
|
|
1529
|
+
throw error;
|
|
1530
|
+
}
|
|
2202
1531
|
}, []);
|
|
2203
|
-
const getMessages = react.useCallback(() => {
|
|
2204
|
-
return messages;
|
|
2205
|
-
}, [messages]);
|
|
2206
|
-
const approveUserAction = react.useCallback(
|
|
2207
|
-
async (otp) => {
|
|
2208
|
-
const request = userActionStateRef.current.request;
|
|
2209
|
-
if (!request) return;
|
|
2210
|
-
try {
|
|
2211
|
-
await submitUserAction(configRef.current, request.userActionId, { otp });
|
|
2212
|
-
} catch (error) {
|
|
2213
|
-
setUserActionState((prev) => ({
|
|
2214
|
-
...prev,
|
|
2215
|
-
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
2216
|
-
}));
|
|
2217
|
-
callbacksRef.current.onError?.(error);
|
|
2218
|
-
throw error;
|
|
2219
|
-
}
|
|
2220
|
-
},
|
|
2221
|
-
[]
|
|
2222
|
-
);
|
|
2223
1532
|
const rejectUserAction = react.useCallback(async () => {
|
|
2224
1533
|
const request = userActionStateRef.current.request;
|
|
2225
1534
|
if (!request) return;
|
|
@@ -2253,43 +1562,91 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2253
1562
|
throw error;
|
|
2254
1563
|
}
|
|
2255
1564
|
}, []);
|
|
1565
|
+
const inFlightLoadRef = react.useRef(null);
|
|
1566
|
+
const loadingSessionIdRef = react.useRef(void 0);
|
|
1567
|
+
loadingSessionIdRef.current = loadingSessionId;
|
|
1568
|
+
const loadSession = react.useCallback(async (sessionId) => {
|
|
1569
|
+
const inFlight = inFlightLoadRef.current;
|
|
1570
|
+
if (inFlight && inFlight.sessionId === sessionId) {
|
|
1571
|
+
return inFlight.promise;
|
|
1572
|
+
}
|
|
1573
|
+
if (sessionIdRef.current === sessionId && loadingSessionIdRef.current !== sessionId) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const run = async () => {
|
|
1577
|
+
const scope = scopeKeyRef.current;
|
|
1578
|
+
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1579
|
+
sessionIdRef.current = sessionId;
|
|
1580
|
+
setCurrentSessionId(sessionId);
|
|
1581
|
+
callbacksRef.current.onSessionIdChange?.(sessionId);
|
|
1582
|
+
const streamKey = streamKeyFor(scope, sessionId);
|
|
1583
|
+
const active = activeStreamStore.get(streamKey);
|
|
1584
|
+
if (active) {
|
|
1585
|
+
setMessages(active.messages);
|
|
1586
|
+
setIsWaitingForResponse(active.isWaiting);
|
|
1587
|
+
setLoadingSessionId(void 0);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
setIsWaitingForResponse(false);
|
|
1591
|
+
setMessages([]);
|
|
1592
|
+
chatStore.delete(scope);
|
|
1593
|
+
setLoadingSessionId(sessionId);
|
|
1594
|
+
try {
|
|
1595
|
+
const response = await listConversations(configRef.current, { sessionId });
|
|
1596
|
+
const entries = response.data ?? [];
|
|
1597
|
+
const historical = entries.flatMap((entry) => conversationEntryToMessages(entry, sessionId));
|
|
1598
|
+
if (sessionIdRef.current === sessionId) {
|
|
1599
|
+
setMessages(historical);
|
|
1600
|
+
}
|
|
1601
|
+
if (historical.length > 0) {
|
|
1602
|
+
chatStore.set(scope, historical);
|
|
1603
|
+
}
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
callbacksRef.current.onError?.(error);
|
|
1606
|
+
throw error;
|
|
1607
|
+
} finally {
|
|
1608
|
+
setLoadingSessionId((current) => current === sessionId ? void 0 : current);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
const promise = run().finally(() => {
|
|
1612
|
+
if (inFlightLoadRef.current?.sessionId === sessionId) {
|
|
1613
|
+
inFlightLoadRef.current = null;
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
inFlightLoadRef.current = { sessionId, promise };
|
|
1617
|
+
return promise;
|
|
1618
|
+
}, []);
|
|
2256
1619
|
react.useEffect(() => {
|
|
2257
|
-
const
|
|
2258
|
-
|
|
2259
|
-
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1620
|
+
const key = streamKeyFor(scopeKey, currentSessionId);
|
|
1621
|
+
const unsubscribe = activeStreamStore.subscribe(key, (msgs, isWaiting) => {
|
|
2260
1622
|
setMessages(msgs);
|
|
2261
1623
|
setIsWaitingForResponse(isWaiting);
|
|
2262
1624
|
});
|
|
2263
|
-
const active = activeStreamStore.get(
|
|
1625
|
+
const active = activeStreamStore.get(key);
|
|
2264
1626
|
if (active) {
|
|
2265
1627
|
setMessages(active.messages);
|
|
2266
1628
|
setIsWaitingForResponse(active.isWaiting);
|
|
2267
1629
|
}
|
|
2268
1630
|
return unsubscribe;
|
|
2269
|
-
}, []);
|
|
1631
|
+
}, [scopeKey, currentSessionId]);
|
|
2270
1632
|
react.useEffect(() => {
|
|
2271
|
-
if (!config.userId) return;
|
|
2272
1633
|
const toSave = messages.filter((m) => !m.isStreaming);
|
|
2273
1634
|
if (toSave.length > 0) {
|
|
2274
|
-
chatStore.set(
|
|
1635
|
+
chatStore.set(scopeKey, toSave);
|
|
2275
1636
|
}
|
|
2276
|
-
}, [messages,
|
|
1637
|
+
}, [messages, scopeKey]);
|
|
2277
1638
|
react.useEffect(() => {
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
if (
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
setMessages(stored);
|
|
2290
|
-
sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
|
|
2291
|
-
}
|
|
2292
|
-
}, [config.userId]);
|
|
1639
|
+
const prevKey = prevScopeKeyRef.current;
|
|
1640
|
+
prevScopeKeyRef.current = scopeKey;
|
|
1641
|
+
if (prevKey === scopeKey) return;
|
|
1642
|
+
const stored = chatStore.get(scopeKey);
|
|
1643
|
+
setMessages(stored);
|
|
1644
|
+
const restoredSessionId = stored.find((m) => m.sessionId)?.sessionId ?? configRef.current.session?.initialId ?? void 0;
|
|
1645
|
+
sessionIdRef.current = restoredSessionId;
|
|
1646
|
+
setCurrentSessionId(restoredSessionId);
|
|
1647
|
+
setIsWaitingForResponse(false);
|
|
1648
|
+
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1649
|
+
}, [scopeKey]);
|
|
2293
1650
|
return {
|
|
2294
1651
|
messages,
|
|
2295
1652
|
sendMessage,
|
|
@@ -2300,11 +1657,13 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2300
1657
|
getSessionId,
|
|
2301
1658
|
getMessages,
|
|
2302
1659
|
isWaitingForResponse,
|
|
2303
|
-
sessionId:
|
|
1660
|
+
sessionId: currentSessionId,
|
|
2304
1661
|
userActionState,
|
|
2305
1662
|
approveUserAction,
|
|
2306
1663
|
rejectUserAction,
|
|
2307
|
-
resendOtp
|
|
1664
|
+
resendOtp,
|
|
1665
|
+
loadSession,
|
|
1666
|
+
loadingSessionId
|
|
2308
1667
|
};
|
|
2309
1668
|
}
|
|
2310
1669
|
function useVoice() {
|
|
@@ -2424,15 +1783,18 @@ function useVoice() {
|
|
|
2424
1783
|
}
|
|
2425
1784
|
|
|
2426
1785
|
exports.buildFormattedThinking = buildFormattedThinking;
|
|
1786
|
+
exports.buildScopeKey = buildScopeKey;
|
|
2427
1787
|
exports.cancelUserAction = cancelUserAction;
|
|
2428
1788
|
exports.createInitialV2State = createInitialV2State;
|
|
2429
1789
|
exports.generateId = generateId;
|
|
1790
|
+
exports.listConversations = listConversations;
|
|
1791
|
+
exports.listSessions = listSessions;
|
|
2430
1792
|
exports.processStreamEventV2 = processStreamEventV2;
|
|
2431
1793
|
exports.resendUserAction = resendUserAction;
|
|
2432
1794
|
exports.streamWorkflowEvents = streamWorkflowEvents;
|
|
2433
1795
|
exports.submitUserAction = submitUserAction;
|
|
2434
|
-
exports.useChat = useChat;
|
|
2435
1796
|
exports.useChatV2 = useChatV2;
|
|
2436
1797
|
exports.useVoice = useVoice;
|
|
1798
|
+
exports.workingPhaseDetailForDisplay = workingPhaseDetailForDisplay;
|
|
2437
1799
|
//# sourceMappingURL=index.native.js.map
|
|
2438
1800
|
//# sourceMappingURL=index.native.js.map
|