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