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