@paymanai/payman-typescript-ask-sdk 1.2.11 → 2.0.1

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() {
@@ -147,13 +147,13 @@ function getEventMessage(event) {
147
147
  case "USER_ACTION_REQUIRED":
148
148
  return "Waiting for verification...";
149
149
  case "USER_ACTION_SUCCESS":
150
- return "Verification successful";
150
+ return "Verification approved";
151
151
  case "USER_ACTION_EXPIRED":
152
152
  return "Verification expired";
153
153
  case "USER_ACTION_INVALID":
154
- return "Invalid input, please try again";
154
+ return "Invalid code. Please try again.";
155
155
  case "USER_ACTION_REJECTED":
156
- return "Verification rejected";
156
+ return "Verification cancelled";
157
157
  case "USER_ACTION_RESENT":
158
158
  return "Verification code resent";
159
159
  case "USER_ACTION_FAILED":
@@ -168,6 +168,31 @@ function getEventMessage(event) {
168
168
  return eventType;
169
169
  }
170
170
  }
171
+ function isUserActionPrompt(text) {
172
+ return /\benter\b.+\b(code|otp)\b/i.test(text) || /\b(authorization|verification)\s+code\b/i.test(text) || /\bsent\s+to\s+your\b/i.test(text);
173
+ }
174
+ function getUserActionDisplayMessage(eventType, rawMessage) {
175
+ const raw = rawMessage?.trim();
176
+ const safeRaw = raw && !isUserActionPrompt(raw) ? raw : "";
177
+ switch (eventType) {
178
+ case "USER_ACTION_REQUIRED":
179
+ return raw || "Waiting for verification...";
180
+ case "USER_ACTION_SUCCESS":
181
+ return safeRaw || "Verification approved";
182
+ case "USER_ACTION_INVALID":
183
+ return safeRaw || "Invalid code. Please try again.";
184
+ case "USER_ACTION_REJECTED":
185
+ return safeRaw || "Verification cancelled";
186
+ case "USER_ACTION_EXPIRED":
187
+ return safeRaw || "Verification expired";
188
+ case "USER_ACTION_RESENT":
189
+ return safeRaw || "Verification code resent";
190
+ case "USER_ACTION_FAILED":
191
+ return safeRaw || "Verification failed";
192
+ default:
193
+ return safeRaw || raw || eventType;
194
+ }
195
+ }
171
196
  function workingPhaseDetailForDisplay(raw) {
172
197
  const t = raw.trim();
173
198
  if (!t) return "";
@@ -198,290 +223,8 @@ function extractResponseContent(response) {
198
223
  }
199
224
  return "";
200
225
  }
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
226
 
483
227
  // src/utils/messageStateManager.ts
484
- var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
485
228
  function buildFormattedThinking(steps, allThinkingText) {
486
229
  const parts = [];
487
230
  const safeSteps = steps ?? [];
@@ -538,91 +281,32 @@ function buildFormattedThinking(steps, allThinkingText) {
538
281
  break;
539
282
  case "USER_ACTION_REQUIRED":
540
283
  parts.push("**Verification Required**");
541
- if (step.message) parts.push(step.message);
284
+ if (step.message) {
285
+ parts.push(getUserActionDisplayMessage(step.eventType, step.message));
286
+ }
542
287
  break;
543
288
  case "USER_ACTION_SUCCESS":
544
- parts.push(`\u2713 ${step.message || "Verification successful"}`);
289
+ parts.push(`\u2713 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
545
290
  break;
546
291
  case "USER_ACTION_REJECTED":
547
- parts.push(`\u2717 ${step.message || "Verification rejected"}`);
292
+ parts.push(getUserActionDisplayMessage(step.eventType, step.message));
548
293
  break;
549
294
  case "USER_ACTION_EXPIRED":
550
- parts.push(`\u2717 ${step.message || "Verification expired"}`);
295
+ parts.push(`\u2717 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
296
+ break;
297
+ case "USER_ACTION_INVALID":
298
+ parts.push(`\u2717 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
299
+ break;
300
+ case "USER_ACTION_RESENT":
301
+ parts.push(getUserActionDisplayMessage(step.eventType, step.message));
551
302
  break;
552
303
  case "USER_ACTION_FAILED":
553
- parts.push(`\u2717 ${step.message || "Verification failed"}`);
304
+ parts.push(`\u2717 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
554
305
  break;
555
306
  }
556
307
  }
557
308
  return parts.length > 0 ? parts.join("\n") : allThinkingText;
558
309
  }
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
310
  function createCancelledMessageUpdate(steps, currentMessage) {
627
311
  const updatedSteps = steps.map((step) => {
628
312
  if (step.status === "in_progress") {
@@ -642,35 +326,44 @@ function createCancelledMessageUpdate(steps, currentMessage) {
642
326
 
643
327
  // src/utils/requestBuilder.ts
644
328
  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;
329
+ const owner = config.session?.owner;
330
+ const sessionAttributes = owner?.attributes && Object.keys(owner.attributes).length > 0 ? owner.attributes : void 0;
647
331
  return {
648
- workflowName: config.workflowName,
332
+ workflowName: config.workflow.name,
649
333
  userInput: userMessage,
650
334
  sessionId,
651
- sessionOwnerId: sessionOwner?.id || "",
652
- sessionOwnerLabel: sessionOwner?.name || "",
335
+ sessionOwnerId: owner?.id || "",
336
+ sessionOwnerLabel: owner?.name || "",
653
337
  sessionAttributes,
654
338
  options: {
655
339
  clientTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
656
340
  }
657
341
  };
658
342
  }
343
+ function getStageParamName(config) {
344
+ return config.api.stageQueryParam ?? "stage";
345
+ }
346
+ function getStage(config) {
347
+ return config.workflow.stage ?? "DEV";
348
+ }
659
349
  function buildStreamingUrl(config) {
660
350
  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));
351
+ const queryParams = new URLSearchParams({
352
+ [getStageParamName(config)]: getStage(config)
353
+ });
354
+ if (config.workflow.version !== void 0) {
355
+ queryParams.append("workflowVersion", String(config.workflow.version));
666
356
  }
667
357
  return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
668
358
  }
669
- function buildUserActionUrl(config, userActionId, action) {
359
+ function deriveBasePath(config) {
670
360
  const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
671
361
  const [endpointPath] = endpoint.split("?");
672
- const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
673
- const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
362
+ const normalized = endpointPath.replace(/\/+$/, "");
363
+ return normalized.endsWith("/stream") ? normalized.slice(0, -"/stream".length) : normalized;
364
+ }
365
+ function buildUserActionUrl(config, userActionId, action) {
366
+ const basePath = deriveBasePath(config);
674
367
  const encodedUserActionId = encodeURIComponent(userActionId);
675
368
  return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
676
369
  }
@@ -678,21 +371,23 @@ function buildResolveImagesUrl(config) {
678
371
  if (config.api.resolveImagesEndpoint) {
679
372
  return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
680
373
  }
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`;
374
+ return `${config.api.baseUrl}${deriveBasePath(config)}/resolve-image-urls`;
686
375
  }
687
376
  function buildRequestHeaders(config) {
688
- const headers = {
689
- ...config.api.headers
690
- };
377
+ const headers = { ...config.api.headers };
691
378
  if (config.api.authToken) {
692
379
  headers.Authorization = `Bearer ${config.api.authToken}`;
693
380
  }
694
381
  return headers;
695
382
  }
383
+ function buildScopeKey(config) {
384
+ return [
385
+ config.session?.userId ?? "",
386
+ config.workflow.id ?? "",
387
+ config.workflow.version ?? "",
388
+ config.workflow.stage ?? ""
389
+ ].join("|");
390
+ }
696
391
 
697
392
  // src/utils/userActionClient.ts
698
393
  async function sendUserActionRequest(config, userActionId, action, data) {
@@ -708,683 +403,96 @@ async function sendUserActionRequest(config, userActionId, action, data) {
708
403
  if (!response.ok) {
709
404
  const errorText = await response.text();
710
405
  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
- };
406
+ }
407
+ return await response.json();
408
+ }
409
+ async function submitUserAction(config, userActionId, data) {
410
+ return sendUserActionRequest(config, userActionId, "submit", data);
411
+ }
412
+ async function cancelUserAction(config, userActionId) {
413
+ return sendUserActionRequest(config, userActionId, "cancel");
1387
414
  }
415
+ async function resendUserAction(config, userActionId) {
416
+ return sendUserActionRequest(config, userActionId, "resend");
417
+ }
418
+
419
+ // src/utils/chatStore.ts
420
+ var memoryStore = /* @__PURE__ */ new Map();
421
+ var chatStore = {
422
+ get(key) {
423
+ return memoryStore.get(key) ?? [];
424
+ },
425
+ set(key, messages) {
426
+ memoryStore.set(key, messages);
427
+ },
428
+ delete(key) {
429
+ memoryStore.delete(key);
430
+ }
431
+ };
432
+
433
+ // src/utils/activeStreamStore.ts
434
+ var streams = /* @__PURE__ */ new Map();
435
+ var activeStreamStore = {
436
+ has(key) {
437
+ return streams.has(key);
438
+ },
439
+ get(key) {
440
+ const entry = streams.get(key);
441
+ if (!entry) return null;
442
+ return { messages: entry.messages, isWaiting: entry.isWaiting };
443
+ },
444
+ // Called before startStream — registers the controller and initial messages
445
+ start(key, abortController, initialMessages) {
446
+ const existing = streams.get(key);
447
+ streams.set(key, {
448
+ messages: initialMessages,
449
+ isWaiting: true,
450
+ abortController,
451
+ listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
452
+ });
453
+ },
454
+ // Called by the stream on every event — applies the same updater pattern React uses
455
+ applyMessages(key, updater) {
456
+ const entry = streams.get(key);
457
+ if (!entry) return;
458
+ const next = typeof updater === "function" ? updater(entry.messages) : updater;
459
+ entry.messages = next;
460
+ entry.listeners.forEach((l) => l(next, entry.isWaiting));
461
+ },
462
+ setWaiting(key, waiting) {
463
+ const entry = streams.get(key);
464
+ if (!entry) return;
465
+ entry.isWaiting = waiting;
466
+ entry.listeners.forEach((l) => l(entry.messages, waiting));
467
+ },
468
+ // Called when stream completes — persists to chatStore and cleans up
469
+ complete(key) {
470
+ const entry = streams.get(key);
471
+ if (!entry) return;
472
+ entry.isWaiting = false;
473
+ entry.listeners.forEach((l) => l(entry.messages, false));
474
+ const toSave = entry.messages.filter((m) => !m.isStreaming);
475
+ if (toSave.length > 0) chatStore.set(key, toSave);
476
+ streams.delete(key);
477
+ },
478
+ // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
479
+ subscribe(key, listener) {
480
+ const entry = streams.get(key);
481
+ if (!entry) return () => {
482
+ };
483
+ entry.listeners.add(listener);
484
+ return () => {
485
+ streams.get(key)?.listeners.delete(listener);
486
+ };
487
+ },
488
+ // Explicit user cancel — aborts the controller and removes the entry
489
+ abort(key) {
490
+ const entry = streams.get(key);
491
+ if (!entry) return;
492
+ entry.abortController.abort();
493
+ streams.delete(key);
494
+ }
495
+ };
1388
496
 
1389
497
  // src/utils/v2EventProcessor.ts
1390
498
  function getEventText(event, field) {
@@ -1410,7 +518,7 @@ function addThinkingLine(state, header, detail) {
1410
518
  function appendThinkingText(state, text) {
1411
519
  state.formattedThinkingText += text;
1412
520
  }
1413
- function completeLastInProgressStep2(steps) {
521
+ function completeLastInProgressStep(steps) {
1414
522
  for (let i = steps.length - 1; i >= 0; i--) {
1415
523
  if (steps[i].status === "in_progress") {
1416
524
  steps[i].status = "completed";
@@ -1624,7 +732,7 @@ function processStreamEventV2(event, state) {
1624
732
  break;
1625
733
  }
1626
734
  case "USER_ACTION_REQUIRED": {
1627
- completeLastInProgressStep2(state.steps);
735
+ completeLastInProgressStep(state.steps);
1628
736
  if (event.userActionRequest) {
1629
737
  state.userActionRequest = {
1630
738
  userActionId: event.userActionRequest.userActionId,
@@ -1636,14 +744,18 @@ function processStreamEventV2(event, state) {
1636
744
  }
1637
745
  state.userActionPending = true;
1638
746
  const req = event.userActionRequest;
747
+ const displayMessage = getUserActionDisplayMessage(
748
+ eventType,
749
+ req?.message || message
750
+ );
1639
751
  if (req) {
1640
- addThinkingLine(state, "**Verification Required**", req.message || "Waiting for authorization...");
752
+ addThinkingLine(state, "**Verification Required**", displayMessage);
1641
753
  }
1642
754
  const stepId = `step-${state.stepCounter++}`;
1643
755
  state.steps.push({
1644
756
  id: stepId,
1645
757
  eventType,
1646
- message,
758
+ message: displayMessage,
1647
759
  status: "in_progress",
1648
760
  timestamp: Date.now(),
1649
761
  elapsedMs: event.elapsedMs
@@ -1653,8 +765,9 @@ function processStreamEventV2(event, state) {
1653
765
  break;
1654
766
  }
1655
767
  case "USER_ACTION_SUCCESS": {
1656
- appendThinkingText(state, "\n\u2713 " + (event.message || "Verification successful"));
1657
- completeLastInProgressStep2(state.steps);
768
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
769
+ appendThinkingText(state, "\n\u2713 " + displayMessage);
770
+ completeLastInProgressStep(state.steps);
1658
771
  state.userActionRequest = void 0;
1659
772
  state.userActionPending = false;
1660
773
  state.userActionResult = "approved";
@@ -1662,7 +775,7 @@ function processStreamEventV2(event, state) {
1662
775
  state.steps.push({
1663
776
  id: stepId,
1664
777
  eventType,
1665
- message,
778
+ message: displayMessage,
1666
779
  status: "completed",
1667
780
  timestamp: Date.now(),
1668
781
  elapsedMs: event.elapsedMs
@@ -1671,12 +784,14 @@ function processStreamEventV2(event, state) {
1671
784
  break;
1672
785
  }
1673
786
  case "USER_ACTION_INVALID": {
1674
- completeLastInProgressStep2(state.steps);
787
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
788
+ appendThinkingText(state, "\n\u2717 " + displayMessage);
789
+ completeLastInProgressStep(state.steps);
1675
790
  const errorStepId = `step-${state.stepCounter++}`;
1676
791
  state.steps.push({
1677
792
  id: errorStepId,
1678
793
  eventType,
1679
- message,
794
+ message: displayMessage,
1680
795
  status: "error",
1681
796
  timestamp: Date.now(),
1682
797
  elapsedMs: event.elapsedMs
@@ -1694,8 +809,9 @@ function processStreamEventV2(event, state) {
1694
809
  break;
1695
810
  }
1696
811
  case "USER_ACTION_REJECTED": {
1697
- appendThinkingText(state, "\n\u2717 " + (event.message || "Verification rejected"));
1698
- completeLastInProgressStep2(state.steps);
812
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
813
+ appendThinkingText(state, "\n" + displayMessage);
814
+ completeLastInProgressStep(state.steps);
1699
815
  state.userActionRequest = void 0;
1700
816
  state.userActionPending = false;
1701
817
  state.userActionResult = "rejected";
@@ -1703,7 +819,7 @@ function processStreamEventV2(event, state) {
1703
819
  state.steps.push({
1704
820
  id: stepId,
1705
821
  eventType,
1706
- message,
822
+ message: displayMessage,
1707
823
  status: "completed",
1708
824
  timestamp: Date.now(),
1709
825
  elapsedMs: event.elapsedMs
@@ -1712,15 +828,16 @@ function processStreamEventV2(event, state) {
1712
828
  break;
1713
829
  }
1714
830
  case "USER_ACTION_EXPIRED": {
1715
- appendThinkingText(state, "\n\u2717 " + (event.message || "Verification expired"));
1716
- completeLastInProgressStep2(state.steps);
831
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
832
+ appendThinkingText(state, "\n\u2717 " + displayMessage);
833
+ completeLastInProgressStep(state.steps);
1717
834
  state.userActionRequest = void 0;
1718
835
  state.userActionPending = false;
1719
836
  const stepId = `step-${state.stepCounter++}`;
1720
837
  state.steps.push({
1721
838
  id: stepId,
1722
839
  eventType,
1723
- message,
840
+ message: displayMessage,
1724
841
  status: "error",
1725
842
  timestamp: Date.now(),
1726
843
  elapsedMs: event.elapsedMs
@@ -1729,11 +846,13 @@ function processStreamEventV2(event, state) {
1729
846
  break;
1730
847
  }
1731
848
  case "USER_ACTION_RESENT": {
849
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
850
+ appendThinkingText(state, "\n" + displayMessage);
1732
851
  const stepId = `step-${state.stepCounter++}`;
1733
852
  state.steps.push({
1734
853
  id: stepId,
1735
854
  eventType,
1736
- message,
855
+ message: displayMessage,
1737
856
  status: "completed",
1738
857
  timestamp: Date.now(),
1739
858
  elapsedMs: event.elapsedMs
@@ -1742,15 +861,19 @@ function processStreamEventV2(event, state) {
1742
861
  break;
1743
862
  }
1744
863
  case "USER_ACTION_FAILED": {
1745
- appendThinkingText(state, "\n\u2717 " + (event.message || "Verification failed"));
1746
- completeLastInProgressStep2(state.steps);
864
+ const displayMessage = getUserActionDisplayMessage(
865
+ eventType,
866
+ event.message || event.errorMessage
867
+ );
868
+ appendThinkingText(state, "\n\u2717 " + displayMessage);
869
+ completeLastInProgressStep(state.steps);
1747
870
  state.userActionRequest = void 0;
1748
871
  state.userActionPending = false;
1749
872
  const stepId = `step-${state.stepCounter++}`;
1750
873
  state.steps.push({
1751
874
  id: stepId,
1752
875
  eventType,
1753
- message,
876
+ message: displayMessage,
1754
877
  status: "error",
1755
878
  timestamp: Date.now(),
1756
879
  elapsedMs: event.elapsedMs
@@ -1781,8 +904,58 @@ function processStreamEventV2(event, state) {
1781
904
  return state;
1782
905
  }
1783
906
 
907
+ // src/utils/ragImageResolver.ts
908
+ var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
909
+ function hasRagImages(content) {
910
+ return RAG_IMAGE_REGEX.test(content);
911
+ }
912
+ async function waitForNextPaint(signal) {
913
+ if (signal?.aborted) return;
914
+ await new Promise((resolve) => {
915
+ let isSettled = false;
916
+ const finish = () => {
917
+ if (isSettled) return;
918
+ isSettled = true;
919
+ signal?.removeEventListener("abort", finish);
920
+ resolve();
921
+ };
922
+ signal?.addEventListener("abort", finish, { once: true });
923
+ if (typeof requestAnimationFrame === "function") {
924
+ requestAnimationFrame(() => {
925
+ setTimeout(finish, 0);
926
+ });
927
+ return;
928
+ }
929
+ setTimeout(finish, 0);
930
+ });
931
+ }
932
+ async function resolveRagImageUrls(config, content, signal) {
933
+ const url = buildResolveImagesUrl(config);
934
+ const baseHeaders = buildRequestHeaders(config);
935
+ const headers = { "Content-Type": "application/json", ...baseHeaders };
936
+ const response = await fetch(url, {
937
+ method: "POST",
938
+ headers,
939
+ body: JSON.stringify({ input: content }),
940
+ signal
941
+ });
942
+ if (!response.ok) {
943
+ const errorText = await response.text();
944
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
945
+ }
946
+ const text = await response.text();
947
+ try {
948
+ const parsed = JSON.parse(text);
949
+ if (typeof parsed === "string") return parsed;
950
+ if (typeof parsed.output === "string") return parsed.output;
951
+ if (typeof parsed.result === "string") return parsed.result;
952
+ } catch {
953
+ }
954
+ return text;
955
+ }
956
+
1784
957
  // src/hooks/useStreamManagerV2.ts
1785
- var FRIENDLY_ERROR_MESSAGE2 = "Oops, something went wrong. Please try again.";
958
+ var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
1786
959
  function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForResponse) {
1787
960
  const abortControllerRef = react.useRef(null);
1788
961
  const configRef = react.useRef(config);
@@ -1822,7 +995,10 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1822
995
  if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
1823
996
  callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
1824
997
  } else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
1825
- const msg = event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event);
998
+ const msg = getUserActionDisplayMessage(
999
+ eventType,
1000
+ event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event)
1001
+ );
1826
1002
  callbacksRef.current.onUserActionEvent?.(eventType, msg);
1827
1003
  }
1828
1004
  const activeStep = state.steps.find((s) => s.id === state.currentExecutingStepId);
@@ -1830,8 +1006,8 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1830
1006
  const currentMessage = activeStep?.message || lastInProgressStep?.message || getEventMessage(event);
1831
1007
  if (state.hasError) {
1832
1008
  updateMessage({
1833
- streamingContent: FRIENDLY_ERROR_MESSAGE2,
1834
- content: FRIENDLY_ERROR_MESSAGE2,
1009
+ streamingContent: FRIENDLY_ERROR_MESSAGE,
1010
+ content: FRIENDLY_ERROR_MESSAGE,
1835
1011
  streamProgress: "error",
1836
1012
  isError: true,
1837
1013
  errorDetails: state.errorMessage,
@@ -1881,7 +1057,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1881
1057
  isError: !isAborted,
1882
1058
  isCancelled: isAborted,
1883
1059
  errorDetails: isAborted ? void 0 : error.message,
1884
- content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE2,
1060
+ content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1885
1061
  currentMessage: isAborted ? "Thinking..." : void 0,
1886
1062
  formattedThinkingText: state.formattedThinkingText || void 0,
1887
1063
  steps: [...state.steps].map((step) => {
@@ -1913,7 +1089,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1913
1089
  id: streamingId,
1914
1090
  sessionId: state.sessionId || sessionId,
1915
1091
  role: "assistant",
1916
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE2 : state.finalResponse || "",
1092
+ content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.finalResponse || "",
1917
1093
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1918
1094
  isStreaming: false,
1919
1095
  streamProgress: state.hasError ? "error" : "completed",
@@ -1984,7 +1160,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1984
1160
  isError: !isAborted,
1985
1161
  isCancelled: isAborted,
1986
1162
  errorDetails: isAborted ? void 0 : error.message,
1987
- content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE2,
1163
+ content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1988
1164
  formattedThinkingText: state.formattedThinkingText || void 0,
1989
1165
  steps: [...state.steps].map((step) => {
1990
1166
  if (step.status === "in_progress" && isAborted) {
@@ -2011,45 +1187,199 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
2011
1187
  };
2012
1188
  }
2013
1189
 
1190
+ // src/utils/workflowUsersClient.ts
1191
+ var DEFAULT_SESSION_PAGE_SIZE = 20;
1192
+ var DEFAULT_CONVERSATION_PAGE_SIZE = 50;
1193
+ function getStageParamName2(config) {
1194
+ return config.api.stageQueryParam ?? "stage";
1195
+ }
1196
+ function requireOwnerId(config) {
1197
+ const ownerId = config.session?.owner?.id;
1198
+ if (!ownerId) {
1199
+ throw new Error(
1200
+ "workflowUsersClient: session.owner.id is required to call this endpoint."
1201
+ );
1202
+ }
1203
+ return ownerId;
1204
+ }
1205
+ function requireWorkflowName(config) {
1206
+ const workflowName = config.workflow.name;
1207
+ if (!workflowName) {
1208
+ throw new Error(
1209
+ "workflowUsersClient: workflow.name is required to call this endpoint."
1210
+ );
1211
+ }
1212
+ return workflowName;
1213
+ }
1214
+ function requireWorkflowId(config) {
1215
+ const workflowId = config.workflow.id;
1216
+ if (!workflowId) {
1217
+ throw new Error(
1218
+ "workflowUsersClient: workflow.id is required to call this endpoint."
1219
+ );
1220
+ }
1221
+ return workflowId;
1222
+ }
1223
+ function isPlaygroundYaakAuth(config) {
1224
+ if (config.api.authToken) return false;
1225
+ const headers = config.api.headers;
1226
+ if (!headers) return false;
1227
+ for (const key of Object.keys(headers)) {
1228
+ const lower = key.toLowerCase();
1229
+ if (lower === "yaak-api-key" || lower === "x-yaak-api-key") return true;
1230
+ }
1231
+ return false;
1232
+ }
1233
+ async function fetchJson(url, headers, signal) {
1234
+ const response = await fetch(url, {
1235
+ method: "GET",
1236
+ headers,
1237
+ signal
1238
+ });
1239
+ if (!response.ok) {
1240
+ const errorText = await response.text();
1241
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
1242
+ }
1243
+ return await response.json();
1244
+ }
1245
+ async function listSessions(config, opts = {}) {
1246
+ const ownerId = requireOwnerId(config);
1247
+ const useYaakEndpoint = isPlaygroundYaakAuth(config);
1248
+ const params = new URLSearchParams({
1249
+ page: String(opts.page ?? 0),
1250
+ size: String(opts.size ?? DEFAULT_SESSION_PAGE_SIZE)
1251
+ });
1252
+ if (useYaakEndpoint) {
1253
+ params.set("workflowUserId", ownerId);
1254
+ params.set("workflowName", requireWorkflowName(config));
1255
+ } else {
1256
+ params.set("workflowId", requireWorkflowId(config));
1257
+ }
1258
+ if (config.workflow.stage) {
1259
+ params.set(getStageParamName2(config), config.workflow.stage);
1260
+ }
1261
+ const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/sessions?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1262
+ ownerId
1263
+ )}/sessions?${params.toString()}`;
1264
+ return fetchJson(
1265
+ url,
1266
+ buildRequestHeaders(config),
1267
+ opts.signal
1268
+ );
1269
+ }
1270
+ async function listConversations(config, opts) {
1271
+ const ownerId = requireOwnerId(config);
1272
+ const useYaakEndpoint = isPlaygroundYaakAuth(config);
1273
+ const params = new URLSearchParams({
1274
+ sessionId: opts.sessionId,
1275
+ page: String(opts.page ?? 0),
1276
+ size: String(opts.size ?? DEFAULT_CONVERSATION_PAGE_SIZE)
1277
+ });
1278
+ if (useYaakEndpoint) {
1279
+ params.set("workflowUserId", ownerId);
1280
+ params.set("workflowName", requireWorkflowName(config));
1281
+ }
1282
+ if (config.workflow.stage) {
1283
+ params.set(getStageParamName2(config), config.workflow.stage);
1284
+ }
1285
+ const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/conversations?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1286
+ ownerId
1287
+ )}/conversations?${params.toString()}`;
1288
+ return fetchJson(
1289
+ url,
1290
+ buildRequestHeaders(config),
1291
+ opts.signal
1292
+ );
1293
+ }
1294
+
2014
1295
  // src/hooks/useChatV2.ts
1296
+ function conversationEntryToMessages(entry, sessionId) {
1297
+ return [
1298
+ {
1299
+ id: `history-user-${entry.executionId}`,
1300
+ sessionId,
1301
+ role: "user",
1302
+ content: entry.query,
1303
+ timestamp: entry.createdAt,
1304
+ isHistorical: true
1305
+ },
1306
+ {
1307
+ id: `history-assistant-${entry.executionId}`,
1308
+ sessionId,
1309
+ role: "assistant",
1310
+ content: entry.response,
1311
+ timestamp: entry.createdAt,
1312
+ executionId: entry.executionId,
1313
+ isHistorical: true
1314
+ }
1315
+ ];
1316
+ }
1317
+ function streamKeyFor(scopeKey, sessionId) {
1318
+ return `${scopeKey}|sid:${sessionId ?? ""}`;
1319
+ }
2015
1320
  function useChatV2(config, callbacks = {}) {
1321
+ const scopeKey = react.useMemo(() => buildScopeKey(config), [
1322
+ config.session?.userId,
1323
+ config.workflow.id,
1324
+ config.workflow.version,
1325
+ config.workflow.stage
1326
+ ]);
1327
+ const initialSessionId = chatStore.get(scopeKey).find((m) => m.sessionId)?.sessionId ?? config.session?.initialId ?? void 0;
2016
1328
  const [messages, setMessages] = react.useState(() => {
2017
- if (config.userId) return chatStore.get(config.userId);
2018
- return config.initialMessages ?? [];
1329
+ const stored = chatStore.get(scopeKey);
1330
+ if (stored.length > 0) return stored;
1331
+ return config.session?.initialMessages ?? [];
2019
1332
  });
2020
1333
  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);
1334
+ const [loadingSessionId, setLoadingSessionId] = react.useState(void 0);
1335
+ const [currentSessionId, setCurrentSessionId] = react.useState(initialSessionId);
1336
+ const sessionIdRef = react.useRef(initialSessionId);
1337
+ const prevScopeKeyRef = react.useRef(scopeKey);
1338
+ const activeStreamSessionRef = react.useRef(void 0);
2025
1339
  const callbacksRef = react.useRef(callbacks);
2026
1340
  callbacksRef.current = callbacks;
2027
1341
  const configRef = react.useRef(config);
2028
1342
  configRef.current = config;
1343
+ const scopeKeyRef = react.useRef(scopeKey);
1344
+ scopeKeyRef.current = scopeKey;
2029
1345
  const messagesRef = react.useRef(messages);
2030
1346
  messagesRef.current = messages;
2031
1347
  const storeAwareSetMessages = react.useCallback(
2032
1348
  (updater) => {
2033
- const { userId } = configRef.current;
2034
- if (userId && activeStreamStore.has(userId)) {
2035
- activeStreamStore.applyMessages(userId, updater);
1349
+ const scope = scopeKeyRef.current;
1350
+ const streamSid = activeStreamSessionRef.current;
1351
+ if (streamSid !== void 0) {
1352
+ const streamKey = streamKeyFor(scope, streamSid);
1353
+ if (activeStreamStore.has(streamKey)) {
1354
+ activeStreamStore.applyMessages(
1355
+ streamKey,
1356
+ updater
1357
+ );
1358
+ }
1359
+ if (sessionIdRef.current === streamSid) {
1360
+ setMessages(updater);
1361
+ }
1362
+ return;
2036
1363
  }
2037
1364
  setMessages(updater);
2038
1365
  },
2039
- // eslint-disable-next-line react-hooks/exhaustive-deps
2040
1366
  []
2041
1367
  );
2042
- const storeAwareSetIsWaiting = react.useCallback(
2043
- (waiting) => {
2044
- const { userId } = configRef.current;
2045
- if (userId && activeStreamStore.has(userId)) {
2046
- activeStreamStore.setWaiting(userId, waiting);
1368
+ const storeAwareSetIsWaiting = react.useCallback((waiting) => {
1369
+ const scope = scopeKeyRef.current;
1370
+ const streamSid = activeStreamSessionRef.current;
1371
+ if (streamSid !== void 0) {
1372
+ const streamKey = streamKeyFor(scope, streamSid);
1373
+ if (activeStreamStore.has(streamKey)) {
1374
+ activeStreamStore.setWaiting(streamKey, waiting);
2047
1375
  }
2048
- setIsWaitingForResponse(waiting);
2049
- },
2050
- // eslint-disable-next-line react-hooks/exhaustive-deps
2051
- []
2052
- );
1376
+ if (sessionIdRef.current === streamSid) {
1377
+ setIsWaitingForResponse(waiting);
1378
+ }
1379
+ return;
1380
+ }
1381
+ setIsWaitingForResponse(waiting);
1382
+ }, []);
2053
1383
  const [userActionState, setUserActionState] = react.useState({
2054
1384
  request: null,
2055
1385
  result: null,
@@ -2057,38 +1387,43 @@ function useChatV2(config, callbacks = {}) {
2057
1387
  });
2058
1388
  const userActionStateRef = react.useRef(userActionState);
2059
1389
  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;
1390
+ const wrappedCallbacks = react.useMemo(
1391
+ () => ({
1392
+ ...callbacksRef.current,
1393
+ onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
1394
+ onStreamStart: () => callbacksRef.current.onStreamStart?.(),
1395
+ onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
1396
+ onError: (error) => callbacksRef.current.onError?.(error),
1397
+ onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1398
+ onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1399
+ onUserActionRequired: (request) => {
1400
+ setUserActionState((prev) => ({ ...prev, request, result: null }));
1401
+ callbacksRef.current.onUserActionRequired?.(request);
1402
+ },
1403
+ onUserActionEvent: (eventType, message) => {
1404
+ switch (eventType) {
1405
+ case "USER_ACTION_SUCCESS":
1406
+ setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1407
+ break;
1408
+ case "USER_ACTION_REJECTED":
1409
+ setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1410
+ break;
1411
+ case "USER_ACTION_EXPIRED":
1412
+ case "USER_ACTION_FAILED":
1413
+ setUserActionState((prev) => ({ ...prev, request: null }));
1414
+ break;
1415
+ case "USER_ACTION_INVALID":
1416
+ setUserActionState((prev) => ({
1417
+ ...prev,
1418
+ clearOtpTrigger: prev.clearOtpTrigger + 1
1419
+ }));
1420
+ break;
1421
+ }
1422
+ callbacksRef.current.onUserActionEvent?.(eventType, message);
2087
1423
  }
2088
- callbacksRef.current.onUserActionEvent?.(eventType, message);
2089
- }
2090
- // eslint-disable-next-line react-hooks/exhaustive-deps
2091
- }), []);
1424
+ }),
1425
+ []
1426
+ );
2092
1427
  const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
2093
1428
  config,
2094
1429
  wrappedCallbacks,
@@ -2098,8 +1433,10 @@ function useChatV2(config, callbacks = {}) {
2098
1433
  const sendMessage = react.useCallback(
2099
1434
  async (userMessage) => {
2100
1435
  if (!userMessage.trim()) return;
2101
- if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1436
+ const autoGen = configRef.current.session?.autoGenerateId;
1437
+ if (!sessionIdRef.current && autoGen !== false) {
2102
1438
  sessionIdRef.current = generateId();
1439
+ setCurrentSessionId(sessionIdRef.current);
2103
1440
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
2104
1441
  }
2105
1442
  const userMessageId = `user-${Date.now()}`;
@@ -2131,38 +1468,40 @@ function useChatV2(config, callbacks = {}) {
2131
1468
  };
2132
1469
  setMessages((prev) => [...prev, streamingMsg]);
2133
1470
  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
- }
1471
+ const scope = scopeKeyRef.current;
1472
+ const streamSessionId = sessionIdRef.current;
1473
+ const streamKey = streamKeyFor(scope, streamSessionId);
1474
+ activeStreamSessionRef.current = streamSessionId;
1475
+ const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1476
+ activeStreamStore.start(streamKey, abortController, initialMessages);
2139
1477
  const newSessionId = await startStream(
2140
1478
  userMessage,
2141
1479
  streamingId,
2142
- sessionIdRef.current,
1480
+ streamSessionId,
2143
1481
  abortController
2144
1482
  );
2145
- if (userId) {
2146
- activeStreamStore.complete(userId);
2147
- }
2148
- if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1483
+ activeStreamStore.complete(streamKey);
1484
+ activeStreamSessionRef.current = void 0;
1485
+ if (!abortController.signal.aborted && newSessionId && newSessionId !== streamSessionId && sessionIdRef.current === streamSessionId) {
2149
1486
  sessionIdRef.current = newSessionId;
1487
+ setCurrentSessionId(newSessionId);
2150
1488
  }
2151
1489
  },
2152
1490
  [startStream]
2153
1491
  );
2154
1492
  const clearMessages = react.useCallback(() => {
2155
- if (configRef.current.userId) {
2156
- chatStore.delete(configRef.current.userId);
2157
- }
1493
+ chatStore.delete(scopeKeyRef.current);
2158
1494
  setMessages([]);
2159
1495
  }, []);
2160
1496
  const prependMessages = react.useCallback((msgs) => {
2161
1497
  setMessages((prev) => [...msgs, ...prev]);
2162
1498
  }, []);
2163
1499
  const cancelStream = react.useCallback(() => {
2164
- if (configRef.current.userId) {
2165
- activeStreamStore.abort(configRef.current.userId);
1500
+ const scope = scopeKeyRef.current;
1501
+ const viewSid = sessionIdRef.current;
1502
+ const viewStreamKey = streamKeyFor(scope, viewSid);
1503
+ if (activeStreamStore.has(viewStreamKey)) {
1504
+ activeStreamStore.abort(viewStreamKey);
2166
1505
  }
2167
1506
  cancelStreamManager();
2168
1507
  setIsWaitingForResponse(false);
@@ -2172,10 +1511,7 @@ function useChatV2(config, callbacks = {}) {
2172
1511
  if (msg.isStreaming) {
2173
1512
  return {
2174
1513
  ...msg,
2175
- ...createCancelledMessageUpdate(
2176
- msg.steps || [],
2177
- msg.currentMessage
2178
- )
1514
+ ...createCancelledMessageUpdate(msg.steps || [], msg.currentMessage)
2179
1515
  };
2180
1516
  }
2181
1517
  return msg;
@@ -2183,39 +1519,37 @@ function useChatV2(config, callbacks = {}) {
2183
1519
  );
2184
1520
  }, [cancelStreamManager]);
2185
1521
  const resetSession = react.useCallback(() => {
2186
- if (configRef.current.userId) {
2187
- activeStreamStore.abort(configRef.current.userId);
2188
- chatStore.delete(configRef.current.userId);
1522
+ const scope = scopeKeyRef.current;
1523
+ const viewSid = sessionIdRef.current;
1524
+ const viewStreamKey = streamKeyFor(scope, viewSid);
1525
+ if (activeStreamStore.has(viewStreamKey)) {
1526
+ activeStreamStore.abort(viewStreamKey);
2189
1527
  }
1528
+ chatStore.delete(scope);
2190
1529
  setMessages([]);
2191
1530
  sessionIdRef.current = void 0;
1531
+ setCurrentSessionId(void 0);
1532
+ activeStreamSessionRef.current = void 0;
2192
1533
  abortControllerRef.current?.abort();
2193
1534
  setIsWaitingForResponse(false);
2194
1535
  setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
2195
1536
  }, []);
2196
- const getSessionId = react.useCallback(() => {
2197
- return sessionIdRef.current;
1537
+ const getSessionId = react.useCallback(() => sessionIdRef.current, []);
1538
+ const getMessages = react.useCallback(() => messages, [messages]);
1539
+ const approveUserAction = react.useCallback(async (otp) => {
1540
+ const request = userActionStateRef.current.request;
1541
+ if (!request) return;
1542
+ try {
1543
+ await submitUserAction(configRef.current, request.userActionId, { otp });
1544
+ } catch (error) {
1545
+ setUserActionState((prev) => ({
1546
+ ...prev,
1547
+ clearOtpTrigger: prev.clearOtpTrigger + 1
1548
+ }));
1549
+ callbacksRef.current.onError?.(error);
1550
+ throw error;
1551
+ }
2198
1552
  }, []);
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
1553
  const rejectUserAction = react.useCallback(async () => {
2220
1554
  const request = userActionStateRef.current.request;
2221
1555
  if (!request) return;
@@ -2249,43 +1583,91 @@ function useChatV2(config, callbacks = {}) {
2249
1583
  throw error;
2250
1584
  }
2251
1585
  }, []);
1586
+ const inFlightLoadRef = react.useRef(null);
1587
+ const loadingSessionIdRef = react.useRef(void 0);
1588
+ loadingSessionIdRef.current = loadingSessionId;
1589
+ const loadSession = react.useCallback(async (sessionId) => {
1590
+ const inFlight = inFlightLoadRef.current;
1591
+ if (inFlight && inFlight.sessionId === sessionId) {
1592
+ return inFlight.promise;
1593
+ }
1594
+ if (sessionIdRef.current === sessionId && loadingSessionIdRef.current !== sessionId) {
1595
+ return;
1596
+ }
1597
+ const run = async () => {
1598
+ const scope = scopeKeyRef.current;
1599
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1600
+ sessionIdRef.current = sessionId;
1601
+ setCurrentSessionId(sessionId);
1602
+ callbacksRef.current.onSessionIdChange?.(sessionId);
1603
+ const streamKey = streamKeyFor(scope, sessionId);
1604
+ const active = activeStreamStore.get(streamKey);
1605
+ if (active) {
1606
+ setMessages(active.messages);
1607
+ setIsWaitingForResponse(active.isWaiting);
1608
+ setLoadingSessionId(void 0);
1609
+ return;
1610
+ }
1611
+ setIsWaitingForResponse(false);
1612
+ setMessages([]);
1613
+ chatStore.delete(scope);
1614
+ setLoadingSessionId(sessionId);
1615
+ try {
1616
+ const response = await listConversations(configRef.current, { sessionId });
1617
+ const entries = response.data ?? [];
1618
+ const historical = entries.flatMap((entry) => conversationEntryToMessages(entry, sessionId));
1619
+ if (sessionIdRef.current === sessionId) {
1620
+ setMessages(historical);
1621
+ }
1622
+ if (historical.length > 0) {
1623
+ chatStore.set(scope, historical);
1624
+ }
1625
+ } catch (error) {
1626
+ callbacksRef.current.onError?.(error);
1627
+ throw error;
1628
+ } finally {
1629
+ setLoadingSessionId((current) => current === sessionId ? void 0 : current);
1630
+ }
1631
+ };
1632
+ const promise = run().finally(() => {
1633
+ if (inFlightLoadRef.current?.sessionId === sessionId) {
1634
+ inFlightLoadRef.current = null;
1635
+ }
1636
+ });
1637
+ inFlightLoadRef.current = { sessionId, promise };
1638
+ return promise;
1639
+ }, []);
2252
1640
  react.useEffect(() => {
2253
- const { userId } = config;
2254
- if (!userId) return;
2255
- const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1641
+ const key = streamKeyFor(scopeKey, currentSessionId);
1642
+ const unsubscribe = activeStreamStore.subscribe(key, (msgs, isWaiting) => {
2256
1643
  setMessages(msgs);
2257
1644
  setIsWaitingForResponse(isWaiting);
2258
1645
  });
2259
- const active = activeStreamStore.get(userId);
1646
+ const active = activeStreamStore.get(key);
2260
1647
  if (active) {
2261
1648
  setMessages(active.messages);
2262
1649
  setIsWaitingForResponse(active.isWaiting);
2263
1650
  }
2264
1651
  return unsubscribe;
2265
- }, []);
1652
+ }, [scopeKey, currentSessionId]);
2266
1653
  react.useEffect(() => {
2267
- if (!config.userId) return;
2268
1654
  const toSave = messages.filter((m) => !m.isStreaming);
2269
1655
  if (toSave.length > 0) {
2270
- chatStore.set(config.userId, toSave);
1656
+ chatStore.set(scopeKey, toSave);
2271
1657
  }
2272
- }, [messages, config.userId]);
1658
+ }, [messages, scopeKey]);
2273
1659
  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]);
1660
+ const prevKey = prevScopeKeyRef.current;
1661
+ prevScopeKeyRef.current = scopeKey;
1662
+ if (prevKey === scopeKey) return;
1663
+ const stored = chatStore.get(scopeKey);
1664
+ setMessages(stored);
1665
+ const restoredSessionId = stored.find((m) => m.sessionId)?.sessionId ?? configRef.current.session?.initialId ?? void 0;
1666
+ sessionIdRef.current = restoredSessionId;
1667
+ setCurrentSessionId(restoredSessionId);
1668
+ setIsWaitingForResponse(false);
1669
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1670
+ }, [scopeKey]);
2289
1671
  return {
2290
1672
  messages,
2291
1673
  sendMessage,
@@ -2296,11 +1678,13 @@ function useChatV2(config, callbacks = {}) {
2296
1678
  getSessionId,
2297
1679
  getMessages,
2298
1680
  isWaitingForResponse,
2299
- sessionId: sessionIdRef.current,
1681
+ sessionId: currentSessionId,
2300
1682
  userActionState,
2301
1683
  approveUserAction,
2302
1684
  rejectUserAction,
2303
- resendOtp
1685
+ resendOtp,
1686
+ loadSession,
1687
+ loadingSessionId
2304
1688
  };
2305
1689
  }
2306
1690
  function getSpeechRecognition() {
@@ -2520,14 +1904,16 @@ function useVoice(config = {}, callbacks = {}) {
2520
1904
  }
2521
1905
 
2522
1906
  exports.buildFormattedThinking = buildFormattedThinking;
1907
+ exports.buildScopeKey = buildScopeKey;
2523
1908
  exports.cancelUserAction = cancelUserAction;
2524
1909
  exports.createInitialV2State = createInitialV2State;
2525
1910
  exports.generateId = generateId;
1911
+ exports.listConversations = listConversations;
1912
+ exports.listSessions = listSessions;
2526
1913
  exports.processStreamEventV2 = processStreamEventV2;
2527
1914
  exports.resendUserAction = resendUserAction;
2528
1915
  exports.streamWorkflowEvents = streamWorkflowEvents;
2529
1916
  exports.submitUserAction = submitUserAction;
2530
- exports.useChat = useChat;
2531
1917
  exports.useChatV2 = useChatV2;
2532
1918
  exports.useVoice = useVoice;
2533
1919
  exports.workingPhaseDetailForDisplay = workingPhaseDetailForDisplay;