@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.
@@ -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/useChat.ts
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 sessionOwner = config.sessionParams;
672
- const sessionAttributes = sessionOwner?.attributes && Object.keys(sessionOwner.attributes).length > 0 ? sessionOwner.attributes : void 0;
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.workflowName,
325
+ workflowName: config.workflow.name,
675
326
  userInput: userMessage,
676
327
  sessionId,
677
- sessionOwnerId: sessionOwner?.id || "",
678
- sessionOwnerLabel: sessionOwner?.name || "",
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 stage = config.stage || "DEV";
688
- const stageParamName = config.stageQueryParam ?? "stage";
689
- const queryParams = new URLSearchParams({ [stageParamName]: stage });
690
- if (config.workflowVersion !== void 0) {
691
- queryParams.append("workflowVersion", String(config.workflowVersion));
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 buildUserActionUrl(config, userActionId, action) {
352
+ function deriveBasePath(config) {
696
353
  const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
697
354
  const [endpointPath] = endpoint.split("?");
698
- const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
699
- const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
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
- const streamEndpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
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 completeLastInProgressStep2(steps) {
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
- completeLastInProgressStep2(state.steps);
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
- completeLastInProgressStep2(state.steps);
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
- completeLastInProgressStep2(state.steps);
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
- completeLastInProgressStep2(state.steps);
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
- completeLastInProgressStep2(state.steps);
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
- completeLastInProgressStep2(state.steps);
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 FRIENDLY_ERROR_MESSAGE2 = "Oops, something went wrong. Please try again.";
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: FRIENDLY_ERROR_MESSAGE2,
1864
- content: FRIENDLY_ERROR_MESSAGE2,
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 || FRIENDLY_ERROR_MESSAGE2,
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 ? FRIENDLY_ERROR_MESSAGE2 : state.finalResponse || "",
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 || FRIENDLY_ERROR_MESSAGE2,
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
- if (config.userId) return chatStore.get(config.userId);
2048
- return config.initialMessages ?? [];
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 sessionIdRef = react.useRef(
2052
- config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
2053
- );
2054
- const prevUserIdRef = react.useRef(config.userId);
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 { userId } = configRef.current;
2064
- if (userId && activeStreamStore.has(userId)) {
2065
- activeStreamStore.applyMessages(userId, updater);
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
- (waiting) => {
2074
- const { userId } = configRef.current;
2075
- if (userId && activeStreamStore.has(userId)) {
2076
- activeStreamStore.setWaiting(userId, waiting);
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
- setIsWaitingForResponse(waiting);
2079
- },
2080
- // eslint-disable-next-line react-hooks/exhaustive-deps
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
- ...callbacksRef.current,
2092
- onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
2093
- onStreamStart: () => callbacksRef.current.onStreamStart?.(),
2094
- onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
2095
- onError: (error) => callbacksRef.current.onError?.(error),
2096
- onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
2097
- onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
2098
- onUserActionRequired: (request) => {
2099
- setUserActionState((prev) => ({ ...prev, request, result: null }));
2100
- callbacksRef.current.onUserActionRequired?.(request);
2101
- },
2102
- onUserActionEvent: (eventType, message) => {
2103
- switch (eventType) {
2104
- case "USER_ACTION_SUCCESS":
2105
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
2106
- break;
2107
- case "USER_ACTION_REJECTED":
2108
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
2109
- break;
2110
- case "USER_ACTION_EXPIRED":
2111
- case "USER_ACTION_FAILED":
2112
- setUserActionState((prev) => ({ ...prev, request: null }));
2113
- break;
2114
- case "USER_ACTION_INVALID":
2115
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
2116
- break;
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
- callbacksRef.current.onUserActionEvent?.(eventType, message);
2119
- }
2120
- // eslint-disable-next-line react-hooks/exhaustive-deps
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
- if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
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 { userId } = configRef.current;
2165
- if (userId) {
2166
- const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
2167
- activeStreamStore.start(userId, abortController, initialMessages);
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
- sessionIdRef.current,
1459
+ streamSessionId,
2173
1460
  abortController
2174
1461
  );
2175
- if (userId) {
2176
- activeStreamStore.complete(userId);
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
- if (configRef.current.userId) {
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
- if (configRef.current.userId) {
2195
- activeStreamStore.abort(configRef.current.userId);
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
- if (configRef.current.userId) {
2217
- activeStreamStore.abort(configRef.current.userId);
2218
- chatStore.delete(configRef.current.userId);
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
- return sessionIdRef.current;
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 { userId } = config;
2284
- if (!userId) return;
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(userId);
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(config.userId, toSave);
1635
+ chatStore.set(scopeKey, toSave);
2301
1636
  }
2302
- }, [messages, config.userId]);
1637
+ }, [messages, scopeKey]);
2303
1638
  react.useEffect(() => {
2304
- const prevUserId = prevUserIdRef.current;
2305
- prevUserIdRef.current = config.userId;
2306
- if (prevUserId === config.userId) return;
2307
- if (prevUserId && !config.userId) {
2308
- chatStore.delete(prevUserId);
2309
- setMessages([]);
2310
- sessionIdRef.current = void 0;
2311
- setIsWaitingForResponse(false);
2312
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
2313
- } else if (config.userId) {
2314
- const stored = chatStore.get(config.userId);
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: sessionIdRef.current,
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;