@paymanai/payman-ask-sdk 4.0.11 → 4.0.13
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 +15 -62
- package/dist/index.d.ts +15 -62
- package/dist/index.js +2753 -411
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2709 -334
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +416 -0
- package/dist/styles.css.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { buildFormattedThinking, cancelUserAction, createInitialV2State, generateId, processStreamEventV2, resendUserAction, streamWorkflowEvents, submitUserAction, useChatV2, useVoice } from '@paymanai/payman-typescript-ask-sdk';
|
|
1
|
+
import { createContext, forwardRef, useCallback, useRef, useState, useImperativeHandle, useEffect, useMemo, useLayoutEffect, useContext } from 'react';
|
|
3
2
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
4
|
-
import { createContext, forwardRef, useRef, useState, useCallback, useImperativeHandle, useEffect, useMemo, useLayoutEffect, useContext } from 'react';
|
|
5
3
|
import { clsx } from 'clsx';
|
|
6
4
|
import { twMerge } from 'tailwind-merge';
|
|
7
5
|
import * as Sentry from '@sentry/react';
|
|
8
|
-
import { ArrowDown, Pencil, X, RotateCcw, Telescope, Zap, Plus, ImagePlus, Paperclip, Mic, ArrowUp, Check, AlertCircle, Copy, WifiOff, ThumbsUp, ThumbsDown, Binoculars,
|
|
6
|
+
import { ArrowDown, Pencil, X, RotateCcw, Telescope, Zap, Plus, ImagePlus, Paperclip, Mic, ArrowUp, Check, AlertCircle, Copy, WifiOff, ThumbsUp, ThumbsDown, Binoculars, Info, Download, Loader2, ChevronDown, FileText, User, Clock, Sparkles, ImageOff, Eye, ChevronRight, ShieldCheck } from 'lucide-react';
|
|
9
7
|
import ReactMarkdown from 'react-markdown';
|
|
10
8
|
import remarkGfm from 'remark-gfm';
|
|
11
9
|
import { createPortal } from 'react-dom';
|
|
@@ -13,6 +11,1963 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
|
13
11
|
import remarkBreaks from 'remark-breaks';
|
|
14
12
|
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
|
|
15
13
|
|
|
14
|
+
var __defProp = Object.defineProperty;
|
|
15
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
16
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "", value);
|
|
17
|
+
function generateId() {
|
|
18
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
19
|
+
const r = Math.random() * 16 | 0;
|
|
20
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
21
|
+
return v.toString(16);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function yieldAfterProgressEvent(event) {
|
|
25
|
+
const t = event.eventType;
|
|
26
|
+
if (t === "RUN_IN_PROGRESS" || t === "INTENT_PROGRESS" || t === "AGGREGATOR_THINKING_CONT" || t === "INTENT_THINKING_CONT" || t === "THINKING_DELTA") {
|
|
27
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
28
|
+
}
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
}
|
|
31
|
+
function parseJSONBuffer(buffer) {
|
|
32
|
+
const events = [];
|
|
33
|
+
let braceCount = 0;
|
|
34
|
+
let startIndex = 0;
|
|
35
|
+
let inString = false;
|
|
36
|
+
let escapeNext = false;
|
|
37
|
+
let lastParsedIndex = -1;
|
|
38
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
39
|
+
const char = buffer[i];
|
|
40
|
+
if (escapeNext) {
|
|
41
|
+
escapeNext = false;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (char === "\\") {
|
|
45
|
+
escapeNext = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === '"' && !escapeNext) {
|
|
49
|
+
inString = !inString;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (inString) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (char === "{") {
|
|
56
|
+
if (braceCount === 0) {
|
|
57
|
+
startIndex = i;
|
|
58
|
+
}
|
|
59
|
+
braceCount++;
|
|
60
|
+
} else if (char === "}") {
|
|
61
|
+
braceCount--;
|
|
62
|
+
if (braceCount === 0) {
|
|
63
|
+
const jsonStr = buffer.substring(startIndex, i + 1);
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(jsonStr);
|
|
66
|
+
const event = parsed;
|
|
67
|
+
events.push(event);
|
|
68
|
+
lastParsedIndex = i;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error("Failed to parse JSON event:", jsonStr, err);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const remaining = lastParsedIndex >= 0 ? buffer.substring(lastParsedIndex + 1) : buffer;
|
|
76
|
+
return { events, remaining };
|
|
77
|
+
}
|
|
78
|
+
async function streamWorkflowEvents(url, body, headers, options = {}) {
|
|
79
|
+
const { signal, onEvent, onError, onComplete } = options;
|
|
80
|
+
try {
|
|
81
|
+
const response = await fetch(url, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
...headers
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify(body),
|
|
88
|
+
signal
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
const errorText = await response.text();
|
|
92
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
93
|
+
}
|
|
94
|
+
const reader = response.body?.getReader();
|
|
95
|
+
if (!reader) {
|
|
96
|
+
throw new Error("No response body");
|
|
97
|
+
}
|
|
98
|
+
const decoder = new TextDecoder();
|
|
99
|
+
let buffer = "";
|
|
100
|
+
while (true) {
|
|
101
|
+
const { done, value } = await reader.read();
|
|
102
|
+
if (done) {
|
|
103
|
+
if (buffer.trim()) {
|
|
104
|
+
const { events: events2 } = parseJSONBuffer(buffer);
|
|
105
|
+
for (const event of events2) {
|
|
106
|
+
onEvent?.(event);
|
|
107
|
+
await yieldAfterProgressEvent(event);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
buffer += decoder.decode(value, { stream: true });
|
|
113
|
+
const { events, remaining } = parseJSONBuffer(buffer);
|
|
114
|
+
for (const event of events) {
|
|
115
|
+
onEvent?.(event);
|
|
116
|
+
await yieldAfterProgressEvent(event);
|
|
117
|
+
}
|
|
118
|
+
buffer = remaining;
|
|
119
|
+
}
|
|
120
|
+
onComplete?.();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
onError?.(error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
var STAGE_LABELS = {
|
|
129
|
+
sanitizer: "Checking your request",
|
|
130
|
+
analyzer: "Understanding what you're asking",
|
|
131
|
+
prefetcher: "Gathering context",
|
|
132
|
+
planner: "Planning how to handle this",
|
|
133
|
+
execution: "Working on it",
|
|
134
|
+
formatter: "Writing the response"
|
|
135
|
+
};
|
|
136
|
+
function stageLabel(stage) {
|
|
137
|
+
const label = STAGE_LABELS[stage];
|
|
138
|
+
if (label) return label;
|
|
139
|
+
const pretty = stage.replace(/[_-]/g, " ");
|
|
140
|
+
return `Running ${pretty}`;
|
|
141
|
+
}
|
|
142
|
+
function isBlandStatus(message) {
|
|
143
|
+
if (!message) return true;
|
|
144
|
+
const normalized = message.trim().toLowerCase().replace(/[…\.]+$/, "").trim();
|
|
145
|
+
return BLAND_STATUS_LABELS.has(normalized);
|
|
146
|
+
}
|
|
147
|
+
var BLAND_STATUS_LABELS = /* @__PURE__ */ new Set([
|
|
148
|
+
"executing",
|
|
149
|
+
"working on it",
|
|
150
|
+
"thinking",
|
|
151
|
+
"processing",
|
|
152
|
+
"reviewing your request",
|
|
153
|
+
"composing response",
|
|
154
|
+
"checking your request",
|
|
155
|
+
"polishing the response"
|
|
156
|
+
]);
|
|
157
|
+
function getEventMessage(event) {
|
|
158
|
+
if (event.message?.trim()) {
|
|
159
|
+
return event.message.trim();
|
|
160
|
+
}
|
|
161
|
+
if (event.errorMessage?.trim()) {
|
|
162
|
+
return event.errorMessage.trim();
|
|
163
|
+
}
|
|
164
|
+
const eventType = event.eventType;
|
|
165
|
+
switch (eventType) {
|
|
166
|
+
case "RUN_STARTED":
|
|
167
|
+
return "Starting agent run...";
|
|
168
|
+
case "TOOL_CALL_STARTED": {
|
|
169
|
+
const description = typeof event.description === "string" ? event.description.trim() : "";
|
|
170
|
+
if (description) return description;
|
|
171
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "";
|
|
172
|
+
return toolName ? `Calling ${toolName}...` : "Calling tool...";
|
|
173
|
+
}
|
|
174
|
+
case "TOOL_CALL_COMPLETED": {
|
|
175
|
+
const description = typeof event.description === "string" ? event.description.trim() : "";
|
|
176
|
+
if (description) return description;
|
|
177
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "";
|
|
178
|
+
return toolName ? `${toolName} completed` : "Tool call completed";
|
|
179
|
+
}
|
|
180
|
+
case "RUN_IN_PROGRESS":
|
|
181
|
+
return event.partialText || "Thinking...";
|
|
182
|
+
case "RUN_COMPLETED":
|
|
183
|
+
return "Agent run completed";
|
|
184
|
+
case "RUN_FAILED":
|
|
185
|
+
return event.errorMessage || "Agent run failed";
|
|
186
|
+
case "KEEP_ALIVE":
|
|
187
|
+
return event.description || "";
|
|
188
|
+
case "THINKING_DELTA":
|
|
189
|
+
return event.text || "";
|
|
190
|
+
default:
|
|
191
|
+
return eventType;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function extractResponseContent(response) {
|
|
195
|
+
if (typeof response === "string") {
|
|
196
|
+
return response;
|
|
197
|
+
}
|
|
198
|
+
if (typeof response === "object" && response !== null) {
|
|
199
|
+
const resp = response;
|
|
200
|
+
if ("text" in resp && typeof resp.text === "string") {
|
|
201
|
+
return resp.text;
|
|
202
|
+
}
|
|
203
|
+
if ("content" in resp && typeof resp.content === "string") {
|
|
204
|
+
return resp.content;
|
|
205
|
+
}
|
|
206
|
+
if ("message" in resp && typeof resp.message === "string") {
|
|
207
|
+
return resp.message;
|
|
208
|
+
}
|
|
209
|
+
if ("answer" in resp && typeof resp.answer === "string") {
|
|
210
|
+
return resp.answer;
|
|
211
|
+
}
|
|
212
|
+
return JSON.stringify(response);
|
|
213
|
+
}
|
|
214
|
+
return "";
|
|
215
|
+
}
|
|
216
|
+
function normalizeEvent(event) {
|
|
217
|
+
const type = event.eventType;
|
|
218
|
+
switch (type) {
|
|
219
|
+
case "RUN_STARTED":
|
|
220
|
+
return { ...event, eventType: "WORKFLOW_STARTED" };
|
|
221
|
+
case "TOOL_CALL_STARTED": {
|
|
222
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : void 0;
|
|
223
|
+
const description = typeof event.description === "string" ? event.description.trim() : "";
|
|
224
|
+
return {
|
|
225
|
+
...event,
|
|
226
|
+
eventType: "INTENT_STARTED",
|
|
227
|
+
workerName: toolName ?? event.workerName,
|
|
228
|
+
message: description || event.message
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
case "TOOL_CALL_COMPLETED": {
|
|
232
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : void 0;
|
|
233
|
+
const description = typeof event.description === "string" ? event.description.trim() : "";
|
|
234
|
+
return {
|
|
235
|
+
...event,
|
|
236
|
+
eventType: "INTENT_COMPLETED",
|
|
237
|
+
workerName: toolName ?? event.workerName,
|
|
238
|
+
message: description || event.message
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
case "RUN_IN_PROGRESS":
|
|
242
|
+
return {
|
|
243
|
+
...event,
|
|
244
|
+
eventType: "INTENT_PROGRESS",
|
|
245
|
+
message: event.partialText ?? event.message
|
|
246
|
+
};
|
|
247
|
+
case "RUN_COMPLETED":
|
|
248
|
+
return {
|
|
249
|
+
...event,
|
|
250
|
+
eventType: "WORKFLOW_COMPLETED",
|
|
251
|
+
// state machine reads event.response for the final content
|
|
252
|
+
response: event.response ?? event.message
|
|
253
|
+
};
|
|
254
|
+
case "RUN_FAILED":
|
|
255
|
+
return {
|
|
256
|
+
...event,
|
|
257
|
+
eventType: "WORKFLOW_ERROR",
|
|
258
|
+
errorMessage: event.errorMessage ?? event.message
|
|
259
|
+
};
|
|
260
|
+
default:
|
|
261
|
+
return event;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function isVerificationSchema(schema) {
|
|
265
|
+
const props = schema?.properties;
|
|
266
|
+
if (!props) return false;
|
|
267
|
+
const keys = Object.keys(props);
|
|
268
|
+
return keys.length === 1 && keys[0] === "verificationCode";
|
|
269
|
+
}
|
|
270
|
+
function classifyUserActionKind(action, schema) {
|
|
271
|
+
switch ((action || "").toLowerCase()) {
|
|
272
|
+
case "userverificationrequest":
|
|
273
|
+
return "verification";
|
|
274
|
+
case "usernotificationrequest":
|
|
275
|
+
return "notification";
|
|
276
|
+
case "userformrequest":
|
|
277
|
+
return isVerificationSchema(schema) ? "verification" : "form";
|
|
278
|
+
default:
|
|
279
|
+
return isVerificationSchema(schema) ? "verification" : "form";
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function userActionHeader(kind) {
|
|
283
|
+
return kind === "verification" ? "**Verification required**" : "**Action required**";
|
|
284
|
+
}
|
|
285
|
+
function workingPhaseDetailForDisplay(raw) {
|
|
286
|
+
const t = raw.trim();
|
|
287
|
+
if (!t) return "";
|
|
288
|
+
if (/^Identified\s+\d+\s+tasks?\s+to\s+execute\.?$/i.test(t)) {
|
|
289
|
+
return "";
|
|
290
|
+
}
|
|
291
|
+
return t;
|
|
292
|
+
}
|
|
293
|
+
function getEventText(event, field) {
|
|
294
|
+
const value = event[field];
|
|
295
|
+
return typeof value === "string" ? value.trim() : "";
|
|
296
|
+
}
|
|
297
|
+
function shouldShowIntentHeader(event) {
|
|
298
|
+
const workerName = getEventText(event, "workerName");
|
|
299
|
+
const intentId = getEventText(event, "intentId");
|
|
300
|
+
return Boolean(workerName && intentId && workerName === intentId);
|
|
301
|
+
}
|
|
302
|
+
function addThinkingHeader(state, header) {
|
|
303
|
+
state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + header;
|
|
304
|
+
}
|
|
305
|
+
function addThinkingDetail(state, detail) {
|
|
306
|
+
const trimmed = detail.trim();
|
|
307
|
+
if (!trimmed) return;
|
|
308
|
+
state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + trimmed;
|
|
309
|
+
}
|
|
310
|
+
function addThinkingLine(state, header, detail) {
|
|
311
|
+
state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + header + "\n" + detail;
|
|
312
|
+
}
|
|
313
|
+
function appendThinkingText(state, text) {
|
|
314
|
+
state.formattedThinkingText += text;
|
|
315
|
+
}
|
|
316
|
+
function updateExecutionStageMessage(state, msg) {
|
|
317
|
+
if (!msg) return;
|
|
318
|
+
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
319
|
+
const s = state.steps[i];
|
|
320
|
+
if (s.eventType === "STAGE_STARTED" && (s.stage === "executor" || s.stage === "execution") && s.status === "in_progress") {
|
|
321
|
+
s.message = msg;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function completeLastInProgressStep(steps) {
|
|
327
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
328
|
+
if (steps[i].status === "in_progress") {
|
|
329
|
+
steps[i].status = "completed";
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function createInitialV2State() {
|
|
335
|
+
return {
|
|
336
|
+
formattedThinkingText: "",
|
|
337
|
+
finalResponse: "",
|
|
338
|
+
currentWorker: "",
|
|
339
|
+
lastEventType: "",
|
|
340
|
+
sessionId: void 0,
|
|
341
|
+
executionId: void 0,
|
|
342
|
+
hasError: false,
|
|
343
|
+
errorMessage: "",
|
|
344
|
+
userActions: [],
|
|
345
|
+
notifications: [],
|
|
346
|
+
lastUserAction: void 0,
|
|
347
|
+
lastNotification: void 0,
|
|
348
|
+
finalData: void 0,
|
|
349
|
+
steps: [],
|
|
350
|
+
stepCounter: 0,
|
|
351
|
+
currentExecutingStepId: void 0
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function upsertUserAction(state, req) {
|
|
355
|
+
const active = { ...req, status: "pending" };
|
|
356
|
+
const matchIdx = state.userActions.findIndex(
|
|
357
|
+
(p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
|
|
358
|
+
);
|
|
359
|
+
if (matchIdx >= 0) {
|
|
360
|
+
state.userActions[matchIdx] = active;
|
|
361
|
+
} else {
|
|
362
|
+
state.userActions.push(active);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function processStreamEventV2(rawEvent, state) {
|
|
366
|
+
const event = normalizeEvent(rawEvent);
|
|
367
|
+
const eventType = event.eventType;
|
|
368
|
+
state.lastUserAction = void 0;
|
|
369
|
+
state.lastNotification = void 0;
|
|
370
|
+
if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
371
|
+
if (event.executionId) state.executionId = event.executionId;
|
|
372
|
+
if (event.sessionId) state.sessionId = event.sessionId;
|
|
373
|
+
const description = typeof event.description === "string" ? event.description : "";
|
|
374
|
+
if (description) {
|
|
375
|
+
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
376
|
+
if (state.steps[i].status === "in_progress") {
|
|
377
|
+
state.steps[i].message = description;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return state;
|
|
383
|
+
}
|
|
384
|
+
if (typeof eventType === "string" && eventType.toUpperCase() === "THINKING_DELTA") {
|
|
385
|
+
const text = typeof event.text === "string" ? event.text : "";
|
|
386
|
+
if (text) {
|
|
387
|
+
appendThinkingText(state, text);
|
|
388
|
+
}
|
|
389
|
+
if (event.executionId) state.executionId = event.executionId;
|
|
390
|
+
if (event.sessionId) state.sessionId = event.sessionId;
|
|
391
|
+
state.lastEventType = "THINKING_DELTA";
|
|
392
|
+
return state;
|
|
393
|
+
}
|
|
394
|
+
if (event.executionId) state.executionId = event.executionId;
|
|
395
|
+
if (event.sessionId) state.sessionId = event.sessionId;
|
|
396
|
+
const message = getEventMessage(event);
|
|
397
|
+
switch (eventType) {
|
|
398
|
+
case "WORKFLOW_STARTED":
|
|
399
|
+
case "STARTED":
|
|
400
|
+
state.lastEventType = eventType;
|
|
401
|
+
break;
|
|
402
|
+
case "INTENT_PROGRESS": {
|
|
403
|
+
const rawMessage = typeof event.message === "string" ? event.message : "";
|
|
404
|
+
const rawPartial = typeof event.partialText === "string" ? event.partialText : "";
|
|
405
|
+
const delta = rawMessage || rawPartial;
|
|
406
|
+
if (delta.length > 0) {
|
|
407
|
+
state.finalResponse += delta;
|
|
408
|
+
}
|
|
409
|
+
state.lastEventType = eventType;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
case "INTENT_THINKING": {
|
|
413
|
+
const worker = getEventText(event, "workerName") || "Worker";
|
|
414
|
+
const msg = getEventText(event, "message") || "Thinking...";
|
|
415
|
+
const showHeader = shouldShowIntentHeader(event);
|
|
416
|
+
if (worker !== state.currentWorker) {
|
|
417
|
+
state.currentWorker = worker;
|
|
418
|
+
if (showHeader && msg && msg !== worker) {
|
|
419
|
+
addThinkingLine(state, `**${worker}**`, msg);
|
|
420
|
+
} else if (showHeader) {
|
|
421
|
+
addThinkingHeader(state, `**${worker}**`);
|
|
422
|
+
} else if (msg !== "Thinking...") {
|
|
423
|
+
addThinkingDetail(state, msg);
|
|
424
|
+
}
|
|
425
|
+
} else if (showHeader || msg !== "Thinking...") {
|
|
426
|
+
appendThinkingText(state, "\n" + msg);
|
|
427
|
+
}
|
|
428
|
+
const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
|
|
429
|
+
if (lastInProgress) {
|
|
430
|
+
lastInProgress.thinkingText = "";
|
|
431
|
+
lastInProgress.isThinking = true;
|
|
432
|
+
}
|
|
433
|
+
state.lastEventType = "INTENT_THINKING";
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
case "INTENT_THINKING_CONT": {
|
|
437
|
+
const msg = event.message || "";
|
|
438
|
+
if (!msg) break;
|
|
439
|
+
if (state.lastEventType === "INTENT_THINKING") {
|
|
440
|
+
appendThinkingText(state, "\n" + msg);
|
|
441
|
+
} else {
|
|
442
|
+
appendThinkingText(state, msg);
|
|
443
|
+
}
|
|
444
|
+
const thinkingStep = [...state.steps].reverse().find((s) => s.isThinking);
|
|
445
|
+
if (thinkingStep) {
|
|
446
|
+
thinkingStep.thinkingText = (thinkingStep.thinkingText || "") + msg;
|
|
447
|
+
}
|
|
448
|
+
state.lastEventType = "INTENT_THINKING_CONT";
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case "ORCHESTRATOR_THINKING": {
|
|
452
|
+
addThinkingLine(state, "**Planning**", event.message || "Understanding your request...");
|
|
453
|
+
const stepId = `step-${state.stepCounter++}`;
|
|
454
|
+
state.steps.push({
|
|
455
|
+
id: stepId,
|
|
456
|
+
eventType,
|
|
457
|
+
message,
|
|
458
|
+
status: "in_progress",
|
|
459
|
+
timestamp: Date.now(),
|
|
460
|
+
elapsedMs: event.elapsedMs
|
|
461
|
+
});
|
|
462
|
+
state.currentExecutingStepId = stepId;
|
|
463
|
+
state.lastEventType = eventType;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
case "ORCHESTRATOR_COMPLETED": {
|
|
467
|
+
const workingDetail = workingPhaseDetailForDisplay(message);
|
|
468
|
+
if (workingDetail) {
|
|
469
|
+
addThinkingLine(state, "**Working**", workingDetail);
|
|
470
|
+
} else {
|
|
471
|
+
addThinkingHeader(state, "**Working**");
|
|
472
|
+
}
|
|
473
|
+
state.steps.push({
|
|
474
|
+
id: `step-${state.stepCounter++}`,
|
|
475
|
+
eventType: "WORKING",
|
|
476
|
+
message: workingDetail,
|
|
477
|
+
status: "completed",
|
|
478
|
+
timestamp: Date.now()
|
|
479
|
+
});
|
|
480
|
+
const step = state.steps.find((s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress");
|
|
481
|
+
if (step) {
|
|
482
|
+
step.status = "completed";
|
|
483
|
+
if (event.elapsedMs) step.elapsedMs = event.elapsedMs;
|
|
484
|
+
if (step.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
|
|
485
|
+
}
|
|
486
|
+
state.lastEventType = eventType;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
case "INTENT_STARTED": {
|
|
490
|
+
const worker = getEventText(event, "workerName") || "Worker";
|
|
491
|
+
const msg = getEventText(event, "message") || "Starting...";
|
|
492
|
+
const showHeader = shouldShowIntentHeader(event);
|
|
493
|
+
state.currentWorker = worker;
|
|
494
|
+
if (showHeader && msg !== worker) {
|
|
495
|
+
addThinkingLine(state, `**${worker}**`, msg);
|
|
496
|
+
} else if (showHeader) {
|
|
497
|
+
addThinkingHeader(state, `**${worker}**`);
|
|
498
|
+
} else {
|
|
499
|
+
addThinkingDetail(state, msg);
|
|
500
|
+
}
|
|
501
|
+
const thinkingStep = state.steps.find((s) => s.isThinking);
|
|
502
|
+
if (thinkingStep) thinkingStep.isThinking = false;
|
|
503
|
+
const stepId = `step-${state.stepCounter++}`;
|
|
504
|
+
state.steps.push({
|
|
505
|
+
id: stepId,
|
|
506
|
+
eventType,
|
|
507
|
+
message,
|
|
508
|
+
status: "in_progress",
|
|
509
|
+
timestamp: Date.now(),
|
|
510
|
+
elapsedMs: event.elapsedMs
|
|
511
|
+
});
|
|
512
|
+
state.currentExecutingStepId = stepId;
|
|
513
|
+
updateExecutionStageMessage(state, message);
|
|
514
|
+
state.lastEventType = eventType;
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
case "INTENT_COMPLETED": {
|
|
518
|
+
const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
|
|
519
|
+
if (intentStep) {
|
|
520
|
+
intentStep.status = "completed";
|
|
521
|
+
intentStep.isThinking = false;
|
|
522
|
+
if (event.elapsedMs) intentStep.elapsedMs = event.elapsedMs;
|
|
523
|
+
if (intentStep.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
|
|
524
|
+
}
|
|
525
|
+
state.lastEventType = eventType;
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
case "AGGREGATOR_THINKING": {
|
|
529
|
+
addThinkingLine(state, "**Finalizing**", event.message || "Preparing response...");
|
|
530
|
+
const stepId = `step-${state.stepCounter++}`;
|
|
531
|
+
state.steps.push({
|
|
532
|
+
id: stepId,
|
|
533
|
+
eventType,
|
|
534
|
+
message,
|
|
535
|
+
status: "in_progress",
|
|
536
|
+
timestamp: Date.now(),
|
|
537
|
+
elapsedMs: event.elapsedMs
|
|
538
|
+
});
|
|
539
|
+
state.currentExecutingStepId = stepId;
|
|
540
|
+
state.lastEventType = eventType;
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
case "AGGREGATOR_COMPLETED": {
|
|
544
|
+
appendThinkingText(state, "\n" + (event.message || "Response ready"));
|
|
545
|
+
const step = state.steps.find((s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress");
|
|
546
|
+
if (step) {
|
|
547
|
+
step.status = "completed";
|
|
548
|
+
if (event.elapsedMs) step.elapsedMs = event.elapsedMs;
|
|
549
|
+
if (step.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
|
|
550
|
+
}
|
|
551
|
+
state.lastEventType = eventType;
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
case "WORKFLOW_COMPLETED":
|
|
555
|
+
case "COMPLETED": {
|
|
556
|
+
const totalTime = Number(event.totalTimeMs);
|
|
557
|
+
if (Number.isFinite(totalTime) && totalTime > 0) {
|
|
558
|
+
state.totalElapsedMs = totalTime;
|
|
559
|
+
}
|
|
560
|
+
let content = extractResponseContent(event.response);
|
|
561
|
+
const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
|
|
562
|
+
if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
|
|
563
|
+
content = trace.workflowMsg;
|
|
564
|
+
}
|
|
565
|
+
if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
|
|
566
|
+
const agg = trace.aggregator;
|
|
567
|
+
if (typeof agg.response === "string") content = agg.response;
|
|
568
|
+
else content = extractResponseContent(agg.response);
|
|
569
|
+
}
|
|
570
|
+
if (content) {
|
|
571
|
+
state.finalResponse = content;
|
|
572
|
+
if (event.trace && typeof event.trace === "object") {
|
|
573
|
+
state.finalData = event.trace;
|
|
574
|
+
}
|
|
575
|
+
state.hasError = false;
|
|
576
|
+
state.errorMessage = "";
|
|
577
|
+
} else {
|
|
578
|
+
state.hasError = true;
|
|
579
|
+
state.errorMessage = "WORKFLOW_FAILED";
|
|
580
|
+
}
|
|
581
|
+
state.steps.forEach((step) => {
|
|
582
|
+
if (step.status === "in_progress") {
|
|
583
|
+
step.status = "completed";
|
|
584
|
+
step.isThinking = false;
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
state.lastEventType = eventType;
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
case "USER_ACTION_REQUIRED": {
|
|
591
|
+
const rawAction = typeof event.action === "string" ? event.action : void 0;
|
|
592
|
+
const schema = event.requestedSchema;
|
|
593
|
+
const kind = classifyUserActionKind(rawAction, schema);
|
|
594
|
+
const promptMessage = typeof event.message === "string" && event.message.trim() || message || "";
|
|
595
|
+
const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
|
|
596
|
+
if (kind === "notification") {
|
|
597
|
+
const notification = {
|
|
598
|
+
id: userActionId || `note-${state.stepCounter++}`,
|
|
599
|
+
message: promptMessage
|
|
600
|
+
};
|
|
601
|
+
state.notifications.push(notification);
|
|
602
|
+
state.lastNotification = notification;
|
|
603
|
+
if (promptMessage) addThinkingDetail(state, promptMessage);
|
|
604
|
+
state.lastEventType = eventType;
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
if (!userActionId) {
|
|
608
|
+
state.lastEventType = eventType;
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
completeLastInProgressStep(state.steps);
|
|
612
|
+
const verificationType = event.verificationType === "ALPHANUMERIC_CODE" || event.verificationType === "NUMERIC_CODE" ? event.verificationType : void 0;
|
|
613
|
+
const request = {
|
|
614
|
+
userActionId,
|
|
615
|
+
kind,
|
|
616
|
+
rawAction,
|
|
617
|
+
subAction: typeof event.subAction === "string" ? event.subAction : void 0,
|
|
618
|
+
verificationType,
|
|
619
|
+
expirySeconds: typeof event.expirySeconds === "number" ? event.expirySeconds : void 0,
|
|
620
|
+
message: promptMessage || void 0,
|
|
621
|
+
requestedSchema: schema,
|
|
622
|
+
metadata: event.metadata,
|
|
623
|
+
toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : void 0,
|
|
624
|
+
executionId: state.executionId,
|
|
625
|
+
sessionId: state.sessionId
|
|
626
|
+
};
|
|
627
|
+
upsertUserAction(state, request);
|
|
628
|
+
state.lastUserAction = request;
|
|
629
|
+
const header = userActionHeader(kind);
|
|
630
|
+
if (promptMessage) addThinkingLine(state, header, promptMessage);
|
|
631
|
+
else addThinkingHeader(state, header);
|
|
632
|
+
const stepId = `step-${state.stepCounter++}`;
|
|
633
|
+
state.steps.push({
|
|
634
|
+
id: stepId,
|
|
635
|
+
eventType,
|
|
636
|
+
message: promptMessage || (kind === "verification" ? "Waiting for verification..." : "Waiting for your input..."),
|
|
637
|
+
status: "in_progress",
|
|
638
|
+
timestamp: Date.now(),
|
|
639
|
+
elapsedMs: event.elapsedMs
|
|
640
|
+
});
|
|
641
|
+
state.currentExecutingStepId = stepId;
|
|
642
|
+
state.lastEventType = eventType;
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
case "USER_NOTIFICATION": {
|
|
646
|
+
const noteMessage = typeof event.message === "string" && event.message.trim() || message || "";
|
|
647
|
+
const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
|
|
648
|
+
if (noteMessage || userActionId) {
|
|
649
|
+
const notification = {
|
|
650
|
+
id: userActionId || `note-${state.stepCounter++}`,
|
|
651
|
+
message: noteMessage
|
|
652
|
+
};
|
|
653
|
+
state.notifications.push(notification);
|
|
654
|
+
state.lastNotification = notification;
|
|
655
|
+
if (noteMessage) addThinkingDetail(state, noteMessage);
|
|
656
|
+
}
|
|
657
|
+
state.lastEventType = eventType;
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
case "WORKFLOW_ERROR":
|
|
661
|
+
case "ERROR":
|
|
662
|
+
state.hasError = true;
|
|
663
|
+
state.errorMessage = event.errorMessage || event.message || "Workflow error";
|
|
664
|
+
state.lastEventType = eventType;
|
|
665
|
+
break;
|
|
666
|
+
case "INTENT_ERROR": {
|
|
667
|
+
state.errorMessage = message || event.errorMessage || "An error occurred";
|
|
668
|
+
const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
|
|
669
|
+
if (intentStep) {
|
|
670
|
+
intentStep.status = "error";
|
|
671
|
+
intentStep.isThinking = false;
|
|
672
|
+
}
|
|
673
|
+
state.lastEventType = eventType;
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
// ---- K2 pipeline stage lifecycle events ----
|
|
677
|
+
//
|
|
678
|
+
// The k2-server playground streaming API emits
|
|
679
|
+
// STAGE_STARTED / STAGE_COMPLETED / STAGE_FAILED /
|
|
680
|
+
// STAGE_SKIPPED around each pipeline stage so the UI can
|
|
681
|
+
// show real progress (sanitizer → analyzer → planner → …)
|
|
682
|
+
// instead of a static placeholder. Each STAGE_STARTED
|
|
683
|
+
// pushes an in-progress step with a user-facing label; the
|
|
684
|
+
// matching STAGE_COMPLETED/FAILED closes it WITHOUT
|
|
685
|
+
// rewriting the label — a terse "completed" flash between
|
|
686
|
+
// stages was more noise than signal. STAGE_SKIPPED removes
|
|
687
|
+
// the just-opened step entirely (skipping is expected on
|
|
688
|
+
// DIRECT_RESPONSE short-circuits).
|
|
689
|
+
//
|
|
690
|
+
// Finding the matching step uses `stepId` directly rather
|
|
691
|
+
// than comparing labels — the processor remembers which id
|
|
692
|
+
// it minted for the latest open STAGE_STARTED and the
|
|
693
|
+
// closing events look it up from state.currentExecutingStepId.
|
|
694
|
+
case "STAGE_STARTED": {
|
|
695
|
+
const stage = getEventText(event, "stage");
|
|
696
|
+
if (!stage) {
|
|
697
|
+
state.lastEventType = eventType;
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
const serverMessage = getEventText(event, "message");
|
|
701
|
+
let initialMessage = serverMessage || stageLabel(stage);
|
|
702
|
+
if (stage === "executor" || stage === "execution") {
|
|
703
|
+
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
704
|
+
const s = state.steps[i];
|
|
705
|
+
if (s.eventType === "STAGE_STARTED" && s.stage === "analyzer" && s.status === "completed" && s.message) {
|
|
706
|
+
initialMessage = s.message;
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const stepId = `stage-${stage}-${state.stepCounter++}`;
|
|
712
|
+
state.steps.push({
|
|
713
|
+
id: stepId,
|
|
714
|
+
eventType,
|
|
715
|
+
message: initialMessage,
|
|
716
|
+
status: "in_progress",
|
|
717
|
+
timestamp: Date.now(),
|
|
718
|
+
stage
|
|
719
|
+
});
|
|
720
|
+
state.currentExecutingStepId = stepId;
|
|
721
|
+
state.lastEventType = eventType;
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
case "STAGE_COMPLETED": {
|
|
725
|
+
const stage = getEventText(event, "stage");
|
|
726
|
+
if (!stage) {
|
|
727
|
+
state.lastEventType = eventType;
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
730
|
+
const serverMessage = getEventText(event, "message");
|
|
731
|
+
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
732
|
+
const s = state.steps[i];
|
|
733
|
+
if (s.eventType === "STAGE_STARTED" && s.stage === stage && s.status === "in_progress") {
|
|
734
|
+
if (serverMessage) s.message = serverMessage;
|
|
735
|
+
s.status = "completed";
|
|
736
|
+
const durationMs = Number(event.durationMs);
|
|
737
|
+
if (Number.isFinite(durationMs)) s.elapsedMs = durationMs;
|
|
738
|
+
if (s.id === state.currentExecutingStepId) {
|
|
739
|
+
state.currentExecutingStepId = void 0;
|
|
740
|
+
}
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
state.lastEventType = eventType;
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
case "STAGE_FAILED": {
|
|
748
|
+
const stage = getEventText(event, "stage");
|
|
749
|
+
if (!stage) {
|
|
750
|
+
state.lastEventType = eventType;
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
const serverMessage = getEventText(event, "message");
|
|
754
|
+
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
755
|
+
const s = state.steps[i];
|
|
756
|
+
if (s.eventType === "STAGE_STARTED" && s.stage === stage && s.status === "in_progress") {
|
|
757
|
+
if (serverMessage) s.message = serverMessage;
|
|
758
|
+
s.status = "error";
|
|
759
|
+
const durationMs = Number(event.durationMs);
|
|
760
|
+
if (Number.isFinite(durationMs)) s.elapsedMs = durationMs;
|
|
761
|
+
if (s.id === state.currentExecutingStepId) {
|
|
762
|
+
state.currentExecutingStepId = void 0;
|
|
763
|
+
}
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
state.lastEventType = eventType;
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
case "STAGE_SKIPPED": {
|
|
771
|
+
const stage = getEventText(event, "stage");
|
|
772
|
+
if (!stage) {
|
|
773
|
+
state.lastEventType = eventType;
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
777
|
+
const s = state.steps[i];
|
|
778
|
+
if (s.eventType === "STAGE_STARTED" && s.stage === stage && s.status === "in_progress") {
|
|
779
|
+
if (s.id === state.currentExecutingStepId) {
|
|
780
|
+
state.currentExecutingStepId = void 0;
|
|
781
|
+
}
|
|
782
|
+
s.status = "skipped";
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
state.lastEventType = eventType;
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
default:
|
|
790
|
+
state.lastEventType = eventType;
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
return state;
|
|
794
|
+
}
|
|
795
|
+
function createCancelledMessageUpdate(steps, currentMessage) {
|
|
796
|
+
const updatedSteps = steps.map((step) => {
|
|
797
|
+
if (step.status === "in_progress") {
|
|
798
|
+
return { ...step, status: "pending" };
|
|
799
|
+
}
|
|
800
|
+
return step;
|
|
801
|
+
});
|
|
802
|
+
return {
|
|
803
|
+
isStreaming: false,
|
|
804
|
+
isCancelled: true,
|
|
805
|
+
steps: updatedSteps,
|
|
806
|
+
currentExecutingStepId: void 0,
|
|
807
|
+
// Preserve currentMessage so UI can show it with X icon
|
|
808
|
+
currentMessage: currentMessage || "Thinking..."
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
var DEFAULT_STREAM_ENDPOINT = "/api/playground/ask/stream";
|
|
812
|
+
function buildRequestBody(config, userMessage, sessionId, options) {
|
|
813
|
+
const sessionOwner = config.sessionParams;
|
|
814
|
+
const sessionOwnerId = sessionOwner?.id?.trim();
|
|
815
|
+
if (!sessionOwnerId) {
|
|
816
|
+
throw new Error("ChatConfig.sessionParams.id is required to send ask requests.");
|
|
817
|
+
}
|
|
818
|
+
const sessionAttributes = sessionOwner?.attributes && Object.keys(sessionOwner.attributes).length > 0 ? sessionOwner.attributes : void 0;
|
|
819
|
+
return {
|
|
820
|
+
agentId: config.agentId,
|
|
821
|
+
userInput: userMessage,
|
|
822
|
+
sessionId,
|
|
823
|
+
sessionOwnerId,
|
|
824
|
+
sessionOwnerLabel: sessionOwner?.name || void 0,
|
|
825
|
+
sessionAttributes,
|
|
826
|
+
analysisMode: options?.analysisMode,
|
|
827
|
+
locale: resolveLocale(config.locale),
|
|
828
|
+
timezone: resolveTimezone(config.timezone)
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
function resolveLocale(configured) {
|
|
832
|
+
if (configured && configured.trim().length > 0) return configured.trim();
|
|
833
|
+
if (typeof navigator !== "undefined") {
|
|
834
|
+
const navLocale = navigator.language;
|
|
835
|
+
if (navLocale && navLocale.length > 0) return navLocale;
|
|
836
|
+
}
|
|
837
|
+
return "en-US";
|
|
838
|
+
}
|
|
839
|
+
function resolveTimezone(configured) {
|
|
840
|
+
if (configured && configured.trim().length > 0) return configured.trim();
|
|
841
|
+
try {
|
|
842
|
+
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
843
|
+
if (zone && zone.length > 0) return zone;
|
|
844
|
+
} catch {
|
|
845
|
+
}
|
|
846
|
+
return "UTC";
|
|
847
|
+
}
|
|
848
|
+
function buildStreamingUrl(config) {
|
|
849
|
+
const endpoint = config.api.streamEndpoint || DEFAULT_STREAM_ENDPOINT;
|
|
850
|
+
const stage = config.stage || "DEV";
|
|
851
|
+
const stageParamName = config.stageQueryParam ?? "stage";
|
|
852
|
+
const queryParams = new URLSearchParams({ [stageParamName]: stage });
|
|
853
|
+
return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
|
|
854
|
+
}
|
|
855
|
+
function buildUserActionUrl(config, userActionId, action) {
|
|
856
|
+
const endpoint = config.api.streamEndpoint || DEFAULT_STREAM_ENDPOINT;
|
|
857
|
+
const [endpointPath] = endpoint.split("?");
|
|
858
|
+
const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
|
|
859
|
+
const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
|
|
860
|
+
const encodedUserActionId = encodeURIComponent(userActionId);
|
|
861
|
+
return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
|
|
862
|
+
}
|
|
863
|
+
function buildResolveImagesUrl(config) {
|
|
864
|
+
if (config.api.resolveImagesEndpoint) {
|
|
865
|
+
return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
|
|
866
|
+
}
|
|
867
|
+
const streamEndpoint = config.api.streamEndpoint || DEFAULT_STREAM_ENDPOINT;
|
|
868
|
+
const [endpointPath] = streamEndpoint.split("?");
|
|
869
|
+
const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
|
|
870
|
+
const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
|
|
871
|
+
return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
|
|
872
|
+
}
|
|
873
|
+
function buildRequestHeaders(config) {
|
|
874
|
+
const headers = {
|
|
875
|
+
...config.api.headers
|
|
876
|
+
};
|
|
877
|
+
if (config.api.authToken) {
|
|
878
|
+
headers.Authorization = `Bearer ${config.api.authToken}`;
|
|
879
|
+
}
|
|
880
|
+
return headers;
|
|
881
|
+
}
|
|
882
|
+
var UserActionStaleError = class extends Error {
|
|
883
|
+
constructor(userActionId, message = "User action is no longer actionable") {
|
|
884
|
+
super(message);
|
|
885
|
+
__publicField(this, "userActionId");
|
|
886
|
+
this.name = "UserActionStaleError";
|
|
887
|
+
this.userActionId = userActionId;
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
async function sendUserActionRequest(config, userActionId, action, data) {
|
|
891
|
+
const url = buildUserActionUrl(config, userActionId, action);
|
|
892
|
+
const baseHeaders = buildRequestHeaders(config);
|
|
893
|
+
const hasBody = data !== void 0;
|
|
894
|
+
const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
|
|
895
|
+
const response = await fetch(url, {
|
|
896
|
+
method: "POST",
|
|
897
|
+
headers,
|
|
898
|
+
body: hasBody ? JSON.stringify(data) : void 0
|
|
899
|
+
});
|
|
900
|
+
if (response.status === 404) {
|
|
901
|
+
throw new UserActionStaleError(userActionId);
|
|
902
|
+
}
|
|
903
|
+
if (!response.ok) {
|
|
904
|
+
const errorText = await response.text();
|
|
905
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
906
|
+
}
|
|
907
|
+
return await response.json();
|
|
908
|
+
}
|
|
909
|
+
async function submitUserAction(config, userActionId, content) {
|
|
910
|
+
return sendUserActionRequest(config, userActionId, "submit", content ?? {});
|
|
911
|
+
}
|
|
912
|
+
async function cancelUserAction(config, userActionId) {
|
|
913
|
+
return sendUserActionRequest(config, userActionId, "cancel");
|
|
914
|
+
}
|
|
915
|
+
async function resendUserAction(config, userActionId) {
|
|
916
|
+
return sendUserActionRequest(config, userActionId, "resend");
|
|
917
|
+
}
|
|
918
|
+
var memoryStore = /* @__PURE__ */ new Map();
|
|
919
|
+
var chatStore = {
|
|
920
|
+
get(key) {
|
|
921
|
+
return memoryStore.get(key) ?? [];
|
|
922
|
+
},
|
|
923
|
+
set(key, messages) {
|
|
924
|
+
memoryStore.set(key, messages);
|
|
925
|
+
},
|
|
926
|
+
delete(key) {
|
|
927
|
+
memoryStore.delete(key);
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
var streams = /* @__PURE__ */ new Map();
|
|
931
|
+
var activeStreamStore = {
|
|
932
|
+
has(key) {
|
|
933
|
+
return streams.has(key);
|
|
934
|
+
},
|
|
935
|
+
get(key) {
|
|
936
|
+
const entry = streams.get(key);
|
|
937
|
+
if (!entry) return null;
|
|
938
|
+
return { messages: entry.messages, isWaiting: entry.isWaiting };
|
|
939
|
+
},
|
|
940
|
+
// Called before startStream — registers the controller and initial messages
|
|
941
|
+
start(key, abortController, initialMessages) {
|
|
942
|
+
const existing = streams.get(key);
|
|
943
|
+
streams.set(key, {
|
|
944
|
+
messages: initialMessages,
|
|
945
|
+
isWaiting: true,
|
|
946
|
+
abortController,
|
|
947
|
+
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
948
|
+
});
|
|
949
|
+
},
|
|
950
|
+
// Called by the stream on every event — applies the same updater pattern React uses
|
|
951
|
+
applyMessages(key, updater) {
|
|
952
|
+
const entry = streams.get(key);
|
|
953
|
+
if (!entry) return;
|
|
954
|
+
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
955
|
+
entry.messages = next;
|
|
956
|
+
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
957
|
+
},
|
|
958
|
+
setWaiting(key, waiting) {
|
|
959
|
+
const entry = streams.get(key);
|
|
960
|
+
if (!entry) return;
|
|
961
|
+
entry.isWaiting = waiting;
|
|
962
|
+
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
963
|
+
},
|
|
964
|
+
// Called when stream completes — persists to chatStore and cleans up
|
|
965
|
+
complete(key) {
|
|
966
|
+
const entry = streams.get(key);
|
|
967
|
+
if (!entry) return;
|
|
968
|
+
entry.isWaiting = false;
|
|
969
|
+
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
970
|
+
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
971
|
+
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
972
|
+
streams.delete(key);
|
|
973
|
+
},
|
|
974
|
+
// Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
|
|
975
|
+
subscribe(key, listener) {
|
|
976
|
+
const entry = streams.get(key);
|
|
977
|
+
if (!entry) return () => {
|
|
978
|
+
};
|
|
979
|
+
entry.listeners.add(listener);
|
|
980
|
+
return () => {
|
|
981
|
+
streams.get(key)?.listeners.delete(listener);
|
|
982
|
+
};
|
|
983
|
+
},
|
|
984
|
+
// Rename an entry — used when the server assigns a new session ID mid-stream
|
|
985
|
+
rename(oldKey, newKey) {
|
|
986
|
+
const entry = streams.get(oldKey);
|
|
987
|
+
if (!entry) return;
|
|
988
|
+
streams.set(newKey, entry);
|
|
989
|
+
streams.delete(oldKey);
|
|
990
|
+
},
|
|
991
|
+
// Explicit user cancel — aborts the controller and removes the entry
|
|
992
|
+
abort(key) {
|
|
993
|
+
const entry = streams.get(key);
|
|
994
|
+
if (!entry) return;
|
|
995
|
+
entry.abortController.abort();
|
|
996
|
+
streams.delete(key);
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
1000
|
+
function hasRagImages(content) {
|
|
1001
|
+
return RAG_IMAGE_REGEX.test(content);
|
|
1002
|
+
}
|
|
1003
|
+
async function waitForNextPaint(signal) {
|
|
1004
|
+
if (signal?.aborted) return;
|
|
1005
|
+
await new Promise((resolve) => {
|
|
1006
|
+
let isSettled = false;
|
|
1007
|
+
const finish = () => {
|
|
1008
|
+
if (isSettled) return;
|
|
1009
|
+
isSettled = true;
|
|
1010
|
+
signal?.removeEventListener("abort", finish);
|
|
1011
|
+
resolve();
|
|
1012
|
+
};
|
|
1013
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
1014
|
+
if (typeof requestAnimationFrame === "function") {
|
|
1015
|
+
requestAnimationFrame(() => {
|
|
1016
|
+
setTimeout(finish, 0);
|
|
1017
|
+
});
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
setTimeout(finish, 0);
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
async function resolveRagImageUrls(config, content, signal) {
|
|
1024
|
+
const url = buildResolveImagesUrl(config);
|
|
1025
|
+
const baseHeaders = buildRequestHeaders(config);
|
|
1026
|
+
const headers = { "Content-Type": "application/json", ...baseHeaders };
|
|
1027
|
+
const response = await fetch(url, {
|
|
1028
|
+
method: "POST",
|
|
1029
|
+
headers,
|
|
1030
|
+
body: JSON.stringify({ input: content }),
|
|
1031
|
+
signal
|
|
1032
|
+
});
|
|
1033
|
+
if (!response.ok) {
|
|
1034
|
+
const errorText = await response.text();
|
|
1035
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1036
|
+
}
|
|
1037
|
+
const text = await response.text();
|
|
1038
|
+
try {
|
|
1039
|
+
const parsed = JSON.parse(text);
|
|
1040
|
+
if (typeof parsed === "string") return parsed;
|
|
1041
|
+
if (typeof parsed.output === "string") return parsed.output;
|
|
1042
|
+
if (typeof parsed.result === "string") return parsed.result;
|
|
1043
|
+
} catch {
|
|
1044
|
+
}
|
|
1045
|
+
return text;
|
|
1046
|
+
}
|
|
1047
|
+
var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
|
|
1048
|
+
function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForResponse) {
|
|
1049
|
+
const abortControllerRef = useRef(null);
|
|
1050
|
+
const configRef = useRef(config);
|
|
1051
|
+
configRef.current = config;
|
|
1052
|
+
const callbacksRef = useRef(callbacks);
|
|
1053
|
+
callbacksRef.current = callbacks;
|
|
1054
|
+
const startStream = useCallback(
|
|
1055
|
+
async (userMessage, streamingId, sessionId, externalAbortController, options) => {
|
|
1056
|
+
abortControllerRef.current?.abort();
|
|
1057
|
+
const abortController = externalAbortController ?? new AbortController();
|
|
1058
|
+
abortControllerRef.current = abortController;
|
|
1059
|
+
const state = createInitialV2State();
|
|
1060
|
+
const updateMessage = (update) => {
|
|
1061
|
+
if (abortController.signal.aborted) return;
|
|
1062
|
+
setMessages(
|
|
1063
|
+
(prev) => prev.map(
|
|
1064
|
+
(msg) => msg.id === streamingId ? { ...msg, ...update } : msg
|
|
1065
|
+
)
|
|
1066
|
+
);
|
|
1067
|
+
};
|
|
1068
|
+
try {
|
|
1069
|
+
const currentConfig = configRef.current;
|
|
1070
|
+
const requestBody = buildRequestBody(
|
|
1071
|
+
currentConfig,
|
|
1072
|
+
userMessage,
|
|
1073
|
+
sessionId,
|
|
1074
|
+
options
|
|
1075
|
+
);
|
|
1076
|
+
const url = buildStreamingUrl(currentConfig);
|
|
1077
|
+
const headers = buildRequestHeaders(currentConfig);
|
|
1078
|
+
await streamWorkflowEvents(url, requestBody, headers, {
|
|
1079
|
+
signal: abortController.signal,
|
|
1080
|
+
onEvent: (event) => {
|
|
1081
|
+
if (abortController.signal.aborted) return;
|
|
1082
|
+
processStreamEventV2(event, state);
|
|
1083
|
+
if (state.lastUserAction) {
|
|
1084
|
+
callbacksRef.current.onUserActionRequired?.(state.lastUserAction);
|
|
1085
|
+
}
|
|
1086
|
+
if (state.lastNotification) {
|
|
1087
|
+
callbacksRef.current.onUserNotification?.(state.lastNotification);
|
|
1088
|
+
}
|
|
1089
|
+
const activeStep = state.steps.find((s) => s.id === state.currentExecutingStepId);
|
|
1090
|
+
const lastInProgressStep = [...state.steps].reverse().find((s) => s.status === "in_progress");
|
|
1091
|
+
const useful = (m) => m && !isBlandStatus(m) ? m : void 0;
|
|
1092
|
+
const latestUsefulStep = [...state.steps].reverse().find(
|
|
1093
|
+
(s) => s.message && !isBlandStatus(s.message)
|
|
1094
|
+
);
|
|
1095
|
+
const currentMessage = useful(activeStep?.message) ?? useful(lastInProgressStep?.message) ?? latestUsefulStep?.message ?? useful(getEventMessage(event)) ?? // Last-resort: every candidate is bland (very first event,
|
|
1096
|
+
// nothing useful seen yet). Render the bland label so the
|
|
1097
|
+
// bubble isn't blank.
|
|
1098
|
+
activeStep?.message ?? lastInProgressStep?.message ?? getEventMessage(event);
|
|
1099
|
+
if (currentMessage) {
|
|
1100
|
+
callbacksRef.current.onStatusMessage?.(currentMessage);
|
|
1101
|
+
}
|
|
1102
|
+
callbacksRef.current.onStepsUpdate?.([...state.steps]);
|
|
1103
|
+
if (state.hasError) {
|
|
1104
|
+
updateMessage({
|
|
1105
|
+
streamingContent: FRIENDLY_ERROR_MESSAGE,
|
|
1106
|
+
content: FRIENDLY_ERROR_MESSAGE,
|
|
1107
|
+
streamProgress: "error",
|
|
1108
|
+
isError: true,
|
|
1109
|
+
errorDetails: state.errorMessage,
|
|
1110
|
+
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1111
|
+
steps: [...state.steps],
|
|
1112
|
+
currentExecutingStepId: void 0,
|
|
1113
|
+
executionId: state.executionId,
|
|
1114
|
+
sessionId: state.sessionId
|
|
1115
|
+
});
|
|
1116
|
+
} else {
|
|
1117
|
+
updateMessage({
|
|
1118
|
+
streamingContent: state.finalResponse,
|
|
1119
|
+
content: "",
|
|
1120
|
+
currentMessage,
|
|
1121
|
+
streamProgress: "processing",
|
|
1122
|
+
isError: false,
|
|
1123
|
+
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1124
|
+
steps: [...state.steps],
|
|
1125
|
+
currentExecutingStepId: state.currentExecutingStepId,
|
|
1126
|
+
executionId: state.executionId,
|
|
1127
|
+
sessionId: state.sessionId,
|
|
1128
|
+
isCancelled: false
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
onError: (error) => {
|
|
1133
|
+
setIsWaitingForResponse(false);
|
|
1134
|
+
callbacksRef.current.onStatusMessage?.(null);
|
|
1135
|
+
if (error.name !== "AbortError") {
|
|
1136
|
+
callbacksRef.current.onError?.(error);
|
|
1137
|
+
}
|
|
1138
|
+
const isAborted = error.name === "AbortError";
|
|
1139
|
+
setMessages(
|
|
1140
|
+
(prev) => prev.map(
|
|
1141
|
+
(msg) => msg.id === streamingId ? {
|
|
1142
|
+
...msg,
|
|
1143
|
+
isStreaming: false,
|
|
1144
|
+
streamProgress: isAborted ? "processing" : "error",
|
|
1145
|
+
isError: !isAborted,
|
|
1146
|
+
isCancelled: isAborted,
|
|
1147
|
+
errorDetails: isAborted ? void 0 : error.message,
|
|
1148
|
+
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1149
|
+
currentMessage: isAborted ? "Thinking..." : void 0,
|
|
1150
|
+
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1151
|
+
steps: [...state.steps].map((step) => {
|
|
1152
|
+
if (step.status === "in_progress" && isAborted) {
|
|
1153
|
+
return { ...step, status: "pending" };
|
|
1154
|
+
}
|
|
1155
|
+
return step;
|
|
1156
|
+
}),
|
|
1157
|
+
currentExecutingStepId: void 0
|
|
1158
|
+
} : msg
|
|
1159
|
+
)
|
|
1160
|
+
);
|
|
1161
|
+
},
|
|
1162
|
+
onComplete: () => {
|
|
1163
|
+
setIsWaitingForResponse(false);
|
|
1164
|
+
callbacksRef.current.onStatusMessage?.(null);
|
|
1165
|
+
callbacksRef.current.onStepsUpdate?.([]);
|
|
1166
|
+
if (state.sessionId && state.sessionId !== sessionId) {
|
|
1167
|
+
callbacksRef.current.onSessionIdChange?.(state.sessionId);
|
|
1168
|
+
}
|
|
1169
|
+
const needsImageResolve = !state.hasError && !abortController.signal.aborted && hasRagImages(state.finalResponse);
|
|
1170
|
+
const finalMessage = {
|
|
1171
|
+
id: streamingId,
|
|
1172
|
+
sessionId: state.sessionId || sessionId,
|
|
1173
|
+
role: "assistant",
|
|
1174
|
+
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.finalResponse || "",
|
|
1175
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1176
|
+
isStreaming: false,
|
|
1177
|
+
streamProgress: state.hasError ? "error" : "completed",
|
|
1178
|
+
isError: state.hasError,
|
|
1179
|
+
errorDetails: state.hasError ? state.errorMessage : void 0,
|
|
1180
|
+
executionId: state.executionId,
|
|
1181
|
+
// Defensive: tracingData must be an object (or undefined)
|
|
1182
|
+
// so the JSON viewer doesn't char-iterate a string. The
|
|
1183
|
+
// event processor already drops bare strings; this is
|
|
1184
|
+
// the last stop before the message leaves the SDK.
|
|
1185
|
+
tracingData: state.finalData != null && typeof state.finalData === "object" ? state.finalData : void 0,
|
|
1186
|
+
steps: state.hasError ? [] : [...state.steps],
|
|
1187
|
+
isCancelled: false,
|
|
1188
|
+
currentExecutingStepId: void 0,
|
|
1189
|
+
formattedThinkingText: state.hasError ? void 0 : state.formattedThinkingText || void 0,
|
|
1190
|
+
isResolvingImages: needsImageResolve,
|
|
1191
|
+
totalElapsedMs: state.hasError ? void 0 : state.totalElapsedMs
|
|
1192
|
+
};
|
|
1193
|
+
setMessages(
|
|
1194
|
+
(prev) => prev.map(
|
|
1195
|
+
(msg) => msg.id === streamingId ? finalMessage : msg
|
|
1196
|
+
)
|
|
1197
|
+
);
|
|
1198
|
+
callbacksRef.current.onStreamComplete?.(finalMessage);
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
const shouldResolveImages = !abortController.signal.aborted && !state.hasError && hasRagImages(state.finalResponse);
|
|
1202
|
+
if (shouldResolveImages) {
|
|
1203
|
+
await waitForNextPaint(abortController.signal);
|
|
1204
|
+
}
|
|
1205
|
+
if (shouldResolveImages && !abortController.signal.aborted) {
|
|
1206
|
+
try {
|
|
1207
|
+
const resolvedContent = await resolveRagImageUrls(
|
|
1208
|
+
currentConfig,
|
|
1209
|
+
state.finalResponse,
|
|
1210
|
+
abortController.signal
|
|
1211
|
+
);
|
|
1212
|
+
setMessages(
|
|
1213
|
+
(prev) => prev.map(
|
|
1214
|
+
(msg) => msg.id === streamingId ? { ...msg, content: resolvedContent, isResolvingImages: false } : msg
|
|
1215
|
+
)
|
|
1216
|
+
);
|
|
1217
|
+
} catch {
|
|
1218
|
+
setMessages(
|
|
1219
|
+
(prev) => prev.map(
|
|
1220
|
+
(msg) => msg.id === streamingId ? { ...msg, isResolvingImages: false } : msg
|
|
1221
|
+
)
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return state.sessionId;
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
setIsWaitingForResponse(false);
|
|
1228
|
+
if (error.name !== "AbortError") {
|
|
1229
|
+
callbacksRef.current.onError?.(error);
|
|
1230
|
+
}
|
|
1231
|
+
const isAborted = error.name === "AbortError";
|
|
1232
|
+
setMessages(
|
|
1233
|
+
(prev) => prev.map(
|
|
1234
|
+
(msg) => msg.id === streamingId ? {
|
|
1235
|
+
...msg,
|
|
1236
|
+
isStreaming: false,
|
|
1237
|
+
streamProgress: isAborted ? "processing" : "error",
|
|
1238
|
+
isError: !isAborted,
|
|
1239
|
+
isCancelled: isAborted,
|
|
1240
|
+
errorDetails: isAborted ? void 0 : error.message,
|
|
1241
|
+
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1242
|
+
formattedThinkingText: state.formattedThinkingText || void 0,
|
|
1243
|
+
steps: [...state.steps].map((step) => {
|
|
1244
|
+
if (step.status === "in_progress" && isAborted) {
|
|
1245
|
+
return { ...step, status: "pending" };
|
|
1246
|
+
}
|
|
1247
|
+
return step;
|
|
1248
|
+
}),
|
|
1249
|
+
currentExecutingStepId: void 0
|
|
1250
|
+
} : msg
|
|
1251
|
+
)
|
|
1252
|
+
);
|
|
1253
|
+
return state.sessionId;
|
|
1254
|
+
}
|
|
1255
|
+
},
|
|
1256
|
+
[setMessages, setIsWaitingForResponse]
|
|
1257
|
+
);
|
|
1258
|
+
const cancelStream = useCallback(() => {
|
|
1259
|
+
abortControllerRef.current?.abort();
|
|
1260
|
+
}, []);
|
|
1261
|
+
return {
|
|
1262
|
+
startStream,
|
|
1263
|
+
cancelStream,
|
|
1264
|
+
abortControllerRef
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
|
|
1268
|
+
function upsertPrompt(prompts, req) {
|
|
1269
|
+
const active = { ...req, status: "pending" };
|
|
1270
|
+
const idx = prompts.findIndex(
|
|
1271
|
+
(p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
|
|
1272
|
+
);
|
|
1273
|
+
if (idx >= 0) {
|
|
1274
|
+
const next = prompts.slice();
|
|
1275
|
+
next[idx] = active;
|
|
1276
|
+
return next;
|
|
1277
|
+
}
|
|
1278
|
+
return [...prompts, active];
|
|
1279
|
+
}
|
|
1280
|
+
function getStoredOrInitialMessages(config) {
|
|
1281
|
+
if (!config.userId) return config.initialMessages ?? [];
|
|
1282
|
+
const activeStream = activeStreamStore.get(config.userId);
|
|
1283
|
+
if (activeStream) return activeStream.messages;
|
|
1284
|
+
const stored = chatStore.get(config.userId);
|
|
1285
|
+
if (stored.length > 0) return stored;
|
|
1286
|
+
if (config.initialMessages?.length) {
|
|
1287
|
+
chatStore.set(config.userId, config.initialMessages);
|
|
1288
|
+
return config.initialMessages;
|
|
1289
|
+
}
|
|
1290
|
+
return [];
|
|
1291
|
+
}
|
|
1292
|
+
function getSessionIdFromMessages(messages) {
|
|
1293
|
+
return messages.find((message) => message.sessionId)?.sessionId;
|
|
1294
|
+
}
|
|
1295
|
+
function useChatV2(config, callbacks = {}) {
|
|
1296
|
+
const [messages, setMessages] = useState(() => getStoredOrInitialMessages(config));
|
|
1297
|
+
const [isWaitingForResponse, setIsWaitingForResponse] = useState(() => {
|
|
1298
|
+
if (!config.userId) return false;
|
|
1299
|
+
return activeStreamStore.get(config.userId)?.isWaiting ?? false;
|
|
1300
|
+
});
|
|
1301
|
+
const sessionIdRef = useRef(
|
|
1302
|
+
getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
|
|
1303
|
+
);
|
|
1304
|
+
const prevUserIdRef = useRef(config.userId);
|
|
1305
|
+
const streamUserIdRef = useRef(void 0);
|
|
1306
|
+
const subscriptionPrevUserIdRef = useRef(config.userId);
|
|
1307
|
+
const callbacksRef = useRef(callbacks);
|
|
1308
|
+
callbacksRef.current = callbacks;
|
|
1309
|
+
const configRef = useRef(config);
|
|
1310
|
+
configRef.current = config;
|
|
1311
|
+
const messagesRef = useRef(messages);
|
|
1312
|
+
messagesRef.current = messages;
|
|
1313
|
+
const storeAwareSetMessages = useCallback(
|
|
1314
|
+
(updater) => {
|
|
1315
|
+
const streamUserId = streamUserIdRef.current;
|
|
1316
|
+
const currentUserId = configRef.current.userId;
|
|
1317
|
+
const storeKey = streamUserId ?? currentUserId;
|
|
1318
|
+
if (storeKey && activeStreamStore.has(storeKey)) {
|
|
1319
|
+
activeStreamStore.applyMessages(storeKey, updater);
|
|
1320
|
+
}
|
|
1321
|
+
if (!streamUserId || streamUserId === currentUserId) {
|
|
1322
|
+
setMessages(updater);
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1326
|
+
[]
|
|
1327
|
+
);
|
|
1328
|
+
const storeAwareSetIsWaiting = useCallback(
|
|
1329
|
+
(waiting) => {
|
|
1330
|
+
const streamUserId = streamUserIdRef.current;
|
|
1331
|
+
const currentUserId = configRef.current.userId;
|
|
1332
|
+
const storeKey = streamUserId ?? currentUserId;
|
|
1333
|
+
if (storeKey && activeStreamStore.has(storeKey)) {
|
|
1334
|
+
activeStreamStore.setWaiting(storeKey, waiting);
|
|
1335
|
+
}
|
|
1336
|
+
if (!streamUserId || streamUserId === currentUserId) {
|
|
1337
|
+
setIsWaitingForResponse(waiting);
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1341
|
+
[]
|
|
1342
|
+
);
|
|
1343
|
+
const [userActionState, setUserActionState] = useState(EMPTY_USER_ACTION_STATE);
|
|
1344
|
+
const wrappedCallbacks = useMemo(() => ({
|
|
1345
|
+
...callbacksRef.current,
|
|
1346
|
+
onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
|
|
1347
|
+
onStreamStart: () => callbacksRef.current.onStreamStart?.(),
|
|
1348
|
+
onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
|
|
1349
|
+
onError: (error) => callbacksRef.current.onError?.(error),
|
|
1350
|
+
onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
|
|
1351
|
+
onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
|
|
1352
|
+
onUserActionRequired: (request) => {
|
|
1353
|
+
setUserActionState((prev) => ({
|
|
1354
|
+
...prev,
|
|
1355
|
+
prompts: upsertPrompt(prev.prompts, request)
|
|
1356
|
+
}));
|
|
1357
|
+
callbacksRef.current.onUserActionRequired?.(request);
|
|
1358
|
+
},
|
|
1359
|
+
onUserNotification: (notification) => {
|
|
1360
|
+
setUserActionState(
|
|
1361
|
+
(prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
|
|
1362
|
+
);
|
|
1363
|
+
callbacksRef.current.onUserNotification?.(notification);
|
|
1364
|
+
}
|
|
1365
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1366
|
+
}), []);
|
|
1367
|
+
const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
|
|
1368
|
+
config,
|
|
1369
|
+
wrappedCallbacks,
|
|
1370
|
+
storeAwareSetMessages,
|
|
1371
|
+
storeAwareSetIsWaiting
|
|
1372
|
+
);
|
|
1373
|
+
const sendMessage = useCallback(
|
|
1374
|
+
async (userMessage, options) => {
|
|
1375
|
+
if (!userMessage.trim()) return;
|
|
1376
|
+
if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
|
|
1377
|
+
sessionIdRef.current = generateId();
|
|
1378
|
+
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
1379
|
+
}
|
|
1380
|
+
const userMessageId = `user-${Date.now()}`;
|
|
1381
|
+
const userMsg = {
|
|
1382
|
+
id: userMessageId,
|
|
1383
|
+
sessionId: sessionIdRef.current,
|
|
1384
|
+
role: "user",
|
|
1385
|
+
content: userMessage,
|
|
1386
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1387
|
+
};
|
|
1388
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
1389
|
+
callbacksRef.current.onMessageSent?.(userMessage);
|
|
1390
|
+
setIsWaitingForResponse(true);
|
|
1391
|
+
callbacksRef.current.onStreamStart?.();
|
|
1392
|
+
const streamingId = `assistant-${Date.now()}`;
|
|
1393
|
+
const streamingMsg = {
|
|
1394
|
+
id: streamingId,
|
|
1395
|
+
sessionId: sessionIdRef.current,
|
|
1396
|
+
role: "assistant",
|
|
1397
|
+
content: "",
|
|
1398
|
+
streamingContent: "",
|
|
1399
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1400
|
+
isStreaming: true,
|
|
1401
|
+
streamProgress: "started",
|
|
1402
|
+
steps: [],
|
|
1403
|
+
currentExecutingStepId: void 0,
|
|
1404
|
+
isCancelled: false,
|
|
1405
|
+
currentMessage: void 0
|
|
1406
|
+
};
|
|
1407
|
+
setMessages((prev) => [...prev, streamingMsg]);
|
|
1408
|
+
const abortController = new AbortController();
|
|
1409
|
+
const { userId } = configRef.current;
|
|
1410
|
+
if (userId) {
|
|
1411
|
+
streamUserIdRef.current = userId;
|
|
1412
|
+
const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
|
|
1413
|
+
activeStreamStore.start(userId, abortController, initialMessages);
|
|
1414
|
+
}
|
|
1415
|
+
const newSessionId = await startStream(
|
|
1416
|
+
userMessage,
|
|
1417
|
+
streamingId,
|
|
1418
|
+
sessionIdRef.current,
|
|
1419
|
+
abortController,
|
|
1420
|
+
options
|
|
1421
|
+
);
|
|
1422
|
+
const finalStreamUserId = streamUserIdRef.current ?? userId;
|
|
1423
|
+
if (finalStreamUserId) {
|
|
1424
|
+
activeStreamStore.complete(finalStreamUserId);
|
|
1425
|
+
}
|
|
1426
|
+
streamUserIdRef.current = void 0;
|
|
1427
|
+
if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
|
|
1428
|
+
sessionIdRef.current = newSessionId;
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
[startStream]
|
|
1432
|
+
);
|
|
1433
|
+
const clearMessages = useCallback(() => {
|
|
1434
|
+
if (configRef.current.userId) {
|
|
1435
|
+
chatStore.delete(configRef.current.userId);
|
|
1436
|
+
}
|
|
1437
|
+
setMessages([]);
|
|
1438
|
+
}, []);
|
|
1439
|
+
const prependMessages = useCallback((msgs) => {
|
|
1440
|
+
setMessages((prev) => [...msgs, ...prev]);
|
|
1441
|
+
}, []);
|
|
1442
|
+
const cancelStream = useCallback(() => {
|
|
1443
|
+
const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
|
|
1444
|
+
if (streamUserId) {
|
|
1445
|
+
activeStreamStore.abort(streamUserId);
|
|
1446
|
+
}
|
|
1447
|
+
streamUserIdRef.current = void 0;
|
|
1448
|
+
cancelStreamManager();
|
|
1449
|
+
setIsWaitingForResponse(false);
|
|
1450
|
+
setUserActionState((prev) => ({ ...prev, prompts: [] }));
|
|
1451
|
+
setMessages(
|
|
1452
|
+
(prev) => prev.map((msg) => {
|
|
1453
|
+
if (msg.isStreaming) {
|
|
1454
|
+
return {
|
|
1455
|
+
...msg,
|
|
1456
|
+
...createCancelledMessageUpdate(
|
|
1457
|
+
msg.steps || [],
|
|
1458
|
+
msg.currentMessage
|
|
1459
|
+
)
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
return msg;
|
|
1463
|
+
})
|
|
1464
|
+
);
|
|
1465
|
+
}, [cancelStreamManager]);
|
|
1466
|
+
const resetSession = useCallback(() => {
|
|
1467
|
+
const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
|
|
1468
|
+
if (streamUserId) {
|
|
1469
|
+
activeStreamStore.abort(streamUserId);
|
|
1470
|
+
}
|
|
1471
|
+
if (configRef.current.userId) {
|
|
1472
|
+
chatStore.delete(configRef.current.userId);
|
|
1473
|
+
}
|
|
1474
|
+
streamUserIdRef.current = void 0;
|
|
1475
|
+
setMessages([]);
|
|
1476
|
+
sessionIdRef.current = void 0;
|
|
1477
|
+
abortControllerRef.current?.abort();
|
|
1478
|
+
setIsWaitingForResponse(false);
|
|
1479
|
+
setUserActionState(EMPTY_USER_ACTION_STATE);
|
|
1480
|
+
}, []);
|
|
1481
|
+
const getSessionId = useCallback(() => {
|
|
1482
|
+
return sessionIdRef.current;
|
|
1483
|
+
}, []);
|
|
1484
|
+
const getMessages = useCallback(() => {
|
|
1485
|
+
return messages;
|
|
1486
|
+
}, [messages]);
|
|
1487
|
+
const setPromptStatus = useCallback(
|
|
1488
|
+
(userActionId, status) => {
|
|
1489
|
+
setUserActionState((prev) => ({
|
|
1490
|
+
...prev,
|
|
1491
|
+
prompts: prev.prompts.map(
|
|
1492
|
+
(p) => p.userActionId === userActionId ? { ...p, status } : p
|
|
1493
|
+
)
|
|
1494
|
+
}));
|
|
1495
|
+
},
|
|
1496
|
+
[]
|
|
1497
|
+
);
|
|
1498
|
+
const removePrompt = useCallback((userActionId) => {
|
|
1499
|
+
setUserActionState((prev) => ({
|
|
1500
|
+
...prev,
|
|
1501
|
+
prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
|
|
1502
|
+
}));
|
|
1503
|
+
}, []);
|
|
1504
|
+
const submitUserAction2 = useCallback(
|
|
1505
|
+
async (userActionId, content) => {
|
|
1506
|
+
setPromptStatus(userActionId, "submitting");
|
|
1507
|
+
try {
|
|
1508
|
+
await submitUserAction(configRef.current, userActionId, content);
|
|
1509
|
+
removePrompt(userActionId);
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
if (error instanceof UserActionStaleError) {
|
|
1512
|
+
setPromptStatus(userActionId, "stale");
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
setPromptStatus(userActionId, "pending");
|
|
1516
|
+
callbacksRef.current.onError?.(error);
|
|
1517
|
+
throw error;
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1520
|
+
[removePrompt, setPromptStatus]
|
|
1521
|
+
);
|
|
1522
|
+
const cancelUserAction2 = useCallback(
|
|
1523
|
+
async (userActionId) => {
|
|
1524
|
+
setPromptStatus(userActionId, "submitting");
|
|
1525
|
+
try {
|
|
1526
|
+
await cancelUserAction(configRef.current, userActionId);
|
|
1527
|
+
removePrompt(userActionId);
|
|
1528
|
+
} catch (error) {
|
|
1529
|
+
if (error instanceof UserActionStaleError) {
|
|
1530
|
+
removePrompt(userActionId);
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
setPromptStatus(userActionId, "pending");
|
|
1534
|
+
callbacksRef.current.onError?.(error);
|
|
1535
|
+
throw error;
|
|
1536
|
+
}
|
|
1537
|
+
},
|
|
1538
|
+
[removePrompt, setPromptStatus]
|
|
1539
|
+
);
|
|
1540
|
+
const resendUserAction2 = useCallback(
|
|
1541
|
+
async (userActionId) => {
|
|
1542
|
+
setPromptStatus(userActionId, "submitting");
|
|
1543
|
+
try {
|
|
1544
|
+
await resendUserAction(configRef.current, userActionId);
|
|
1545
|
+
setPromptStatus(userActionId, "pending");
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
if (error instanceof UserActionStaleError) {
|
|
1548
|
+
setPromptStatus(userActionId, "stale");
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
setPromptStatus(userActionId, "pending");
|
|
1552
|
+
callbacksRef.current.onError?.(error);
|
|
1553
|
+
throw error;
|
|
1554
|
+
}
|
|
1555
|
+
},
|
|
1556
|
+
[setPromptStatus]
|
|
1557
|
+
);
|
|
1558
|
+
const dismissNotification = useCallback((id) => {
|
|
1559
|
+
setUserActionState((prev) => ({
|
|
1560
|
+
...prev,
|
|
1561
|
+
notifications: prev.notifications.filter((n) => n.id !== id)
|
|
1562
|
+
}));
|
|
1563
|
+
}, []);
|
|
1564
|
+
useEffect(() => {
|
|
1565
|
+
const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
|
|
1566
|
+
subscriptionPrevUserIdRef.current = config.userId;
|
|
1567
|
+
const { userId } = config;
|
|
1568
|
+
if (!userId) return;
|
|
1569
|
+
if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
|
|
1570
|
+
streamUserIdRef.current = userId;
|
|
1571
|
+
}
|
|
1572
|
+
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1573
|
+
setMessages(msgs);
|
|
1574
|
+
setIsWaitingForResponse(isWaiting);
|
|
1575
|
+
});
|
|
1576
|
+
const active = activeStreamStore.get(userId);
|
|
1577
|
+
if (active) {
|
|
1578
|
+
setMessages(active.messages);
|
|
1579
|
+
setIsWaitingForResponse(active.isWaiting);
|
|
1580
|
+
}
|
|
1581
|
+
return unsubscribe;
|
|
1582
|
+
}, [config.userId]);
|
|
1583
|
+
useEffect(() => {
|
|
1584
|
+
if (!config.userId) return;
|
|
1585
|
+
if (prevUserIdRef.current !== config.userId) return;
|
|
1586
|
+
const toSave = messages.filter((m) => !m.isStreaming);
|
|
1587
|
+
if (toSave.length > 0) {
|
|
1588
|
+
chatStore.set(config.userId, toSave);
|
|
1589
|
+
}
|
|
1590
|
+
}, [messages, config.userId]);
|
|
1591
|
+
useEffect(() => {
|
|
1592
|
+
if (!config.userId || activeStreamStore.has(config.userId)) return;
|
|
1593
|
+
if (!config.initialMessages?.length || messagesRef.current.length > 0) return;
|
|
1594
|
+
chatStore.set(config.userId, config.initialMessages);
|
|
1595
|
+
setMessages(config.initialMessages);
|
|
1596
|
+
sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
|
|
1597
|
+
}, [config.initialMessages, config.initialSessionId, config.userId]);
|
|
1598
|
+
useEffect(() => {
|
|
1599
|
+
const prevUserId = prevUserIdRef.current;
|
|
1600
|
+
prevUserIdRef.current = config.userId;
|
|
1601
|
+
if (prevUserId === config.userId) return;
|
|
1602
|
+
if (prevUserId && !config.userId) {
|
|
1603
|
+
chatStore.delete(prevUserId);
|
|
1604
|
+
setMessages([]);
|
|
1605
|
+
sessionIdRef.current = void 0;
|
|
1606
|
+
setIsWaitingForResponse(false);
|
|
1607
|
+
setUserActionState(EMPTY_USER_ACTION_STATE);
|
|
1608
|
+
} else if (config.userId) {
|
|
1609
|
+
const active = activeStreamStore.get(config.userId);
|
|
1610
|
+
if (active) {
|
|
1611
|
+
setMessages(active.messages);
|
|
1612
|
+
setIsWaitingForResponse(active.isWaiting);
|
|
1613
|
+
sessionIdRef.current = getSessionIdFromMessages(active.messages) ?? config.initialSessionId;
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
const nextMessages = getStoredOrInitialMessages(config);
|
|
1617
|
+
setMessages(nextMessages);
|
|
1618
|
+
sessionIdRef.current = getSessionIdFromMessages(nextMessages) ?? config.initialSessionId;
|
|
1619
|
+
setIsWaitingForResponse(false);
|
|
1620
|
+
}
|
|
1621
|
+
}, [config]);
|
|
1622
|
+
return {
|
|
1623
|
+
messages,
|
|
1624
|
+
sendMessage,
|
|
1625
|
+
clearMessages,
|
|
1626
|
+
prependMessages,
|
|
1627
|
+
cancelStream,
|
|
1628
|
+
resetSession,
|
|
1629
|
+
getSessionId,
|
|
1630
|
+
getMessages,
|
|
1631
|
+
isWaitingForResponse,
|
|
1632
|
+
sessionId: sessionIdRef.current,
|
|
1633
|
+
userActionState,
|
|
1634
|
+
submitUserAction: submitUserAction2,
|
|
1635
|
+
cancelUserAction: cancelUserAction2,
|
|
1636
|
+
resendUserAction: resendUserAction2,
|
|
1637
|
+
dismissNotification
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
function getSpeechRecognition() {
|
|
1641
|
+
if (typeof window === "undefined") return null;
|
|
1642
|
+
return window.SpeechRecognition || window.webkitSpeechRecognition || null;
|
|
1643
|
+
}
|
|
1644
|
+
function useVoice(config = {}, callbacks = {}) {
|
|
1645
|
+
const [voiceState, setVoiceState] = useState("idle");
|
|
1646
|
+
const [transcribedText, setTranscribedText] = useState("");
|
|
1647
|
+
const [isAvailable, setIsAvailable] = useState(false);
|
|
1648
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
1649
|
+
const recognitionRef = useRef(null);
|
|
1650
|
+
const autoStopTimerRef = useRef(null);
|
|
1651
|
+
const {
|
|
1652
|
+
lang = "en-US",
|
|
1653
|
+
interimResults = true,
|
|
1654
|
+
continuous = true,
|
|
1655
|
+
maxAlternatives = 1,
|
|
1656
|
+
autoStopAfterSilence
|
|
1657
|
+
} = config;
|
|
1658
|
+
const { onStart, onEnd, onResult, onError, onStateChange } = callbacks;
|
|
1659
|
+
useEffect(() => {
|
|
1660
|
+
const SpeechRecognitionAPI = getSpeechRecognition();
|
|
1661
|
+
setIsAvailable(SpeechRecognitionAPI !== null);
|
|
1662
|
+
}, []);
|
|
1663
|
+
useEffect(() => {
|
|
1664
|
+
onStateChange?.(voiceState);
|
|
1665
|
+
}, [voiceState, onStateChange]);
|
|
1666
|
+
const requestPermissions = useCallback(async () => {
|
|
1667
|
+
try {
|
|
1668
|
+
const result = await navigator.mediaDevices.getUserMedia({
|
|
1669
|
+
audio: true
|
|
1670
|
+
});
|
|
1671
|
+
result.getTracks().forEach((track) => track.stop());
|
|
1672
|
+
return {
|
|
1673
|
+
granted: true,
|
|
1674
|
+
status: "granted"
|
|
1675
|
+
};
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
return {
|
|
1678
|
+
granted: false,
|
|
1679
|
+
status: "denied"
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
}, []);
|
|
1683
|
+
const getPermissions = useCallback(async () => {
|
|
1684
|
+
if (typeof navigator === "undefined" || !navigator.permissions) {
|
|
1685
|
+
return {
|
|
1686
|
+
granted: false,
|
|
1687
|
+
status: "undetermined"
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
try {
|
|
1691
|
+
const result = await navigator.permissions.query({
|
|
1692
|
+
name: "microphone"
|
|
1693
|
+
});
|
|
1694
|
+
return {
|
|
1695
|
+
granted: result.state === "granted",
|
|
1696
|
+
status: result.state === "granted" ? "granted" : result.state === "denied" ? "denied" : "undetermined"
|
|
1697
|
+
};
|
|
1698
|
+
} catch {
|
|
1699
|
+
return {
|
|
1700
|
+
granted: false,
|
|
1701
|
+
status: "undetermined"
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
}, []);
|
|
1705
|
+
const clearAutoStopTimer = useCallback(() => {
|
|
1706
|
+
if (autoStopTimerRef.current) {
|
|
1707
|
+
clearTimeout(autoStopTimerRef.current);
|
|
1708
|
+
autoStopTimerRef.current = null;
|
|
1709
|
+
}
|
|
1710
|
+
}, []);
|
|
1711
|
+
const resetAutoStopTimer = useCallback(() => {
|
|
1712
|
+
clearAutoStopTimer();
|
|
1713
|
+
if (autoStopAfterSilence && autoStopAfterSilence > 0) {
|
|
1714
|
+
autoStopTimerRef.current = setTimeout(() => {
|
|
1715
|
+
if (recognitionRef.current && isRecording) {
|
|
1716
|
+
recognitionRef.current.stop();
|
|
1717
|
+
}
|
|
1718
|
+
}, autoStopAfterSilence);
|
|
1719
|
+
}
|
|
1720
|
+
}, [autoStopAfterSilence, clearAutoStopTimer, isRecording]);
|
|
1721
|
+
const stopRecording = useCallback(() => {
|
|
1722
|
+
if (recognitionRef.current) {
|
|
1723
|
+
try {
|
|
1724
|
+
recognitionRef.current.stop();
|
|
1725
|
+
} catch (error) {
|
|
1726
|
+
console.warn("Error stopping speech recognition:", error);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
clearAutoStopTimer();
|
|
1730
|
+
setIsRecording(false);
|
|
1731
|
+
setVoiceState("idle");
|
|
1732
|
+
}, [clearAutoStopTimer]);
|
|
1733
|
+
const startRecording = useCallback(async () => {
|
|
1734
|
+
const SpeechRecognitionAPI = getSpeechRecognition();
|
|
1735
|
+
if (!SpeechRecognitionAPI) {
|
|
1736
|
+
onError?.("Speech recognition not supported in this browser");
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
try {
|
|
1740
|
+
try {
|
|
1741
|
+
await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1742
|
+
} catch (permError) {
|
|
1743
|
+
onError?.("Microphone access denied. Please allow microphone access in your browser settings.");
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
const recognition = new SpeechRecognitionAPI();
|
|
1747
|
+
recognition.continuous = continuous;
|
|
1748
|
+
recognition.interimResults = interimResults;
|
|
1749
|
+
recognition.lang = lang;
|
|
1750
|
+
recognition.maxAlternatives = maxAlternatives;
|
|
1751
|
+
recognition.onstart = () => {
|
|
1752
|
+
setVoiceState("listening");
|
|
1753
|
+
setIsRecording(true);
|
|
1754
|
+
onStart?.();
|
|
1755
|
+
resetAutoStopTimer();
|
|
1756
|
+
};
|
|
1757
|
+
recognition.onend = () => {
|
|
1758
|
+
setVoiceState("idle");
|
|
1759
|
+
setIsRecording(false);
|
|
1760
|
+
clearAutoStopTimer();
|
|
1761
|
+
onEnd?.();
|
|
1762
|
+
};
|
|
1763
|
+
recognition.onresult = (event) => {
|
|
1764
|
+
const results = event.results;
|
|
1765
|
+
let transcript = "";
|
|
1766
|
+
for (let i = 0; i < results.length; i++) {
|
|
1767
|
+
const result = results[i];
|
|
1768
|
+
if (result && result[0]) {
|
|
1769
|
+
const text = result[0].transcript;
|
|
1770
|
+
if (transcript && !transcript.endsWith(" ") && !text.startsWith(" ")) {
|
|
1771
|
+
transcript += " " + text;
|
|
1772
|
+
} else {
|
|
1773
|
+
transcript += text;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
transcript = transcript.trim();
|
|
1778
|
+
if (transcript) {
|
|
1779
|
+
setTranscribedText(transcript);
|
|
1780
|
+
onResult?.(transcript);
|
|
1781
|
+
resetAutoStopTimer();
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
recognition.onerror = (event) => {
|
|
1785
|
+
setVoiceState("error");
|
|
1786
|
+
setIsRecording(false);
|
|
1787
|
+
clearAutoStopTimer();
|
|
1788
|
+
let errorMessage = event.error;
|
|
1789
|
+
if (event.error === "not-allowed") {
|
|
1790
|
+
errorMessage = "Microphone access denied. Please allow microphone access in your browser settings.";
|
|
1791
|
+
} else if (event.error === "no-speech") {
|
|
1792
|
+
errorMessage = "No speech detected. Please try again.";
|
|
1793
|
+
} else if (event.error === "audio-capture") {
|
|
1794
|
+
errorMessage = "No microphone found or microphone is in use.";
|
|
1795
|
+
} else if (event.error === "network") {
|
|
1796
|
+
errorMessage = "Network error occurred. Please check your connection.";
|
|
1797
|
+
}
|
|
1798
|
+
onError?.(errorMessage);
|
|
1799
|
+
};
|
|
1800
|
+
recognitionRef.current = recognition;
|
|
1801
|
+
setTranscribedText("");
|
|
1802
|
+
recognition.start();
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
setVoiceState("error");
|
|
1805
|
+
setIsRecording(false);
|
|
1806
|
+
onError?.(
|
|
1807
|
+
error instanceof Error ? error.message : "Failed to start recording"
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
}, [
|
|
1811
|
+
lang,
|
|
1812
|
+
interimResults,
|
|
1813
|
+
continuous,
|
|
1814
|
+
maxAlternatives,
|
|
1815
|
+
onStart,
|
|
1816
|
+
onEnd,
|
|
1817
|
+
onResult,
|
|
1818
|
+
onError,
|
|
1819
|
+
getPermissions,
|
|
1820
|
+
resetAutoStopTimer,
|
|
1821
|
+
clearAutoStopTimer
|
|
1822
|
+
]);
|
|
1823
|
+
const clearTranscript = useCallback(() => {
|
|
1824
|
+
setTranscribedText("");
|
|
1825
|
+
}, []);
|
|
1826
|
+
const reset = useCallback(() => {
|
|
1827
|
+
stopRecording();
|
|
1828
|
+
setTranscribedText("");
|
|
1829
|
+
setVoiceState("idle");
|
|
1830
|
+
}, [stopRecording]);
|
|
1831
|
+
useEffect(() => {
|
|
1832
|
+
return () => {
|
|
1833
|
+
if (recognitionRef.current) {
|
|
1834
|
+
try {
|
|
1835
|
+
recognitionRef.current.stop();
|
|
1836
|
+
} catch {
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
clearAutoStopTimer();
|
|
1840
|
+
};
|
|
1841
|
+
}, [clearAutoStopTimer]);
|
|
1842
|
+
return {
|
|
1843
|
+
voiceState,
|
|
1844
|
+
transcribedText,
|
|
1845
|
+
isAvailable,
|
|
1846
|
+
isRecording,
|
|
1847
|
+
startRecording,
|
|
1848
|
+
stopRecording,
|
|
1849
|
+
requestPermissions,
|
|
1850
|
+
getPermissions,
|
|
1851
|
+
clearTranscript,
|
|
1852
|
+
reset
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
function classifyField(field) {
|
|
1856
|
+
if (!field) return "text";
|
|
1857
|
+
if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
|
|
1858
|
+
switch (field.type) {
|
|
1859
|
+
case "boolean":
|
|
1860
|
+
return "boolean";
|
|
1861
|
+
case "integer":
|
|
1862
|
+
return "integer";
|
|
1863
|
+
case "number":
|
|
1864
|
+
return "decimal";
|
|
1865
|
+
case "string":
|
|
1866
|
+
return "text";
|
|
1867
|
+
default:
|
|
1868
|
+
return "text";
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
function isNestedOrUnsupported(field) {
|
|
1872
|
+
if (!field) return false;
|
|
1873
|
+
if (field.type === "object" || field.type === "array") return true;
|
|
1874
|
+
if ("properties" in field && field.properties != null) return true;
|
|
1875
|
+
if ("items" in field && field.items != null) return true;
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
function getOptions(field) {
|
|
1879
|
+
if (!field || !Array.isArray(field.oneOf)) return [];
|
|
1880
|
+
return field.oneOf.filter(
|
|
1881
|
+
(o) => !!o && typeof o === "object" && typeof o.const === "string"
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
function isRequired(schema, key) {
|
|
1885
|
+
return Array.isArray(schema?.required) && schema.required.includes(key);
|
|
1886
|
+
}
|
|
1887
|
+
function coerceValue(field, raw) {
|
|
1888
|
+
const widget = classifyField(field);
|
|
1889
|
+
if (widget === "boolean") {
|
|
1890
|
+
if (typeof raw === "boolean") return raw;
|
|
1891
|
+
if (raw === "true") return true;
|
|
1892
|
+
if (raw === "false") return false;
|
|
1893
|
+
return Boolean(raw);
|
|
1894
|
+
}
|
|
1895
|
+
if (widget === "integer" || widget === "decimal") {
|
|
1896
|
+
if (raw === "" || raw == null) return void 0;
|
|
1897
|
+
const num = typeof raw === "number" ? raw : Number(String(raw).trim());
|
|
1898
|
+
if (Number.isNaN(num)) return void 0;
|
|
1899
|
+
return widget === "integer" ? Math.trunc(num) : num;
|
|
1900
|
+
}
|
|
1901
|
+
if (raw == null) return void 0;
|
|
1902
|
+
const str = String(raw);
|
|
1903
|
+
return str === "" ? void 0 : str;
|
|
1904
|
+
}
|
|
1905
|
+
function defaultValueFor(field) {
|
|
1906
|
+
if (!field || field.default === void 0) {
|
|
1907
|
+
return classifyField(field) === "boolean" ? false : "";
|
|
1908
|
+
}
|
|
1909
|
+
return field.default;
|
|
1910
|
+
}
|
|
1911
|
+
function validateField(field, value, required) {
|
|
1912
|
+
const widget = classifyField(field);
|
|
1913
|
+
const label = field?.title || "This field";
|
|
1914
|
+
const isEmpty = value === void 0 || value === null || typeof value === "string" && value.trim() === "";
|
|
1915
|
+
if (isEmpty) {
|
|
1916
|
+
if (required && widget !== "boolean") return `${label} is required.`;
|
|
1917
|
+
return null;
|
|
1918
|
+
}
|
|
1919
|
+
if (widget === "integer" || widget === "decimal") {
|
|
1920
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
1921
|
+
if (Number.isNaN(num)) return `${label} must be a number.`;
|
|
1922
|
+
if (widget === "integer" && !Number.isInteger(num)) {
|
|
1923
|
+
return `${label} must be a whole number.`;
|
|
1924
|
+
}
|
|
1925
|
+
if (typeof field?.minimum === "number" && num < field.minimum) {
|
|
1926
|
+
return `${label} must be at least ${field.minimum}.`;
|
|
1927
|
+
}
|
|
1928
|
+
if (typeof field?.maximum === "number" && num > field.maximum) {
|
|
1929
|
+
return `${label} must be at most ${field.maximum}.`;
|
|
1930
|
+
}
|
|
1931
|
+
return null;
|
|
1932
|
+
}
|
|
1933
|
+
if (widget === "select") {
|
|
1934
|
+
const allowed = getOptions(field).map((o) => o.const);
|
|
1935
|
+
if (allowed.length > 0 && !allowed.includes(String(value))) {
|
|
1936
|
+
return `${label} has an invalid selection.`;
|
|
1937
|
+
}
|
|
1938
|
+
return null;
|
|
1939
|
+
}
|
|
1940
|
+
const str = String(value);
|
|
1941
|
+
if (typeof field?.minLength === "number" && str.length < field.minLength) {
|
|
1942
|
+
return `${label} must be at least ${field.minLength} characters.`;
|
|
1943
|
+
}
|
|
1944
|
+
if (typeof field?.maxLength === "number" && str.length > field.maxLength) {
|
|
1945
|
+
return `${label} must be at most ${field.maxLength} characters.`;
|
|
1946
|
+
}
|
|
1947
|
+
return null;
|
|
1948
|
+
}
|
|
1949
|
+
function renderableFields(schema) {
|
|
1950
|
+
const props = schema?.properties;
|
|
1951
|
+
if (!props) return [];
|
|
1952
|
+
return Object.entries(props).filter(([, field]) => !isNestedOrUnsupported(field));
|
|
1953
|
+
}
|
|
1954
|
+
function validateForm(schema, values) {
|
|
1955
|
+
const errors = {};
|
|
1956
|
+
for (const [key, field] of renderableFields(schema)) {
|
|
1957
|
+
const coerced = coerceValue(field, values[key]);
|
|
1958
|
+
const err = validateField(field, coerced, isRequired(schema, key));
|
|
1959
|
+
if (err) errors[key] = err;
|
|
1960
|
+
}
|
|
1961
|
+
return errors;
|
|
1962
|
+
}
|
|
1963
|
+
function buildContent(schema, values) {
|
|
1964
|
+
const content = {};
|
|
1965
|
+
for (const [key, field] of renderableFields(schema)) {
|
|
1966
|
+
const coerced = coerceValue(field, values[key]);
|
|
1967
|
+
if (coerced !== void 0) content[key] = coerced;
|
|
1968
|
+
}
|
|
1969
|
+
return content;
|
|
1970
|
+
}
|
|
16
1971
|
var PaymanChatContext = createContext(void 0);
|
|
17
1972
|
function usePaymanChat() {
|
|
18
1973
|
const context = useContext(PaymanChatContext);
|
|
@@ -232,7 +2187,7 @@ var NEGATIVE_FEEDBACK_REASONS = [
|
|
|
232
2187
|
{ value: "REPORT_CONTENT", label: "Report content" },
|
|
233
2188
|
{ value: "OTHER", label: "Other" }
|
|
234
2189
|
];
|
|
235
|
-
var
|
|
2190
|
+
var DEFAULT_STREAM_ENDPOINT2 = "/api/playground/ask/stream";
|
|
236
2191
|
async function submitFeedback({
|
|
237
2192
|
baseUrl,
|
|
238
2193
|
streamEndpoint,
|
|
@@ -246,7 +2201,7 @@ async function submitFeedback({
|
|
|
246
2201
|
signal
|
|
247
2202
|
}) {
|
|
248
2203
|
const base = baseUrl.replace(/\/+$/, "");
|
|
249
|
-
const endpointPath = (streamEndpoint ||
|
|
2204
|
+
const endpointPath = (streamEndpoint || DEFAULT_STREAM_ENDPOINT2).split("?")[0].replace(/\/+$/, "");
|
|
250
2205
|
const basePath = endpointPath.endsWith("/stream") ? endpointPath.slice(0, -"/stream".length) : endpointPath;
|
|
251
2206
|
const query = new URLSearchParams();
|
|
252
2207
|
if (stage) query.set(stageQueryParam ?? "stage", stage);
|
|
@@ -518,47 +2473,7 @@ function createMarkdownComponents(options = {}) {
|
|
|
518
2473
|
td: ({ children }) => /* @__PURE__ */ jsx("td", { className: "p-3 align-middle text-sm whitespace-nowrap", children })
|
|
519
2474
|
};
|
|
520
2475
|
}
|
|
521
|
-
|
|
522
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
523
|
-
const hasContent = typeof text === "string" && text.trim().length > 0;
|
|
524
|
-
if (!hasContent) return null;
|
|
525
|
-
return /* @__PURE__ */ jsxs("div", { className: "mt-1.5 mb-1.5", children: [
|
|
526
|
-
/* @__PURE__ */ jsxs(
|
|
527
|
-
"button",
|
|
528
|
-
{
|
|
529
|
-
type: "button",
|
|
530
|
-
onClick: () => setIsOpen((prev) => !prev),
|
|
531
|
-
className: "inline-flex items-center gap-1 text-[10px] payman-agent-thinking-toggle transition-colors",
|
|
532
|
-
"aria-expanded": isOpen,
|
|
533
|
-
"aria-label": isOpen ? "Collapse thought process" : "Expand thought process",
|
|
534
|
-
children: [
|
|
535
|
-
/* @__PURE__ */ jsx(
|
|
536
|
-
motion.div,
|
|
537
|
-
{
|
|
538
|
-
animate: { rotate: isOpen ? 180 : 0 },
|
|
539
|
-
transition: { duration: 0.15 },
|
|
540
|
-
className: "shrink-0",
|
|
541
|
-
children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3" })
|
|
542
|
-
}
|
|
543
|
-
),
|
|
544
|
-
/* @__PURE__ */ jsx("span", { children: "Thought process" })
|
|
545
|
-
]
|
|
546
|
-
}
|
|
547
|
-
),
|
|
548
|
-
/* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ jsx(
|
|
549
|
-
motion.div,
|
|
550
|
-
{
|
|
551
|
-
initial: { height: 0, opacity: 0 },
|
|
552
|
-
animate: { height: "auto", opacity: 1 },
|
|
553
|
-
exit: { height: 0, opacity: 0 },
|
|
554
|
-
transition: { duration: 0.2, ease: "easeInOut" },
|
|
555
|
-
className: "overflow-hidden",
|
|
556
|
-
children: /* @__PURE__ */ jsx("div", { className: "mt-1 payman-agent-thinking-block rounded-md p-2 overflow-y-auto overflow-x-hidden", children: /* @__PURE__ */ jsx("p", { className: "m-0 text-xs payman-agent-thinking-block-text whitespace-pre-wrap leading-relaxed", children: text }) })
|
|
557
|
-
}
|
|
558
|
-
) })
|
|
559
|
-
] });
|
|
560
|
-
}
|
|
561
|
-
var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
|
|
2476
|
+
var FRIENDLY_ERROR_MESSAGE2 = "Oops, something went wrong. Please try again.";
|
|
562
2477
|
function looksLikeRawError(text) {
|
|
563
2478
|
if (!text || text.length < 10) return false;
|
|
564
2479
|
return text.includes("errorType=") || /failed:\s*\{/.test(text);
|
|
@@ -600,8 +2515,6 @@ function AgentMessage({
|
|
|
600
2515
|
const completedWithNoContent = !isStreaming && !isCancelled && content.length === 0 && (message.streamProgress === "completed" || message.streamProgress === "error");
|
|
601
2516
|
const conflictErrorMessage = getConflictErrorMessage(message.errorDetails);
|
|
602
2517
|
const isError = !!conflictErrorMessage || (isFriendlyWorkflowError(message.errorDetails) || looksLikeRawError(content)) && !hasMeaningfulContent || completedWithNoContent;
|
|
603
|
-
const activeThinkingText = message.activeThinkingText;
|
|
604
|
-
const allThinkingText = message.allThinkingText;
|
|
605
2518
|
const currentStep = useMemo(
|
|
606
2519
|
() => message.steps?.find(
|
|
607
2520
|
(s) => s.id === currentExecutingStepId && s.status === "in_progress"
|
|
@@ -644,7 +2557,7 @@ function AgentMessage({
|
|
|
644
2557
|
return /* @__PURE__ */ jsx("div", { className: wrapper, children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5 payman-agent-step-icon--error" }) });
|
|
645
2558
|
return /* @__PURE__ */ jsx("div", { className: wrapper, children: /* @__PURE__ */ jsx("div", { className: "h-1.5 w-1.5 rounded-full payman-agent-step-icon--pending-dim" }) });
|
|
646
2559
|
};
|
|
647
|
-
const stepsListContent = hasSteps && /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: isStepsExpanded && /* @__PURE__ */
|
|
2560
|
+
const stepsListContent = hasSteps && /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: isStepsExpanded && /* @__PURE__ */ jsx(
|
|
648
2561
|
motion.div,
|
|
649
2562
|
{
|
|
650
2563
|
initial: { height: 0, opacity: 0 },
|
|
@@ -652,37 +2565,34 @@ function AgentMessage({
|
|
|
652
2565
|
exit: { height: 0, opacity: 0 },
|
|
653
2566
|
transition: { duration: 0.2, ease: "easeInOut" },
|
|
654
2567
|
className: "overflow-hidden",
|
|
655
|
-
children:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
/* @__PURE__ */
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
] }, step.id);
|
|
684
|
-
}) })
|
|
685
|
-
]
|
|
2568
|
+
children: /* @__PURE__ */ jsx("div", { className: "pt-1.5", children: message.steps.map((step) => {
|
|
2569
|
+
const isCurrentlyExecuting = step.id === currentExecutingStepId && step.status === "in_progress" && isStreaming && !isCancelled;
|
|
2570
|
+
const hasTime = step.elapsedMs != null && step.elapsedMs > 0;
|
|
2571
|
+
return /* @__PURE__ */ jsxs("div", { className: "mb-1.5", children: [
|
|
2572
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1.5 items-start", children: [
|
|
2573
|
+
/* @__PURE__ */ jsx("div", { className: "mt-px", children: renderStepIcon(step, isCurrentlyExecuting) }),
|
|
2574
|
+
/* @__PURE__ */ jsx(
|
|
2575
|
+
"span",
|
|
2576
|
+
{
|
|
2577
|
+
className: cn(
|
|
2578
|
+
"text-xs leading-relaxed min-w-0 break-words",
|
|
2579
|
+
isCurrentlyExecuting && "shimmer-text font-medium",
|
|
2580
|
+
!isCurrentlyExecuting && step.status === "error" && "payman-agent-step-text--error",
|
|
2581
|
+
!isCurrentlyExecuting && step.eventType === "USER_ACTION_SUCCESS" && "payman-agent-step-text--success",
|
|
2582
|
+
!isCurrentlyExecuting && step.status === "completed" && "payman-agent-step-text--completed",
|
|
2583
|
+
!isCurrentlyExecuting && step.status === "pending" && "payman-agent-step-text--pending",
|
|
2584
|
+
!isCurrentlyExecuting && step.status === "in_progress" && "payman-agent-step-text--in-progress"
|
|
2585
|
+
),
|
|
2586
|
+
children: step.message
|
|
2587
|
+
}
|
|
2588
|
+
)
|
|
2589
|
+
] }),
|
|
2590
|
+
hasTime && /* @__PURE__ */ jsx("div", { className: "pl-[22px] mt-1", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 px-1.5 py-0.5 rounded-md payman-agent-step-time leading-none", children: [
|
|
2591
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-2.5 w-2.5 shrink-0 payman-agent-step-time-icon" }),
|
|
2592
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono payman-agent-step-time-text", children: formatElapsedTime(step.elapsedMs) })
|
|
2593
|
+
] }) })
|
|
2594
|
+
] }, step.id);
|
|
2595
|
+
}) })
|
|
686
2596
|
}
|
|
687
2597
|
) });
|
|
688
2598
|
const stepsToggleRef = useRef(null);
|
|
@@ -724,95 +2634,77 @@ function AgentMessage({
|
|
|
724
2634
|
/* @__PURE__ */ jsx("div", { className: "h-2.5 w-2.5 rounded-full payman-agent-avatar-dot animate-pulse" }),
|
|
725
2635
|
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 h-2.5 w-2.5 rounded-full payman-agent-avatar-dot-ping animate-ping" })
|
|
726
2636
|
] }) }) : /* @__PURE__ */ jsx("div", { className: "h-8 w-8 rounded-full payman-agent-avatar flex items-center justify-center", children: /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 payman-agent-avatar-icon" }) }) }),
|
|
727
|
-
/* @__PURE__ */
|
|
2637
|
+
/* @__PURE__ */ jsx(
|
|
728
2638
|
"div",
|
|
729
2639
|
{
|
|
730
2640
|
className: cn(
|
|
731
2641
|
"min-w-0",
|
|
732
2642
|
layout === "centered" ? "max-w-[85%]" : showAvatar ? "max-w-[85%]" : "max-w-[80%]"
|
|
733
2643
|
),
|
|
734
|
-
children:
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
2644
|
+
children: /* @__PURE__ */ jsxs(
|
|
2645
|
+
"div",
|
|
2646
|
+
{
|
|
2647
|
+
className: cn(
|
|
2648
|
+
"overflow-hidden w-full min-w-0 px-4 py-3 rounded-2xl rounded-tl-md transition-all duration-200",
|
|
2649
|
+
layout === "centered" ? cn(
|
|
2650
|
+
"payman-agent-bubble payman-agent-bubble--centered",
|
|
2651
|
+
isStreaming && "streaming",
|
|
2652
|
+
isError && "error"
|
|
2653
|
+
) : cn(
|
|
2654
|
+
"payman-agent-bubble",
|
|
2655
|
+
isError && "payman-agent-bubble--error"
|
|
2656
|
+
)
|
|
2657
|
+
),
|
|
2658
|
+
children: [
|
|
2659
|
+
showAgentName && /* @__PURE__ */ jsx(
|
|
2660
|
+
"p",
|
|
2661
|
+
{
|
|
2662
|
+
className: cn(
|
|
2663
|
+
"text-sm font-semibold mb-1 leading-none payman-agent-name",
|
|
2664
|
+
isStreaming && !content && "animate-pulse"
|
|
2665
|
+
),
|
|
2666
|
+
children: isStreaming && !content ? "Thought Process" : agentName
|
|
2667
|
+
}
|
|
755
2668
|
),
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
{
|
|
770
|
-
className:
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
className: cn(
|
|
796
|
-
"payman-markdown payman-agent-markdown prose prose-sm dark:prose-invert max-w-none min-w-0 w-full break-words overflow-wrap-anywhere",
|
|
797
|
-
isError && "payman-agent-markdown--error",
|
|
798
|
-
isStreaming && content && "streaming-cursor"
|
|
799
|
-
),
|
|
800
|
-
children: /* @__PURE__ */ jsx(
|
|
801
|
-
ReactMarkdown,
|
|
802
|
-
{
|
|
803
|
-
remarkPlugins: [remarkGfm],
|
|
804
|
-
components: markdownRenderers,
|
|
805
|
-
children: isError ? conflictErrorMessage ?? FRIENDLY_ERROR_MESSAGE : content || (isStreaming ? "Thinking..." : isCancelled ? "Request was stopped." : "")
|
|
806
|
-
}
|
|
807
|
-
)
|
|
808
|
-
}
|
|
809
|
-
)
|
|
810
|
-
}
|
|
811
|
-
)
|
|
812
|
-
]
|
|
813
|
-
}
|
|
814
|
-
)
|
|
815
|
-
]
|
|
2669
|
+
/* @__PURE__ */ jsx(
|
|
2670
|
+
"div",
|
|
2671
|
+
{
|
|
2672
|
+
className: cn(
|
|
2673
|
+
"text-sm leading-relaxed min-w-0 w-full break-words overflow-wrap-anywhere",
|
|
2674
|
+
showAgentName && "mt-1"
|
|
2675
|
+
),
|
|
2676
|
+
children: isStreaming && !content ? currentStep ? /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5 items-start", children: [
|
|
2677
|
+
/* @__PURE__ */ jsx("div", { className: "mt-0.5 shrink-0", children: renderStepIcon(currentStep, true) }),
|
|
2678
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm leading-relaxed min-w-0 break-words shimmer-text font-medium flex-1", children: currentStep.message })
|
|
2679
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
2680
|
+
/* @__PURE__ */ jsx(Loader2, { className: "w-4 h-4 mt-0.5 payman-agent-thinking-spinner animate-spin shrink-0" }),
|
|
2681
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm payman-agent-thinking-text flex-1", children: currentMessage || "Thinking..." })
|
|
2682
|
+
] }) : isCancelled && !content ? /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
2683
|
+
/* @__PURE__ */ jsx(X, { className: "w-4 h-4 mt-0.5 payman-agent-cancelled-icon shrink-0" }),
|
|
2684
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm payman-agent-cancelled-text italic flex-1", children: currentMessage || "Request was stopped." })
|
|
2685
|
+
] }) : /* @__PURE__ */ jsx(
|
|
2686
|
+
"div",
|
|
2687
|
+
{
|
|
2688
|
+
className: cn(
|
|
2689
|
+
"payman-markdown payman-agent-markdown prose prose-sm dark:prose-invert max-w-none min-w-0 w-full break-words overflow-wrap-anywhere",
|
|
2690
|
+
isError && "payman-agent-markdown--error",
|
|
2691
|
+
isStreaming && content && "streaming-cursor"
|
|
2692
|
+
),
|
|
2693
|
+
children: /* @__PURE__ */ jsx(
|
|
2694
|
+
ReactMarkdown,
|
|
2695
|
+
{
|
|
2696
|
+
remarkPlugins: [remarkGfm],
|
|
2697
|
+
components: markdownRenderers,
|
|
2698
|
+
children: isError ? conflictErrorMessage ?? FRIENDLY_ERROR_MESSAGE2 : content || (isStreaming ? "Thinking..." : isCancelled ? "Request was stopped." : "")
|
|
2699
|
+
}
|
|
2700
|
+
)
|
|
2701
|
+
}
|
|
2702
|
+
)
|
|
2703
|
+
}
|
|
2704
|
+
)
|
|
2705
|
+
]
|
|
2706
|
+
}
|
|
2707
|
+
)
|
|
816
2708
|
}
|
|
817
2709
|
)
|
|
818
2710
|
] }),
|
|
@@ -1656,7 +3548,62 @@ function MarkdownImageV2({
|
|
|
1656
3548
|
}
|
|
1657
3549
|
) });
|
|
1658
3550
|
}
|
|
1659
|
-
function
|
|
3551
|
+
function PdfBlockV2({ title, href, onOpen }) {
|
|
3552
|
+
return /* @__PURE__ */ jsxs(
|
|
3553
|
+
"button",
|
|
3554
|
+
{
|
|
3555
|
+
type: "button",
|
|
3556
|
+
className: "payman-v2-pdf-block",
|
|
3557
|
+
onClick: (e) => {
|
|
3558
|
+
e.preventDefault();
|
|
3559
|
+
onOpen(href, title);
|
|
3560
|
+
},
|
|
3561
|
+
"aria-label": `Open PDF: ${title}`,
|
|
3562
|
+
children: [
|
|
3563
|
+
/* @__PURE__ */ jsx("div", { className: "payman-v2-pdf-block-icon-area", children: /* @__PURE__ */ jsx(FileText, { size: 18, strokeWidth: 1.5 }) }),
|
|
3564
|
+
/* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-block-body", children: [
|
|
3565
|
+
/* @__PURE__ */ jsx("span", { className: "payman-v2-pdf-block-name", children: title }),
|
|
3566
|
+
/* @__PURE__ */ jsx("span", { className: "payman-v2-pdf-block-sub", children: "PDF \xB7 Click to preview" })
|
|
3567
|
+
] })
|
|
3568
|
+
]
|
|
3569
|
+
}
|
|
3570
|
+
);
|
|
3571
|
+
}
|
|
3572
|
+
function isPdfUrl(href) {
|
|
3573
|
+
if (!href) return false;
|
|
3574
|
+
try {
|
|
3575
|
+
const url = new URL(href);
|
|
3576
|
+
const filename = url.searchParams.get("filename");
|
|
3577
|
+
if (filename?.toLowerCase().endsWith(".pdf")) return true;
|
|
3578
|
+
return url.pathname.toLowerCase().endsWith(".pdf");
|
|
3579
|
+
} catch {
|
|
3580
|
+
return href.toLowerCase().endsWith(".pdf");
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
function getPdfTitleFromUrl(href) {
|
|
3584
|
+
try {
|
|
3585
|
+
const url = new URL(href);
|
|
3586
|
+
const filename = url.searchParams.get("filename");
|
|
3587
|
+
if (filename) {
|
|
3588
|
+
return filename.replace(/\.pdf$/i, "").replace(/[-_]+/g, " ").trim();
|
|
3589
|
+
}
|
|
3590
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
3591
|
+
const last = parts[parts.length - 1];
|
|
3592
|
+
if (last) return decodeURIComponent(last).replace(/\.pdf$/i, "").replace(/[-_]+/g, " ").trim();
|
|
3593
|
+
} catch {
|
|
3594
|
+
}
|
|
3595
|
+
return "Document";
|
|
3596
|
+
}
|
|
3597
|
+
function childrenToText(children) {
|
|
3598
|
+
if (typeof children === "string") return children;
|
|
3599
|
+
if (typeof children === "number") return String(children);
|
|
3600
|
+
if (Array.isArray(children)) return children.map(childrenToText).join("");
|
|
3601
|
+
if (children && typeof children === "object" && "props" in children) {
|
|
3602
|
+
return childrenToText(children.props.children);
|
|
3603
|
+
}
|
|
3604
|
+
return "";
|
|
3605
|
+
}
|
|
3606
|
+
function buildComponents(onImageClick, isResolvingRef, onPdfClick) {
|
|
1660
3607
|
return {
|
|
1661
3608
|
p: ({ children }) => /* @__PURE__ */ jsx("p", { children }),
|
|
1662
3609
|
code: ({ children }) => /* @__PURE__ */ jsx("code", { children }),
|
|
@@ -1671,7 +3618,15 @@ function buildComponents(onImageClick, isResolvingRef) {
|
|
|
1671
3618
|
em: ({ children }) => /* @__PURE__ */ jsx("em", { children }),
|
|
1672
3619
|
blockquote: ({ children }) => /* @__PURE__ */ jsx("blockquote", { children }),
|
|
1673
3620
|
hr: () => /* @__PURE__ */ jsx("hr", {}),
|
|
1674
|
-
a: ({ href, children }) =>
|
|
3621
|
+
a: ({ href, children }) => {
|
|
3622
|
+
const url = href ?? "";
|
|
3623
|
+
if (onPdfClick && isPdfUrl(url)) {
|
|
3624
|
+
const linkText = childrenToText(children).trim();
|
|
3625
|
+
const title = linkText || getPdfTitleFromUrl(url);
|
|
3626
|
+
return /* @__PURE__ */ jsx(PdfBlockV2, { href: url, title, onOpen: onPdfClick });
|
|
3627
|
+
}
|
|
3628
|
+
return /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer", children });
|
|
3629
|
+
},
|
|
1675
3630
|
img: ({ src, alt }) => /* @__PURE__ */ jsx(
|
|
1676
3631
|
MarkdownImageV2,
|
|
1677
3632
|
{
|
|
@@ -1692,13 +3647,14 @@ function MarkdownRendererV2({
|
|
|
1692
3647
|
content,
|
|
1693
3648
|
isStreaming,
|
|
1694
3649
|
isResolvingImages,
|
|
1695
|
-
onImageClick
|
|
3650
|
+
onImageClick,
|
|
3651
|
+
onPdfClick
|
|
1696
3652
|
}) {
|
|
1697
3653
|
const isResolvingRef = useRef(isResolvingImages);
|
|
1698
3654
|
isResolvingRef.current = isResolvingImages;
|
|
1699
3655
|
const components = useMemo(
|
|
1700
|
-
() => buildComponents(onImageClick, isResolvingRef),
|
|
1701
|
-
[onImageClick]
|
|
3656
|
+
() => buildComponents(onImageClick, isResolvingRef, onPdfClick),
|
|
3657
|
+
[onImageClick, onPdfClick]
|
|
1702
3658
|
);
|
|
1703
3659
|
return /* @__PURE__ */ jsx(
|
|
1704
3660
|
"div",
|
|
@@ -1963,6 +3919,199 @@ function FeedbackReasonModal({
|
|
|
1963
3919
|
}
|
|
1964
3920
|
) : null });
|
|
1965
3921
|
}
|
|
3922
|
+
var MIN_WIDTH = 320;
|
|
3923
|
+
var MAX_WIDTH_RATIO = 0.92;
|
|
3924
|
+
var DEFAULT_WIDTH = 680;
|
|
3925
|
+
function PdfSheetV2({ src, title, onClose }) {
|
|
3926
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
3927
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
3928
|
+
const [width, setWidth] = useState(DEFAULT_WIDTH);
|
|
3929
|
+
const panelRef = useRef(null);
|
|
3930
|
+
const isDragging = useRef(false);
|
|
3931
|
+
const dragStartX = useRef(0);
|
|
3932
|
+
const dragStartWidth = useRef(0);
|
|
3933
|
+
useEffect(() => {
|
|
3934
|
+
setIsMounted(true);
|
|
3935
|
+
return () => setIsMounted(false);
|
|
3936
|
+
}, []);
|
|
3937
|
+
useEffect(() => {
|
|
3938
|
+
setIsLoaded(false);
|
|
3939
|
+
}, [src]);
|
|
3940
|
+
useEffect(() => {
|
|
3941
|
+
const onResize = () => {
|
|
3942
|
+
const max = Math.floor(window.innerWidth * MAX_WIDTH_RATIO);
|
|
3943
|
+
setWidth((w) => Math.min(w, max));
|
|
3944
|
+
};
|
|
3945
|
+
window.addEventListener("resize", onResize);
|
|
3946
|
+
return () => window.removeEventListener("resize", onResize);
|
|
3947
|
+
}, []);
|
|
3948
|
+
const handleKeyDown = useCallback(
|
|
3949
|
+
(e) => {
|
|
3950
|
+
if (e.key === "Escape") onClose();
|
|
3951
|
+
},
|
|
3952
|
+
[onClose]
|
|
3953
|
+
);
|
|
3954
|
+
useEffect(() => {
|
|
3955
|
+
if (!src || typeof document === "undefined") return;
|
|
3956
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
3957
|
+
const previousOverflow = document.body.style.overflow;
|
|
3958
|
+
document.body.style.overflow = "hidden";
|
|
3959
|
+
return () => {
|
|
3960
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
3961
|
+
document.body.style.overflow = previousOverflow;
|
|
3962
|
+
};
|
|
3963
|
+
}, [src, handleKeyDown]);
|
|
3964
|
+
const handleDownload = () => {
|
|
3965
|
+
if (!src || typeof document === "undefined") return;
|
|
3966
|
+
const a = document.createElement("a");
|
|
3967
|
+
a.href = src;
|
|
3968
|
+
a.download = title ? `${title}.pdf` : "document.pdf";
|
|
3969
|
+
document.body.appendChild(a);
|
|
3970
|
+
a.click();
|
|
3971
|
+
a.remove();
|
|
3972
|
+
};
|
|
3973
|
+
const onResizeMouseDown = (e) => {
|
|
3974
|
+
e.preventDefault();
|
|
3975
|
+
isDragging.current = true;
|
|
3976
|
+
dragStartX.current = e.clientX;
|
|
3977
|
+
dragStartWidth.current = panelRef.current?.offsetWidth ?? width;
|
|
3978
|
+
document.body.style.cursor = "ew-resize";
|
|
3979
|
+
document.body.style.userSelect = "none";
|
|
3980
|
+
};
|
|
3981
|
+
useEffect(() => {
|
|
3982
|
+
const onMouseMove = (e) => {
|
|
3983
|
+
if (!isDragging.current || !panelRef.current) return;
|
|
3984
|
+
const delta = dragStartX.current - e.clientX;
|
|
3985
|
+
const maxW = Math.floor(window.innerWidth * MAX_WIDTH_RATIO);
|
|
3986
|
+
const newW = Math.max(MIN_WIDTH, Math.min(maxW, dragStartWidth.current + delta));
|
|
3987
|
+
panelRef.current.style.width = `${newW}px`;
|
|
3988
|
+
};
|
|
3989
|
+
const onMouseUp = () => {
|
|
3990
|
+
if (!isDragging.current) return;
|
|
3991
|
+
isDragging.current = false;
|
|
3992
|
+
document.body.style.cursor = "";
|
|
3993
|
+
document.body.style.userSelect = "";
|
|
3994
|
+
if (panelRef.current) {
|
|
3995
|
+
setWidth(panelRef.current.offsetWidth);
|
|
3996
|
+
}
|
|
3997
|
+
};
|
|
3998
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
3999
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
4000
|
+
return () => {
|
|
4001
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
4002
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
4003
|
+
};
|
|
4004
|
+
}, []);
|
|
4005
|
+
if (!isMounted || typeof document === "undefined") return null;
|
|
4006
|
+
return createPortal(
|
|
4007
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: src ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4008
|
+
/* @__PURE__ */ jsx(
|
|
4009
|
+
motion.div,
|
|
4010
|
+
{
|
|
4011
|
+
className: "payman-v2-pdf-sheet-overlay",
|
|
4012
|
+
initial: { opacity: 0 },
|
|
4013
|
+
animate: { opacity: 1 },
|
|
4014
|
+
exit: { opacity: 0 },
|
|
4015
|
+
transition: { duration: 0.22 },
|
|
4016
|
+
onClick: onClose,
|
|
4017
|
+
"aria-hidden": "true"
|
|
4018
|
+
},
|
|
4019
|
+
"pdf-sheet-backdrop"
|
|
4020
|
+
),
|
|
4021
|
+
/* @__PURE__ */ jsxs(
|
|
4022
|
+
motion.div,
|
|
4023
|
+
{
|
|
4024
|
+
ref: panelRef,
|
|
4025
|
+
className: "payman-v2-pdf-sheet",
|
|
4026
|
+
style: { width },
|
|
4027
|
+
initial: { x: "100%" },
|
|
4028
|
+
animate: { x: 0 },
|
|
4029
|
+
exit: { x: "100%" },
|
|
4030
|
+
transition: {
|
|
4031
|
+
type: "spring",
|
|
4032
|
+
stiffness: 340,
|
|
4033
|
+
damping: 34,
|
|
4034
|
+
mass: 0.85
|
|
4035
|
+
},
|
|
4036
|
+
role: "dialog",
|
|
4037
|
+
"aria-modal": "true",
|
|
4038
|
+
"aria-label": title || "PDF Preview",
|
|
4039
|
+
children: [
|
|
4040
|
+
/* @__PURE__ */ jsx(
|
|
4041
|
+
"div",
|
|
4042
|
+
{
|
|
4043
|
+
className: "payman-v2-pdf-sheet-resize-handle",
|
|
4044
|
+
onMouseDown: onResizeMouseDown,
|
|
4045
|
+
"aria-hidden": "true",
|
|
4046
|
+
children: /* @__PURE__ */ jsx("div", { className: "payman-v2-pdf-sheet-resize-grip" })
|
|
4047
|
+
}
|
|
4048
|
+
),
|
|
4049
|
+
/* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-header", children: [
|
|
4050
|
+
/* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-header-left", children: [
|
|
4051
|
+
/* @__PURE__ */ jsx("div", { className: "payman-v2-pdf-sheet-file-icon", children: /* @__PURE__ */ jsx(FileText, { size: 14, strokeWidth: 1.75 }) }),
|
|
4052
|
+
/* @__PURE__ */ jsx("span", { className: "payman-v2-pdf-sheet-title", title, children: title || "Document" })
|
|
4053
|
+
] }),
|
|
4054
|
+
/* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-header-actions", children: [
|
|
4055
|
+
/* @__PURE__ */ jsxs(
|
|
4056
|
+
"button",
|
|
4057
|
+
{
|
|
4058
|
+
type: "button",
|
|
4059
|
+
className: "payman-v2-pdf-sheet-download-btn",
|
|
4060
|
+
"aria-label": "Download PDF",
|
|
4061
|
+
onClick: handleDownload,
|
|
4062
|
+
children: [
|
|
4063
|
+
/* @__PURE__ */ jsx(Download, { size: 13, strokeWidth: 2 }),
|
|
4064
|
+
/* @__PURE__ */ jsx("span", { children: "Download" })
|
|
4065
|
+
]
|
|
4066
|
+
}
|
|
4067
|
+
),
|
|
4068
|
+
/* @__PURE__ */ jsx(
|
|
4069
|
+
"button",
|
|
4070
|
+
{
|
|
4071
|
+
type: "button",
|
|
4072
|
+
className: "payman-v2-pdf-sheet-close-btn",
|
|
4073
|
+
"aria-label": "Close preview",
|
|
4074
|
+
onClick: onClose,
|
|
4075
|
+
children: /* @__PURE__ */ jsx(X, { size: 15, strokeWidth: 2.25 })
|
|
4076
|
+
}
|
|
4077
|
+
)
|
|
4078
|
+
] })
|
|
4079
|
+
] }),
|
|
4080
|
+
/* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-body", children: [
|
|
4081
|
+
!isLoaded && /* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-loading", children: [
|
|
4082
|
+
/* @__PURE__ */ jsx(
|
|
4083
|
+
Loader2,
|
|
4084
|
+
{
|
|
4085
|
+
size: 20,
|
|
4086
|
+
strokeWidth: 2,
|
|
4087
|
+
style: {
|
|
4088
|
+
animation: "payman-v2-spin 0.65s linear infinite",
|
|
4089
|
+
color: "var(--payman-v2-text-3)"
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
),
|
|
4093
|
+
/* @__PURE__ */ jsx("p", { className: "payman-v2-pdf-sheet-loading-text", children: "Loading PDF\u2026" })
|
|
4094
|
+
] }),
|
|
4095
|
+
/* @__PURE__ */ jsx(
|
|
4096
|
+
"iframe",
|
|
4097
|
+
{
|
|
4098
|
+
src,
|
|
4099
|
+
title: title || "PDF Preview",
|
|
4100
|
+
className: "payman-v2-pdf-sheet-iframe",
|
|
4101
|
+
style: { opacity: isLoaded ? 1 : 0, transition: "opacity 0.3s ease" },
|
|
4102
|
+
onLoad: () => setIsLoaded(true)
|
|
4103
|
+
},
|
|
4104
|
+
src
|
|
4105
|
+
)
|
|
4106
|
+
] })
|
|
4107
|
+
]
|
|
4108
|
+
},
|
|
4109
|
+
"pdf-sheet-panel"
|
|
4110
|
+
)
|
|
4111
|
+
] }) : null }),
|
|
4112
|
+
document.body
|
|
4113
|
+
);
|
|
4114
|
+
}
|
|
1966
4115
|
var RESPONSE_SPEED = {
|
|
1967
4116
|
normal: [2, 4],
|
|
1968
4117
|
fast: 1,
|
|
@@ -2090,6 +4239,10 @@ function AssistantMessageV2({
|
|
|
2090
4239
|
() => getFeedbackState(message)
|
|
2091
4240
|
);
|
|
2092
4241
|
const [reasonModalOpen, setReasonModalOpen] = useState(false);
|
|
4242
|
+
const [pdfSheet, setPdfSheet] = useState(null);
|
|
4243
|
+
const handlePdfClick = useCallback((href, title) => {
|
|
4244
|
+
setPdfSheet({ href, title });
|
|
4245
|
+
}, []);
|
|
2093
4246
|
const canSubmitFeedback = !!onSubmitFeedback && !!message.executionId;
|
|
2094
4247
|
const [toast, setToast] = useState(null);
|
|
2095
4248
|
const copyResetTimerRef = useRef(null);
|
|
@@ -2287,7 +4440,8 @@ function AssistantMessageV2({
|
|
|
2287
4440
|
content: displayContent,
|
|
2288
4441
|
isStreaming: message.isStreaming && !isCancelled || isResponseTyping,
|
|
2289
4442
|
isResolvingImages: message.isResolvingImages,
|
|
2290
|
-
onImageClick
|
|
4443
|
+
onImageClick,
|
|
4444
|
+
onPdfClick: handlePdfClick
|
|
2291
4445
|
}
|
|
2292
4446
|
) : !isThinkingStreaming ? /* @__PURE__ */ jsx("span", { className: "payman-v2-assistant-msg-placeholder", children: "..." }) : null }),
|
|
2293
4447
|
isCancelled && message.isStreaming && /* @__PURE__ */ jsxs("div", { className: "payman-v2-assistant-msg-paused", children: [
|
|
@@ -2385,6 +4539,14 @@ function AssistantMessageV2({
|
|
|
2385
4539
|
showToast("Thank you for your feedback", "success");
|
|
2386
4540
|
}
|
|
2387
4541
|
}
|
|
4542
|
+
),
|
|
4543
|
+
/* @__PURE__ */ jsx(
|
|
4544
|
+
PdfSheetV2,
|
|
4545
|
+
{
|
|
4546
|
+
src: pdfSheet?.href ?? null,
|
|
4547
|
+
title: pdfSheet?.title ?? "",
|
|
4548
|
+
onClose: () => setPdfSheet(null)
|
|
4549
|
+
}
|
|
2388
4550
|
)
|
|
2389
4551
|
] });
|
|
2390
4552
|
}
|
|
@@ -2483,56 +4645,61 @@ function OtpInputV2({
|
|
|
2483
4645
|
i
|
|
2484
4646
|
)) });
|
|
2485
4647
|
}
|
|
4648
|
+
var DEFAULT_CODE_LEN = 6;
|
|
2486
4649
|
var RESEND_COOLDOWN_S = 30;
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
4650
|
+
function codeLengthFromSchema(prompt) {
|
|
4651
|
+
const field = prompt.requestedSchema?.properties?.verificationCode;
|
|
4652
|
+
const max = typeof field?.maxLength === "number" ? field.maxLength : void 0;
|
|
4653
|
+
const min = typeof field?.minLength === "number" ? field.minLength : void 0;
|
|
4654
|
+
return max ?? min ?? DEFAULT_CODE_LEN;
|
|
4655
|
+
}
|
|
4656
|
+
function VerificationInline({
|
|
4657
|
+
prompt,
|
|
4658
|
+
secondsLeft,
|
|
4659
|
+
expired,
|
|
4660
|
+
onSubmit,
|
|
4661
|
+
onCancel,
|
|
2495
4662
|
onResend
|
|
2496
4663
|
}) {
|
|
2497
|
-
const
|
|
2498
|
-
const
|
|
4664
|
+
const isNumeric = prompt.verificationType !== "ALPHANUMERIC_CODE";
|
|
4665
|
+
const codeLen = useMemo(() => codeLengthFromSchema(prompt), [prompt]);
|
|
4666
|
+
const [code, setCode] = useState("");
|
|
4667
|
+
const [errored, setErrored] = useState(false);
|
|
2499
4668
|
const [resendSec, setResendSec] = useState(0);
|
|
2500
|
-
const [localPending, setLocalPending] = useState(false);
|
|
2501
4669
|
const lastSubmittedRef = useRef(null);
|
|
2502
4670
|
const resendTimerRef = useRef(void 0);
|
|
4671
|
+
const status = prompt.status;
|
|
4672
|
+
const busy = status === "submitting";
|
|
4673
|
+
const stale = status === "stale";
|
|
4674
|
+
const locked = busy || stale || expired;
|
|
2503
4675
|
useEffect(() => {
|
|
2504
|
-
if (
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
setOtp("");
|
|
2508
|
-
setOtpErrored(false);
|
|
2509
|
-
setLocalPending(false);
|
|
2510
|
-
lastSubmittedRef.current = null;
|
|
2511
|
-
}, 600);
|
|
2512
|
-
return () => window.clearTimeout(t);
|
|
2513
|
-
}, [clearOtpTrigger]);
|
|
2514
|
-
useEffect(() => {
|
|
2515
|
-
if (otp.length < OTP_LEN) {
|
|
4676
|
+
if (prompt.subAction === "SubmissionInvalid") {
|
|
4677
|
+
setErrored(true);
|
|
4678
|
+
setCode("");
|
|
2516
4679
|
lastSubmittedRef.current = null;
|
|
2517
4680
|
}
|
|
2518
|
-
}, [
|
|
4681
|
+
}, [prompt.subAction, prompt.userActionId]);
|
|
2519
4682
|
useEffect(() => {
|
|
2520
|
-
if (
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
4683
|
+
if (code.length < codeLen) lastSubmittedRef.current = null;
|
|
4684
|
+
}, [code, codeLen]);
|
|
4685
|
+
const doSubmit = useCallback(
|
|
4686
|
+
(value) => {
|
|
4687
|
+
if (locked || !value) return;
|
|
4688
|
+
if (lastSubmittedRef.current === value) return;
|
|
4689
|
+
lastSubmittedRef.current = value;
|
|
4690
|
+
void onSubmit(prompt.userActionId, { verificationCode: value }).catch(() => {
|
|
4691
|
+
lastSubmittedRef.current = null;
|
|
4692
|
+
setErrored(true);
|
|
4693
|
+
});
|
|
4694
|
+
},
|
|
4695
|
+
[locked, onSubmit, prompt.userActionId]
|
|
4696
|
+
);
|
|
2524
4697
|
useEffect(() => {
|
|
2525
|
-
if (
|
|
2526
|
-
|
|
4698
|
+
if (!isNumeric || locked) return;
|
|
4699
|
+
if (code.length === codeLen && /^\d+$/.test(code)) {
|
|
4700
|
+
doSubmit(code);
|
|
2527
4701
|
}
|
|
2528
|
-
|
|
2529
|
-
lastSubmittedRef.current = otp;
|
|
2530
|
-
setLocalPending(true);
|
|
2531
|
-
void onApprove(messageId, otp).catch(() => {
|
|
2532
|
-
lastSubmittedRef.current = null;
|
|
2533
|
-
setLocalPending(false);
|
|
2534
|
-
});
|
|
2535
|
-
}, [messageId, onApprove, otp, status]);
|
|
4702
|
+
}, [code, codeLen, doSubmit, isNumeric, locked]);
|
|
2536
4703
|
useEffect(() => {
|
|
2537
4704
|
return () => {
|
|
2538
4705
|
if (typeof resendTimerRef.current === "number") {
|
|
@@ -2559,94 +4726,309 @@ function VerificationCardV2({
|
|
|
2559
4726
|
}, 1e3);
|
|
2560
4727
|
}, []);
|
|
2561
4728
|
const handleResend = useCallback(async () => {
|
|
2562
|
-
if (resendSec > 0
|
|
2563
|
-
|
|
4729
|
+
if (locked || resendSec > 0) return;
|
|
4730
|
+
setErrored(false);
|
|
4731
|
+
setCode("");
|
|
4732
|
+
lastSubmittedRef.current = null;
|
|
2564
4733
|
try {
|
|
2565
|
-
await onResend(
|
|
4734
|
+
await onResend(prompt.userActionId);
|
|
2566
4735
|
startResendCooldown();
|
|
2567
|
-
}
|
|
2568
|
-
setLocalPending(false);
|
|
2569
|
-
}
|
|
2570
|
-
}, [localPending, messageId, onResend, resendSec, startResendCooldown, status]);
|
|
2571
|
-
const handleCancel = useCallback(async () => {
|
|
2572
|
-
if (status === "verifying" || localPending) return;
|
|
2573
|
-
setLocalPending(true);
|
|
2574
|
-
try {
|
|
2575
|
-
await onReject(messageId);
|
|
2576
|
-
} finally {
|
|
2577
|
-
setLocalPending(false);
|
|
4736
|
+
} catch {
|
|
2578
4737
|
}
|
|
2579
|
-
}, [
|
|
2580
|
-
const
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
{
|
|
2587
|
-
className: "payman-v2-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
4738
|
+
}, [locked, onResend, prompt.userActionId, resendSec, startResendCooldown]);
|
|
4739
|
+
const handleCancel = useCallback(() => {
|
|
4740
|
+
if (busy) return;
|
|
4741
|
+
void onCancel(prompt.userActionId);
|
|
4742
|
+
}, [busy, onCancel, prompt.userActionId]);
|
|
4743
|
+
const description = prompt.message?.trim() || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
|
|
4744
|
+
return /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua", role: "group", "aria-label": "Verification required", children: [
|
|
4745
|
+
/* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-head", children: [
|
|
4746
|
+
/* @__PURE__ */ jsx(ShieldCheck, { className: "payman-v2-ua-icon", size: 15, strokeWidth: 1.75, "aria-hidden": true }),
|
|
4747
|
+
/* @__PURE__ */ jsx("span", { className: "payman-v2-ua-title", children: "Verification required" }),
|
|
4748
|
+
typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
|
|
4749
|
+
] }),
|
|
4750
|
+
/* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: description }),
|
|
4751
|
+
stale ? /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4752
|
+
isNumeric ? /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-field", children: /* @__PURE__ */ jsx(
|
|
4753
|
+
OtpInputV2,
|
|
4754
|
+
{
|
|
4755
|
+
value: code,
|
|
4756
|
+
onChange: (v) => {
|
|
4757
|
+
setErrored(false);
|
|
4758
|
+
setCode(v);
|
|
4759
|
+
},
|
|
4760
|
+
maxLength: codeLen,
|
|
4761
|
+
disabled: locked,
|
|
4762
|
+
error: errored
|
|
4763
|
+
}
|
|
4764
|
+
) }) : /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-field", children: /* @__PURE__ */ jsx(
|
|
4765
|
+
"input",
|
|
4766
|
+
{
|
|
4767
|
+
type: "text",
|
|
4768
|
+
className: cn("payman-v2-ua-input", errored && "payman-v2-ua-input-error"),
|
|
4769
|
+
value: code,
|
|
4770
|
+
disabled: locked,
|
|
4771
|
+
autoComplete: "one-time-code",
|
|
4772
|
+
placeholder: "Verification code",
|
|
4773
|
+
onChange: (e) => {
|
|
4774
|
+
setErrored(false);
|
|
4775
|
+
setCode(e.target.value);
|
|
4776
|
+
},
|
|
4777
|
+
onKeyDown: (e) => {
|
|
4778
|
+
if (e.key === "Enter") {
|
|
4779
|
+
e.preventDefault();
|
|
4780
|
+
doSubmit(code.trim());
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
) }),
|
|
4785
|
+
/* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-actions", children: [
|
|
4786
|
+
!isNumeric && /* @__PURE__ */ jsx(
|
|
4787
|
+
"button",
|
|
2594
4788
|
{
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
),
|
|
2599
|
-
children:
|
|
2600
|
-
/* @__PURE__ */ jsxs("div", { className: "payman-v2-verification-header", children: [
|
|
2601
|
-
/* @__PURE__ */ jsxs("div", { className: "payman-v2-verification-header-row", children: [
|
|
2602
|
-
/* @__PURE__ */ jsx(
|
|
2603
|
-
ShieldCheck,
|
|
2604
|
-
{
|
|
2605
|
-
className: "payman-v2-verification-icon",
|
|
2606
|
-
size: 18,
|
|
2607
|
-
strokeWidth: 1.75,
|
|
2608
|
-
"aria-hidden": true
|
|
2609
|
-
}
|
|
2610
|
-
),
|
|
2611
|
-
/* @__PURE__ */ jsx("p", { className: "payman-v2-verification-title", children: "Verify" }),
|
|
2612
|
-
action.amount != null ? /* @__PURE__ */ jsx("span", { className: "payman-v2-verification-amount", children: action.amount }) : action.payeeName ? /* @__PURE__ */ jsx("span", { className: "payman-v2-verification-payee", children: action.payeeName }) : null
|
|
2613
|
-
] }),
|
|
2614
|
-
/* @__PURE__ */ jsx("p", { className: "payman-v2-verification-description", children: "Enter the 6-digit code sent to your phone" }),
|
|
2615
|
-
/* @__PURE__ */ jsx(
|
|
2616
|
-
OtpInputV2,
|
|
2617
|
-
{
|
|
2618
|
-
value: otp,
|
|
2619
|
-
onChange: setOtp,
|
|
2620
|
-
maxLength: OTP_LEN,
|
|
2621
|
-
disabled: busy,
|
|
2622
|
-
error: otpErrored
|
|
2623
|
-
}
|
|
2624
|
-
)
|
|
2625
|
-
] }),
|
|
2626
|
-
/* @__PURE__ */ jsx("div", { className: "payman-v2-verification-actions", children: /* @__PURE__ */ jsx(
|
|
2627
|
-
"button",
|
|
2628
|
-
{
|
|
2629
|
-
type: "button",
|
|
2630
|
-
className: "payman-v2-verification-cancel-btn",
|
|
2631
|
-
disabled: busy,
|
|
2632
|
-
onClick: () => void handleCancel(),
|
|
2633
|
-
children: "Cancel"
|
|
2634
|
-
}
|
|
2635
|
-
) })
|
|
2636
|
-
]
|
|
4789
|
+
type: "button",
|
|
4790
|
+
className: "payman-v2-ua-btn payman-v2-ua-btn-primary",
|
|
4791
|
+
disabled: locked || !code.trim(),
|
|
4792
|
+
onClick: () => doSubmit(code.trim()),
|
|
4793
|
+
children: busy ? "Submitting\u2026" : "Submit"
|
|
2637
4794
|
}
|
|
2638
4795
|
),
|
|
2639
4796
|
/* @__PURE__ */ jsx(
|
|
2640
4797
|
"button",
|
|
2641
4798
|
{
|
|
2642
4799
|
type: "button",
|
|
2643
|
-
className: "payman-v2-
|
|
2644
|
-
disabled:
|
|
4800
|
+
className: "payman-v2-ua-link",
|
|
4801
|
+
disabled: locked || resendSec > 0,
|
|
2645
4802
|
onClick: () => void handleResend(),
|
|
2646
4803
|
children: resendSec > 0 ? `Resend (${resendSec}s)` : "Resend"
|
|
2647
4804
|
}
|
|
4805
|
+
),
|
|
4806
|
+
/* @__PURE__ */ jsx(
|
|
4807
|
+
"button",
|
|
4808
|
+
{
|
|
4809
|
+
type: "button",
|
|
4810
|
+
className: "payman-v2-ua-link payman-v2-ua-link-danger",
|
|
4811
|
+
disabled: busy,
|
|
4812
|
+
onClick: handleCancel,
|
|
4813
|
+
children: "Cancel"
|
|
4814
|
+
}
|
|
2648
4815
|
)
|
|
2649
|
-
]
|
|
4816
|
+
] })
|
|
4817
|
+
] })
|
|
4818
|
+
] });
|
|
4819
|
+
}
|
|
4820
|
+
function SchemaFormInline({
|
|
4821
|
+
prompt,
|
|
4822
|
+
secondsLeft,
|
|
4823
|
+
expired,
|
|
4824
|
+
onSubmit,
|
|
4825
|
+
onCancel
|
|
4826
|
+
}) {
|
|
4827
|
+
const schema = prompt.requestedSchema;
|
|
4828
|
+
const fields = useMemo(() => renderableFields(schema), [schema]);
|
|
4829
|
+
const [values, setValues] = useState(() => {
|
|
4830
|
+
const init2 = {};
|
|
4831
|
+
for (const [key, field] of fields) init2[key] = defaultValueFor(field);
|
|
4832
|
+
return init2;
|
|
4833
|
+
});
|
|
4834
|
+
const [errors, setErrors] = useState({});
|
|
4835
|
+
const status = prompt.status;
|
|
4836
|
+
const busy = status === "submitting";
|
|
4837
|
+
const stale = status === "stale";
|
|
4838
|
+
const locked = busy || stale || expired;
|
|
4839
|
+
const setValue = (key, value) => {
|
|
4840
|
+
setValues((prev) => ({ ...prev, [key]: value }));
|
|
4841
|
+
setErrors((prev) => {
|
|
4842
|
+
if (!prev[key]) return prev;
|
|
4843
|
+
const next = { ...prev };
|
|
4844
|
+
delete next[key];
|
|
4845
|
+
return next;
|
|
4846
|
+
});
|
|
4847
|
+
};
|
|
4848
|
+
const handleSubmit = () => {
|
|
4849
|
+
if (locked) return;
|
|
4850
|
+
const validation = validateForm(schema, values);
|
|
4851
|
+
if (Object.keys(validation).length > 0) {
|
|
4852
|
+
setErrors(validation);
|
|
4853
|
+
return;
|
|
4854
|
+
}
|
|
4855
|
+
void onSubmit(prompt.userActionId, buildContent(schema, values)).catch(() => {
|
|
4856
|
+
});
|
|
4857
|
+
};
|
|
4858
|
+
return /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua", role: "group", "aria-label": "Action required", children: [
|
|
4859
|
+
/* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-head", children: [
|
|
4860
|
+
/* @__PURE__ */ jsx(Pencil, { className: "payman-v2-ua-icon", size: 14, strokeWidth: 1.75, "aria-hidden": true }),
|
|
4861
|
+
/* @__PURE__ */ jsx("span", { className: "payman-v2-ua-title", children: "Action required" }),
|
|
4862
|
+
typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
|
|
4863
|
+
] }),
|
|
4864
|
+
prompt.message?.trim() && /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: prompt.message }),
|
|
4865
|
+
stale ? /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : fields.length === 0 ? /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: "This action has no inputs to fill." }) : /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-form", children: fields.map(([key, field]) => {
|
|
4866
|
+
const widget = classifyField(field);
|
|
4867
|
+
const label = field.title || key;
|
|
4868
|
+
const required = isRequired(schema, key);
|
|
4869
|
+
const err = errors[key];
|
|
4870
|
+
const fieldId = `ua-${prompt.userActionId}-${key}`;
|
|
4871
|
+
if (widget === "boolean") {
|
|
4872
|
+
return /* @__PURE__ */ jsxs("label", { className: "payman-v2-ua-check", children: [
|
|
4873
|
+
/* @__PURE__ */ jsx(
|
|
4874
|
+
"input",
|
|
4875
|
+
{
|
|
4876
|
+
type: "checkbox",
|
|
4877
|
+
checked: Boolean(values[key]),
|
|
4878
|
+
disabled: locked,
|
|
4879
|
+
onChange: (e) => setValue(key, e.target.checked)
|
|
4880
|
+
}
|
|
4881
|
+
),
|
|
4882
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
4883
|
+
label,
|
|
4884
|
+
required && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-req", children: "*" })
|
|
4885
|
+
] })
|
|
4886
|
+
] }, key);
|
|
4887
|
+
}
|
|
4888
|
+
return /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-row", children: [
|
|
4889
|
+
/* @__PURE__ */ jsxs("label", { htmlFor: fieldId, className: "payman-v2-ua-label", children: [
|
|
4890
|
+
label,
|
|
4891
|
+
required && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-req", children: "*" })
|
|
4892
|
+
] }),
|
|
4893
|
+
field.description && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-hint", children: field.description }),
|
|
4894
|
+
widget === "select" ? /* @__PURE__ */ jsxs(
|
|
4895
|
+
"select",
|
|
4896
|
+
{
|
|
4897
|
+
id: fieldId,
|
|
4898
|
+
className: cn("payman-v2-ua-input", err && "payman-v2-ua-input-error"),
|
|
4899
|
+
value: String(values[key] ?? ""),
|
|
4900
|
+
disabled: locked,
|
|
4901
|
+
onChange: (e) => setValue(key, e.target.value),
|
|
4902
|
+
children: [
|
|
4903
|
+
/* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select\u2026" }),
|
|
4904
|
+
getOptions(field).map((opt) => /* @__PURE__ */ jsx("option", { value: opt.const, children: opt.title || opt.const }, opt.const))
|
|
4905
|
+
]
|
|
4906
|
+
}
|
|
4907
|
+
) : /* @__PURE__ */ jsx(
|
|
4908
|
+
"input",
|
|
4909
|
+
{
|
|
4910
|
+
id: fieldId,
|
|
4911
|
+
type: widget === "integer" || widget === "decimal" ? "number" : "text",
|
|
4912
|
+
inputMode: widget === "integer" ? "numeric" : widget === "decimal" ? "decimal" : void 0,
|
|
4913
|
+
step: widget === "decimal" ? "any" : widget === "integer" ? "1" : void 0,
|
|
4914
|
+
className: cn("payman-v2-ua-input", err && "payman-v2-ua-input-error"),
|
|
4915
|
+
value: String(values[key] ?? ""),
|
|
4916
|
+
disabled: locked,
|
|
4917
|
+
placeholder: field.description ? void 0 : label,
|
|
4918
|
+
onChange: (e) => setValue(key, e.target.value),
|
|
4919
|
+
onKeyDown: (e) => {
|
|
4920
|
+
if (e.key === "Enter") {
|
|
4921
|
+
e.preventDefault();
|
|
4922
|
+
handleSubmit();
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
),
|
|
4927
|
+
err && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-error", children: err })
|
|
4928
|
+
] }, key);
|
|
4929
|
+
}) }),
|
|
4930
|
+
!stale && /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-actions", children: [
|
|
4931
|
+
/* @__PURE__ */ jsx(
|
|
4932
|
+
"button",
|
|
4933
|
+
{
|
|
4934
|
+
type: "button",
|
|
4935
|
+
className: "payman-v2-ua-btn payman-v2-ua-btn-primary",
|
|
4936
|
+
disabled: locked,
|
|
4937
|
+
onClick: handleSubmit,
|
|
4938
|
+
children: busy ? "Submitting\u2026" : "Submit"
|
|
4939
|
+
}
|
|
4940
|
+
),
|
|
4941
|
+
/* @__PURE__ */ jsx(
|
|
4942
|
+
"button",
|
|
4943
|
+
{
|
|
4944
|
+
type: "button",
|
|
4945
|
+
className: "payman-v2-ua-link payman-v2-ua-link-danger",
|
|
4946
|
+
disabled: busy,
|
|
4947
|
+
onClick: () => void onCancel(prompt.userActionId),
|
|
4948
|
+
children: "Cancel"
|
|
4949
|
+
}
|
|
4950
|
+
)
|
|
4951
|
+
] })
|
|
4952
|
+
] });
|
|
4953
|
+
}
|
|
4954
|
+
function NotificationInline({ notification, onDismiss }) {
|
|
4955
|
+
return /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-note", role: "status", children: [
|
|
4956
|
+
/* @__PURE__ */ jsx(Info, { className: "payman-v2-ua-note-icon", size: 14, strokeWidth: 1.75, "aria-hidden": true }),
|
|
4957
|
+
/* @__PURE__ */ jsx("span", { className: "payman-v2-ua-note-text", children: notification.message }),
|
|
4958
|
+
onDismiss && /* @__PURE__ */ jsx(
|
|
4959
|
+
"button",
|
|
4960
|
+
{
|
|
4961
|
+
type: "button",
|
|
4962
|
+
className: "payman-v2-ua-note-dismiss",
|
|
4963
|
+
"aria-label": "Dismiss notification",
|
|
4964
|
+
onClick: () => onDismiss(notification.id),
|
|
4965
|
+
children: /* @__PURE__ */ jsx(X, { size: 13, strokeWidth: 2 })
|
|
4966
|
+
}
|
|
4967
|
+
)
|
|
4968
|
+
] });
|
|
4969
|
+
}
|
|
4970
|
+
function useExpiryCountdown(prompt) {
|
|
4971
|
+
const initial = typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Math.floor(prompt.expirySeconds) : void 0;
|
|
4972
|
+
const [secondsLeft, setSecondsLeft] = useState(initial);
|
|
4973
|
+
useEffect(() => {
|
|
4974
|
+
if (initial === void 0) {
|
|
4975
|
+
setSecondsLeft(void 0);
|
|
4976
|
+
return;
|
|
4977
|
+
}
|
|
4978
|
+
setSecondsLeft(initial);
|
|
4979
|
+
const id = window.setInterval(() => {
|
|
4980
|
+
setSecondsLeft((s) => {
|
|
4981
|
+
if (s === void 0) return s;
|
|
4982
|
+
if (s <= 1) {
|
|
4983
|
+
window.clearInterval(id);
|
|
4984
|
+
return 0;
|
|
4985
|
+
}
|
|
4986
|
+
return s - 1;
|
|
4987
|
+
});
|
|
4988
|
+
}, 1e3);
|
|
4989
|
+
return () => window.clearInterval(id);
|
|
4990
|
+
}, [prompt.userActionId, prompt.subAction, initial]);
|
|
4991
|
+
const expired = initial !== void 0 && secondsLeft === 0;
|
|
4992
|
+
return [secondsLeft, expired];
|
|
4993
|
+
}
|
|
4994
|
+
function UserActionInline({ prompt, onSubmit, onCancel, onResend }) {
|
|
4995
|
+
const [secondsLeft, expired] = useExpiryCountdown(prompt);
|
|
4996
|
+
let body;
|
|
4997
|
+
if (prompt.kind === "verification") {
|
|
4998
|
+
body = /* @__PURE__ */ jsx(
|
|
4999
|
+
VerificationInline,
|
|
5000
|
+
{
|
|
5001
|
+
prompt,
|
|
5002
|
+
secondsLeft,
|
|
5003
|
+
expired,
|
|
5004
|
+
onSubmit,
|
|
5005
|
+
onCancel,
|
|
5006
|
+
onResend
|
|
5007
|
+
}
|
|
5008
|
+
);
|
|
5009
|
+
} else if (prompt.kind === "notification") {
|
|
5010
|
+
const note = { id: prompt.userActionId, message: prompt.message ?? "" };
|
|
5011
|
+
body = /* @__PURE__ */ jsx(NotificationInline, { notification: note });
|
|
5012
|
+
} else {
|
|
5013
|
+
body = /* @__PURE__ */ jsx(
|
|
5014
|
+
SchemaFormInline,
|
|
5015
|
+
{
|
|
5016
|
+
prompt,
|
|
5017
|
+
secondsLeft,
|
|
5018
|
+
expired,
|
|
5019
|
+
onSubmit,
|
|
5020
|
+
onCancel
|
|
5021
|
+
}
|
|
5022
|
+
);
|
|
5023
|
+
}
|
|
5024
|
+
return /* @__PURE__ */ jsx(
|
|
5025
|
+
motion.div,
|
|
5026
|
+
{
|
|
5027
|
+
className: "payman-v2-ua-wrap",
|
|
5028
|
+
initial: { opacity: 0, y: 8 },
|
|
5029
|
+
animate: { opacity: 1, y: 0 },
|
|
5030
|
+
transition: { type: "spring", stiffness: 320, damping: 28 },
|
|
5031
|
+
children: body
|
|
2650
5032
|
}
|
|
2651
5033
|
);
|
|
2652
5034
|
}
|
|
@@ -2662,13 +5044,17 @@ var MessageListV2 = forwardRef(
|
|
|
2662
5044
|
onExecutionTraceClick,
|
|
2663
5045
|
messageActions,
|
|
2664
5046
|
retryDisabled = false,
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
5047
|
+
userActionPrompts,
|
|
5048
|
+
notifications,
|
|
5049
|
+
onSubmitUserAction,
|
|
5050
|
+
onCancelUserAction,
|
|
5051
|
+
onResendUserAction,
|
|
5052
|
+
onDismissNotification,
|
|
2669
5053
|
onSubmitFeedback,
|
|
2670
5054
|
typingSpeed = 4
|
|
2671
5055
|
}, ref) {
|
|
5056
|
+
const noop = useCallback(async () => {
|
|
5057
|
+
}, []);
|
|
2672
5058
|
const scrollRef = useRef(null);
|
|
2673
5059
|
const scrollInnerRef = useRef(null);
|
|
2674
5060
|
const isNearBottomRef = useRef(true);
|
|
@@ -2811,20 +5197,24 @@ var MessageListV2 = forwardRef(
|
|
|
2811
5197
|
typingSpeed
|
|
2812
5198
|
}
|
|
2813
5199
|
) }, message.id)),
|
|
2814
|
-
|
|
2815
|
-
|
|
5200
|
+
notifications?.map((note) => /* @__PURE__ */ jsx(
|
|
5201
|
+
NotificationInline,
|
|
2816
5202
|
{
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
5203
|
+
notification: note,
|
|
5204
|
+
onDismiss: onDismissNotification
|
|
5205
|
+
},
|
|
5206
|
+
note.id
|
|
5207
|
+
)),
|
|
5208
|
+
userActionPrompts?.map((prompt) => /* @__PURE__ */ jsx(
|
|
5209
|
+
UserActionInline,
|
|
5210
|
+
{
|
|
5211
|
+
prompt,
|
|
5212
|
+
onSubmit: onSubmitUserAction ?? noop,
|
|
5213
|
+
onCancel: onCancelUserAction ?? noop,
|
|
5214
|
+
onResend: onResendUserAction ?? noop
|
|
5215
|
+
},
|
|
5216
|
+
prompt.toolCallId || prompt.userActionId
|
|
5217
|
+
))
|
|
2828
5218
|
]
|
|
2829
5219
|
}
|
|
2830
5220
|
)
|
|
@@ -3885,10 +6275,13 @@ function TimelineBars({
|
|
|
3885
6275
|
] });
|
|
3886
6276
|
}
|
|
3887
6277
|
var DEFAULT_USER_ACTION_STATE = {
|
|
3888
|
-
|
|
3889
|
-
|
|
6278
|
+
prompts: [],
|
|
6279
|
+
notifications: []
|
|
6280
|
+
};
|
|
3890
6281
|
var NOOP_ASYNC = async () => {
|
|
3891
6282
|
};
|
|
6283
|
+
var NOOP = () => {
|
|
6284
|
+
};
|
|
3892
6285
|
function useSentryChatCallbacks(callbacks, config) {
|
|
3893
6286
|
const sentryCtxRef = useRef({});
|
|
3894
6287
|
const callbacksRef = useRef(callbacks);
|
|
@@ -3958,7 +6351,7 @@ function useSentryChatCallbacks(callbacks, config) {
|
|
|
3958
6351
|
onAttachFileClick: () => callbacksRef.current?.onAttachFileClick?.(),
|
|
3959
6352
|
onMessageFeedback: (data) => callbacksRef.current?.onMessageFeedback?.(data),
|
|
3960
6353
|
onUserActionRequired: (req) => callbacksRef.current?.onUserActionRequired?.(req),
|
|
3961
|
-
|
|
6354
|
+
onUserNotification: (note) => callbacksRef.current?.onUserNotification?.(note),
|
|
3962
6355
|
onStatusMessage: (msg) => callbacksRef.current?.onStatusMessage?.(msg),
|
|
3963
6356
|
onStepsUpdate: (steps) => callbacksRef.current?.onStepsUpdate?.(steps)
|
|
3964
6357
|
}),
|
|
@@ -4026,10 +6419,11 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
|
|
|
4026
6419
|
}
|
|
4027
6420
|
}, [editingMessageId, messages]);
|
|
4028
6421
|
const userActionState = chat.userActionState ?? DEFAULT_USER_ACTION_STATE;
|
|
4029
|
-
const
|
|
4030
|
-
const
|
|
4031
|
-
const
|
|
4032
|
-
const
|
|
6422
|
+
const submitUserAction2 = chat.submitUserAction ?? NOOP_ASYNC;
|
|
6423
|
+
const cancelUserAction2 = chat.cancelUserAction ?? NOOP_ASYNC;
|
|
6424
|
+
const resendUserAction2 = chat.resendUserAction ?? NOOP_ASYNC;
|
|
6425
|
+
const dismissNotification = chat.dismissNotification ?? NOOP;
|
|
6426
|
+
const isUserActionSupported = typeof chat.submitUserAction === "function" && typeof chat.cancelUserAction === "function" && typeof chat.resendUserAction === "function";
|
|
4033
6427
|
const {
|
|
4034
6428
|
transcribedText,
|
|
4035
6429
|
isAvailable: voiceAvailable,
|
|
@@ -4270,23 +6664,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
|
|
|
4270
6664
|
setLightboxSrc(src);
|
|
4271
6665
|
setLightboxAlt(alt);
|
|
4272
6666
|
};
|
|
4273
|
-
const
|
|
4274
|
-
|
|
4275
|
-
const req = userActionState.request;
|
|
4276
|
-
let status = "pending";
|
|
4277
|
-
if (userActionState.result === "approved") status = "approved";
|
|
4278
|
-
else if (userActionState.result === "rejected") status = "rejected";
|
|
4279
|
-
return {
|
|
4280
|
-
messageId: `ua-${req.userActionId || Date.now()}`,
|
|
4281
|
-
action: {
|
|
4282
|
-
type: req.userActionType || "generic",
|
|
4283
|
-
message: req.message || "Verify this action",
|
|
4284
|
-
amount: req.metadata?.amount,
|
|
4285
|
-
payeeName: req.metadata?.payeeName
|
|
4286
|
-
},
|
|
4287
|
-
status
|
|
4288
|
-
};
|
|
4289
|
-
}, [isUserActionSupported, userActionState.request, userActionState.result]);
|
|
6667
|
+
const userActionPrompts = isUserActionSupported ? userActionState.prompts : void 0;
|
|
6668
|
+
const notifications = userActionState.notifications;
|
|
4290
6669
|
const handleV2Send = (text) => {
|
|
4291
6670
|
if (isRecording) stopRecording();
|
|
4292
6671
|
if (text.trim() && !disableInput && isSessionParamsConfigured) {
|
|
@@ -4339,7 +6718,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
|
|
|
4339
6718
|
style,
|
|
4340
6719
|
children: [
|
|
4341
6720
|
children,
|
|
4342
|
-
|
|
6721
|
+
/* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: isEmpty && !hasEverSentMessage ? /* @__PURE__ */ jsx(
|
|
4343
6722
|
motion.div,
|
|
4344
6723
|
{
|
|
4345
6724
|
initial: { opacity: 1 },
|
|
@@ -4438,16 +6817,12 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
|
|
|
4438
6817
|
messageActions,
|
|
4439
6818
|
retryDisabled: isWaitingForResponse,
|
|
4440
6819
|
typingSpeed: config.typingSpeed ?? 4,
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
} : void 0,
|
|
4448
|
-
onResendAction: isUserActionSupported ? async () => {
|
|
4449
|
-
await resendOtp();
|
|
4450
|
-
} : void 0,
|
|
6820
|
+
userActionPrompts,
|
|
6821
|
+
notifications,
|
|
6822
|
+
onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
|
|
6823
|
+
onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
|
|
6824
|
+
onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
|
|
6825
|
+
onDismissNotification: dismissNotification,
|
|
4451
6826
|
onSubmitFeedback: handleSubmitFeedback
|
|
4452
6827
|
}
|
|
4453
6828
|
),
|
|
@@ -4530,6 +6905,6 @@ var PaymanChat = forwardRef(
|
|
|
4530
6905
|
}
|
|
4531
6906
|
);
|
|
4532
6907
|
|
|
4533
|
-
export { PaymanChat, PaymanChatContext, captureSentryError, cn, formatDate, usePaymanChat };
|
|
6908
|
+
export { PaymanChat, PaymanChatContext, UserActionStaleError, cancelUserAction, captureSentryError, cn, formatDate, resendUserAction, submitUserAction, useChatV2, usePaymanChat, useVoice };
|
|
4534
6909
|
//# sourceMappingURL=index.mjs.map
|
|
4535
6910
|
//# sourceMappingURL=index.mjs.map
|