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