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