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