@paymanai/payman-typescript-ask-sdk 1.2.10 → 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 +141 -108
- package/dist/index.d.ts +141 -108
- package/dist/index.js +551 -1189
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +549 -1190
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +553 -1191
- 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() {
|
|
@@ -168,6 +168,14 @@ function getEventMessage(event) {
|
|
|
168
168
|
return eventType;
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
+
function workingPhaseDetailForDisplay(raw) {
|
|
172
|
+
const t = raw.trim();
|
|
173
|
+
if (!t) return "";
|
|
174
|
+
if (/^Identified\s+\d+\s+tasks?\s+to\s+execute\.?$/i.test(t)) {
|
|
175
|
+
return "";
|
|
176
|
+
}
|
|
177
|
+
return t;
|
|
178
|
+
}
|
|
171
179
|
function extractResponseContent(response) {
|
|
172
180
|
if (typeof response === "string") {
|
|
173
181
|
return response;
|
|
@@ -190,290 +198,8 @@ function extractResponseContent(response) {
|
|
|
190
198
|
}
|
|
191
199
|
return "";
|
|
192
200
|
}
|
|
193
|
-
function completeLastInProgressStep(steps) {
|
|
194
|
-
for (let i = steps.length - 1; i >= 0; i--) {
|
|
195
|
-
if (steps[i].status === "in_progress") {
|
|
196
|
-
steps[i].status = "completed";
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
function processStreamEvent(event, state) {
|
|
202
|
-
const eventType = event.eventType;
|
|
203
|
-
if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
204
|
-
return state;
|
|
205
|
-
}
|
|
206
|
-
const message = getEventMessage(event);
|
|
207
|
-
if (eventType !== "INTENT_THINKING" && eventType !== "INTENT_THINKING_CONT") {
|
|
208
|
-
if (state.currentThinkingStepId) {
|
|
209
|
-
const thinkingStep = state.steps.find((s) => s.id === state.currentThinkingStepId);
|
|
210
|
-
if (thinkingStep) {
|
|
211
|
-
thinkingStep.isThinking = false;
|
|
212
|
-
}
|
|
213
|
-
state.currentThinkingStepId = void 0;
|
|
214
|
-
}
|
|
215
|
-
state.activeThinkingText = void 0;
|
|
216
|
-
}
|
|
217
|
-
if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
|
|
218
|
-
let content = extractResponseContent(event.response);
|
|
219
|
-
const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
|
|
220
|
-
if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
|
|
221
|
-
content = trace.workflowMsg;
|
|
222
|
-
}
|
|
223
|
-
if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
|
|
224
|
-
const agg = trace.aggregator;
|
|
225
|
-
if (typeof agg.response === "string") content = agg.response;
|
|
226
|
-
else content = extractResponseContent(agg.response);
|
|
227
|
-
}
|
|
228
|
-
if (content) {
|
|
229
|
-
state.accumulatedContent = content;
|
|
230
|
-
state.finalData = event.response ?? event.trace;
|
|
231
|
-
state.hasError = false;
|
|
232
|
-
state.errorMessage = "";
|
|
233
|
-
} else {
|
|
234
|
-
state.hasError = true;
|
|
235
|
-
state.errorMessage = "WORKFLOW_FAILED";
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (eventType === "STARTED" || eventType === "WORKFLOW_STARTED") ; else if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
|
|
239
|
-
state.steps.forEach((step) => {
|
|
240
|
-
if (step.status === "in_progress") {
|
|
241
|
-
step.status = "completed";
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
} else if (eventType === "INTENT_ERROR") {
|
|
245
|
-
state.errorMessage = message || event.errorMessage || "An error occurred";
|
|
246
|
-
const intentStep = state.steps.find(
|
|
247
|
-
(s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
|
|
248
|
-
);
|
|
249
|
-
if (intentStep) {
|
|
250
|
-
intentStep.status = "error";
|
|
251
|
-
}
|
|
252
|
-
} else if (eventType === "ERROR" || eventType === "WORKFLOW_ERROR") {
|
|
253
|
-
state.hasError = true;
|
|
254
|
-
state.errorMessage = message || event.errorMessage || "An error occurred";
|
|
255
|
-
} else if (eventType === "ORCHESTRATOR_COMPLETED") {
|
|
256
|
-
state.inOrchestratorPhase = false;
|
|
257
|
-
const orchestratorStep = state.steps.find(
|
|
258
|
-
(s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress"
|
|
259
|
-
);
|
|
260
|
-
if (orchestratorStep) {
|
|
261
|
-
orchestratorStep.status = "completed";
|
|
262
|
-
if (event.elapsedMs) {
|
|
263
|
-
orchestratorStep.elapsedMs = event.elapsedMs;
|
|
264
|
-
}
|
|
265
|
-
if (orchestratorStep.id === state.currentExecutingStepId) {
|
|
266
|
-
state.currentExecutingStepId = void 0;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
} else if (eventType === "INTENT_COMPLETED") {
|
|
270
|
-
const intentStep = state.steps.find(
|
|
271
|
-
(s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
|
|
272
|
-
);
|
|
273
|
-
if (intentStep) {
|
|
274
|
-
intentStep.status = "completed";
|
|
275
|
-
if (event.elapsedMs) {
|
|
276
|
-
intentStep.elapsedMs = event.elapsedMs;
|
|
277
|
-
}
|
|
278
|
-
if (intentStep.id === state.currentExecutingStepId) {
|
|
279
|
-
state.currentExecutingStepId = void 0;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
} else if (eventType === "AGGREGATOR_COMPLETED") {
|
|
283
|
-
state.inAggregatorPhase = false;
|
|
284
|
-
const aggregatorStep = state.steps.find(
|
|
285
|
-
(s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress"
|
|
286
|
-
);
|
|
287
|
-
if (aggregatorStep) {
|
|
288
|
-
aggregatorStep.status = "completed";
|
|
289
|
-
if (event.elapsedMs) {
|
|
290
|
-
aggregatorStep.elapsedMs = event.elapsedMs;
|
|
291
|
-
}
|
|
292
|
-
if (aggregatorStep.id === state.currentExecutingStepId) {
|
|
293
|
-
state.currentExecutingStepId = void 0;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
} else if (eventType === "ORCHESTRATOR_THINKING" || eventType === "INTENT_STARTED" || eventType === "INTENT_PROGRESS" || eventType === "AGGREGATOR_THINKING") {
|
|
297
|
-
if (eventType === "ORCHESTRATOR_THINKING") {
|
|
298
|
-
state.inOrchestratorPhase = true;
|
|
299
|
-
}
|
|
300
|
-
if (eventType === "AGGREGATOR_THINKING") {
|
|
301
|
-
state.inAggregatorPhase = true;
|
|
302
|
-
}
|
|
303
|
-
if (eventType === "INTENT_PROGRESS") {
|
|
304
|
-
const intentStep = state.steps.find(
|
|
305
|
-
(s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
|
|
306
|
-
);
|
|
307
|
-
if (intentStep) {
|
|
308
|
-
intentStep.message = message;
|
|
309
|
-
state.currentExecutingStepId = intentStep.id;
|
|
310
|
-
} else {
|
|
311
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
312
|
-
state.steps.push({
|
|
313
|
-
id: stepId,
|
|
314
|
-
eventType: "INTENT_STARTED",
|
|
315
|
-
message,
|
|
316
|
-
status: "in_progress",
|
|
317
|
-
timestamp: Date.now(),
|
|
318
|
-
elapsedMs: event.elapsedMs
|
|
319
|
-
});
|
|
320
|
-
state.currentExecutingStepId = stepId;
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
324
|
-
state.steps.push({
|
|
325
|
-
id: stepId,
|
|
326
|
-
eventType,
|
|
327
|
-
message,
|
|
328
|
-
status: "in_progress",
|
|
329
|
-
timestamp: Date.now(),
|
|
330
|
-
elapsedMs: event.elapsedMs
|
|
331
|
-
});
|
|
332
|
-
state.currentExecutingStepId = stepId;
|
|
333
|
-
}
|
|
334
|
-
} else if (eventType === "USER_ACTION_REQUIRED") {
|
|
335
|
-
completeLastInProgressStep(state.steps);
|
|
336
|
-
if (event.userActionRequest) {
|
|
337
|
-
state.userActionRequest = {
|
|
338
|
-
userActionId: event.userActionRequest.userActionId,
|
|
339
|
-
userActionType: event.userActionRequest.userActionType,
|
|
340
|
-
message: event.userActionRequest.message,
|
|
341
|
-
requestedSchema: event.userActionRequest.requestedSchema,
|
|
342
|
-
metadata: event.userActionRequest.metadata
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
state.userActionPending = true;
|
|
346
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
347
|
-
state.steps.push({
|
|
348
|
-
id: stepId,
|
|
349
|
-
eventType,
|
|
350
|
-
message,
|
|
351
|
-
status: "in_progress",
|
|
352
|
-
timestamp: Date.now(),
|
|
353
|
-
elapsedMs: event.elapsedMs
|
|
354
|
-
});
|
|
355
|
-
state.currentExecutingStepId = stepId;
|
|
356
|
-
} else if (eventType === "USER_ACTION_SUCCESS") {
|
|
357
|
-
completeLastInProgressStep(state.steps);
|
|
358
|
-
state.userActionRequest = void 0;
|
|
359
|
-
state.userActionPending = false;
|
|
360
|
-
state.userActionResult = "approved";
|
|
361
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
362
|
-
state.steps.push({
|
|
363
|
-
id: stepId,
|
|
364
|
-
eventType,
|
|
365
|
-
message,
|
|
366
|
-
status: "completed",
|
|
367
|
-
timestamp: Date.now(),
|
|
368
|
-
elapsedMs: event.elapsedMs
|
|
369
|
-
});
|
|
370
|
-
} else if (eventType === "USER_ACTION_INVALID") {
|
|
371
|
-
completeLastInProgressStep(state.steps);
|
|
372
|
-
const errorStepId = `step-${state.stepCounter++}`;
|
|
373
|
-
state.steps.push({
|
|
374
|
-
id: errorStepId,
|
|
375
|
-
eventType,
|
|
376
|
-
message,
|
|
377
|
-
status: "error",
|
|
378
|
-
timestamp: Date.now(),
|
|
379
|
-
elapsedMs: event.elapsedMs
|
|
380
|
-
});
|
|
381
|
-
const retryStepId = `step-${state.stepCounter++}`;
|
|
382
|
-
state.steps.push({
|
|
383
|
-
id: retryStepId,
|
|
384
|
-
eventType: "USER_ACTION_REQUIRED",
|
|
385
|
-
message: "Waiting for verification...",
|
|
386
|
-
status: "in_progress",
|
|
387
|
-
timestamp: Date.now()
|
|
388
|
-
});
|
|
389
|
-
state.currentExecutingStepId = retryStepId;
|
|
390
|
-
} else if (eventType === "USER_ACTION_EXPIRED") {
|
|
391
|
-
completeLastInProgressStep(state.steps);
|
|
392
|
-
state.userActionRequest = void 0;
|
|
393
|
-
state.userActionPending = false;
|
|
394
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
395
|
-
state.steps.push({
|
|
396
|
-
id: stepId,
|
|
397
|
-
eventType,
|
|
398
|
-
message,
|
|
399
|
-
status: "error",
|
|
400
|
-
timestamp: Date.now(),
|
|
401
|
-
elapsedMs: event.elapsedMs
|
|
402
|
-
});
|
|
403
|
-
} else if (eventType === "USER_ACTION_REJECTED") {
|
|
404
|
-
completeLastInProgressStep(state.steps);
|
|
405
|
-
state.userActionRequest = void 0;
|
|
406
|
-
state.userActionPending = false;
|
|
407
|
-
state.userActionResult = "rejected";
|
|
408
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
409
|
-
state.steps.push({
|
|
410
|
-
id: stepId,
|
|
411
|
-
eventType,
|
|
412
|
-
message,
|
|
413
|
-
status: "completed",
|
|
414
|
-
timestamp: Date.now(),
|
|
415
|
-
elapsedMs: event.elapsedMs
|
|
416
|
-
});
|
|
417
|
-
} else if (eventType === "USER_ACTION_RESENT") {
|
|
418
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
419
|
-
state.steps.push({
|
|
420
|
-
id: stepId,
|
|
421
|
-
eventType,
|
|
422
|
-
message,
|
|
423
|
-
status: "completed",
|
|
424
|
-
timestamp: Date.now(),
|
|
425
|
-
elapsedMs: event.elapsedMs
|
|
426
|
-
});
|
|
427
|
-
} else if (eventType === "USER_ACTION_FAILED") {
|
|
428
|
-
completeLastInProgressStep(state.steps);
|
|
429
|
-
state.userActionRequest = void 0;
|
|
430
|
-
state.userActionPending = false;
|
|
431
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
432
|
-
state.steps.push({
|
|
433
|
-
id: stepId,
|
|
434
|
-
eventType,
|
|
435
|
-
message,
|
|
436
|
-
status: "error",
|
|
437
|
-
timestamp: Date.now(),
|
|
438
|
-
elapsedMs: event.elapsedMs
|
|
439
|
-
});
|
|
440
|
-
} else if (eventType === "INTENT_THINKING") {
|
|
441
|
-
if (state.currentThinkingStepId) {
|
|
442
|
-
const prev = state.steps.find((s) => s.id === state.currentThinkingStepId);
|
|
443
|
-
if (prev) prev.isThinking = false;
|
|
444
|
-
}
|
|
445
|
-
const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
|
|
446
|
-
if (lastInProgress) {
|
|
447
|
-
lastInProgress.thinkingText = "";
|
|
448
|
-
lastInProgress.isThinking = true;
|
|
449
|
-
state.currentThinkingStepId = lastInProgress.id;
|
|
450
|
-
} else {
|
|
451
|
-
state.currentThinkingStepId = void 0;
|
|
452
|
-
}
|
|
453
|
-
if (state.allThinkingText) state.allThinkingText += "\n\n";
|
|
454
|
-
if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
|
|
455
|
-
state.activeThinkingText = "";
|
|
456
|
-
}
|
|
457
|
-
} else if (eventType === "INTENT_THINKING_CONT") {
|
|
458
|
-
const delta = event.message || "";
|
|
459
|
-
if (!delta) return state;
|
|
460
|
-
if (state.currentThinkingStepId) {
|
|
461
|
-
const step = state.steps.find((s) => s.id === state.currentThinkingStepId);
|
|
462
|
-
if (step) {
|
|
463
|
-
step.thinkingText = (step.thinkingText || "") + delta;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
state.allThinkingText += delta;
|
|
467
|
-
if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
|
|
468
|
-
if (state.activeThinkingText == null) state.activeThinkingText = "";
|
|
469
|
-
state.activeThinkingText += delta;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
return state;
|
|
473
|
-
}
|
|
474
201
|
|
|
475
202
|
// src/utils/messageStateManager.ts
|
|
476
|
-
var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
|
|
477
203
|
function buildFormattedThinking(steps, allThinkingText) {
|
|
478
204
|
const parts = [];
|
|
479
205
|
const safeSteps = steps ?? [];
|
|
@@ -503,6 +229,12 @@ function buildFormattedThinking(steps, allThinkingText) {
|
|
|
503
229
|
parts.push("**Planning**");
|
|
504
230
|
if (step.message) parts.push(step.message);
|
|
505
231
|
break;
|
|
232
|
+
case "WORKING": {
|
|
233
|
+
const detail = workingPhaseDetailForDisplay(step.message || "");
|
|
234
|
+
parts.push("**Working**");
|
|
235
|
+
if (detail) parts.push(detail);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
506
238
|
case "INTENT_STARTED": {
|
|
507
239
|
let label = step.message || "Processing";
|
|
508
240
|
const started = label.match(/^(.+?)\s+started$/i);
|
|
@@ -542,73 +274,6 @@ function buildFormattedThinking(steps, allThinkingText) {
|
|
|
542
274
|
}
|
|
543
275
|
return parts.length > 0 ? parts.join("\n") : allThinkingText;
|
|
544
276
|
}
|
|
545
|
-
function createStreamingMessageUpdate(state) {
|
|
546
|
-
const hasCompletedContent = state.accumulatedContent && state.finalData !== void 0;
|
|
547
|
-
const steps = state.hasError ? [] : [...state.steps];
|
|
548
|
-
const allThinking = state.hasError ? void 0 : state.allThinkingText;
|
|
549
|
-
return {
|
|
550
|
-
streamingContent: state.hasError ? FRIENDLY_ERROR_MESSAGE : hasCompletedContent ? state.accumulatedContent : "",
|
|
551
|
-
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : "",
|
|
552
|
-
currentMessage: state.hasError ? void 0 : state.currentMessage,
|
|
553
|
-
streamProgress: state.hasError ? "error" : "processing",
|
|
554
|
-
isError: state.hasError,
|
|
555
|
-
errorDetails: state.hasError ? state.errorMessage : void 0,
|
|
556
|
-
executionId: state.executionId,
|
|
557
|
-
sessionId: state.sessionId,
|
|
558
|
-
steps,
|
|
559
|
-
currentExecutingStepId: state.hasError ? void 0 : state.currentExecutingStepId,
|
|
560
|
-
isCancelled: false,
|
|
561
|
-
userActionResult: state.userActionResult,
|
|
562
|
-
activeThinkingText: state.hasError ? void 0 : state.activeThinkingText,
|
|
563
|
-
allThinkingText: allThinking,
|
|
564
|
-
formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || "")
|
|
565
|
-
};
|
|
566
|
-
}
|
|
567
|
-
function createErrorMessageUpdate(error, state) {
|
|
568
|
-
const isAborted = error.name === "AbortError";
|
|
569
|
-
return {
|
|
570
|
-
isStreaming: false,
|
|
571
|
-
streamProgress: isAborted ? "processing" : "error",
|
|
572
|
-
isError: !isAborted,
|
|
573
|
-
isCancelled: isAborted,
|
|
574
|
-
errorDetails: isAborted ? void 0 : error.message,
|
|
575
|
-
content: isAborted ? state.accumulatedContent || "" : state.accumulatedContent || FRIENDLY_ERROR_MESSAGE,
|
|
576
|
-
// Preserve currentMessage when cancelled so UI can show it
|
|
577
|
-
currentMessage: isAborted ? state.currentMessage || "Thinking..." : void 0,
|
|
578
|
-
steps: [...state.steps].map((step) => {
|
|
579
|
-
if (step.status === "in_progress" && isAborted) {
|
|
580
|
-
return { ...step, status: "pending" };
|
|
581
|
-
}
|
|
582
|
-
return step;
|
|
583
|
-
}),
|
|
584
|
-
currentExecutingStepId: void 0
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
function createFinalMessage(streamingId, state) {
|
|
588
|
-
const steps = state.hasError ? [] : [...state.steps];
|
|
589
|
-
const allThinking = state.hasError ? void 0 : state.allThinkingText;
|
|
590
|
-
return {
|
|
591
|
-
id: streamingId,
|
|
592
|
-
sessionId: state.sessionId,
|
|
593
|
-
role: "assistant",
|
|
594
|
-
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.accumulatedContent || "",
|
|
595
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
596
|
-
isStreaming: false,
|
|
597
|
-
streamProgress: state.hasError ? "error" : "completed",
|
|
598
|
-
isError: state.hasError,
|
|
599
|
-
errorDetails: state.hasError ? state.errorMessage : void 0,
|
|
600
|
-
executionId: state.executionId,
|
|
601
|
-
tracingData: state.finalData,
|
|
602
|
-
steps,
|
|
603
|
-
isCancelled: false,
|
|
604
|
-
currentExecutingStepId: void 0,
|
|
605
|
-
userActionResult: state.userActionResult,
|
|
606
|
-
activeThinkingText: void 0,
|
|
607
|
-
allThinkingText: allThinking,
|
|
608
|
-
formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || ""),
|
|
609
|
-
isResolvingImages: state.hasError ? void 0 : state.isResolvingImages
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
277
|
function createCancelledMessageUpdate(steps, currentMessage) {
|
|
613
278
|
const updatedSteps = steps.map((step) => {
|
|
614
279
|
if (step.status === "in_progress") {
|
|
@@ -628,35 +293,44 @@ function createCancelledMessageUpdate(steps, currentMessage) {
|
|
|
628
293
|
|
|
629
294
|
// src/utils/requestBuilder.ts
|
|
630
295
|
function buildRequestBody(config, userMessage, sessionId) {
|
|
631
|
-
const
|
|
632
|
-
const sessionAttributes =
|
|
296
|
+
const owner = config.session?.owner;
|
|
297
|
+
const sessionAttributes = owner?.attributes && Object.keys(owner.attributes).length > 0 ? owner.attributes : void 0;
|
|
633
298
|
return {
|
|
634
|
-
workflowName: config.
|
|
299
|
+
workflowName: config.workflow.name,
|
|
635
300
|
userInput: userMessage,
|
|
636
301
|
sessionId,
|
|
637
|
-
sessionOwnerId:
|
|
638
|
-
sessionOwnerLabel:
|
|
302
|
+
sessionOwnerId: owner?.id || "",
|
|
303
|
+
sessionOwnerLabel: owner?.name || "",
|
|
639
304
|
sessionAttributes,
|
|
640
305
|
options: {
|
|
641
306
|
clientTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
642
307
|
}
|
|
643
308
|
};
|
|
644
309
|
}
|
|
310
|
+
function getStageParamName(config) {
|
|
311
|
+
return config.api.stageQueryParam ?? "stage";
|
|
312
|
+
}
|
|
313
|
+
function getStage(config) {
|
|
314
|
+
return config.workflow.stage ?? "DEV";
|
|
315
|
+
}
|
|
645
316
|
function buildStreamingUrl(config) {
|
|
646
317
|
const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (config.
|
|
651
|
-
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));
|
|
652
323
|
}
|
|
653
324
|
return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
|
|
654
325
|
}
|
|
655
|
-
function
|
|
326
|
+
function deriveBasePath(config) {
|
|
656
327
|
const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
|
|
657
328
|
const [endpointPath] = endpoint.split("?");
|
|
658
|
-
const
|
|
659
|
-
|
|
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);
|
|
660
334
|
const encodedUserActionId = encodeURIComponent(userActionId);
|
|
661
335
|
return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
|
|
662
336
|
}
|
|
@@ -664,21 +338,23 @@ function buildResolveImagesUrl(config) {
|
|
|
664
338
|
if (config.api.resolveImagesEndpoint) {
|
|
665
339
|
return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
|
|
666
340
|
}
|
|
667
|
-
|
|
668
|
-
const [endpointPath] = streamEndpoint.split("?");
|
|
669
|
-
const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
|
|
670
|
-
const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
|
|
671
|
-
return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
|
|
341
|
+
return `${config.api.baseUrl}${deriveBasePath(config)}/resolve-image-urls`;
|
|
672
342
|
}
|
|
673
343
|
function buildRequestHeaders(config) {
|
|
674
|
-
const headers = {
|
|
675
|
-
...config.api.headers
|
|
676
|
-
};
|
|
344
|
+
const headers = { ...config.api.headers };
|
|
677
345
|
if (config.api.authToken) {
|
|
678
346
|
headers.Authorization = `Bearer ${config.api.authToken}`;
|
|
679
347
|
}
|
|
680
348
|
return headers;
|
|
681
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
|
+
}
|
|
682
358
|
|
|
683
359
|
// src/utils/userActionClient.ts
|
|
684
360
|
async function sendUserActionRequest(config, userActionId, action, data) {
|
|
@@ -688,690 +364,103 @@ async function sendUserActionRequest(config, userActionId, action, data) {
|
|
|
688
364
|
const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
|
|
689
365
|
const response = await fetch(url, {
|
|
690
366
|
method: "POST",
|
|
691
|
-
headers,
|
|
692
|
-
body: hasBody ? JSON.stringify(data) : void 0
|
|
693
|
-
});
|
|
694
|
-
if (!response.ok) {
|
|
695
|
-
const errorText = await response.text();
|
|
696
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
697
|
-
}
|
|
698
|
-
return await response.json();
|
|
699
|
-
}
|
|
700
|
-
async function submitUserAction(config, userActionId, data) {
|
|
701
|
-
return sendUserActionRequest(config, userActionId, "submit", data);
|
|
702
|
-
}
|
|
703
|
-
async function cancelUserAction(config, userActionId) {
|
|
704
|
-
return sendUserActionRequest(config, userActionId, "cancel");
|
|
705
|
-
}
|
|
706
|
-
async function resendUserAction(config, userActionId) {
|
|
707
|
-
return sendUserActionRequest(config, userActionId, "resend");
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
// src/utils/chatStore.ts
|
|
711
|
-
var memoryStore = /* @__PURE__ */ new Map();
|
|
712
|
-
var chatStore = {
|
|
713
|
-
get(key) {
|
|
714
|
-
return memoryStore.get(key) ?? [];
|
|
715
|
-
},
|
|
716
|
-
set(key, messages) {
|
|
717
|
-
memoryStore.set(key, messages);
|
|
718
|
-
},
|
|
719
|
-
delete(key) {
|
|
720
|
-
memoryStore.delete(key);
|
|
721
|
-
}
|
|
722
|
-
};
|
|
723
|
-
|
|
724
|
-
// src/utils/activeStreamStore.ts
|
|
725
|
-
var streams = /* @__PURE__ */ new Map();
|
|
726
|
-
var activeStreamStore = {
|
|
727
|
-
has(key) {
|
|
728
|
-
return streams.has(key);
|
|
729
|
-
},
|
|
730
|
-
get(key) {
|
|
731
|
-
const entry = streams.get(key);
|
|
732
|
-
if (!entry) return null;
|
|
733
|
-
return { messages: entry.messages, isWaiting: entry.isWaiting };
|
|
734
|
-
},
|
|
735
|
-
// Called before startStream — registers the controller and initial messages
|
|
736
|
-
start(key, abortController, initialMessages) {
|
|
737
|
-
const existing = streams.get(key);
|
|
738
|
-
streams.set(key, {
|
|
739
|
-
messages: initialMessages,
|
|
740
|
-
isWaiting: true,
|
|
741
|
-
abortController,
|
|
742
|
-
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
743
|
-
});
|
|
744
|
-
},
|
|
745
|
-
// Called by the stream on every event — applies the same updater pattern React uses
|
|
746
|
-
applyMessages(key, updater) {
|
|
747
|
-
const entry = streams.get(key);
|
|
748
|
-
if (!entry) return;
|
|
749
|
-
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
750
|
-
entry.messages = next;
|
|
751
|
-
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
752
|
-
},
|
|
753
|
-
setWaiting(key, waiting) {
|
|
754
|
-
const entry = streams.get(key);
|
|
755
|
-
if (!entry) return;
|
|
756
|
-
entry.isWaiting = waiting;
|
|
757
|
-
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
758
|
-
},
|
|
759
|
-
// Called when stream completes — persists to chatStore and cleans up
|
|
760
|
-
complete(key) {
|
|
761
|
-
const entry = streams.get(key);
|
|
762
|
-
if (!entry) return;
|
|
763
|
-
entry.isWaiting = false;
|
|
764
|
-
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
765
|
-
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
766
|
-
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
767
|
-
streams.delete(key);
|
|
768
|
-
},
|
|
769
|
-
// Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
|
|
770
|
-
subscribe(key, listener) {
|
|
771
|
-
const entry = streams.get(key);
|
|
772
|
-
if (!entry) return () => {
|
|
773
|
-
};
|
|
774
|
-
entry.listeners.add(listener);
|
|
775
|
-
return () => {
|
|
776
|
-
streams.get(key)?.listeners.delete(listener);
|
|
777
|
-
};
|
|
778
|
-
},
|
|
779
|
-
// Explicit user cancel — aborts the controller and removes the entry
|
|
780
|
-
abort(key) {
|
|
781
|
-
const entry = streams.get(key);
|
|
782
|
-
if (!entry) return;
|
|
783
|
-
entry.abortController.abort();
|
|
784
|
-
streams.delete(key);
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
|
-
|
|
788
|
-
// src/utils/ragImageResolver.ts
|
|
789
|
-
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
790
|
-
function hasRagImages(content) {
|
|
791
|
-
return RAG_IMAGE_REGEX.test(content);
|
|
792
|
-
}
|
|
793
|
-
async function waitForNextPaint(signal) {
|
|
794
|
-
if (signal?.aborted) return;
|
|
795
|
-
await new Promise((resolve) => {
|
|
796
|
-
let isSettled = false;
|
|
797
|
-
const finish = () => {
|
|
798
|
-
if (isSettled) return;
|
|
799
|
-
isSettled = true;
|
|
800
|
-
signal?.removeEventListener("abort", finish);
|
|
801
|
-
resolve();
|
|
802
|
-
};
|
|
803
|
-
signal?.addEventListener("abort", finish, { once: true });
|
|
804
|
-
if (typeof requestAnimationFrame === "function") {
|
|
805
|
-
requestAnimationFrame(() => {
|
|
806
|
-
setTimeout(finish, 0);
|
|
807
|
-
});
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
setTimeout(finish, 0);
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
async function resolveRagImageUrls(config, content, signal) {
|
|
814
|
-
const url = buildResolveImagesUrl(config);
|
|
815
|
-
const baseHeaders = buildRequestHeaders(config);
|
|
816
|
-
const headers = { "Content-Type": "application/json", ...baseHeaders };
|
|
817
|
-
const response = await fetch(url, {
|
|
818
|
-
method: "POST",
|
|
819
|
-
headers,
|
|
820
|
-
body: JSON.stringify({ input: content }),
|
|
821
|
-
signal
|
|
822
|
-
});
|
|
823
|
-
if (!response.ok) {
|
|
824
|
-
const errorText = await response.text();
|
|
825
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
826
|
-
}
|
|
827
|
-
const text = await response.text();
|
|
828
|
-
try {
|
|
829
|
-
const parsed = JSON.parse(text);
|
|
830
|
-
if (typeof parsed === "string") return parsed;
|
|
831
|
-
if (typeof parsed.output === "string") return parsed.output;
|
|
832
|
-
if (typeof parsed.result === "string") return parsed.result;
|
|
833
|
-
} catch {
|
|
834
|
-
}
|
|
835
|
-
return text;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// src/hooks/useStreamManager.ts
|
|
839
|
-
function useStreamManager(config, callbacks, setMessages, setIsWaitingForResponse) {
|
|
840
|
-
const abortControllerRef = react.useRef(null);
|
|
841
|
-
const configRef = react.useRef(config);
|
|
842
|
-
configRef.current = config;
|
|
843
|
-
const callbacksRef = react.useRef(callbacks);
|
|
844
|
-
callbacksRef.current = callbacks;
|
|
845
|
-
const startStream = react.useCallback(
|
|
846
|
-
async (userMessage, streamingId, sessionId, externalAbortController) => {
|
|
847
|
-
abortControllerRef.current?.abort();
|
|
848
|
-
const abortController = externalAbortController ?? new AbortController();
|
|
849
|
-
abortControllerRef.current = abortController;
|
|
850
|
-
const state = {
|
|
851
|
-
accumulatedContent: "",
|
|
852
|
-
executionId: void 0,
|
|
853
|
-
currentSessionId: void 0,
|
|
854
|
-
finalData: void 0,
|
|
855
|
-
steps: [],
|
|
856
|
-
stepCounter: 0,
|
|
857
|
-
currentExecutingStepId: void 0,
|
|
858
|
-
currentThinkingStepId: void 0,
|
|
859
|
-
hasError: false,
|
|
860
|
-
errorMessage: "",
|
|
861
|
-
userActionRequest: void 0,
|
|
862
|
-
userActionPending: false,
|
|
863
|
-
userActionResult: void 0,
|
|
864
|
-
allThinkingText: "",
|
|
865
|
-
inOrchestratorPhase: false,
|
|
866
|
-
inAggregatorPhase: false
|
|
867
|
-
};
|
|
868
|
-
const THROTTLE_MS = 120;
|
|
869
|
-
const CHARS_PER_TICK = 10;
|
|
870
|
-
const displayedLengthRef = { current: 0 };
|
|
871
|
-
let throttleIntervalId = null;
|
|
872
|
-
const getActiveStepMessage = () => state.steps.find((s) => s.id === state.currentExecutingStepId)?.message || [...state.steps].reverse().find((s) => s.status === "in_progress")?.message;
|
|
873
|
-
const advanceDisplayLength = (full) => {
|
|
874
|
-
let newLen = Math.min(displayedLengthRef.current + CHARS_PER_TICK, full.length);
|
|
875
|
-
if (newLen > 0 && newLen < full.length) {
|
|
876
|
-
const code = full.charCodeAt(newLen - 1);
|
|
877
|
-
if (code >= 55296 && code <= 56319) {
|
|
878
|
-
newLen = Math.min(newLen + 1, full.length);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
displayedLengthRef.current = newLen;
|
|
882
|
-
};
|
|
883
|
-
const clearThrottle = () => {
|
|
884
|
-
if (throttleIntervalId != null) {
|
|
885
|
-
clearInterval(throttleIntervalId);
|
|
886
|
-
throttleIntervalId = null;
|
|
887
|
-
}
|
|
888
|
-
};
|
|
889
|
-
abortController.signal.addEventListener("abort", clearThrottle, { once: true });
|
|
890
|
-
const ensureThrottle = () => {
|
|
891
|
-
if (throttleIntervalId != null) return;
|
|
892
|
-
throttleIntervalId = setInterval(() => {
|
|
893
|
-
if (abortController.signal.aborted) {
|
|
894
|
-
clearThrottle();
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
|
-
const full = state.activeThinkingText ?? "";
|
|
898
|
-
if (displayedLengthRef.current < full.length) {
|
|
899
|
-
advanceDisplayLength(full);
|
|
900
|
-
const displayText = full.slice(0, displayedLengthRef.current);
|
|
901
|
-
setMessages(
|
|
902
|
-
(prev) => prev.map(
|
|
903
|
-
(msg) => msg.id === streamingId ? {
|
|
904
|
-
...msg,
|
|
905
|
-
...createStreamingMessageUpdate({
|
|
906
|
-
...state,
|
|
907
|
-
activeThinkingText: displayText,
|
|
908
|
-
currentMessage: getActiveStepMessage() || "Thinking..."
|
|
909
|
-
})
|
|
910
|
-
} : msg
|
|
911
|
-
)
|
|
912
|
-
);
|
|
913
|
-
}
|
|
914
|
-
}, THROTTLE_MS);
|
|
915
|
-
};
|
|
916
|
-
const currentConfig = configRef.current;
|
|
917
|
-
const requestBody = buildRequestBody(currentConfig, userMessage, sessionId);
|
|
918
|
-
const url = buildStreamingUrl(currentConfig);
|
|
919
|
-
const headers = buildRequestHeaders(currentConfig);
|
|
920
|
-
try {
|
|
921
|
-
await streamWorkflowEvents(url, requestBody, headers, {
|
|
922
|
-
signal: abortController.signal,
|
|
923
|
-
onEvent: (event) => {
|
|
924
|
-
if (abortController.signal.aborted) {
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
if (typeof event.eventType === "string" && event.eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
928
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
929
|
-
if (event.sessionId) state.currentSessionId = event.sessionId;
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
933
|
-
if (event.sessionId) state.currentSessionId = event.sessionId;
|
|
934
|
-
const activeThinkingLengthBeforeProcess = state.activeThinkingText?.length ?? 0;
|
|
935
|
-
processStreamEvent(event, state);
|
|
936
|
-
const eventType = event.eventType;
|
|
937
|
-
if (eventType === "INTENT_THINKING") {
|
|
938
|
-
displayedLengthRef.current = 0;
|
|
939
|
-
ensureThrottle();
|
|
940
|
-
} else if (eventType !== "INTENT_THINKING_CONT") {
|
|
941
|
-
displayedLengthRef.current = activeThinkingLengthBeforeProcess;
|
|
942
|
-
clearThrottle();
|
|
943
|
-
}
|
|
944
|
-
if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
|
|
945
|
-
callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
|
|
946
|
-
} else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
|
|
947
|
-
const msg = event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event);
|
|
948
|
-
callbacksRef.current.onUserActionEvent?.(eventType, msg);
|
|
949
|
-
}
|
|
950
|
-
const isIntentThinkingEvent = eventType === "INTENT_THINKING" || eventType === "INTENT_THINKING_CONT";
|
|
951
|
-
const rawMessage = event.message?.trim() || event.errorMessage?.trim();
|
|
952
|
-
const currentMessage = isIntentThinkingEvent ? getActiveStepMessage() || "Thinking..." : rawMessage || (event.eventType?.startsWith("USER_ACTION_") ? getEventMessage(event) : getActiveStepMessage() || getEventMessage(event));
|
|
953
|
-
const displayThinking = state.activeThinkingText != null ? state.activeThinkingText.slice(0, displayedLengthRef.current) : state.activeThinkingText;
|
|
954
|
-
setMessages(
|
|
955
|
-
(prev) => prev.map(
|
|
956
|
-
(msg) => msg.id === streamingId ? {
|
|
957
|
-
...msg,
|
|
958
|
-
...createStreamingMessageUpdate({
|
|
959
|
-
...state,
|
|
960
|
-
activeThinkingText: displayThinking,
|
|
961
|
-
currentMessage
|
|
962
|
-
})
|
|
963
|
-
} : msg
|
|
964
|
-
)
|
|
965
|
-
);
|
|
966
|
-
},
|
|
967
|
-
onError: (error) => {
|
|
968
|
-
clearThrottle();
|
|
969
|
-
setIsWaitingForResponse(false);
|
|
970
|
-
if (error.name !== "AbortError") {
|
|
971
|
-
callbacksRef.current.onError?.(error);
|
|
972
|
-
}
|
|
973
|
-
if (state.userActionPending) {
|
|
974
|
-
state.userActionPending = false;
|
|
975
|
-
state.userActionRequest = void 0;
|
|
976
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
977
|
-
"USER_ACTION_FAILED",
|
|
978
|
-
"Connection lost. Please try again."
|
|
979
|
-
);
|
|
980
|
-
}
|
|
981
|
-
setMessages(
|
|
982
|
-
(prev) => prev.map(
|
|
983
|
-
(msg) => msg.id === streamingId ? {
|
|
984
|
-
...msg,
|
|
985
|
-
...createErrorMessageUpdate(error, state)
|
|
986
|
-
} : msg
|
|
987
|
-
)
|
|
988
|
-
);
|
|
989
|
-
},
|
|
990
|
-
onComplete: () => {
|
|
991
|
-
clearThrottle();
|
|
992
|
-
setIsWaitingForResponse(false);
|
|
993
|
-
if (state.userActionPending) {
|
|
994
|
-
state.userActionPending = false;
|
|
995
|
-
state.userActionRequest = void 0;
|
|
996
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
997
|
-
"USER_ACTION_FAILED",
|
|
998
|
-
"Verification could not be completed."
|
|
999
|
-
);
|
|
1000
|
-
}
|
|
1001
|
-
if (state.currentSessionId && state.currentSessionId !== sessionId) {
|
|
1002
|
-
callbacksRef.current.onSessionIdChange?.(state.currentSessionId);
|
|
1003
|
-
}
|
|
1004
|
-
const needsImageResolve = !state.hasError && !abortController.signal.aborted && hasRagImages(state.accumulatedContent);
|
|
1005
|
-
const finalMessage = createFinalMessage(streamingId, {
|
|
1006
|
-
...state,
|
|
1007
|
-
sessionId: state.currentSessionId || sessionId,
|
|
1008
|
-
isResolvingImages: needsImageResolve
|
|
1009
|
-
});
|
|
1010
|
-
setMessages(
|
|
1011
|
-
(prev) => prev.map(
|
|
1012
|
-
(msg) => msg.id === streamingId ? finalMessage : msg
|
|
1013
|
-
)
|
|
1014
|
-
);
|
|
1015
|
-
callbacksRef.current.onStreamComplete?.(finalMessage);
|
|
1016
|
-
}
|
|
1017
|
-
});
|
|
1018
|
-
clearThrottle();
|
|
1019
|
-
const shouldResolveImages = !abortController.signal.aborted && !state.hasError && hasRagImages(state.accumulatedContent);
|
|
1020
|
-
if (shouldResolveImages) {
|
|
1021
|
-
await waitForNextPaint(abortController.signal);
|
|
1022
|
-
}
|
|
1023
|
-
if (shouldResolveImages && !abortController.signal.aborted) {
|
|
1024
|
-
try {
|
|
1025
|
-
const resolvedContent = await resolveRagImageUrls(
|
|
1026
|
-
currentConfig,
|
|
1027
|
-
state.accumulatedContent,
|
|
1028
|
-
abortController.signal
|
|
1029
|
-
);
|
|
1030
|
-
setMessages(
|
|
1031
|
-
(prev) => prev.map(
|
|
1032
|
-
(msg) => msg.id === streamingId ? { ...msg, content: resolvedContent, isResolvingImages: false } : msg
|
|
1033
|
-
)
|
|
1034
|
-
);
|
|
1035
|
-
} catch {
|
|
1036
|
-
setMessages(
|
|
1037
|
-
(prev) => prev.map(
|
|
1038
|
-
(msg) => msg.id === streamingId ? { ...msg, isResolvingImages: false } : msg
|
|
1039
|
-
)
|
|
1040
|
-
);
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
return state.currentSessionId;
|
|
1044
|
-
} catch (error) {
|
|
1045
|
-
clearThrottle();
|
|
1046
|
-
setIsWaitingForResponse(false);
|
|
1047
|
-
if (error.name !== "AbortError") {
|
|
1048
|
-
callbacksRef.current.onError?.(error);
|
|
1049
|
-
}
|
|
1050
|
-
if (state.userActionPending) {
|
|
1051
|
-
state.userActionPending = false;
|
|
1052
|
-
state.userActionRequest = void 0;
|
|
1053
|
-
callbacksRef.current.onUserActionEvent?.(
|
|
1054
|
-
"USER_ACTION_FAILED",
|
|
1055
|
-
"Connection lost. Please try again."
|
|
1056
|
-
);
|
|
1057
|
-
}
|
|
1058
|
-
setMessages(
|
|
1059
|
-
(prev) => prev.map(
|
|
1060
|
-
(msg) => msg.id === streamingId ? {
|
|
1061
|
-
...msg,
|
|
1062
|
-
...createErrorMessageUpdate(error, state)
|
|
1063
|
-
} : msg
|
|
1064
|
-
)
|
|
1065
|
-
);
|
|
1066
|
-
return state.currentSessionId;
|
|
1067
|
-
}
|
|
1068
|
-
},
|
|
1069
|
-
[setMessages, setIsWaitingForResponse]
|
|
1070
|
-
);
|
|
1071
|
-
const cancelStream = react.useCallback(() => {
|
|
1072
|
-
abortControllerRef.current?.abort();
|
|
1073
|
-
}, []);
|
|
1074
|
-
return {
|
|
1075
|
-
startStream,
|
|
1076
|
-
cancelStream,
|
|
1077
|
-
abortControllerRef
|
|
1078
|
-
};
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
// src/hooks/useChat.ts
|
|
1082
|
-
function useChat(config, callbacks = {}) {
|
|
1083
|
-
const [messages, setMessages] = react.useState(() => {
|
|
1084
|
-
if (config.userId) return chatStore.get(config.userId);
|
|
1085
|
-
return config.initialMessages ?? [];
|
|
1086
|
-
});
|
|
1087
|
-
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
|
|
1088
|
-
const sessionIdRef = react.useRef(
|
|
1089
|
-
config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
|
|
1090
|
-
);
|
|
1091
|
-
const prevUserIdRef = react.useRef(config.userId);
|
|
1092
|
-
const callbacksRef = react.useRef(callbacks);
|
|
1093
|
-
callbacksRef.current = callbacks;
|
|
1094
|
-
const configRef = react.useRef(config);
|
|
1095
|
-
configRef.current = config;
|
|
1096
|
-
const messagesRef = react.useRef(messages);
|
|
1097
|
-
messagesRef.current = messages;
|
|
1098
|
-
const storeAwareSetMessages = react.useCallback(
|
|
1099
|
-
(updater) => {
|
|
1100
|
-
const { userId } = configRef.current;
|
|
1101
|
-
if (userId && activeStreamStore.has(userId)) {
|
|
1102
|
-
activeStreamStore.applyMessages(userId, updater);
|
|
1103
|
-
}
|
|
1104
|
-
setMessages(updater);
|
|
1105
|
-
},
|
|
1106
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1107
|
-
[]
|
|
1108
|
-
);
|
|
1109
|
-
const storeAwareSetIsWaiting = react.useCallback(
|
|
1110
|
-
(waiting) => {
|
|
1111
|
-
const { userId } = configRef.current;
|
|
1112
|
-
if (userId && activeStreamStore.has(userId)) {
|
|
1113
|
-
activeStreamStore.setWaiting(userId, waiting);
|
|
1114
|
-
}
|
|
1115
|
-
setIsWaitingForResponse(waiting);
|
|
1116
|
-
},
|
|
1117
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1118
|
-
[]
|
|
1119
|
-
);
|
|
1120
|
-
const [userActionState, setUserActionState] = react.useState({
|
|
1121
|
-
request: null,
|
|
1122
|
-
result: null,
|
|
1123
|
-
clearOtpTrigger: 0
|
|
1124
|
-
});
|
|
1125
|
-
const userActionStateRef = react.useRef(userActionState);
|
|
1126
|
-
userActionStateRef.current = userActionState;
|
|
1127
|
-
const wrappedCallbacks = react.useMemo(() => ({
|
|
1128
|
-
...callbacksRef.current,
|
|
1129
|
-
onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
|
|
1130
|
-
onStreamStart: () => callbacksRef.current.onStreamStart?.(),
|
|
1131
|
-
onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
|
|
1132
|
-
onError: (error) => callbacksRef.current.onError?.(error),
|
|
1133
|
-
onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
|
|
1134
|
-
onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
|
|
1135
|
-
onUserActionRequired: (request) => {
|
|
1136
|
-
setUserActionState((prev) => ({ ...prev, request, result: null }));
|
|
1137
|
-
callbacksRef.current.onUserActionRequired?.(request);
|
|
1138
|
-
},
|
|
1139
|
-
onUserActionEvent: (eventType, message) => {
|
|
1140
|
-
switch (eventType) {
|
|
1141
|
-
case "USER_ACTION_SUCCESS":
|
|
1142
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
|
|
1143
|
-
break;
|
|
1144
|
-
case "USER_ACTION_REJECTED":
|
|
1145
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
|
|
1146
|
-
break;
|
|
1147
|
-
case "USER_ACTION_EXPIRED":
|
|
1148
|
-
case "USER_ACTION_FAILED":
|
|
1149
|
-
setUserActionState((prev) => ({ ...prev, request: null }));
|
|
1150
|
-
break;
|
|
1151
|
-
case "USER_ACTION_INVALID":
|
|
1152
|
-
setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
|
|
1153
|
-
break;
|
|
1154
|
-
}
|
|
1155
|
-
callbacksRef.current.onUserActionEvent?.(eventType, message);
|
|
1156
|
-
}
|
|
1157
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1158
|
-
}), []);
|
|
1159
|
-
const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManager(
|
|
1160
|
-
config,
|
|
1161
|
-
wrappedCallbacks,
|
|
1162
|
-
storeAwareSetMessages,
|
|
1163
|
-
storeAwareSetIsWaiting
|
|
1164
|
-
);
|
|
1165
|
-
const sendMessage = react.useCallback(
|
|
1166
|
-
async (userMessage) => {
|
|
1167
|
-
if (!userMessage.trim()) return;
|
|
1168
|
-
if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
|
|
1169
|
-
sessionIdRef.current = generateId();
|
|
1170
|
-
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
1171
|
-
}
|
|
1172
|
-
const userMessageId = `user-${Date.now()}`;
|
|
1173
|
-
const userMsg = {
|
|
1174
|
-
id: userMessageId,
|
|
1175
|
-
sessionId: sessionIdRef.current,
|
|
1176
|
-
role: "user",
|
|
1177
|
-
content: userMessage,
|
|
1178
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1179
|
-
};
|
|
1180
|
-
setMessages((prev) => [...prev, userMsg]);
|
|
1181
|
-
callbacksRef.current.onMessageSent?.(userMessage);
|
|
1182
|
-
setIsWaitingForResponse(true);
|
|
1183
|
-
callbacksRef.current.onStreamStart?.();
|
|
1184
|
-
const streamingId = `assistant-${Date.now()}`;
|
|
1185
|
-
const streamingMsg = {
|
|
1186
|
-
id: streamingId,
|
|
1187
|
-
sessionId: sessionIdRef.current,
|
|
1188
|
-
role: "assistant",
|
|
1189
|
-
content: "",
|
|
1190
|
-
streamingContent: "",
|
|
1191
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1192
|
-
isStreaming: true,
|
|
1193
|
-
streamProgress: "started",
|
|
1194
|
-
steps: [],
|
|
1195
|
-
currentExecutingStepId: void 0,
|
|
1196
|
-
isCancelled: false,
|
|
1197
|
-
currentMessage: void 0
|
|
1198
|
-
};
|
|
1199
|
-
setMessages((prev) => [...prev, streamingMsg]);
|
|
1200
|
-
const abortController = new AbortController();
|
|
1201
|
-
const { userId } = configRef.current;
|
|
1202
|
-
if (userId) {
|
|
1203
|
-
const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
|
|
1204
|
-
activeStreamStore.start(userId, abortController, initialMessages);
|
|
1205
|
-
}
|
|
1206
|
-
const newSessionId = await startStream(
|
|
1207
|
-
userMessage,
|
|
1208
|
-
streamingId,
|
|
1209
|
-
sessionIdRef.current,
|
|
1210
|
-
abortController
|
|
1211
|
-
);
|
|
1212
|
-
if (userId) {
|
|
1213
|
-
activeStreamStore.complete(userId);
|
|
1214
|
-
}
|
|
1215
|
-
if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
|
|
1216
|
-
sessionIdRef.current = newSessionId;
|
|
1217
|
-
}
|
|
1218
|
-
},
|
|
1219
|
-
[startStream]
|
|
1220
|
-
);
|
|
1221
|
-
const clearMessages = react.useCallback(() => {
|
|
1222
|
-
if (configRef.current.userId) {
|
|
1223
|
-
chatStore.delete(configRef.current.userId);
|
|
1224
|
-
}
|
|
1225
|
-
setMessages([]);
|
|
1226
|
-
}, []);
|
|
1227
|
-
const prependMessages = react.useCallback((msgs) => {
|
|
1228
|
-
setMessages((prev) => [...msgs, ...prev]);
|
|
1229
|
-
}, []);
|
|
1230
|
-
const cancelStream = react.useCallback(() => {
|
|
1231
|
-
if (configRef.current.userId) {
|
|
1232
|
-
activeStreamStore.abort(configRef.current.userId);
|
|
1233
|
-
}
|
|
1234
|
-
cancelStreamManager();
|
|
1235
|
-
setIsWaitingForResponse(false);
|
|
1236
|
-
setUserActionState((prev) => ({ ...prev, request: null, result: null }));
|
|
1237
|
-
setMessages(
|
|
1238
|
-
(prev) => prev.map((msg) => {
|
|
1239
|
-
if (msg.isStreaming) {
|
|
1240
|
-
return {
|
|
1241
|
-
...msg,
|
|
1242
|
-
...createCancelledMessageUpdate(
|
|
1243
|
-
msg.steps || [],
|
|
1244
|
-
msg.currentMessage
|
|
1245
|
-
)
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
return msg;
|
|
1249
|
-
})
|
|
1250
|
-
);
|
|
1251
|
-
}, [cancelStreamManager]);
|
|
1252
|
-
const resetSession = react.useCallback(() => {
|
|
1253
|
-
if (configRef.current.userId) {
|
|
1254
|
-
activeStreamStore.abort(configRef.current.userId);
|
|
1255
|
-
chatStore.delete(configRef.current.userId);
|
|
1256
|
-
}
|
|
1257
|
-
setMessages([]);
|
|
1258
|
-
sessionIdRef.current = void 0;
|
|
1259
|
-
abortControllerRef.current?.abort();
|
|
1260
|
-
setIsWaitingForResponse(false);
|
|
1261
|
-
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1262
|
-
}, []);
|
|
1263
|
-
const getSessionId = react.useCallback(() => {
|
|
1264
|
-
return sessionIdRef.current;
|
|
1265
|
-
}, []);
|
|
1266
|
-
const getMessages = react.useCallback(() => {
|
|
1267
|
-
return messages;
|
|
1268
|
-
}, [messages]);
|
|
1269
|
-
const approveUserAction = react.useCallback(
|
|
1270
|
-
async (otp) => {
|
|
1271
|
-
const request = userActionStateRef.current.request;
|
|
1272
|
-
if (!request) return;
|
|
1273
|
-
try {
|
|
1274
|
-
await submitUserAction(configRef.current, request.userActionId, { otp });
|
|
1275
|
-
} catch (error) {
|
|
1276
|
-
setUserActionState((prev) => ({
|
|
1277
|
-
...prev,
|
|
1278
|
-
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
1279
|
-
}));
|
|
1280
|
-
callbacksRef.current.onError?.(error);
|
|
1281
|
-
throw error;
|
|
1282
|
-
}
|
|
1283
|
-
},
|
|
1284
|
-
[]
|
|
1285
|
-
);
|
|
1286
|
-
const rejectUserAction = react.useCallback(async () => {
|
|
1287
|
-
const request = userActionStateRef.current.request;
|
|
1288
|
-
if (!request) return;
|
|
1289
|
-
try {
|
|
1290
|
-
setMessages((prev) => {
|
|
1291
|
-
let lastStreamingIdx = -1;
|
|
1292
|
-
for (let i = prev.length - 1; i >= 0; i--) {
|
|
1293
|
-
if (prev[i].role === "assistant" && prev[i].isStreaming) {
|
|
1294
|
-
lastStreamingIdx = i;
|
|
1295
|
-
break;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
if (lastStreamingIdx === -1) return prev;
|
|
1299
|
-
return prev.map(
|
|
1300
|
-
(msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
|
|
1301
|
-
);
|
|
1302
|
-
});
|
|
1303
|
-
await cancelUserAction(configRef.current, request.userActionId);
|
|
1304
|
-
} catch (error) {
|
|
1305
|
-
callbacksRef.current.onError?.(error);
|
|
1306
|
-
throw error;
|
|
1307
|
-
}
|
|
1308
|
-
}, []);
|
|
1309
|
-
const resendOtp = react.useCallback(async () => {
|
|
1310
|
-
const request = userActionStateRef.current.request;
|
|
1311
|
-
if (!request) return;
|
|
1312
|
-
try {
|
|
1313
|
-
await resendUserAction(configRef.current, request.userActionId);
|
|
1314
|
-
} catch (error) {
|
|
1315
|
-
callbacksRef.current.onError?.(error);
|
|
1316
|
-
throw error;
|
|
1317
|
-
}
|
|
1318
|
-
}, []);
|
|
1319
|
-
react.useEffect(() => {
|
|
1320
|
-
const { userId } = config;
|
|
1321
|
-
if (!userId) return;
|
|
1322
|
-
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1323
|
-
setMessages(msgs);
|
|
1324
|
-
setIsWaitingForResponse(isWaiting);
|
|
1325
|
-
});
|
|
1326
|
-
const active = activeStreamStore.get(userId);
|
|
1327
|
-
if (active) {
|
|
1328
|
-
setMessages(active.messages);
|
|
1329
|
-
setIsWaitingForResponse(active.isWaiting);
|
|
1330
|
-
}
|
|
1331
|
-
return unsubscribe;
|
|
1332
|
-
}, []);
|
|
1333
|
-
react.useEffect(() => {
|
|
1334
|
-
if (!config.userId) return;
|
|
1335
|
-
const toSave = messages.filter((m) => !m.isStreaming);
|
|
1336
|
-
if (toSave.length > 0) {
|
|
1337
|
-
chatStore.set(config.userId, toSave);
|
|
1338
|
-
}
|
|
1339
|
-
}, [messages, config.userId]);
|
|
1340
|
-
react.useEffect(() => {
|
|
1341
|
-
const prevUserId = prevUserIdRef.current;
|
|
1342
|
-
prevUserIdRef.current = config.userId;
|
|
1343
|
-
if (prevUserId === config.userId) return;
|
|
1344
|
-
if (prevUserId && !config.userId) {
|
|
1345
|
-
chatStore.delete(prevUserId);
|
|
1346
|
-
setMessages([]);
|
|
1347
|
-
sessionIdRef.current = void 0;
|
|
1348
|
-
setIsWaitingForResponse(false);
|
|
1349
|
-
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1350
|
-
} else if (config.userId) {
|
|
1351
|
-
const stored = chatStore.get(config.userId);
|
|
1352
|
-
setMessages(stored);
|
|
1353
|
-
sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
|
|
1354
|
-
}
|
|
1355
|
-
}, [config.userId]);
|
|
1356
|
-
return {
|
|
1357
|
-
messages,
|
|
1358
|
-
sendMessage,
|
|
1359
|
-
clearMessages,
|
|
1360
|
-
prependMessages,
|
|
1361
|
-
cancelStream,
|
|
1362
|
-
resetSession,
|
|
1363
|
-
getSessionId,
|
|
1364
|
-
getMessages,
|
|
1365
|
-
isWaitingForResponse,
|
|
1366
|
-
sessionId: sessionIdRef.current,
|
|
1367
|
-
// User action (OTP) state and methods
|
|
1368
|
-
userActionState,
|
|
1369
|
-
approveUserAction,
|
|
1370
|
-
rejectUserAction,
|
|
1371
|
-
resendOtp
|
|
1372
|
-
};
|
|
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");
|
|
381
|
+
}
|
|
382
|
+
async function resendUserAction(config, userActionId) {
|
|
383
|
+
return sendUserActionRequest(config, userActionId, "resend");
|
|
1373
384
|
}
|
|
1374
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
|
+
};
|
|
463
|
+
|
|
1375
464
|
// src/utils/v2EventProcessor.ts
|
|
1376
465
|
function getEventText(event, field) {
|
|
1377
466
|
const value = event[field];
|
|
@@ -1396,7 +485,7 @@ function addThinkingLine(state, header, detail) {
|
|
|
1396
485
|
function appendThinkingText(state, text) {
|
|
1397
486
|
state.formattedThinkingText += text;
|
|
1398
487
|
}
|
|
1399
|
-
function
|
|
488
|
+
function completeLastInProgressStep(steps) {
|
|
1400
489
|
for (let i = steps.length - 1; i >= 0; i--) {
|
|
1401
490
|
if (steps[i].status === "in_progress") {
|
|
1402
491
|
steps[i].status = "completed";
|
|
@@ -1493,7 +582,19 @@ function processStreamEventV2(event, state) {
|
|
|
1493
582
|
break;
|
|
1494
583
|
}
|
|
1495
584
|
case "ORCHESTRATOR_COMPLETED": {
|
|
1496
|
-
|
|
585
|
+
const workingDetail = workingPhaseDetailForDisplay(message);
|
|
586
|
+
if (workingDetail) {
|
|
587
|
+
addThinkingLine(state, "**Working**", workingDetail);
|
|
588
|
+
} else {
|
|
589
|
+
addThinkingHeader(state, "**Working**");
|
|
590
|
+
}
|
|
591
|
+
state.steps.push({
|
|
592
|
+
id: `step-${state.stepCounter++}`,
|
|
593
|
+
eventType: "WORKING",
|
|
594
|
+
message: workingDetail,
|
|
595
|
+
status: "completed",
|
|
596
|
+
timestamp: Date.now()
|
|
597
|
+
});
|
|
1497
598
|
const step = state.steps.find((s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress");
|
|
1498
599
|
if (step) {
|
|
1499
600
|
step.status = "completed";
|
|
@@ -1598,7 +699,7 @@ function processStreamEventV2(event, state) {
|
|
|
1598
699
|
break;
|
|
1599
700
|
}
|
|
1600
701
|
case "USER_ACTION_REQUIRED": {
|
|
1601
|
-
|
|
702
|
+
completeLastInProgressStep(state.steps);
|
|
1602
703
|
if (event.userActionRequest) {
|
|
1603
704
|
state.userActionRequest = {
|
|
1604
705
|
userActionId: event.userActionRequest.userActionId,
|
|
@@ -1628,7 +729,7 @@ function processStreamEventV2(event, state) {
|
|
|
1628
729
|
}
|
|
1629
730
|
case "USER_ACTION_SUCCESS": {
|
|
1630
731
|
appendThinkingText(state, "\n\u2713 " + (event.message || "Verification successful"));
|
|
1631
|
-
|
|
732
|
+
completeLastInProgressStep(state.steps);
|
|
1632
733
|
state.userActionRequest = void 0;
|
|
1633
734
|
state.userActionPending = false;
|
|
1634
735
|
state.userActionResult = "approved";
|
|
@@ -1645,7 +746,7 @@ function processStreamEventV2(event, state) {
|
|
|
1645
746
|
break;
|
|
1646
747
|
}
|
|
1647
748
|
case "USER_ACTION_INVALID": {
|
|
1648
|
-
|
|
749
|
+
completeLastInProgressStep(state.steps);
|
|
1649
750
|
const errorStepId = `step-${state.stepCounter++}`;
|
|
1650
751
|
state.steps.push({
|
|
1651
752
|
id: errorStepId,
|
|
@@ -1669,7 +770,7 @@ function processStreamEventV2(event, state) {
|
|
|
1669
770
|
}
|
|
1670
771
|
case "USER_ACTION_REJECTED": {
|
|
1671
772
|
appendThinkingText(state, "\n\u2717 " + (event.message || "Verification rejected"));
|
|
1672
|
-
|
|
773
|
+
completeLastInProgressStep(state.steps);
|
|
1673
774
|
state.userActionRequest = void 0;
|
|
1674
775
|
state.userActionPending = false;
|
|
1675
776
|
state.userActionResult = "rejected";
|
|
@@ -1687,7 +788,7 @@ function processStreamEventV2(event, state) {
|
|
|
1687
788
|
}
|
|
1688
789
|
case "USER_ACTION_EXPIRED": {
|
|
1689
790
|
appendThinkingText(state, "\n\u2717 " + (event.message || "Verification expired"));
|
|
1690
|
-
|
|
791
|
+
completeLastInProgressStep(state.steps);
|
|
1691
792
|
state.userActionRequest = void 0;
|
|
1692
793
|
state.userActionPending = false;
|
|
1693
794
|
const stepId = `step-${state.stepCounter++}`;
|
|
@@ -1717,7 +818,7 @@ function processStreamEventV2(event, state) {
|
|
|
1717
818
|
}
|
|
1718
819
|
case "USER_ACTION_FAILED": {
|
|
1719
820
|
appendThinkingText(state, "\n\u2717 " + (event.message || "Verification failed"));
|
|
1720
|
-
|
|
821
|
+
completeLastInProgressStep(state.steps);
|
|
1721
822
|
state.userActionRequest = void 0;
|
|
1722
823
|
state.userActionPending = false;
|
|
1723
824
|
const stepId = `step-${state.stepCounter++}`;
|
|
@@ -1755,8 +856,58 @@ function processStreamEventV2(event, state) {
|
|
|
1755
856
|
return state;
|
|
1756
857
|
}
|
|
1757
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
|
+
|
|
1758
909
|
// src/hooks/useStreamManagerV2.ts
|
|
1759
|
-
var
|
|
910
|
+
var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
|
|
1760
911
|
function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForResponse) {
|
|
1761
912
|
const abortControllerRef = react.useRef(null);
|
|
1762
913
|
const configRef = react.useRef(config);
|
|
@@ -1804,8 +955,8 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1804
955
|
const currentMessage = activeStep?.message || lastInProgressStep?.message || getEventMessage(event);
|
|
1805
956
|
if (state.hasError) {
|
|
1806
957
|
updateMessage({
|
|
1807
|
-
streamingContent:
|
|
1808
|
-
content:
|
|
958
|
+
streamingContent: FRIENDLY_ERROR_MESSAGE,
|
|
959
|
+
content: FRIENDLY_ERROR_MESSAGE,
|
|
1809
960
|
streamProgress: "error",
|
|
1810
961
|
isError: true,
|
|
1811
962
|
errorDetails: state.errorMessage,
|
|
@@ -1855,7 +1006,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1855
1006
|
isError: !isAborted,
|
|
1856
1007
|
isCancelled: isAborted,
|
|
1857
1008
|
errorDetails: isAborted ? void 0 : error.message,
|
|
1858
|
-
content: isAborted ? state.finalResponse || "" : state.finalResponse ||
|
|
1009
|
+
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1859
1010
|
currentMessage: isAborted ? "Thinking..." : void 0,
|
|
1860
1011
|
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1861
1012
|
steps: [...state.steps].map((step) => {
|
|
@@ -1887,7 +1038,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1887
1038
|
id: streamingId,
|
|
1888
1039
|
sessionId: state.sessionId || sessionId,
|
|
1889
1040
|
role: "assistant",
|
|
1890
|
-
content: state.hasError ?
|
|
1041
|
+
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.finalResponse || "",
|
|
1891
1042
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1892
1043
|
isStreaming: false,
|
|
1893
1044
|
streamProgress: state.hasError ? "error" : "completed",
|
|
@@ -1958,7 +1109,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1958
1109
|
isError: !isAborted,
|
|
1959
1110
|
isCancelled: isAborted,
|
|
1960
1111
|
errorDetails: isAborted ? void 0 : error.message,
|
|
1961
|
-
content: isAborted ? state.finalResponse || "" : state.finalResponse ||
|
|
1112
|
+
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1962
1113
|
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1963
1114
|
steps: [...state.steps].map((step) => {
|
|
1964
1115
|
if (step.status === "in_progress" && isAborted) {
|
|
@@ -1985,45 +1136,199 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1985
1136
|
};
|
|
1986
1137
|
}
|
|
1987
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
|
+
|
|
1988
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
|
+
}
|
|
1989
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;
|
|
1990
1277
|
const [messages, setMessages] = react.useState(() => {
|
|
1991
|
-
|
|
1992
|
-
|
|
1278
|
+
const stored = chatStore.get(scopeKey);
|
|
1279
|
+
if (stored.length > 0) return stored;
|
|
1280
|
+
return config.session?.initialMessages ?? [];
|
|
1993
1281
|
});
|
|
1994
1282
|
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
|
|
1995
|
-
const
|
|
1996
|
-
|
|
1997
|
-
);
|
|
1998
|
-
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);
|
|
1999
1288
|
const callbacksRef = react.useRef(callbacks);
|
|
2000
1289
|
callbacksRef.current = callbacks;
|
|
2001
1290
|
const configRef = react.useRef(config);
|
|
2002
1291
|
configRef.current = config;
|
|
1292
|
+
const scopeKeyRef = react.useRef(scopeKey);
|
|
1293
|
+
scopeKeyRef.current = scopeKey;
|
|
2003
1294
|
const messagesRef = react.useRef(messages);
|
|
2004
1295
|
messagesRef.current = messages;
|
|
2005
1296
|
const storeAwareSetMessages = react.useCallback(
|
|
2006
1297
|
(updater) => {
|
|
2007
|
-
const
|
|
2008
|
-
|
|
2009
|
-
|
|
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;
|
|
2010
1312
|
}
|
|
2011
1313
|
setMessages(updater);
|
|
2012
1314
|
},
|
|
2013
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2014
1315
|
[]
|
|
2015
1316
|
);
|
|
2016
|
-
const storeAwareSetIsWaiting = react.useCallback(
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
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);
|
|
2021
1324
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
1325
|
+
if (sessionIdRef.current === streamSid) {
|
|
1326
|
+
setIsWaitingForResponse(waiting);
|
|
1327
|
+
}
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
setIsWaitingForResponse(waiting);
|
|
1331
|
+
}, []);
|
|
2027
1332
|
const [userActionState, setUserActionState] = react.useState({
|
|
2028
1333
|
request: null,
|
|
2029
1334
|
result: null,
|
|
@@ -2031,38 +1336,43 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2031
1336
|
});
|
|
2032
1337
|
const userActionStateRef = react.useRef(userActionState);
|
|
2033
1338
|
userActionStateRef.current = userActionState;
|
|
2034
|
-
const wrappedCallbacks = react.useMemo(
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
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);
|
|
2061
1372
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
}), []);
|
|
1373
|
+
}),
|
|
1374
|
+
[]
|
|
1375
|
+
);
|
|
2066
1376
|
const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
|
|
2067
1377
|
config,
|
|
2068
1378
|
wrappedCallbacks,
|
|
@@ -2072,8 +1382,10 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2072
1382
|
const sendMessage = react.useCallback(
|
|
2073
1383
|
async (userMessage) => {
|
|
2074
1384
|
if (!userMessage.trim()) return;
|
|
2075
|
-
|
|
1385
|
+
const autoGen = configRef.current.session?.autoGenerateId;
|
|
1386
|
+
if (!sessionIdRef.current && autoGen !== false) {
|
|
2076
1387
|
sessionIdRef.current = generateId();
|
|
1388
|
+
setCurrentSessionId(sessionIdRef.current);
|
|
2077
1389
|
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
2078
1390
|
}
|
|
2079
1391
|
const userMessageId = `user-${Date.now()}`;
|
|
@@ -2105,38 +1417,40 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2105
1417
|
};
|
|
2106
1418
|
setMessages((prev) => [...prev, streamingMsg]);
|
|
2107
1419
|
const abortController = new AbortController();
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
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);
|
|
2113
1426
|
const newSessionId = await startStream(
|
|
2114
1427
|
userMessage,
|
|
2115
1428
|
streamingId,
|
|
2116
|
-
|
|
1429
|
+
streamSessionId,
|
|
2117
1430
|
abortController
|
|
2118
1431
|
);
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
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) {
|
|
2123
1435
|
sessionIdRef.current = newSessionId;
|
|
1436
|
+
setCurrentSessionId(newSessionId);
|
|
2124
1437
|
}
|
|
2125
1438
|
},
|
|
2126
1439
|
[startStream]
|
|
2127
1440
|
);
|
|
2128
1441
|
const clearMessages = react.useCallback(() => {
|
|
2129
|
-
|
|
2130
|
-
chatStore.delete(configRef.current.userId);
|
|
2131
|
-
}
|
|
1442
|
+
chatStore.delete(scopeKeyRef.current);
|
|
2132
1443
|
setMessages([]);
|
|
2133
1444
|
}, []);
|
|
2134
1445
|
const prependMessages = react.useCallback((msgs) => {
|
|
2135
1446
|
setMessages((prev) => [...msgs, ...prev]);
|
|
2136
1447
|
}, []);
|
|
2137
1448
|
const cancelStream = react.useCallback(() => {
|
|
2138
|
-
|
|
2139
|
-
|
|
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);
|
|
2140
1454
|
}
|
|
2141
1455
|
cancelStreamManager();
|
|
2142
1456
|
setIsWaitingForResponse(false);
|
|
@@ -2146,10 +1460,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2146
1460
|
if (msg.isStreaming) {
|
|
2147
1461
|
return {
|
|
2148
1462
|
...msg,
|
|
2149
|
-
...createCancelledMessageUpdate(
|
|
2150
|
-
msg.steps || [],
|
|
2151
|
-
msg.currentMessage
|
|
2152
|
-
)
|
|
1463
|
+
...createCancelledMessageUpdate(msg.steps || [], msg.currentMessage)
|
|
2153
1464
|
};
|
|
2154
1465
|
}
|
|
2155
1466
|
return msg;
|
|
@@ -2157,39 +1468,37 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2157
1468
|
);
|
|
2158
1469
|
}, [cancelStreamManager]);
|
|
2159
1470
|
const resetSession = react.useCallback(() => {
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
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);
|
|
2163
1476
|
}
|
|
1477
|
+
chatStore.delete(scope);
|
|
2164
1478
|
setMessages([]);
|
|
2165
1479
|
sessionIdRef.current = void 0;
|
|
1480
|
+
setCurrentSessionId(void 0);
|
|
1481
|
+
activeStreamSessionRef.current = void 0;
|
|
2166
1482
|
abortControllerRef.current?.abort();
|
|
2167
1483
|
setIsWaitingForResponse(false);
|
|
2168
1484
|
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
2169
1485
|
}, []);
|
|
2170
|
-
const getSessionId = react.useCallback(() =>
|
|
2171
|
-
|
|
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
|
+
}
|
|
2172
1501
|
}, []);
|
|
2173
|
-
const getMessages = react.useCallback(() => {
|
|
2174
|
-
return messages;
|
|
2175
|
-
}, [messages]);
|
|
2176
|
-
const approveUserAction = react.useCallback(
|
|
2177
|
-
async (otp) => {
|
|
2178
|
-
const request = userActionStateRef.current.request;
|
|
2179
|
-
if (!request) return;
|
|
2180
|
-
try {
|
|
2181
|
-
await submitUserAction(configRef.current, request.userActionId, { otp });
|
|
2182
|
-
} catch (error) {
|
|
2183
|
-
setUserActionState((prev) => ({
|
|
2184
|
-
...prev,
|
|
2185
|
-
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
2186
|
-
}));
|
|
2187
|
-
callbacksRef.current.onError?.(error);
|
|
2188
|
-
throw error;
|
|
2189
|
-
}
|
|
2190
|
-
},
|
|
2191
|
-
[]
|
|
2192
|
-
);
|
|
2193
1502
|
const rejectUserAction = react.useCallback(async () => {
|
|
2194
1503
|
const request = userActionStateRef.current.request;
|
|
2195
1504
|
if (!request) return;
|
|
@@ -2223,43 +1532,91 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2223
1532
|
throw error;
|
|
2224
1533
|
}
|
|
2225
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
|
+
}, []);
|
|
2226
1589
|
react.useEffect(() => {
|
|
2227
|
-
const
|
|
2228
|
-
|
|
2229
|
-
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1590
|
+
const key = streamKeyFor(scopeKey, currentSessionId);
|
|
1591
|
+
const unsubscribe = activeStreamStore.subscribe(key, (msgs, isWaiting) => {
|
|
2230
1592
|
setMessages(msgs);
|
|
2231
1593
|
setIsWaitingForResponse(isWaiting);
|
|
2232
1594
|
});
|
|
2233
|
-
const active = activeStreamStore.get(
|
|
1595
|
+
const active = activeStreamStore.get(key);
|
|
2234
1596
|
if (active) {
|
|
2235
1597
|
setMessages(active.messages);
|
|
2236
1598
|
setIsWaitingForResponse(active.isWaiting);
|
|
2237
1599
|
}
|
|
2238
1600
|
return unsubscribe;
|
|
2239
|
-
}, []);
|
|
1601
|
+
}, [scopeKey, currentSessionId]);
|
|
2240
1602
|
react.useEffect(() => {
|
|
2241
|
-
if (!config.userId) return;
|
|
2242
1603
|
const toSave = messages.filter((m) => !m.isStreaming);
|
|
2243
1604
|
if (toSave.length > 0) {
|
|
2244
|
-
chatStore.set(
|
|
1605
|
+
chatStore.set(scopeKey, toSave);
|
|
2245
1606
|
}
|
|
2246
|
-
}, [messages,
|
|
1607
|
+
}, [messages, scopeKey]);
|
|
2247
1608
|
react.useEffect(() => {
|
|
2248
|
-
const
|
|
2249
|
-
|
|
2250
|
-
if (
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
setMessages(stored);
|
|
2260
|
-
sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
|
|
2261
|
-
}
|
|
2262
|
-
}, [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]);
|
|
2263
1620
|
return {
|
|
2264
1621
|
messages,
|
|
2265
1622
|
sendMessage,
|
|
@@ -2270,11 +1627,13 @@ function useChatV2(config, callbacks = {}) {
|
|
|
2270
1627
|
getSessionId,
|
|
2271
1628
|
getMessages,
|
|
2272
1629
|
isWaitingForResponse,
|
|
2273
|
-
sessionId:
|
|
1630
|
+
sessionId: currentSessionId,
|
|
2274
1631
|
userActionState,
|
|
2275
1632
|
approveUserAction,
|
|
2276
1633
|
rejectUserAction,
|
|
2277
|
-
resendOtp
|
|
1634
|
+
resendOtp,
|
|
1635
|
+
loadSession,
|
|
1636
|
+
loadingSessionId
|
|
2278
1637
|
};
|
|
2279
1638
|
}
|
|
2280
1639
|
function getSpeechRecognition() {
|
|
@@ -2494,15 +1853,18 @@ function useVoice(config = {}, callbacks = {}) {
|
|
|
2494
1853
|
}
|
|
2495
1854
|
|
|
2496
1855
|
exports.buildFormattedThinking = buildFormattedThinking;
|
|
1856
|
+
exports.buildScopeKey = buildScopeKey;
|
|
2497
1857
|
exports.cancelUserAction = cancelUserAction;
|
|
2498
1858
|
exports.createInitialV2State = createInitialV2State;
|
|
2499
1859
|
exports.generateId = generateId;
|
|
1860
|
+
exports.listConversations = listConversations;
|
|
1861
|
+
exports.listSessions = listSessions;
|
|
2500
1862
|
exports.processStreamEventV2 = processStreamEventV2;
|
|
2501
1863
|
exports.resendUserAction = resendUserAction;
|
|
2502
1864
|
exports.streamWorkflowEvents = streamWorkflowEvents;
|
|
2503
1865
|
exports.submitUserAction = submitUserAction;
|
|
2504
|
-
exports.useChat = useChat;
|
|
2505
1866
|
exports.useChatV2 = useChatV2;
|
|
2506
1867
|
exports.useVoice = useVoice;
|
|
1868
|
+
exports.workingPhaseDetailForDisplay = workingPhaseDetailForDisplay;
|
|
2507
1869
|
//# sourceMappingURL=index.js.map
|
|
2508
1870
|
//# sourceMappingURL=index.js.map
|