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