@paymanai/payman-typescript-ask-sdk 1.2.11 → 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 +136 -108
- package/dist/index.d.ts +136 -108
- package/dist/index.js +529 -1194
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +528 -1195
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +531 -1196
- 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() {
|
|
@@ -224,290 +224,8 @@ function extractResponseContent(response) {
|
|
|
224
224
|
}
|
|
225
225
|
return "";
|
|
226
226
|
}
|
|
227
|
-
function completeLastInProgressStep(steps) {
|
|
228
|
-
for (let i = steps.length - 1; i >= 0; i--) {
|
|
229
|
-
if (steps[i].status === "in_progress") {
|
|
230
|
-
steps[i].status = "completed";
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
function processStreamEvent(event, state) {
|
|
236
|
-
const eventType = event.eventType;
|
|
237
|
-
if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
238
|
-
return state;
|
|
239
|
-
}
|
|
240
|
-
const message = getEventMessage(event);
|
|
241
|
-
if (eventType !== "INTENT_THINKING" && eventType !== "INTENT_THINKING_CONT") {
|
|
242
|
-
if (state.currentThinkingStepId) {
|
|
243
|
-
const thinkingStep = state.steps.find((s) => s.id === state.currentThinkingStepId);
|
|
244
|
-
if (thinkingStep) {
|
|
245
|
-
thinkingStep.isThinking = false;
|
|
246
|
-
}
|
|
247
|
-
state.currentThinkingStepId = void 0;
|
|
248
|
-
}
|
|
249
|
-
state.activeThinkingText = void 0;
|
|
250
|
-
}
|
|
251
|
-
if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
|
|
252
|
-
let content = extractResponseContent(event.response);
|
|
253
|
-
const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
|
|
254
|
-
if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
|
|
255
|
-
content = trace.workflowMsg;
|
|
256
|
-
}
|
|
257
|
-
if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
|
|
258
|
-
const agg = trace.aggregator;
|
|
259
|
-
if (typeof agg.response === "string") content = agg.response;
|
|
260
|
-
else content = extractResponseContent(agg.response);
|
|
261
|
-
}
|
|
262
|
-
if (content) {
|
|
263
|
-
state.accumulatedContent = content;
|
|
264
|
-
state.finalData = event.response ?? event.trace;
|
|
265
|
-
state.hasError = false;
|
|
266
|
-
state.errorMessage = "";
|
|
267
|
-
} else {
|
|
268
|
-
state.hasError = true;
|
|
269
|
-
state.errorMessage = "WORKFLOW_FAILED";
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
if (eventType === "STARTED" || eventType === "WORKFLOW_STARTED") ; else if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
|
|
273
|
-
state.steps.forEach((step) => {
|
|
274
|
-
if (step.status === "in_progress") {
|
|
275
|
-
step.status = "completed";
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
} else if (eventType === "INTENT_ERROR") {
|
|
279
|
-
state.errorMessage = message || event.errorMessage || "An error occurred";
|
|
280
|
-
const intentStep = state.steps.find(
|
|
281
|
-
(s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
|
|
282
|
-
);
|
|
283
|
-
if (intentStep) {
|
|
284
|
-
intentStep.status = "error";
|
|
285
|
-
}
|
|
286
|
-
} else if (eventType === "ERROR" || eventType === "WORKFLOW_ERROR") {
|
|
287
|
-
state.hasError = true;
|
|
288
|
-
state.errorMessage = message || event.errorMessage || "An error occurred";
|
|
289
|
-
} else if (eventType === "ORCHESTRATOR_COMPLETED") {
|
|
290
|
-
state.inOrchestratorPhase = false;
|
|
291
|
-
const orchestratorStep = state.steps.find(
|
|
292
|
-
(s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress"
|
|
293
|
-
);
|
|
294
|
-
if (orchestratorStep) {
|
|
295
|
-
orchestratorStep.status = "completed";
|
|
296
|
-
if (event.elapsedMs) {
|
|
297
|
-
orchestratorStep.elapsedMs = event.elapsedMs;
|
|
298
|
-
}
|
|
299
|
-
if (orchestratorStep.id === state.currentExecutingStepId) {
|
|
300
|
-
state.currentExecutingStepId = void 0;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
} else if (eventType === "INTENT_COMPLETED") {
|
|
304
|
-
const intentStep = state.steps.find(
|
|
305
|
-
(s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
|
|
306
|
-
);
|
|
307
|
-
if (intentStep) {
|
|
308
|
-
intentStep.status = "completed";
|
|
309
|
-
if (event.elapsedMs) {
|
|
310
|
-
intentStep.elapsedMs = event.elapsedMs;
|
|
311
|
-
}
|
|
312
|
-
if (intentStep.id === state.currentExecutingStepId) {
|
|
313
|
-
state.currentExecutingStepId = void 0;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
} else if (eventType === "AGGREGATOR_COMPLETED") {
|
|
317
|
-
state.inAggregatorPhase = false;
|
|
318
|
-
const aggregatorStep = state.steps.find(
|
|
319
|
-
(s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress"
|
|
320
|
-
);
|
|
321
|
-
if (aggregatorStep) {
|
|
322
|
-
aggregatorStep.status = "completed";
|
|
323
|
-
if (event.elapsedMs) {
|
|
324
|
-
aggregatorStep.elapsedMs = event.elapsedMs;
|
|
325
|
-
}
|
|
326
|
-
if (aggregatorStep.id === state.currentExecutingStepId) {
|
|
327
|
-
state.currentExecutingStepId = void 0;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
} else if (eventType === "ORCHESTRATOR_THINKING" || eventType === "INTENT_STARTED" || eventType === "INTENT_PROGRESS" || eventType === "AGGREGATOR_THINKING") {
|
|
331
|
-
if (eventType === "ORCHESTRATOR_THINKING") {
|
|
332
|
-
state.inOrchestratorPhase = true;
|
|
333
|
-
}
|
|
334
|
-
if (eventType === "AGGREGATOR_THINKING") {
|
|
335
|
-
state.inAggregatorPhase = true;
|
|
336
|
-
}
|
|
337
|
-
if (eventType === "INTENT_PROGRESS") {
|
|
338
|
-
const intentStep = state.steps.find(
|
|
339
|
-
(s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
|
|
340
|
-
);
|
|
341
|
-
if (intentStep) {
|
|
342
|
-
intentStep.message = message;
|
|
343
|
-
state.currentExecutingStepId = intentStep.id;
|
|
344
|
-
} else {
|
|
345
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
346
|
-
state.steps.push({
|
|
347
|
-
id: stepId,
|
|
348
|
-
eventType: "INTENT_STARTED",
|
|
349
|
-
message,
|
|
350
|
-
status: "in_progress",
|
|
351
|
-
timestamp: Date.now(),
|
|
352
|
-
elapsedMs: event.elapsedMs
|
|
353
|
-
});
|
|
354
|
-
state.currentExecutingStepId = stepId;
|
|
355
|
-
}
|
|
356
|
-
} else {
|
|
357
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
358
|
-
state.steps.push({
|
|
359
|
-
id: stepId,
|
|
360
|
-
eventType,
|
|
361
|
-
message,
|
|
362
|
-
status: "in_progress",
|
|
363
|
-
timestamp: Date.now(),
|
|
364
|
-
elapsedMs: event.elapsedMs
|
|
365
|
-
});
|
|
366
|
-
state.currentExecutingStepId = stepId;
|
|
367
|
-
}
|
|
368
|
-
} else if (eventType === "USER_ACTION_REQUIRED") {
|
|
369
|
-
completeLastInProgressStep(state.steps);
|
|
370
|
-
if (event.userActionRequest) {
|
|
371
|
-
state.userActionRequest = {
|
|
372
|
-
userActionId: event.userActionRequest.userActionId,
|
|
373
|
-
userActionType: event.userActionRequest.userActionType,
|
|
374
|
-
message: event.userActionRequest.message,
|
|
375
|
-
requestedSchema: event.userActionRequest.requestedSchema,
|
|
376
|
-
metadata: event.userActionRequest.metadata
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
state.userActionPending = true;
|
|
380
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
381
|
-
state.steps.push({
|
|
382
|
-
id: stepId,
|
|
383
|
-
eventType,
|
|
384
|
-
message,
|
|
385
|
-
status: "in_progress",
|
|
386
|
-
timestamp: Date.now(),
|
|
387
|
-
elapsedMs: event.elapsedMs
|
|
388
|
-
});
|
|
389
|
-
state.currentExecutingStepId = stepId;
|
|
390
|
-
} else if (eventType === "USER_ACTION_SUCCESS") {
|
|
391
|
-
completeLastInProgressStep(state.steps);
|
|
392
|
-
state.userActionRequest = void 0;
|
|
393
|
-
state.userActionPending = false;
|
|
394
|
-
state.userActionResult = "approved";
|
|
395
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
396
|
-
state.steps.push({
|
|
397
|
-
id: stepId,
|
|
398
|
-
eventType,
|
|
399
|
-
message,
|
|
400
|
-
status: "completed",
|
|
401
|
-
timestamp: Date.now(),
|
|
402
|
-
elapsedMs: event.elapsedMs
|
|
403
|
-
});
|
|
404
|
-
} else if (eventType === "USER_ACTION_INVALID") {
|
|
405
|
-
completeLastInProgressStep(state.steps);
|
|
406
|
-
const errorStepId = `step-${state.stepCounter++}`;
|
|
407
|
-
state.steps.push({
|
|
408
|
-
id: errorStepId,
|
|
409
|
-
eventType,
|
|
410
|
-
message,
|
|
411
|
-
status: "error",
|
|
412
|
-
timestamp: Date.now(),
|
|
413
|
-
elapsedMs: event.elapsedMs
|
|
414
|
-
});
|
|
415
|
-
const retryStepId = `step-${state.stepCounter++}`;
|
|
416
|
-
state.steps.push({
|
|
417
|
-
id: retryStepId,
|
|
418
|
-
eventType: "USER_ACTION_REQUIRED",
|
|
419
|
-
message: "Waiting for verification...",
|
|
420
|
-
status: "in_progress",
|
|
421
|
-
timestamp: Date.now()
|
|
422
|
-
});
|
|
423
|
-
state.currentExecutingStepId = retryStepId;
|
|
424
|
-
} else if (eventType === "USER_ACTION_EXPIRED") {
|
|
425
|
-
completeLastInProgressStep(state.steps);
|
|
426
|
-
state.userActionRequest = void 0;
|
|
427
|
-
state.userActionPending = false;
|
|
428
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
429
|
-
state.steps.push({
|
|
430
|
-
id: stepId,
|
|
431
|
-
eventType,
|
|
432
|
-
message,
|
|
433
|
-
status: "error",
|
|
434
|
-
timestamp: Date.now(),
|
|
435
|
-
elapsedMs: event.elapsedMs
|
|
436
|
-
});
|
|
437
|
-
} else if (eventType === "USER_ACTION_REJECTED") {
|
|
438
|
-
completeLastInProgressStep(state.steps);
|
|
439
|
-
state.userActionRequest = void 0;
|
|
440
|
-
state.userActionPending = false;
|
|
441
|
-
state.userActionResult = "rejected";
|
|
442
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
443
|
-
state.steps.push({
|
|
444
|
-
id: stepId,
|
|
445
|
-
eventType,
|
|
446
|
-
message,
|
|
447
|
-
status: "completed",
|
|
448
|
-
timestamp: Date.now(),
|
|
449
|
-
elapsedMs: event.elapsedMs
|
|
450
|
-
});
|
|
451
|
-
} else if (eventType === "USER_ACTION_RESENT") {
|
|
452
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
453
|
-
state.steps.push({
|
|
454
|
-
id: stepId,
|
|
455
|
-
eventType,
|
|
456
|
-
message,
|
|
457
|
-
status: "completed",
|
|
458
|
-
timestamp: Date.now(),
|
|
459
|
-
elapsedMs: event.elapsedMs
|
|
460
|
-
});
|
|
461
|
-
} else if (eventType === "USER_ACTION_FAILED") {
|
|
462
|
-
completeLastInProgressStep(state.steps);
|
|
463
|
-
state.userActionRequest = void 0;
|
|
464
|
-
state.userActionPending = false;
|
|
465
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
466
|
-
state.steps.push({
|
|
467
|
-
id: stepId,
|
|
468
|
-
eventType,
|
|
469
|
-
message,
|
|
470
|
-
status: "error",
|
|
471
|
-
timestamp: Date.now(),
|
|
472
|
-
elapsedMs: event.elapsedMs
|
|
473
|
-
});
|
|
474
|
-
} else if (eventType === "INTENT_THINKING") {
|
|
475
|
-
if (state.currentThinkingStepId) {
|
|
476
|
-
const prev = state.steps.find((s) => s.id === state.currentThinkingStepId);
|
|
477
|
-
if (prev) prev.isThinking = false;
|
|
478
|
-
}
|
|
479
|
-
const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
|
|
480
|
-
if (lastInProgress) {
|
|
481
|
-
lastInProgress.thinkingText = "";
|
|
482
|
-
lastInProgress.isThinking = true;
|
|
483
|
-
state.currentThinkingStepId = lastInProgress.id;
|
|
484
|
-
} else {
|
|
485
|
-
state.currentThinkingStepId = void 0;
|
|
486
|
-
}
|
|
487
|
-
if (state.allThinkingText) state.allThinkingText += "\n\n";
|
|
488
|
-
if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
|
|
489
|
-
state.activeThinkingText = "";
|
|
490
|
-
}
|
|
491
|
-
} else if (eventType === "INTENT_THINKING_CONT") {
|
|
492
|
-
const delta = event.message || "";
|
|
493
|
-
if (!delta) return state;
|
|
494
|
-
if (state.currentThinkingStepId) {
|
|
495
|
-
const step = state.steps.find((s) => s.id === state.currentThinkingStepId);
|
|
496
|
-
if (step) {
|
|
497
|
-
step.thinkingText = (step.thinkingText || "") + delta;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
state.allThinkingText += delta;
|
|
501
|
-
if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
|
|
502
|
-
if (state.activeThinkingText == null) state.activeThinkingText = "";
|
|
503
|
-
state.activeThinkingText += delta;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
return state;
|
|
507
|
-
}
|
|
508
227
|
|
|
509
228
|
// src/utils/messageStateManager.ts
|
|
510
|
-
var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
|
|
511
229
|
function buildFormattedThinking(steps, allThinkingText) {
|
|
512
230
|
const parts = [];
|
|
513
231
|
const safeSteps = steps ?? [];
|
|
@@ -582,73 +300,6 @@ function buildFormattedThinking(steps, allThinkingText) {
|
|
|
582
300
|
}
|
|
583
301
|
return parts.length > 0 ? parts.join("\n") : allThinkingText;
|
|
584
302
|
}
|
|
585
|
-
function createStreamingMessageUpdate(state) {
|
|
586
|
-
const hasCompletedContent = state.accumulatedContent && state.finalData !== void 0;
|
|
587
|
-
const steps = state.hasError ? [] : [...state.steps];
|
|
588
|
-
const allThinking = state.hasError ? void 0 : state.allThinkingText;
|
|
589
|
-
return {
|
|
590
|
-
streamingContent: state.hasError ? FRIENDLY_ERROR_MESSAGE : hasCompletedContent ? state.accumulatedContent : "",
|
|
591
|
-
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : "",
|
|
592
|
-
currentMessage: state.hasError ? void 0 : state.currentMessage,
|
|
593
|
-
streamProgress: state.hasError ? "error" : "processing",
|
|
594
|
-
isError: state.hasError,
|
|
595
|
-
errorDetails: state.hasError ? state.errorMessage : void 0,
|
|
596
|
-
executionId: state.executionId,
|
|
597
|
-
sessionId: state.sessionId,
|
|
598
|
-
steps,
|
|
599
|
-
currentExecutingStepId: state.hasError ? void 0 : state.currentExecutingStepId,
|
|
600
|
-
isCancelled: false,
|
|
601
|
-
userActionResult: state.userActionResult,
|
|
602
|
-
activeThinkingText: state.hasError ? void 0 : state.activeThinkingText,
|
|
603
|
-
allThinkingText: allThinking,
|
|
604
|
-
formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || "")
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
function createErrorMessageUpdate(error, state) {
|
|
608
|
-
const isAborted = error.name === "AbortError";
|
|
609
|
-
return {
|
|
610
|
-
isStreaming: false,
|
|
611
|
-
streamProgress: isAborted ? "processing" : "error",
|
|
612
|
-
isError: !isAborted,
|
|
613
|
-
isCancelled: isAborted,
|
|
614
|
-
errorDetails: isAborted ? void 0 : error.message,
|
|
615
|
-
content: isAborted ? state.accumulatedContent || "" : state.accumulatedContent || FRIENDLY_ERROR_MESSAGE,
|
|
616
|
-
// Preserve currentMessage when cancelled so UI can show it
|
|
617
|
-
currentMessage: isAborted ? state.currentMessage || "Thinking..." : void 0,
|
|
618
|
-
steps: [...state.steps].map((step) => {
|
|
619
|
-
if (step.status === "in_progress" && isAborted) {
|
|
620
|
-
return { ...step, status: "pending" };
|
|
621
|
-
}
|
|
622
|
-
return step;
|
|
623
|
-
}),
|
|
624
|
-
currentExecutingStepId: void 0
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
function createFinalMessage(streamingId, state) {
|
|
628
|
-
const steps = state.hasError ? [] : [...state.steps];
|
|
629
|
-
const allThinking = state.hasError ? void 0 : state.allThinkingText;
|
|
630
|
-
return {
|
|
631
|
-
id: streamingId,
|
|
632
|
-
sessionId: state.sessionId,
|
|
633
|
-
role: "assistant",
|
|
634
|
-
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.accumulatedContent || "",
|
|
635
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
636
|
-
isStreaming: false,
|
|
637
|
-
streamProgress: state.hasError ? "error" : "completed",
|
|
638
|
-
isError: state.hasError,
|
|
639
|
-
errorDetails: state.hasError ? state.errorMessage : void 0,
|
|
640
|
-
executionId: state.executionId,
|
|
641
|
-
tracingData: state.finalData,
|
|
642
|
-
steps,
|
|
643
|
-
isCancelled: false,
|
|
644
|
-
currentExecutingStepId: void 0,
|
|
645
|
-
userActionResult: state.userActionResult,
|
|
646
|
-
activeThinkingText: void 0,
|
|
647
|
-
allThinkingText: allThinking,
|
|
648
|
-
formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || ""),
|
|
649
|
-
isResolvingImages: state.hasError ? void 0 : state.isResolvingImages
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
303
|
function createCancelledMessageUpdate(steps, currentMessage) {
|
|
653
304
|
const updatedSteps = steps.map((step) => {
|
|
654
305
|
if (step.status === "in_progress") {
|
|
@@ -668,35 +319,44 @@ function createCancelledMessageUpdate(steps, currentMessage) {
|
|
|
668
319
|
|
|
669
320
|
// src/utils/requestBuilder.ts
|
|
670
321
|
function buildRequestBody(config, userMessage, sessionId) {
|
|
671
|
-
const
|
|
672
|
-
const sessionAttributes =
|
|
322
|
+
const owner = config.session?.owner;
|
|
323
|
+
const sessionAttributes = owner?.attributes && Object.keys(owner.attributes).length > 0 ? owner.attributes : void 0;
|
|
673
324
|
return {
|
|
674
|
-
workflowName: config.
|
|
325
|
+
workflowName: config.workflow.name,
|
|
675
326
|
userInput: userMessage,
|
|
676
327
|
sessionId,
|
|
677
|
-
sessionOwnerId:
|
|
678
|
-
sessionOwnerLabel:
|
|
328
|
+
sessionOwnerId: owner?.id || "",
|
|
329
|
+
sessionOwnerLabel: owner?.name || "",
|
|
679
330
|
sessionAttributes,
|
|
680
331
|
options: {
|
|
681
332
|
clientTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
682
333
|
}
|
|
683
334
|
};
|
|
684
335
|
}
|
|
336
|
+
function getStageParamName(config) {
|
|
337
|
+
return config.api.stageQueryParam ?? "stage";
|
|
338
|
+
}
|
|
339
|
+
function getStage(config) {
|
|
340
|
+
return config.workflow.stage ?? "DEV";
|
|
341
|
+
}
|
|
685
342
|
function buildStreamingUrl(config) {
|
|
686
343
|
const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
if (config.
|
|
691
|
-
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));
|
|
692
349
|
}
|
|
693
350
|
return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
|
|
694
351
|
}
|
|
695
|
-
function
|
|
352
|
+
function deriveBasePath(config) {
|
|
696
353
|
const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
|
|
697
354
|
const [endpointPath] = endpoint.split("?");
|
|
698
|
-
const
|
|
699
|
-
|
|
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);
|
|
700
360
|
const encodedUserActionId = encodeURIComponent(userActionId);
|
|
701
361
|
return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
|
|
702
362
|
}
|
|
@@ -704,717 +364,132 @@ function buildResolveImagesUrl(config) {
|
|
|
704
364
|
if (config.api.resolveImagesEndpoint) {
|
|
705
365
|
return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
|
|
706
366
|
}
|
|
707
|
-
|
|
708
|
-
const [endpointPath] = streamEndpoint.split("?");
|
|
709
|
-
const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
|
|
710
|
-
const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
|
|
711
|
-
return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
|
|
367
|
+
return `${config.api.baseUrl}${deriveBasePath(config)}/resolve-image-urls`;
|
|
712
368
|
}
|
|
713
369
|
function buildRequestHeaders(config) {
|
|
714
|
-
const headers = {
|
|
715
|
-
...config.api.headers
|
|
716
|
-
};
|
|
370
|
+
const headers = { ...config.api.headers };
|
|
717
371
|
if (config.api.authToken) {
|
|
718
372
|
headers.Authorization = `Bearer ${config.api.authToken}`;
|
|
719
373
|
}
|
|
720
374
|
return headers;
|
|
721
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
|
+
}
|
|
722
384
|
|
|
723
385
|
// src/utils/userActionClient.ts
|
|
724
386
|
async function sendUserActionRequest(config, userActionId, action, data) {
|
|
725
387
|
const url = buildUserActionUrl(config, userActionId, action);
|
|
726
388
|
const baseHeaders = buildRequestHeaders(config);
|
|
727
|
-
const hasBody = data !== void 0;
|
|
728
|
-
const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
|
|
729
|
-
const response = await fetch(url, {
|
|
730
|
-
method: "POST",
|
|
731
|
-
headers,
|
|
732
|
-
body: hasBody ? JSON.stringify(data) : void 0
|
|
733
|
-
});
|
|
734
|
-
if (!response.ok) {
|
|
735
|
-
const errorText = await response.text();
|
|
736
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
737
|
-
}
|
|
738
|
-
return await response.json();
|
|
739
|
-
}
|
|
740
|
-
async function submitUserAction(config, userActionId, data) {
|
|
741
|
-
return sendUserActionRequest(config, userActionId, "submit", data);
|
|
742
|
-
}
|
|
743
|
-
async function cancelUserAction(config, userActionId) {
|
|
744
|
-
return sendUserActionRequest(config, userActionId, "cancel");
|
|
745
|
-
}
|
|
746
|
-
async function resendUserAction(config, userActionId) {
|
|
747
|
-
return sendUserActionRequest(config, userActionId, "resend");
|
|
748
|
-
}
|
|
749
|
-
var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
|
|
750
|
-
var chatStore = {
|
|
751
|
-
get(key) {
|
|
752
|
-
const raw = storage.getString(key);
|
|
753
|
-
if (!raw) return [];
|
|
754
|
-
try {
|
|
755
|
-
return JSON.parse(raw);
|
|
756
|
-
} catch {
|
|
757
|
-
return [];
|
|
758
|
-
}
|
|
759
|
-
},
|
|
760
|
-
set(key, messages) {
|
|
761
|
-
storage.set(key, JSON.stringify(messages));
|
|
762
|
-
},
|
|
763
|
-
delete(key) {
|
|
764
|
-
storage.delete(key);
|
|
765
|
-
}
|
|
766
|
-
};
|
|
767
|
-
|
|
768
|
-
// src/utils/activeStreamStore.ts
|
|
769
|
-
var streams = /* @__PURE__ */ new Map();
|
|
770
|
-
var activeStreamStore = {
|
|
771
|
-
has(key) {
|
|
772
|
-
return streams.has(key);
|
|
773
|
-
},
|
|
774
|
-
get(key) {
|
|
775
|
-
const entry = streams.get(key);
|
|
776
|
-
if (!entry) return null;
|
|
777
|
-
return { messages: entry.messages, isWaiting: entry.isWaiting };
|
|
778
|
-
},
|
|
779
|
-
// Called before startStream — registers the controller and initial messages
|
|
780
|
-
start(key, abortController, initialMessages) {
|
|
781
|
-
const existing = streams.get(key);
|
|
782
|
-
streams.set(key, {
|
|
783
|
-
messages: initialMessages,
|
|
784
|
-
isWaiting: true,
|
|
785
|
-
abortController,
|
|
786
|
-
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
787
|
-
});
|
|
788
|
-
},
|
|
789
|
-
// Called by the stream on every event — applies the same updater pattern React uses
|
|
790
|
-
applyMessages(key, updater) {
|
|
791
|
-
const entry = streams.get(key);
|
|
792
|
-
if (!entry) return;
|
|
793
|
-
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
794
|
-
entry.messages = next;
|
|
795
|
-
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
796
|
-
},
|
|
797
|
-
setWaiting(key, waiting) {
|
|
798
|
-
const entry = streams.get(key);
|
|
799
|
-
if (!entry) return;
|
|
800
|
-
entry.isWaiting = waiting;
|
|
801
|
-
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
802
|
-
},
|
|
803
|
-
// Called when stream completes — persists to chatStore and cleans up
|
|
804
|
-
complete(key) {
|
|
805
|
-
const entry = streams.get(key);
|
|
806
|
-
if (!entry) return;
|
|
807
|
-
entry.isWaiting = false;
|
|
808
|
-
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
809
|
-
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
810
|
-
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
811
|
-
streams.delete(key);
|
|
812
|
-
},
|
|
813
|
-
// Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
|
|
814
|
-
subscribe(key, listener) {
|
|
815
|
-
const entry = streams.get(key);
|
|
816
|
-
if (!entry) return () => {
|
|
817
|
-
};
|
|
818
|
-
entry.listeners.add(listener);
|
|
819
|
-
return () => {
|
|
820
|
-
streams.get(key)?.listeners.delete(listener);
|
|
821
|
-
};
|
|
822
|
-
},
|
|
823
|
-
// Explicit user cancel — aborts the controller and removes the entry
|
|
824
|
-
abort(key) {
|
|
825
|
-
const entry = streams.get(key);
|
|
826
|
-
if (!entry) return;
|
|
827
|
-
entry.abortController.abort();
|
|
828
|
-
streams.delete(key);
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
|
|
832
|
-
// src/utils/ragImageResolver.ts
|
|
833
|
-
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
834
|
-
function hasRagImages(content) {
|
|
835
|
-
return RAG_IMAGE_REGEX.test(content);
|
|
836
|
-
}
|
|
837
|
-
async function waitForNextPaint(signal) {
|
|
838
|
-
if (signal?.aborted) return;
|
|
839
|
-
await new Promise((resolve) => {
|
|
840
|
-
let isSettled = false;
|
|
841
|
-
const finish = () => {
|
|
842
|
-
if (isSettled) return;
|
|
843
|
-
isSettled = true;
|
|
844
|
-
signal?.removeEventListener("abort", finish);
|
|
845
|
-
resolve();
|
|
846
|
-
};
|
|
847
|
-
signal?.addEventListener("abort", finish, { once: true });
|
|
848
|
-
if (typeof requestAnimationFrame === "function") {
|
|
849
|
-
requestAnimationFrame(() => {
|
|
850
|
-
setTimeout(finish, 0);
|
|
851
|
-
});
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
setTimeout(finish, 0);
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
async function resolveRagImageUrls(config, content, signal) {
|
|
858
|
-
const url = buildResolveImagesUrl(config);
|
|
859
|
-
const baseHeaders = buildRequestHeaders(config);
|
|
860
|
-
const headers = { "Content-Type": "application/json", ...baseHeaders };
|
|
861
|
-
const response = await fetch(url, {
|
|
862
|
-
method: "POST",
|
|
863
|
-
headers,
|
|
864
|
-
body: JSON.stringify({ input: content }),
|
|
865
|
-
signal
|
|
866
|
-
});
|
|
867
|
-
if (!response.ok) {
|
|
868
|
-
const errorText = await response.text();
|
|
869
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
870
|
-
}
|
|
871
|
-
const text = await response.text();
|
|
872
|
-
try {
|
|
873
|
-
const parsed = JSON.parse(text);
|
|
874
|
-
if (typeof parsed === "string") return parsed;
|
|
875
|
-
if (typeof parsed.output === "string") return parsed.output;
|
|
876
|
-
if (typeof parsed.result === "string") return parsed.result;
|
|
877
|
-
} catch {
|
|
878
|
-
}
|
|
879
|
-
return text;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// src/hooks/useStreamManager.ts
|
|
883
|
-
function useStreamManager(config, callbacks, setMessages, setIsWaitingForResponse) {
|
|
884
|
-
const abortControllerRef = react.useRef(null);
|
|
885
|
-
const configRef = react.useRef(config);
|
|
886
|
-
configRef.current = config;
|
|
887
|
-
const callbacksRef = react.useRef(callbacks);
|
|
888
|
-
callbacksRef.current = callbacks;
|
|
889
|
-
const startStream = react.useCallback(
|
|
890
|
-
async (userMessage, streamingId, sessionId, externalAbortController) => {
|
|
891
|
-
abortControllerRef.current?.abort();
|
|
892
|
-
const abortController = externalAbortController ?? new AbortController();
|
|
893
|
-
abortControllerRef.current = abortController;
|
|
894
|
-
const state = {
|
|
895
|
-
accumulatedContent: "",
|
|
896
|
-
executionId: void 0,
|
|
897
|
-
currentSessionId: void 0,
|
|
898
|
-
finalData: void 0,
|
|
899
|
-
steps: [],
|
|
900
|
-
stepCounter: 0,
|
|
901
|
-
currentExecutingStepId: void 0,
|
|
902
|
-
currentThinkingStepId: void 0,
|
|
903
|
-
hasError: false,
|
|
904
|
-
errorMessage: "",
|
|
905
|
-
userActionRequest: void 0,
|
|
906
|
-
userActionPending: false,
|
|
907
|
-
userActionResult: void 0,
|
|
908
|
-
allThinkingText: "",
|
|
909
|
-
inOrchestratorPhase: false,
|
|
910
|
-
inAggregatorPhase: false
|
|
911
|
-
};
|
|
912
|
-
const THROTTLE_MS = 120;
|
|
913
|
-
const CHARS_PER_TICK = 10;
|
|
914
|
-
const displayedLengthRef = { current: 0 };
|
|
915
|
-
let throttleIntervalId = null;
|
|
916
|
-
const getActiveStepMessage = () => state.steps.find((s) => s.id === state.currentExecutingStepId)?.message || [...state.steps].reverse().find((s) => s.status === "in_progress")?.message;
|
|
917
|
-
const advanceDisplayLength = (full) => {
|
|
918
|
-
let newLen = Math.min(displayedLengthRef.current + CHARS_PER_TICK, full.length);
|
|
919
|
-
if (newLen > 0 && newLen < full.length) {
|
|
920
|
-
const code = full.charCodeAt(newLen - 1);
|
|
921
|
-
if (code >= 55296 && code <= 56319) {
|
|
922
|
-
newLen = Math.min(newLen + 1, full.length);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
displayedLengthRef.current = newLen;
|
|
926
|
-
};
|
|
927
|
-
const clearThrottle = () => {
|
|
928
|
-
if (throttleIntervalId != null) {
|
|
929
|
-
clearInterval(throttleIntervalId);
|
|
930
|
-
throttleIntervalId = null;
|
|
931
|
-
}
|
|
932
|
-
};
|
|
933
|
-
abortController.signal.addEventListener("abort", clearThrottle, { once: true });
|
|
934
|
-
const ensureThrottle = () => {
|
|
935
|
-
if (throttleIntervalId != null) return;
|
|
936
|
-
throttleIntervalId = setInterval(() => {
|
|
937
|
-
if (abortController.signal.aborted) {
|
|
938
|
-
clearThrottle();
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
const full = state.activeThinkingText ?? "";
|
|
942
|
-
if (displayedLengthRef.current < full.length) {
|
|
943
|
-
advanceDisplayLength(full);
|
|
944
|
-
const displayText = full.slice(0, displayedLengthRef.current);
|
|
945
|
-
setMessages(
|
|
946
|
-
(prev) => prev.map(
|
|
947
|
-
(msg) => msg.id === streamingId ? {
|
|
948
|
-
...msg,
|
|
949
|
-
...createStreamingMessageUpdate({
|
|
950
|
-
...state,
|
|
951
|
-
activeThinkingText: displayText,
|
|
952
|
-
currentMessage: getActiveStepMessage() || "Thinking..."
|
|
953
|
-
})
|
|
954
|
-
} : msg
|
|
955
|
-
)
|
|
956
|
-
);
|
|
957
|
-
}
|
|
958
|
-
}, THROTTLE_MS);
|
|
959
|
-
};
|
|
960
|
-
const currentConfig = configRef.current;
|
|
961
|
-
const requestBody = buildRequestBody(currentConfig, userMessage, sessionId);
|
|
962
|
-
const url = buildStreamingUrl(currentConfig);
|
|
963
|
-
const headers = buildRequestHeaders(currentConfig);
|
|
964
|
-
try {
|
|
965
|
-
await streamWorkflowEvents(url, requestBody, headers, {
|
|
966
|
-
signal: abortController.signal,
|
|
967
|
-
onEvent: (event) => {
|
|
968
|
-
if (abortController.signal.aborted) {
|
|
969
|
-
return;
|
|
970
|
-
}
|
|
971
|
-
if (typeof event.eventType === "string" && event.eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
972
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
973
|
-
if (event.sessionId) state.currentSessionId = event.sessionId;
|
|
974
|
-
return;
|
|
975
|
-
}
|
|
976
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
977
|
-
if (event.sessionId) state.currentSessionId = event.sessionId;
|
|
978
|
-
const activeThinkingLengthBeforeProcess = state.activeThinkingText?.length ?? 0;
|
|
979
|
-
processStreamEvent(event, state);
|
|
980
|
-
const eventType = event.eventType;
|
|
981
|
-
if (eventType === "INTENT_THINKING") {
|
|
982
|
-
displayedLengthRef.current = 0;
|
|
983
|
-
ensureThrottle();
|
|
984
|
-
} else if (eventType !== "INTENT_THINKING_CONT") {
|
|
985
|
-
displayedLengthRef.current = activeThinkingLengthBeforeProcess;
|
|
986
|
-
clearThrottle();
|
|
987
|
-
}
|
|
988
|
-
if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
|
|
989
|
-
callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
|
|
990
|
-
} else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
|
|
991
|
-
const msg = event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event);
|
|
992
|
-
callbacksRef.current.onUserActionEvent?.(eventType, msg);
|
|
993
|
-
}
|
|
994
|
-
const isIntentThinkingEvent = eventType === "INTENT_THINKING" || eventType === "INTENT_THINKING_CONT";
|
|
995
|
-
const rawMessage = event.message?.trim() || event.errorMessage?.trim();
|
|
996
|
-
const currentMessage = isIntentThinkingEvent ? getActiveStepMessage() || "Thinking..." : rawMessage || (event.eventType?.startsWith("USER_ACTION_") ? getEventMessage(event) : getActiveStepMessage() || getEventMessage(event));
|
|
997
|
-
const displayThinking = state.activeThinkingText != null ? state.activeThinkingText.slice(0, displayedLengthRef.current) : state.activeThinkingText;
|
|
998
|
-
setMessages(
|
|
999
|
-
(prev) => prev.map(
|
|
1000
|
-
(msg) => msg.id === streamingId ? {
|
|
1001
|
-
...msg,
|
|
1002
|
-
...createStreamingMessageUpdate({
|
|
1003
|
-
...state,
|
|
1004
|
-
activeThinkingText: displayThinking,
|
|
1005
|
-
currentMessage
|
|
1006
|
-
})
|
|
1007
|
-
} : msg
|
|
1008
|
-
)
|
|
1009
|
-
);
|
|
1010
|
-
},
|
|
1011
|
-
onError: (error) => {
|
|
1012
|
-
clearThrottle();
|
|
1013
|
-
setIsWaitingForResponse(false);
|
|
1014
|
-
if (error.name !== "AbortError") {
|
|
1015
|
-
callbacksRef.current.onError?.(error);
|
|
1016
|
-
}
|
|
1017
|
-
if (state.userActionPending) {
|
|
1018
|
-
state.userActionPending = false;
|
|
1019
|
-
state.userActionRequest = void 0;
|
|
1020
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1021
|
-
"USER_ACTION_FAILED",
|
|
1022
|
-
"Connection lost. Please try again."
|
|
1023
|
-
);
|
|
1024
|
-
}
|
|
1025
|
-
setMessages(
|
|
1026
|
-
(prev) => prev.map(
|
|
1027
|
-
(msg) => msg.id === streamingId ? {
|
|
1028
|
-
...msg,
|
|
1029
|
-
...createErrorMessageUpdate(error, state)
|
|
1030
|
-
} : msg
|
|
1031
|
-
)
|
|
1032
|
-
);
|
|
1033
|
-
},
|
|
1034
|
-
onComplete: () => {
|
|
1035
|
-
clearThrottle();
|
|
1036
|
-
setIsWaitingForResponse(false);
|
|
1037
|
-
if (state.userActionPending) {
|
|
1038
|
-
state.userActionPending = false;
|
|
1039
|
-
state.userActionRequest = void 0;
|
|
1040
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1041
|
-
"USER_ACTION_FAILED",
|
|
1042
|
-
"Verification could not be completed."
|
|
1043
|
-
);
|
|
1044
|
-
}
|
|
1045
|
-
if (state.currentSessionId && state.currentSessionId !== sessionId) {
|
|
1046
|
-
callbacksRef.current.onSessionIdChange?.(state.currentSessionId);
|
|
1047
|
-
}
|
|
1048
|
-
const needsImageResolve = !state.hasError && !abortController.signal.aborted && hasRagImages(state.accumulatedContent);
|
|
1049
|
-
const finalMessage = createFinalMessage(streamingId, {
|
|
1050
|
-
...state,
|
|
1051
|
-
sessionId: state.currentSessionId || sessionId,
|
|
1052
|
-
isResolvingImages: needsImageResolve
|
|
1053
|
-
});
|
|
1054
|
-
setMessages(
|
|
1055
|
-
(prev) => prev.map(
|
|
1056
|
-
(msg) => msg.id === streamingId ? finalMessage : msg
|
|
1057
|
-
)
|
|
1058
|
-
);
|
|
1059
|
-
callbacksRef.current.onStreamComplete?.(finalMessage);
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
clearThrottle();
|
|
1063
|
-
const shouldResolveImages = !abortController.signal.aborted && !state.hasError && hasRagImages(state.accumulatedContent);
|
|
1064
|
-
if (shouldResolveImages) {
|
|
1065
|
-
await waitForNextPaint(abortController.signal);
|
|
1066
|
-
}
|
|
1067
|
-
if (shouldResolveImages && !abortController.signal.aborted) {
|
|
1068
|
-
try {
|
|
1069
|
-
const resolvedContent = await resolveRagImageUrls(
|
|
1070
|
-
currentConfig,
|
|
1071
|
-
state.accumulatedContent,
|
|
1072
|
-
abortController.signal
|
|
1073
|
-
);
|
|
1074
|
-
setMessages(
|
|
1075
|
-
(prev) => prev.map(
|
|
1076
|
-
(msg) => msg.id === streamingId ? { ...msg, content: resolvedContent, isResolvingImages: false } : msg
|
|
1077
|
-
)
|
|
1078
|
-
);
|
|
1079
|
-
} catch {
|
|
1080
|
-
setMessages(
|
|
1081
|
-
(prev) => prev.map(
|
|
1082
|
-
(msg) => msg.id === streamingId ? { ...msg, isResolvingImages: false } : msg
|
|
1083
|
-
)
|
|
1084
|
-
);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
return state.currentSessionId;
|
|
1088
|
-
} catch (error) {
|
|
1089
|
-
clearThrottle();
|
|
1090
|
-
setIsWaitingForResponse(false);
|
|
1091
|
-
if (error.name !== "AbortError") {
|
|
1092
|
-
callbacksRef.current.onError?.(error);
|
|
1093
|
-
}
|
|
1094
|
-
if (state.userActionPending) {
|
|
1095
|
-
state.userActionPending = false;
|
|
1096
|
-
state.userActionRequest = void 0;
|
|
1097
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1098
|
-
"USER_ACTION_FAILED",
|
|
1099
|
-
"Connection lost. Please try again."
|
|
1100
|
-
);
|
|
1101
|
-
}
|
|
1102
|
-
setMessages(
|
|
1103
|
-
(prev) => prev.map(
|
|
1104
|
-
(msg) => msg.id === streamingId ? {
|
|
1105
|
-
...msg,
|
|
1106
|
-
...createErrorMessageUpdate(error, state)
|
|
1107
|
-
} : msg
|
|
1108
|
-
)
|
|
1109
|
-
);
|
|
1110
|
-
return state.currentSessionId;
|
|
1111
|
-
}
|
|
1112
|
-
},
|
|
1113
|
-
[setMessages, setIsWaitingForResponse]
|
|
1114
|
-
);
|
|
1115
|
-
const cancelStream = react.useCallback(() => {
|
|
1116
|
-
abortControllerRef.current?.abort();
|
|
1117
|
-
}, []);
|
|
1118
|
-
return {
|
|
1119
|
-
startStream,
|
|
1120
|
-
cancelStream,
|
|
1121
|
-
abortControllerRef
|
|
1122
|
-
};
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
// src/hooks/useChat.ts
|
|
1126
|
-
function useChat(config, callbacks = {}) {
|
|
1127
|
-
const [messages, setMessages] = react.useState(() => {
|
|
1128
|
-
if (config.userId) return chatStore.get(config.userId);
|
|
1129
|
-
return config.initialMessages ?? [];
|
|
1130
|
-
});
|
|
1131
|
-
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
|
|
1132
|
-
const sessionIdRef = react.useRef(
|
|
1133
|
-
config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
|
|
1134
|
-
);
|
|
1135
|
-
const prevUserIdRef = react.useRef(config.userId);
|
|
1136
|
-
const callbacksRef = react.useRef(callbacks);
|
|
1137
|
-
callbacksRef.current = callbacks;
|
|
1138
|
-
const configRef = react.useRef(config);
|
|
1139
|
-
configRef.current = config;
|
|
1140
|
-
const messagesRef = react.useRef(messages);
|
|
1141
|
-
messagesRef.current = messages;
|
|
1142
|
-
const storeAwareSetMessages = react.useCallback(
|
|
1143
|
-
(updater) => {
|
|
1144
|
-
const { userId } = configRef.current;
|
|
1145
|
-
if (userId && activeStreamStore.has(userId)) {
|
|
1146
|
-
activeStreamStore.applyMessages(userId, updater);
|
|
1147
|
-
}
|
|
1148
|
-
setMessages(updater);
|
|
1149
|
-
},
|
|
1150
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1151
|
-
[]
|
|
1152
|
-
);
|
|
1153
|
-
const storeAwareSetIsWaiting = react.useCallback(
|
|
1154
|
-
(waiting) => {
|
|
1155
|
-
const { userId } = configRef.current;
|
|
1156
|
-
if (userId && activeStreamStore.has(userId)) {
|
|
1157
|
-
activeStreamStore.setWaiting(userId, waiting);
|
|
1158
|
-
}
|
|
1159
|
-
setIsWaitingForResponse(waiting);
|
|
1160
|
-
},
|
|
1161
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1162
|
-
[]
|
|
1163
|
-
);
|
|
1164
|
-
const [userActionState, setUserActionState] = react.useState({
|
|
1165
|
-
request: null,
|
|
1166
|
-
result: null,
|
|
1167
|
-
clearOtpTrigger: 0
|
|
1168
|
-
});
|
|
1169
|
-
const userActionStateRef = react.useRef(userActionState);
|
|
1170
|
-
userActionStateRef.current = userActionState;
|
|
1171
|
-
const wrappedCallbacks = react.useMemo(() => ({
|
|
1172
|
-
...callbacksRef.current,
|
|
1173
|
-
onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
|
|
1174
|
-
onStreamStart: () => callbacksRef.current.onStreamStart?.(),
|
|
1175
|
-
onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
|
|
1176
|
-
onError: (error) => callbacksRef.current.onError?.(error),
|
|
1177
|
-
onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
|
|
1178
|
-
onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
|
|
1179
|
-
onUserActionRequired: (request) => {
|
|
1180
|
-
setUserActionState((prev) => ({ ...prev, request, result: null }));
|
|
1181
|
-
callbacksRef.current.onUserActionRequired?.(request);
|
|
1182
|
-
},
|
|
1183
|
-
onUserActionEvent: (eventType, message) => {
|
|
1184
|
-
switch (eventType) {
|
|
1185
|
-
case "USER_ACTION_SUCCESS":
|
|
1186
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
|
|
1187
|
-
break;
|
|
1188
|
-
case "USER_ACTION_REJECTED":
|
|
1189
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
|
|
1190
|
-
break;
|
|
1191
|
-
case "USER_ACTION_EXPIRED":
|
|
1192
|
-
case "USER_ACTION_FAILED":
|
|
1193
|
-
setUserActionState((prev) => ({ ...prev, request: null }));
|
|
1194
|
-
break;
|
|
1195
|
-
case "USER_ACTION_INVALID":
|
|
1196
|
-
setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
|
|
1197
|
-
break;
|
|
1198
|
-
}
|
|
1199
|
-
callbacksRef.current.onUserActionEvent?.(eventType, message);
|
|
1200
|
-
}
|
|
1201
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1202
|
-
}), []);
|
|
1203
|
-
const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManager(
|
|
1204
|
-
config,
|
|
1205
|
-
wrappedCallbacks,
|
|
1206
|
-
storeAwareSetMessages,
|
|
1207
|
-
storeAwareSetIsWaiting
|
|
1208
|
-
);
|
|
1209
|
-
const sendMessage = react.useCallback(
|
|
1210
|
-
async (userMessage) => {
|
|
1211
|
-
if (!userMessage.trim()) return;
|
|
1212
|
-
if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
|
|
1213
|
-
sessionIdRef.current = generateId();
|
|
1214
|
-
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
1215
|
-
}
|
|
1216
|
-
const userMessageId = `user-${Date.now()}`;
|
|
1217
|
-
const userMsg = {
|
|
1218
|
-
id: userMessageId,
|
|
1219
|
-
sessionId: sessionIdRef.current,
|
|
1220
|
-
role: "user",
|
|
1221
|
-
content: userMessage,
|
|
1222
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1223
|
-
};
|
|
1224
|
-
setMessages((prev) => [...prev, userMsg]);
|
|
1225
|
-
callbacksRef.current.onMessageSent?.(userMessage);
|
|
1226
|
-
setIsWaitingForResponse(true);
|
|
1227
|
-
callbacksRef.current.onStreamStart?.();
|
|
1228
|
-
const streamingId = `assistant-${Date.now()}`;
|
|
1229
|
-
const streamingMsg = {
|
|
1230
|
-
id: streamingId,
|
|
1231
|
-
sessionId: sessionIdRef.current,
|
|
1232
|
-
role: "assistant",
|
|
1233
|
-
content: "",
|
|
1234
|
-
streamingContent: "",
|
|
1235
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1236
|
-
isStreaming: true,
|
|
1237
|
-
streamProgress: "started",
|
|
1238
|
-
steps: [],
|
|
1239
|
-
currentExecutingStepId: void 0,
|
|
1240
|
-
isCancelled: false,
|
|
1241
|
-
currentMessage: void 0
|
|
1242
|
-
};
|
|
1243
|
-
setMessages((prev) => [...prev, streamingMsg]);
|
|
1244
|
-
const abortController = new AbortController();
|
|
1245
|
-
const { userId } = configRef.current;
|
|
1246
|
-
if (userId) {
|
|
1247
|
-
const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
|
|
1248
|
-
activeStreamStore.start(userId, abortController, initialMessages);
|
|
1249
|
-
}
|
|
1250
|
-
const newSessionId = await startStream(
|
|
1251
|
-
userMessage,
|
|
1252
|
-
streamingId,
|
|
1253
|
-
sessionIdRef.current,
|
|
1254
|
-
abortController
|
|
1255
|
-
);
|
|
1256
|
-
if (userId) {
|
|
1257
|
-
activeStreamStore.complete(userId);
|
|
1258
|
-
}
|
|
1259
|
-
if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
|
|
1260
|
-
sessionIdRef.current = newSessionId;
|
|
1261
|
-
}
|
|
1262
|
-
},
|
|
1263
|
-
[startStream]
|
|
1264
|
-
);
|
|
1265
|
-
const clearMessages = react.useCallback(() => {
|
|
1266
|
-
if (configRef.current.userId) {
|
|
1267
|
-
chatStore.delete(configRef.current.userId);
|
|
1268
|
-
}
|
|
1269
|
-
setMessages([]);
|
|
1270
|
-
}, []);
|
|
1271
|
-
const prependMessages = react.useCallback((msgs) => {
|
|
1272
|
-
setMessages((prev) => [...msgs, ...prev]);
|
|
1273
|
-
}, []);
|
|
1274
|
-
const cancelStream = react.useCallback(() => {
|
|
1275
|
-
if (configRef.current.userId) {
|
|
1276
|
-
activeStreamStore.abort(configRef.current.userId);
|
|
1277
|
-
}
|
|
1278
|
-
cancelStreamManager();
|
|
1279
|
-
setIsWaitingForResponse(false);
|
|
1280
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: null }));
|
|
1281
|
-
setMessages(
|
|
1282
|
-
(prev) => prev.map((msg) => {
|
|
1283
|
-
if (msg.isStreaming) {
|
|
1284
|
-
return {
|
|
1285
|
-
...msg,
|
|
1286
|
-
...createCancelledMessageUpdate(
|
|
1287
|
-
msg.steps || [],
|
|
1288
|
-
msg.currentMessage
|
|
1289
|
-
)
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
|
-
return msg;
|
|
1293
|
-
})
|
|
1294
|
-
);
|
|
1295
|
-
}, [cancelStreamManager]);
|
|
1296
|
-
const resetSession = react.useCallback(() => {
|
|
1297
|
-
if (configRef.current.userId) {
|
|
1298
|
-
activeStreamStore.abort(configRef.current.userId);
|
|
1299
|
-
chatStore.delete(configRef.current.userId);
|
|
1300
|
-
}
|
|
1301
|
-
setMessages([]);
|
|
1302
|
-
sessionIdRef.current = void 0;
|
|
1303
|
-
abortControllerRef.current?.abort();
|
|
1304
|
-
setIsWaitingForResponse(false);
|
|
1305
|
-
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1306
|
-
}, []);
|
|
1307
|
-
const getSessionId = react.useCallback(() => {
|
|
1308
|
-
return sessionIdRef.current;
|
|
1309
|
-
}, []);
|
|
1310
|
-
const getMessages = react.useCallback(() => {
|
|
1311
|
-
return messages;
|
|
1312
|
-
}, [messages]);
|
|
1313
|
-
const approveUserAction = react.useCallback(
|
|
1314
|
-
async (otp) => {
|
|
1315
|
-
const request = userActionStateRef.current.request;
|
|
1316
|
-
if (!request) return;
|
|
1317
|
-
try {
|
|
1318
|
-
await submitUserAction(configRef.current, request.userActionId, { otp });
|
|
1319
|
-
} catch (error) {
|
|
1320
|
-
setUserActionState((prev) => ({
|
|
1321
|
-
...prev,
|
|
1322
|
-
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
1323
|
-
}));
|
|
1324
|
-
callbacksRef.current.onError?.(error);
|
|
1325
|
-
throw error;
|
|
1326
|
-
}
|
|
1327
|
-
},
|
|
1328
|
-
[]
|
|
1329
|
-
);
|
|
1330
|
-
const rejectUserAction = react.useCallback(async () => {
|
|
1331
|
-
const request = userActionStateRef.current.request;
|
|
1332
|
-
if (!request) return;
|
|
1333
|
-
try {
|
|
1334
|
-
setMessages((prev) => {
|
|
1335
|
-
let lastStreamingIdx = -1;
|
|
1336
|
-
for (let i = prev.length - 1; i >= 0; i--) {
|
|
1337
|
-
if (prev[i].role === "assistant" && prev[i].isStreaming) {
|
|
1338
|
-
lastStreamingIdx = i;
|
|
1339
|
-
break;
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
if (lastStreamingIdx === -1) return prev;
|
|
1343
|
-
return prev.map(
|
|
1344
|
-
(msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
|
|
1345
|
-
);
|
|
1346
|
-
});
|
|
1347
|
-
await cancelUserAction(configRef.current, request.userActionId);
|
|
1348
|
-
} catch (error) {
|
|
1349
|
-
callbacksRef.current.onError?.(error);
|
|
1350
|
-
throw error;
|
|
1351
|
-
}
|
|
1352
|
-
}, []);
|
|
1353
|
-
const resendOtp = react.useCallback(async () => {
|
|
1354
|
-
const request = userActionStateRef.current.request;
|
|
1355
|
-
if (!request) return;
|
|
1356
|
-
try {
|
|
1357
|
-
await resendUserAction(configRef.current, request.userActionId);
|
|
1358
|
-
} catch (error) {
|
|
1359
|
-
callbacksRef.current.onError?.(error);
|
|
1360
|
-
throw error;
|
|
1361
|
-
}
|
|
1362
|
-
}, []);
|
|
1363
|
-
react.useEffect(() => {
|
|
1364
|
-
const { userId } = config;
|
|
1365
|
-
if (!userId) return;
|
|
1366
|
-
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1367
|
-
setMessages(msgs);
|
|
1368
|
-
setIsWaitingForResponse(isWaiting);
|
|
1369
|
-
});
|
|
1370
|
-
const active = activeStreamStore.get(userId);
|
|
1371
|
-
if (active) {
|
|
1372
|
-
setMessages(active.messages);
|
|
1373
|
-
setIsWaitingForResponse(active.isWaiting);
|
|
1374
|
-
}
|
|
1375
|
-
return unsubscribe;
|
|
1376
|
-
}, []);
|
|
1377
|
-
react.useEffect(() => {
|
|
1378
|
-
if (!config.userId) return;
|
|
1379
|
-
const toSave = messages.filter((m) => !m.isStreaming);
|
|
1380
|
-
if (toSave.length > 0) {
|
|
1381
|
-
chatStore.set(config.userId, toSave);
|
|
1382
|
-
}
|
|
1383
|
-
}, [messages, config.userId]);
|
|
1384
|
-
react.useEffect(() => {
|
|
1385
|
-
const prevUserId = prevUserIdRef.current;
|
|
1386
|
-
prevUserIdRef.current = config.userId;
|
|
1387
|
-
if (prevUserId === config.userId) return;
|
|
1388
|
-
if (prevUserId && !config.userId) {
|
|
1389
|
-
chatStore.delete(prevUserId);
|
|
1390
|
-
setMessages([]);
|
|
1391
|
-
sessionIdRef.current = void 0;
|
|
1392
|
-
setIsWaitingForResponse(false);
|
|
1393
|
-
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1394
|
-
} else if (config.userId) {
|
|
1395
|
-
const stored = chatStore.get(config.userId);
|
|
1396
|
-
setMessages(stored);
|
|
1397
|
-
sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
|
|
1398
|
-
}
|
|
1399
|
-
}, [config.userId]);
|
|
1400
|
-
return {
|
|
1401
|
-
messages,
|
|
1402
|
-
sendMessage,
|
|
1403
|
-
clearMessages,
|
|
1404
|
-
prependMessages,
|
|
1405
|
-
cancelStream,
|
|
1406
|
-
resetSession,
|
|
1407
|
-
getSessionId,
|
|
1408
|
-
getMessages,
|
|
1409
|
-
isWaitingForResponse,
|
|
1410
|
-
sessionId: sessionIdRef.current,
|
|
1411
|
-
// User action (OTP) state and methods
|
|
1412
|
-
userActionState,
|
|
1413
|
-
approveUserAction,
|
|
1414
|
-
rejectUserAction,
|
|
1415
|
-
resendOtp
|
|
1416
|
-
};
|
|
389
|
+
const hasBody = data !== void 0;
|
|
390
|
+
const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
|
|
391
|
+
const response = await fetch(url, {
|
|
392
|
+
method: "POST",
|
|
393
|
+
headers,
|
|
394
|
+
body: hasBody ? JSON.stringify(data) : void 0
|
|
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");
|
|
407
|
+
}
|
|
408
|
+
async function resendUserAction(config, userActionId) {
|
|
409
|
+
return sendUserActionRequest(config, userActionId, "resend");
|
|
1417
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
|
+
};
|
|
1418
493
|
|
|
1419
494
|
// src/utils/v2EventProcessor.ts
|
|
1420
495
|
function getEventText(event, field) {
|
|
@@ -1440,7 +515,7 @@ function addThinkingLine(state, header, detail) {
|
|
|
1440
515
|
function appendThinkingText(state, text) {
|
|
1441
516
|
state.formattedThinkingText += text;
|
|
1442
517
|
}
|
|
1443
|
-
function
|
|
518
|
+
function completeLastInProgressStep(steps) {
|
|
1444
519
|
for (let i = steps.length - 1; i >= 0; i--) {
|
|
1445
520
|
if (steps[i].status === "in_progress") {
|
|
1446
521
|
steps[i].status = "completed";
|
|
@@ -1654,7 +729,7 @@ function processStreamEventV2(event, state) {
|
|
|
1654
729
|
break;
|
|
1655
730
|
}
|
|
1656
731
|
case "USER_ACTION_REQUIRED": {
|
|
1657
|
-
|
|
732
|
+
completeLastInProgressStep(state.steps);
|
|
1658
733
|
if (event.userActionRequest) {
|
|
1659
734
|
state.userActionRequest = {
|
|
1660
735
|
userActionId: event.userActionRequest.userActionId,
|
|
@@ -1684,7 +759,7 @@ function processStreamEventV2(event, state) {
|
|
|
1684
759
|
}
|
|
1685
760
|
case "USER_ACTION_SUCCESS": {
|
|
1686
761
|
appendThinkingText(state, "\n\u2713 " + (event.message || "Verification successful"));
|
|
1687
|
-
|
|
762
|
+
completeLastInProgressStep(state.steps);
|
|
1688
763
|
state.userActionRequest = void 0;
|
|
1689
764
|
state.userActionPending = false;
|
|
1690
765
|
state.userActionResult = "approved";
|
|
@@ -1701,7 +776,7 @@ function processStreamEventV2(event, state) {
|
|
|
1701
776
|
break;
|
|
1702
777
|
}
|
|
1703
778
|
case "USER_ACTION_INVALID": {
|
|
1704
|
-
|
|
779
|
+
completeLastInProgressStep(state.steps);
|
|
1705
780
|
const errorStepId = `step-${state.stepCounter++}`;
|
|
1706
781
|
state.steps.push({
|
|
1707
782
|
id: errorStepId,
|
|
@@ -1725,7 +800,7 @@ function processStreamEventV2(event, state) {
|
|
|
1725
800
|
}
|
|
1726
801
|
case "USER_ACTION_REJECTED": {
|
|
1727
802
|
appendThinkingText(state, "\n\u2717 " + (event.message || "Verification rejected"));
|
|
1728
|
-
|
|
803
|
+
completeLastInProgressStep(state.steps);
|
|
1729
804
|
state.userActionRequest = void 0;
|
|
1730
805
|
state.userActionPending = false;
|
|
1731
806
|
state.userActionResult = "rejected";
|
|
@@ -1743,7 +818,7 @@ function processStreamEventV2(event, state) {
|
|
|
1743
818
|
}
|
|
1744
819
|
case "USER_ACTION_EXPIRED": {
|
|
1745
820
|
appendThinkingText(state, "\n\u2717 " + (event.message || "Verification expired"));
|
|
1746
|
-
|
|
821
|
+
completeLastInProgressStep(state.steps);
|
|
1747
822
|
state.userActionRequest = void 0;
|
|
1748
823
|
state.userActionPending = false;
|
|
1749
824
|
const stepId = `step-${state.stepCounter++}`;
|
|
@@ -1773,7 +848,7 @@ function processStreamEventV2(event, state) {
|
|
|
1773
848
|
}
|
|
1774
849
|
case "USER_ACTION_FAILED": {
|
|
1775
850
|
appendThinkingText(state, "\n\u2717 " + (event.message || "Verification failed"));
|
|
1776
|
-
|
|
851
|
+
completeLastInProgressStep(state.steps);
|
|
1777
852
|
state.userActionRequest = void 0;
|
|
1778
853
|
state.userActionPending = false;
|
|
1779
854
|
const stepId = `step-${state.stepCounter++}`;
|
|
@@ -1811,8 +886,58 @@ function processStreamEventV2(event, state) {
|
|
|
1811
886
|
return state;
|
|
1812
887
|
}
|
|
1813
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
|
+
|
|
1814
939
|
// src/hooks/useStreamManagerV2.ts
|
|
1815
|
-
var
|
|
940
|
+
var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
|
|
1816
941
|
function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForResponse) {
|
|
1817
942
|
const abortControllerRef = react.useRef(null);
|
|
1818
943
|
const configRef = react.useRef(config);
|
|
@@ -1860,8 +985,8 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1860
985
|
const currentMessage = activeStep?.message || lastInProgressStep?.message || getEventMessage(event);
|
|
1861
986
|
if (state.hasError) {
|
|
1862
987
|
updateMessage({
|
|
1863
|
-
streamingContent:
|
|
1864
|
-
content:
|
|
988
|
+
streamingContent: FRIENDLY_ERROR_MESSAGE,
|
|
989
|
+
content: FRIENDLY_ERROR_MESSAGE,
|
|
1865
990
|
streamProgress: "error",
|
|
1866
991
|
isError: true,
|
|
1867
992
|
errorDetails: state.errorMessage,
|
|
@@ -1911,7 +1036,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1911
1036
|
isError: !isAborted,
|
|
1912
1037
|
isCancelled: isAborted,
|
|
1913
1038
|
errorDetails: isAborted ? void 0 : error.message,
|
|
1914
|
-
content: isAborted ? state.finalResponse || "" : state.finalResponse ||
|
|
1039
|
+
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1915
1040
|
currentMessage: isAborted ? "Thinking..." : void 0,
|
|
1916
1041
|
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1917
1042
|
steps: [...state.steps].map((step) => {
|
|
@@ -1943,7 +1068,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1943
1068
|
id: streamingId,
|
|
1944
1069
|
sessionId: state.sessionId || sessionId,
|
|
1945
1070
|
role: "assistant",
|
|
1946
|
-
content: state.hasError ?
|
|
1071
|
+
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.finalResponse || "",
|
|
1947
1072
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1948
1073
|
isStreaming: false,
|
|
1949
1074
|
streamProgress: state.hasError ? "error" : "completed",
|
|
@@ -2014,7 +1139,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
2014
1139
|
isError: !isAborted,
|
|
2015
1140
|
isCancelled: isAborted,
|
|
2016
1141
|
errorDetails: isAborted ? void 0 : error.message,
|
|
2017
|
-
content: isAborted ? state.finalResponse || "" : state.finalResponse ||
|
|
1142
|
+
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
2018
1143
|
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
2019
1144
|
steps: [...state.steps].map((step) => {
|
|
2020
1145
|
if (step.status === "in_progress" && isAborted) {
|
|
@@ -2041,45 +1166,199 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
2041
1166
|
};
|
|
2042
1167
|
}
|
|
2043
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
|
+
|
|
2044
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
|
+
}
|
|
2045
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;
|
|
2046
1307
|
const [messages, setMessages] = react.useState(() => {
|
|
2047
|
-
|
|
2048
|
-
|
|
1308
|
+
const stored = chatStore.get(scopeKey);
|
|
1309
|
+
if (stored.length > 0) return stored;
|
|
1310
|
+
return config.session?.initialMessages ?? [];
|
|
2049
1311
|
});
|
|
2050
1312
|
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2053
|
-
);
|
|
2054
|
-
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);
|
|
2055
1318
|
const callbacksRef = react.useRef(callbacks);
|
|
2056
1319
|
callbacksRef.current = callbacks;
|
|
2057
1320
|
const configRef = react.useRef(config);
|
|
2058
1321
|
configRef.current = config;
|
|
1322
|
+
const scopeKeyRef = react.useRef(scopeKey);
|
|
1323
|
+
scopeKeyRef.current = scopeKey;
|
|
2059
1324
|
const messagesRef = react.useRef(messages);
|
|
2060
1325
|
messagesRef.current = messages;
|
|
2061
1326
|
const storeAwareSetMessages = react.useCallback(
|
|
2062
1327
|
(updater) => {
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
|
|
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;
|
|
2066
1342
|
}
|
|
2067
1343
|
setMessages(updater);
|
|
2068
1344
|
},
|
|
2069
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2070
1345
|
[]
|
|
2071
1346
|
);
|
|
2072
|
-
const storeAwareSetIsWaiting = react.useCallback(
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
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);
|
|
2077
1354
|
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
1355
|
+
if (sessionIdRef.current === streamSid) {
|
|
1356
|
+
setIsWaitingForResponse(waiting);
|
|
1357
|
+
}
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
setIsWaitingForResponse(waiting);
|
|
1361
|
+
}, []);
|
|
2083
1362
|
const [userActionState, setUserActionState] = react.useState({
|
|
2084
1363
|
request: null,
|
|
2085
1364
|
result: null,
|
|
@@ -2087,38 +1366,43 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2087
1366
|
});
|
|
2088
1367
|
const userActionStateRef = react.useRef(userActionState);
|
|
2089
1368
|
userActionStateRef.current = userActionState;
|
|
2090
|
-
const wrappedCallbacks = react.useMemo(
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
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);
|
|
2117
1402
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
}), []);
|
|
1403
|
+
}),
|
|
1404
|
+
[]
|
|
1405
|
+
);
|
|
2122
1406
|
const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
|
|
2123
1407
|
config,
|
|
2124
1408
|
wrappedCallbacks,
|
|
@@ -2128,8 +1412,10 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2128
1412
|
const sendMessage = react.useCallback(
|
|
2129
1413
|
async (userMessage) => {
|
|
2130
1414
|
if (!userMessage.trim()) return;
|
|
2131
|
-
|
|
1415
|
+
const autoGen = configRef.current.session?.autoGenerateId;
|
|
1416
|
+
if (!sessionIdRef.current && autoGen !== false) {
|
|
2132
1417
|
sessionIdRef.current = generateId();
|
|
1418
|
+
setCurrentSessionId(sessionIdRef.current);
|
|
2133
1419
|
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
2134
1420
|
}
|
|
2135
1421
|
const userMessageId = `user-${Date.now()}`;
|
|
@@ -2161,38 +1447,40 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2161
1447
|
};
|
|
2162
1448
|
setMessages((prev) => [...prev, streamingMsg]);
|
|
2163
1449
|
const abortController = new AbortController();
|
|
2164
|
-
const
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
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);
|
|
2169
1456
|
const newSessionId = await startStream(
|
|
2170
1457
|
userMessage,
|
|
2171
1458
|
streamingId,
|
|
2172
|
-
|
|
1459
|
+
streamSessionId,
|
|
2173
1460
|
abortController
|
|
2174
1461
|
);
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
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) {
|
|
2179
1465
|
sessionIdRef.current = newSessionId;
|
|
1466
|
+
setCurrentSessionId(newSessionId);
|
|
2180
1467
|
}
|
|
2181
1468
|
},
|
|
2182
1469
|
[startStream]
|
|
2183
1470
|
);
|
|
2184
1471
|
const clearMessages = react.useCallback(() => {
|
|
2185
|
-
|
|
2186
|
-
chatStore.delete(configRef.current.userId);
|
|
2187
|
-
}
|
|
1472
|
+
chatStore.delete(scopeKeyRef.current);
|
|
2188
1473
|
setMessages([]);
|
|
2189
1474
|
}, []);
|
|
2190
1475
|
const prependMessages = react.useCallback((msgs) => {
|
|
2191
1476
|
setMessages((prev) => [...msgs, ...prev]);
|
|
2192
1477
|
}, []);
|
|
2193
1478
|
const cancelStream = react.useCallback(() => {
|
|
2194
|
-
|
|
2195
|
-
|
|
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);
|
|
2196
1484
|
}
|
|
2197
1485
|
cancelStreamManager();
|
|
2198
1486
|
setIsWaitingForResponse(false);
|
|
@@ -2202,10 +1490,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2202
1490
|
if (msg.isStreaming) {
|
|
2203
1491
|
return {
|
|
2204
1492
|
...msg,
|
|
2205
|
-
...createCancelledMessageUpdate(
|
|
2206
|
-
msg.steps || [],
|
|
2207
|
-
msg.currentMessage
|
|
2208
|
-
)
|
|
1493
|
+
...createCancelledMessageUpdate(msg.steps || [], msg.currentMessage)
|
|
2209
1494
|
};
|
|
2210
1495
|
}
|
|
2211
1496
|
return msg;
|
|
@@ -2213,39 +1498,37 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2213
1498
|
);
|
|
2214
1499
|
}, [cancelStreamManager]);
|
|
2215
1500
|
const resetSession = react.useCallback(() => {
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
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);
|
|
2219
1506
|
}
|
|
1507
|
+
chatStore.delete(scope);
|
|
2220
1508
|
setMessages([]);
|
|
2221
1509
|
sessionIdRef.current = void 0;
|
|
1510
|
+
setCurrentSessionId(void 0);
|
|
1511
|
+
activeStreamSessionRef.current = void 0;
|
|
2222
1512
|
abortControllerRef.current?.abort();
|
|
2223
1513
|
setIsWaitingForResponse(false);
|
|
2224
1514
|
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
2225
1515
|
}, []);
|
|
2226
|
-
const getSessionId = react.useCallback(() =>
|
|
2227
|
-
|
|
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
|
+
}
|
|
2228
1531
|
}, []);
|
|
2229
|
-
const getMessages = react.useCallback(() => {
|
|
2230
|
-
return messages;
|
|
2231
|
-
}, [messages]);
|
|
2232
|
-
const approveUserAction = react.useCallback(
|
|
2233
|
-
async (otp) => {
|
|
2234
|
-
const request = userActionStateRef.current.request;
|
|
2235
|
-
if (!request) return;
|
|
2236
|
-
try {
|
|
2237
|
-
await submitUserAction(configRef.current, request.userActionId, { otp });
|
|
2238
|
-
} catch (error) {
|
|
2239
|
-
setUserActionState((prev) => ({
|
|
2240
|
-
...prev,
|
|
2241
|
-
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
2242
|
-
}));
|
|
2243
|
-
callbacksRef.current.onError?.(error);
|
|
2244
|
-
throw error;
|
|
2245
|
-
}
|
|
2246
|
-
},
|
|
2247
|
-
[]
|
|
2248
|
-
);
|
|
2249
1532
|
const rejectUserAction = react.useCallback(async () => {
|
|
2250
1533
|
const request = userActionStateRef.current.request;
|
|
2251
1534
|
if (!request) return;
|
|
@@ -2279,43 +1562,91 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2279
1562
|
throw error;
|
|
2280
1563
|
}
|
|
2281
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
|
+
}, []);
|
|
2282
1619
|
react.useEffect(() => {
|
|
2283
|
-
const
|
|
2284
|
-
|
|
2285
|
-
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1620
|
+
const key = streamKeyFor(scopeKey, currentSessionId);
|
|
1621
|
+
const unsubscribe = activeStreamStore.subscribe(key, (msgs, isWaiting) => {
|
|
2286
1622
|
setMessages(msgs);
|
|
2287
1623
|
setIsWaitingForResponse(isWaiting);
|
|
2288
1624
|
});
|
|
2289
|
-
const active = activeStreamStore.get(
|
|
1625
|
+
const active = activeStreamStore.get(key);
|
|
2290
1626
|
if (active) {
|
|
2291
1627
|
setMessages(active.messages);
|
|
2292
1628
|
setIsWaitingForResponse(active.isWaiting);
|
|
2293
1629
|
}
|
|
2294
1630
|
return unsubscribe;
|
|
2295
|
-
}, []);
|
|
1631
|
+
}, [scopeKey, currentSessionId]);
|
|
2296
1632
|
react.useEffect(() => {
|
|
2297
|
-
if (!config.userId) return;
|
|
2298
1633
|
const toSave = messages.filter((m) => !m.isStreaming);
|
|
2299
1634
|
if (toSave.length > 0) {
|
|
2300
|
-
chatStore.set(
|
|
1635
|
+
chatStore.set(scopeKey, toSave);
|
|
2301
1636
|
}
|
|
2302
|
-
}, [messages,
|
|
1637
|
+
}, [messages, scopeKey]);
|
|
2303
1638
|
react.useEffect(() => {
|
|
2304
|
-
const
|
|
2305
|
-
|
|
2306
|
-
if (
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
setMessages(stored);
|
|
2316
|
-
sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
|
|
2317
|
-
}
|
|
2318
|
-
}, [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]);
|
|
2319
1650
|
return {
|
|
2320
1651
|
messages,
|
|
2321
1652
|
sendMessage,
|
|
@@ -2326,11 +1657,13 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2326
1657
|
getSessionId,
|
|
2327
1658
|
getMessages,
|
|
2328
1659
|
isWaitingForResponse,
|
|
2329
|
-
sessionId:
|
|
1660
|
+
sessionId: currentSessionId,
|
|
2330
1661
|
userActionState,
|
|
2331
1662
|
approveUserAction,
|
|
2332
1663
|
rejectUserAction,
|
|
2333
|
-
resendOtp
|
|
1664
|
+
resendOtp,
|
|
1665
|
+
loadSession,
|
|
1666
|
+
loadingSessionId
|
|
2334
1667
|
};
|
|
2335
1668
|
}
|
|
2336
1669
|
function useVoice() {
|
|
@@ -2450,14 +1783,16 @@ function useVoice() {
|
|
|
2450
1783
|
}
|
|
2451
1784
|
|
|
2452
1785
|
exports.buildFormattedThinking = buildFormattedThinking;
|
|
1786
|
+
exports.buildScopeKey = buildScopeKey;
|
|
2453
1787
|
exports.cancelUserAction = cancelUserAction;
|
|
2454
1788
|
exports.createInitialV2State = createInitialV2State;
|
|
2455
1789
|
exports.generateId = generateId;
|
|
1790
|
+
exports.listConversations = listConversations;
|
|
1791
|
+
exports.listSessions = listSessions;
|
|
2456
1792
|
exports.processStreamEventV2 = processStreamEventV2;
|
|
2457
1793
|
exports.resendUserAction = resendUserAction;
|
|
2458
1794
|
exports.streamWorkflowEvents = streamWorkflowEvents;
|
|
2459
1795
|
exports.submitUserAction = submitUserAction;
|
|
2460
|
-
exports.useChat = useChat;
|
|
2461
1796
|
exports.useChatV2 = useChatV2;
|
|
2462
1797
|
exports.useVoice = useVoice;
|
|
2463
1798
|
exports.workingPhaseDetailForDisplay = workingPhaseDetailForDisplay;
|