@paymanai/payman-ask-sdk 4.0.19 → 4.0.20
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 +93 -26
- package/dist/index.d.ts +93 -26
- package/dist/index.js +1745 -391
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1737 -393
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +484 -25
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
- package/dist/index.native.js +0 -3681
- package/dist/index.native.js.map +0 -1
package/dist/index.native.js
DELETED
|
@@ -1,3681 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var lucideReactNative = require('lucide-react-native');
|
|
4
|
-
var react = require('react');
|
|
5
|
-
var reactNative = require('react-native');
|
|
6
|
-
var Markdown = require('react-native-markdown-display');
|
|
7
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
-
var clsx = require('clsx');
|
|
9
|
-
var tailwindMerge = require('tailwind-merge');
|
|
10
|
-
var Sentry = require('@sentry/react');
|
|
11
|
-
|
|
12
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
-
|
|
14
|
-
function _interopNamespace(e) {
|
|
15
|
-
if (e && e.__esModule) return e;
|
|
16
|
-
var n = Object.create(null);
|
|
17
|
-
if (e) {
|
|
18
|
-
Object.keys(e).forEach(function (k) {
|
|
19
|
-
if (k !== 'default') {
|
|
20
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
21
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
22
|
-
enumerable: true,
|
|
23
|
-
get: function () { return e[k]; }
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
n.default = e;
|
|
29
|
-
return Object.freeze(n);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
var Markdown__default = /*#__PURE__*/_interopDefault(Markdown);
|
|
33
|
-
var Sentry__namespace = /*#__PURE__*/_interopNamespace(Sentry);
|
|
34
|
-
|
|
35
|
-
// src/components/PaymanChat/index.native.tsx
|
|
36
|
-
var __defProp = Object.defineProperty;
|
|
37
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
38
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "", value);
|
|
39
|
-
function generateId() {
|
|
40
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
41
|
-
const r = Math.random() * 16 | 0;
|
|
42
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
43
|
-
return v.toString(16);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
var memoryStore = /* @__PURE__ */ new Map();
|
|
47
|
-
var chatStore = {
|
|
48
|
-
get(key) {
|
|
49
|
-
return memoryStore.get(key) ?? [];
|
|
50
|
-
},
|
|
51
|
-
set(key, messages) {
|
|
52
|
-
memoryStore.set(key, messages);
|
|
53
|
-
},
|
|
54
|
-
delete(key) {
|
|
55
|
-
memoryStore.delete(key);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
var streams = /* @__PURE__ */ new Map();
|
|
59
|
-
var activeStreamStore = {
|
|
60
|
-
has(key) {
|
|
61
|
-
return streams.has(key);
|
|
62
|
-
},
|
|
63
|
-
get(key) {
|
|
64
|
-
const entry = streams.get(key);
|
|
65
|
-
if (!entry) return null;
|
|
66
|
-
return { messages: entry.messages, isWaiting: entry.isWaiting };
|
|
67
|
-
},
|
|
68
|
-
// Called before startStream — registers the controller and initial messages
|
|
69
|
-
start(key, abortController, initialMessages) {
|
|
70
|
-
const existing = streams.get(key);
|
|
71
|
-
streams.set(key, {
|
|
72
|
-
messages: initialMessages,
|
|
73
|
-
isWaiting: true,
|
|
74
|
-
abortController,
|
|
75
|
-
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
76
|
-
});
|
|
77
|
-
},
|
|
78
|
-
// Called by the stream on every event — applies the same updater pattern React uses
|
|
79
|
-
applyMessages(key, updater) {
|
|
80
|
-
const entry = streams.get(key);
|
|
81
|
-
if (!entry) return;
|
|
82
|
-
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
83
|
-
entry.messages = next;
|
|
84
|
-
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
85
|
-
},
|
|
86
|
-
setWaiting(key, waiting) {
|
|
87
|
-
const entry = streams.get(key);
|
|
88
|
-
if (!entry) return;
|
|
89
|
-
entry.isWaiting = waiting;
|
|
90
|
-
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
91
|
-
},
|
|
92
|
-
// Called when stream completes — persists to chatStore and cleans up
|
|
93
|
-
complete(key) {
|
|
94
|
-
const entry = streams.get(key);
|
|
95
|
-
if (!entry) return;
|
|
96
|
-
entry.isWaiting = false;
|
|
97
|
-
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
98
|
-
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
99
|
-
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
100
|
-
streams.delete(key);
|
|
101
|
-
},
|
|
102
|
-
// Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
|
|
103
|
-
subscribe(key, listener) {
|
|
104
|
-
const entry = streams.get(key);
|
|
105
|
-
if (!entry) return () => {
|
|
106
|
-
};
|
|
107
|
-
entry.listeners.add(listener);
|
|
108
|
-
return () => {
|
|
109
|
-
streams.get(key)?.listeners.delete(listener);
|
|
110
|
-
};
|
|
111
|
-
},
|
|
112
|
-
// Rename an entry — used when the server assigns a new session ID mid-stream
|
|
113
|
-
rename(oldKey, newKey) {
|
|
114
|
-
const entry = streams.get(oldKey);
|
|
115
|
-
if (!entry) return;
|
|
116
|
-
streams.set(newKey, entry);
|
|
117
|
-
streams.delete(oldKey);
|
|
118
|
-
},
|
|
119
|
-
// Explicit user cancel — aborts the controller and removes the entry
|
|
120
|
-
abort(key) {
|
|
121
|
-
const entry = streams.get(key);
|
|
122
|
-
if (!entry) return;
|
|
123
|
-
entry.abortController.abort();
|
|
124
|
-
streams.delete(key);
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
function yieldAfterProgressEvent(event) {
|
|
128
|
-
const t = event.eventType;
|
|
129
|
-
if (t === "RUN_IN_PROGRESS" || t === "INTENT_PROGRESS" || t === "THINKING_DELTA") {
|
|
130
|
-
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
131
|
-
}
|
|
132
|
-
return Promise.resolve();
|
|
133
|
-
}
|
|
134
|
-
function parseJSONBuffer(buffer) {
|
|
135
|
-
const events = [];
|
|
136
|
-
let braceCount = 0;
|
|
137
|
-
let startIndex = 0;
|
|
138
|
-
let inString = false;
|
|
139
|
-
let escapeNext = false;
|
|
140
|
-
let lastParsedIndex = -1;
|
|
141
|
-
for (let i = 0; i < buffer.length; i++) {
|
|
142
|
-
const char = buffer[i];
|
|
143
|
-
if (escapeNext) {
|
|
144
|
-
escapeNext = false;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (char === "\\") {
|
|
148
|
-
escapeNext = true;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (char === '"' && !escapeNext) {
|
|
152
|
-
inString = !inString;
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (inString) {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
if (char === "{") {
|
|
159
|
-
if (braceCount === 0) {
|
|
160
|
-
startIndex = i;
|
|
161
|
-
}
|
|
162
|
-
braceCount++;
|
|
163
|
-
} else if (char === "}") {
|
|
164
|
-
braceCount--;
|
|
165
|
-
if (braceCount === 0) {
|
|
166
|
-
const jsonStr = buffer.substring(startIndex, i + 1);
|
|
167
|
-
try {
|
|
168
|
-
const parsed = JSON.parse(jsonStr);
|
|
169
|
-
const event = parsed;
|
|
170
|
-
events.push(event);
|
|
171
|
-
lastParsedIndex = i;
|
|
172
|
-
} catch (err) {
|
|
173
|
-
console.error("Failed to parse JSON event:", jsonStr, err);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const remaining = lastParsedIndex >= 0 ? buffer.substring(lastParsedIndex + 1) : buffer;
|
|
179
|
-
return { events, remaining };
|
|
180
|
-
}
|
|
181
|
-
async function streamWorkflowEvents(url, body, headers, options = {}) {
|
|
182
|
-
const { signal, onEvent, onError, onComplete } = options;
|
|
183
|
-
try {
|
|
184
|
-
const response = await fetch(url, {
|
|
185
|
-
method: "POST",
|
|
186
|
-
headers: {
|
|
187
|
-
"Content-Type": "application/json",
|
|
188
|
-
...headers
|
|
189
|
-
},
|
|
190
|
-
body: JSON.stringify(body),
|
|
191
|
-
signal
|
|
192
|
-
});
|
|
193
|
-
if (!response.ok) {
|
|
194
|
-
const errorText = await response.text();
|
|
195
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
196
|
-
}
|
|
197
|
-
const reader = response.body?.getReader();
|
|
198
|
-
if (!reader) {
|
|
199
|
-
throw new Error("No response body");
|
|
200
|
-
}
|
|
201
|
-
const decoder = new TextDecoder();
|
|
202
|
-
let buffer = "";
|
|
203
|
-
while (true) {
|
|
204
|
-
const { done, value } = await reader.read();
|
|
205
|
-
if (done) {
|
|
206
|
-
if (buffer.trim()) {
|
|
207
|
-
const { events: events2 } = parseJSONBuffer(buffer);
|
|
208
|
-
for (const event of events2) {
|
|
209
|
-
onEvent?.(event);
|
|
210
|
-
await yieldAfterProgressEvent(event);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
buffer += decoder.decode(value, { stream: true });
|
|
216
|
-
const { events, remaining } = parseJSONBuffer(buffer);
|
|
217
|
-
for (const event of events) {
|
|
218
|
-
onEvent?.(event);
|
|
219
|
-
await yieldAfterProgressEvent(event);
|
|
220
|
-
}
|
|
221
|
-
buffer = remaining;
|
|
222
|
-
}
|
|
223
|
-
onComplete?.();
|
|
224
|
-
} catch (error) {
|
|
225
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
onError?.(error);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
var STAGE_LABELS = {
|
|
232
|
-
sanitizer: "Checking your request",
|
|
233
|
-
analyzer: "Understanding what you're asking",
|
|
234
|
-
prefetcher: "Gathering context",
|
|
235
|
-
planner: "Planning how to handle this",
|
|
236
|
-
execution: "Working on it",
|
|
237
|
-
formatter: "Writing the response"
|
|
238
|
-
};
|
|
239
|
-
function stageLabel(stage) {
|
|
240
|
-
const label = STAGE_LABELS[stage];
|
|
241
|
-
if (label) return label;
|
|
242
|
-
const pretty = stage.replace(/[_-]/g, " ");
|
|
243
|
-
return `Running ${pretty}`;
|
|
244
|
-
}
|
|
245
|
-
function isBlandStatus(message) {
|
|
246
|
-
if (!message) return true;
|
|
247
|
-
const normalized = message.trim().toLowerCase().replace(/[…\.]+$/, "").trim();
|
|
248
|
-
return BLAND_STATUS_LABELS.has(normalized);
|
|
249
|
-
}
|
|
250
|
-
var BLAND_STATUS_LABELS = /* @__PURE__ */ new Set([
|
|
251
|
-
"executing",
|
|
252
|
-
"working on it",
|
|
253
|
-
"thinking",
|
|
254
|
-
"processing",
|
|
255
|
-
"reviewing your request",
|
|
256
|
-
"composing response",
|
|
257
|
-
"checking your request",
|
|
258
|
-
"polishing the response"
|
|
259
|
-
]);
|
|
260
|
-
function getEventMessage(event) {
|
|
261
|
-
if (event.message?.trim()) {
|
|
262
|
-
return event.message.trim();
|
|
263
|
-
}
|
|
264
|
-
if (event.errorMessage?.trim()) {
|
|
265
|
-
return event.errorMessage.trim();
|
|
266
|
-
}
|
|
267
|
-
const eventType = event.eventType;
|
|
268
|
-
switch (eventType) {
|
|
269
|
-
case "RUN_STARTED":
|
|
270
|
-
return "Starting agent run...";
|
|
271
|
-
case "TOOL_CALL_STARTED": {
|
|
272
|
-
const description = typeof event.description === "string" ? event.description.trim() : "";
|
|
273
|
-
if (description) return description;
|
|
274
|
-
const toolName = typeof event.toolName === "string" ? event.toolName : "";
|
|
275
|
-
return toolName ? `Calling ${toolName}...` : "Calling tool...";
|
|
276
|
-
}
|
|
277
|
-
case "TOOL_CALL_COMPLETED": {
|
|
278
|
-
const description = typeof event.description === "string" ? event.description.trim() : "";
|
|
279
|
-
if (description) return description;
|
|
280
|
-
const toolName = typeof event.toolName === "string" ? event.toolName : "";
|
|
281
|
-
return toolName ? `${toolName} completed` : "Tool call completed";
|
|
282
|
-
}
|
|
283
|
-
case "RUN_IN_PROGRESS":
|
|
284
|
-
return event.partialText || "Thinking...";
|
|
285
|
-
case "RUN_COMPLETED":
|
|
286
|
-
return "Agent run completed";
|
|
287
|
-
case "RUN_FAILED":
|
|
288
|
-
return event.errorMessage || "Agent run failed";
|
|
289
|
-
case "KEEP_ALIVE":
|
|
290
|
-
return event.description || "";
|
|
291
|
-
case "THINKING_DELTA":
|
|
292
|
-
return event.text || "";
|
|
293
|
-
default:
|
|
294
|
-
return eventType;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
function extractResponseContent(response) {
|
|
298
|
-
if (typeof response === "string") {
|
|
299
|
-
return response;
|
|
300
|
-
}
|
|
301
|
-
if (typeof response === "object" && response !== null) {
|
|
302
|
-
const resp = response;
|
|
303
|
-
if ("text" in resp && typeof resp.text === "string") {
|
|
304
|
-
return resp.text;
|
|
305
|
-
}
|
|
306
|
-
if ("content" in resp && typeof resp.content === "string") {
|
|
307
|
-
return resp.content;
|
|
308
|
-
}
|
|
309
|
-
if ("message" in resp && typeof resp.message === "string") {
|
|
310
|
-
return resp.message;
|
|
311
|
-
}
|
|
312
|
-
if ("answer" in resp && typeof resp.answer === "string") {
|
|
313
|
-
return resp.answer;
|
|
314
|
-
}
|
|
315
|
-
return JSON.stringify(response);
|
|
316
|
-
}
|
|
317
|
-
return "";
|
|
318
|
-
}
|
|
319
|
-
function normalizeEvent(event) {
|
|
320
|
-
const type = event.eventType;
|
|
321
|
-
switch (type) {
|
|
322
|
-
case "RUN_STARTED":
|
|
323
|
-
return { ...event, eventType: "WORKFLOW_STARTED" };
|
|
324
|
-
case "TOOL_CALL_STARTED": {
|
|
325
|
-
const toolName = typeof event.toolName === "string" ? event.toolName : void 0;
|
|
326
|
-
const description = typeof event.description === "string" ? event.description.trim() : "";
|
|
327
|
-
return {
|
|
328
|
-
...event,
|
|
329
|
-
eventType: "INTENT_STARTED",
|
|
330
|
-
workerName: toolName ?? event.workerName,
|
|
331
|
-
message: description || event.message
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
case "TOOL_CALL_COMPLETED": {
|
|
335
|
-
const toolName = typeof event.toolName === "string" ? event.toolName : void 0;
|
|
336
|
-
const description = typeof event.description === "string" ? event.description.trim() : "";
|
|
337
|
-
return {
|
|
338
|
-
...event,
|
|
339
|
-
eventType: "INTENT_COMPLETED",
|
|
340
|
-
workerName: toolName ?? event.workerName,
|
|
341
|
-
message: description || event.message
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
case "RUN_IN_PROGRESS":
|
|
345
|
-
return {
|
|
346
|
-
...event,
|
|
347
|
-
eventType: "INTENT_PROGRESS",
|
|
348
|
-
message: event.partialText ?? event.message
|
|
349
|
-
};
|
|
350
|
-
case "RUN_COMPLETED":
|
|
351
|
-
return {
|
|
352
|
-
...event,
|
|
353
|
-
eventType: "WORKFLOW_COMPLETED",
|
|
354
|
-
// state machine reads event.response for the final content
|
|
355
|
-
response: event.response ?? event.message
|
|
356
|
-
};
|
|
357
|
-
case "RUN_FAILED":
|
|
358
|
-
return {
|
|
359
|
-
...event,
|
|
360
|
-
eventType: "WORKFLOW_ERROR",
|
|
361
|
-
errorMessage: event.errorMessage ?? event.message
|
|
362
|
-
};
|
|
363
|
-
default:
|
|
364
|
-
return event;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
function isVerificationSchema(schema) {
|
|
368
|
-
const props = schema?.properties;
|
|
369
|
-
if (!props) return false;
|
|
370
|
-
const keys = Object.keys(props);
|
|
371
|
-
return keys.length === 1 && keys[0] === "verificationCode";
|
|
372
|
-
}
|
|
373
|
-
function classifyUserActionKind(action, schema) {
|
|
374
|
-
switch ((action || "").toLowerCase()) {
|
|
375
|
-
case "userverificationrequest":
|
|
376
|
-
return "verification";
|
|
377
|
-
case "usernotificationrequest":
|
|
378
|
-
return "notification";
|
|
379
|
-
case "userformrequest":
|
|
380
|
-
return isVerificationSchema(schema) ? "verification" : "form";
|
|
381
|
-
default:
|
|
382
|
-
return isVerificationSchema(schema) ? "verification" : "form";
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
function getEventText(event, field) {
|
|
386
|
-
const value = event[field];
|
|
387
|
-
return typeof value === "string" ? value.trim() : "";
|
|
388
|
-
}
|
|
389
|
-
function updateExecutionStageMessage(state, msg) {
|
|
390
|
-
if (!msg) return;
|
|
391
|
-
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
392
|
-
const s2 = state.steps[i];
|
|
393
|
-
if (s2.eventType === "STAGE_STARTED" && (s2.stage === "executor" || s2.stage === "execution") && s2.status === "in_progress") {
|
|
394
|
-
s2.message = msg;
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
function completeLastInProgressStep(steps) {
|
|
400
|
-
for (let i = steps.length - 1; i >= 0; i--) {
|
|
401
|
-
if (steps[i].status === "in_progress") {
|
|
402
|
-
steps[i].status = "completed";
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
function createInitialV2State() {
|
|
408
|
-
return {
|
|
409
|
-
finalResponse: "",
|
|
410
|
-
lastEventType: "",
|
|
411
|
-
sessionId: void 0,
|
|
412
|
-
executionId: void 0,
|
|
413
|
-
hasError: false,
|
|
414
|
-
errorMessage: "",
|
|
415
|
-
userActions: [],
|
|
416
|
-
notifications: [],
|
|
417
|
-
lastUserAction: void 0,
|
|
418
|
-
lastNotification: void 0,
|
|
419
|
-
finalData: void 0,
|
|
420
|
-
steps: [],
|
|
421
|
-
stepCounter: 0,
|
|
422
|
-
currentExecutingStepId: void 0
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
function upsertUserAction(state, req) {
|
|
426
|
-
const active = { ...req, status: "pending" };
|
|
427
|
-
const matchIdx = state.userActions.findIndex(
|
|
428
|
-
(p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
|
|
429
|
-
);
|
|
430
|
-
if (matchIdx >= 0) {
|
|
431
|
-
state.userActions[matchIdx] = active;
|
|
432
|
-
} else {
|
|
433
|
-
state.userActions.push(active);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
function processStreamEventV2(rawEvent, state) {
|
|
437
|
-
const event = normalizeEvent(rawEvent);
|
|
438
|
-
const eventType = event.eventType;
|
|
439
|
-
state.lastUserAction = void 0;
|
|
440
|
-
state.lastNotification = void 0;
|
|
441
|
-
if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
442
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
443
|
-
if (event.sessionId) state.sessionId = event.sessionId;
|
|
444
|
-
const description = typeof event.description === "string" ? event.description : "";
|
|
445
|
-
if (description) {
|
|
446
|
-
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
447
|
-
if (state.steps[i].status === "in_progress") {
|
|
448
|
-
state.steps[i].message = description;
|
|
449
|
-
break;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
return state;
|
|
454
|
-
}
|
|
455
|
-
if (typeof eventType === "string" && eventType.toUpperCase() === "THINKING_DELTA") {
|
|
456
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
457
|
-
if (event.sessionId) state.sessionId = event.sessionId;
|
|
458
|
-
state.lastEventType = "THINKING_DELTA";
|
|
459
|
-
return state;
|
|
460
|
-
}
|
|
461
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
462
|
-
if (event.sessionId) state.sessionId = event.sessionId;
|
|
463
|
-
const message = getEventMessage(event);
|
|
464
|
-
switch (eventType) {
|
|
465
|
-
case "WORKFLOW_STARTED":
|
|
466
|
-
state.lastEventType = eventType;
|
|
467
|
-
break;
|
|
468
|
-
case "INTENT_PROGRESS": {
|
|
469
|
-
const rawMessage = typeof event.message === "string" ? event.message : "";
|
|
470
|
-
const rawPartial = typeof event.partialText === "string" ? event.partialText : "";
|
|
471
|
-
const delta = rawMessage || rawPartial;
|
|
472
|
-
if (delta.length > 0) {
|
|
473
|
-
state.finalResponse += delta;
|
|
474
|
-
}
|
|
475
|
-
state.lastEventType = eventType;
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
case "INTENT_STARTED": {
|
|
479
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
480
|
-
state.steps.push({
|
|
481
|
-
id: stepId,
|
|
482
|
-
eventType,
|
|
483
|
-
message,
|
|
484
|
-
status: "in_progress",
|
|
485
|
-
timestamp: Date.now(),
|
|
486
|
-
elapsedMs: event.elapsedMs
|
|
487
|
-
});
|
|
488
|
-
state.currentExecutingStepId = stepId;
|
|
489
|
-
updateExecutionStageMessage(state, message);
|
|
490
|
-
state.lastEventType = eventType;
|
|
491
|
-
break;
|
|
492
|
-
}
|
|
493
|
-
case "INTENT_COMPLETED": {
|
|
494
|
-
const intentStep = state.steps.find((s2) => s2.eventType === "INTENT_STARTED" && s2.status === "in_progress");
|
|
495
|
-
if (intentStep) {
|
|
496
|
-
intentStep.status = "completed";
|
|
497
|
-
if (event.elapsedMs) intentStep.elapsedMs = event.elapsedMs;
|
|
498
|
-
if (intentStep.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
|
|
499
|
-
}
|
|
500
|
-
state.lastEventType = eventType;
|
|
501
|
-
break;
|
|
502
|
-
}
|
|
503
|
-
case "WORKFLOW_COMPLETED": {
|
|
504
|
-
const totalTime = Number(event.totalTimeMs);
|
|
505
|
-
if (Number.isFinite(totalTime) && totalTime > 0) {
|
|
506
|
-
state.totalElapsedMs = totalTime;
|
|
507
|
-
}
|
|
508
|
-
const content = extractResponseContent(event.response);
|
|
509
|
-
if (content) {
|
|
510
|
-
state.finalResponse = content;
|
|
511
|
-
if (event.trace && typeof event.trace === "object") {
|
|
512
|
-
state.finalData = event.trace;
|
|
513
|
-
}
|
|
514
|
-
state.hasError = false;
|
|
515
|
-
state.errorMessage = "";
|
|
516
|
-
} else {
|
|
517
|
-
state.hasError = true;
|
|
518
|
-
state.errorMessage = "WORKFLOW_FAILED";
|
|
519
|
-
}
|
|
520
|
-
state.steps.forEach((step) => {
|
|
521
|
-
if (step.status === "in_progress") {
|
|
522
|
-
step.status = "completed";
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
state.lastEventType = eventType;
|
|
526
|
-
break;
|
|
527
|
-
}
|
|
528
|
-
case "USER_ACTION_REQUIRED": {
|
|
529
|
-
const rawAction = typeof event.action === "string" ? event.action : void 0;
|
|
530
|
-
const schema = event.requestedSchema;
|
|
531
|
-
const kind = classifyUserActionKind(rawAction, schema);
|
|
532
|
-
const promptMessage = typeof event.message === "string" && event.message.trim() || message || "";
|
|
533
|
-
const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
|
|
534
|
-
if (kind === "notification") {
|
|
535
|
-
const notification = {
|
|
536
|
-
id: userActionId || `note-${state.stepCounter++}`,
|
|
537
|
-
message: promptMessage
|
|
538
|
-
};
|
|
539
|
-
state.notifications.push(notification);
|
|
540
|
-
state.lastNotification = notification;
|
|
541
|
-
state.lastEventType = eventType;
|
|
542
|
-
break;
|
|
543
|
-
}
|
|
544
|
-
if (!userActionId) {
|
|
545
|
-
state.lastEventType = eventType;
|
|
546
|
-
break;
|
|
547
|
-
}
|
|
548
|
-
completeLastInProgressStep(state.steps);
|
|
549
|
-
const verificationType = event.verificationType === "ALPHANUMERIC_CODE" || event.verificationType === "NUMERIC_CODE" ? event.verificationType : void 0;
|
|
550
|
-
const request = {
|
|
551
|
-
userActionId,
|
|
552
|
-
kind,
|
|
553
|
-
rawAction,
|
|
554
|
-
subAction: typeof event.subAction === "string" ? event.subAction : void 0,
|
|
555
|
-
verificationType,
|
|
556
|
-
expirySeconds: typeof event.expirySeconds === "number" ? event.expirySeconds : void 0,
|
|
557
|
-
message: promptMessage || void 0,
|
|
558
|
-
requestedSchema: schema,
|
|
559
|
-
metadata: event.metadata,
|
|
560
|
-
toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : void 0,
|
|
561
|
-
executionId: state.executionId,
|
|
562
|
-
sessionId: state.sessionId
|
|
563
|
-
};
|
|
564
|
-
upsertUserAction(state, request);
|
|
565
|
-
state.lastUserAction = request;
|
|
566
|
-
const stepId = `step-${state.stepCounter++}`;
|
|
567
|
-
state.steps.push({
|
|
568
|
-
id: stepId,
|
|
569
|
-
eventType,
|
|
570
|
-
message: promptMessage || (kind === "verification" ? "Waiting for verification..." : "Waiting for your input..."),
|
|
571
|
-
status: "in_progress",
|
|
572
|
-
timestamp: Date.now(),
|
|
573
|
-
elapsedMs: event.elapsedMs
|
|
574
|
-
});
|
|
575
|
-
state.currentExecutingStepId = stepId;
|
|
576
|
-
state.lastEventType = eventType;
|
|
577
|
-
break;
|
|
578
|
-
}
|
|
579
|
-
case "USER_NOTIFICATION": {
|
|
580
|
-
const noteMessage = typeof event.message === "string" && event.message.trim() || message || "";
|
|
581
|
-
const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
|
|
582
|
-
if (noteMessage || userActionId) {
|
|
583
|
-
const notification = {
|
|
584
|
-
id: userActionId || `note-${state.stepCounter++}`,
|
|
585
|
-
message: noteMessage
|
|
586
|
-
};
|
|
587
|
-
state.notifications.push(notification);
|
|
588
|
-
state.lastNotification = notification;
|
|
589
|
-
}
|
|
590
|
-
state.lastEventType = eventType;
|
|
591
|
-
break;
|
|
592
|
-
}
|
|
593
|
-
case "WORKFLOW_ERROR":
|
|
594
|
-
state.hasError = true;
|
|
595
|
-
state.errorMessage = event.errorMessage || event.message || "Workflow error";
|
|
596
|
-
state.lastEventType = eventType;
|
|
597
|
-
break;
|
|
598
|
-
// ---- K2 pipeline stage lifecycle events ----
|
|
599
|
-
//
|
|
600
|
-
// The k2-server playground streaming API emits
|
|
601
|
-
// STAGE_STARTED / STAGE_COMPLETED / STAGE_FAILED /
|
|
602
|
-
// STAGE_SKIPPED around each pipeline stage so the UI can
|
|
603
|
-
// show real progress (sanitizer → analyzer → planner → …)
|
|
604
|
-
// instead of a static placeholder. Each STAGE_STARTED
|
|
605
|
-
// pushes an in-progress step with a user-facing label; the
|
|
606
|
-
// matching STAGE_COMPLETED/FAILED closes it WITHOUT
|
|
607
|
-
// rewriting the label — a terse "completed" flash between
|
|
608
|
-
// stages was more noise than signal. STAGE_SKIPPED removes
|
|
609
|
-
// the just-opened step entirely (skipping is expected on
|
|
610
|
-
// DIRECT_RESPONSE short-circuits).
|
|
611
|
-
//
|
|
612
|
-
// Finding the matching step uses `stepId` directly rather
|
|
613
|
-
// than comparing labels — the processor remembers which id
|
|
614
|
-
// it minted for the latest open STAGE_STARTED and the
|
|
615
|
-
// closing events look it up from state.currentExecutingStepId.
|
|
616
|
-
case "STAGE_STARTED": {
|
|
617
|
-
const stage = getEventText(event, "stage");
|
|
618
|
-
if (!stage) {
|
|
619
|
-
state.lastEventType = eventType;
|
|
620
|
-
break;
|
|
621
|
-
}
|
|
622
|
-
const serverMessage = getEventText(event, "message");
|
|
623
|
-
let initialMessage = serverMessage || stageLabel(stage);
|
|
624
|
-
if (stage === "executor" || stage === "execution") {
|
|
625
|
-
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
626
|
-
const s2 = state.steps[i];
|
|
627
|
-
if (s2.eventType === "STAGE_STARTED" && s2.stage === "analyzer" && s2.status === "completed" && s2.message) {
|
|
628
|
-
initialMessage = s2.message;
|
|
629
|
-
break;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
const stepId = `stage-${stage}-${state.stepCounter++}`;
|
|
634
|
-
state.steps.push({
|
|
635
|
-
id: stepId,
|
|
636
|
-
eventType,
|
|
637
|
-
message: initialMessage,
|
|
638
|
-
status: "in_progress",
|
|
639
|
-
timestamp: Date.now(),
|
|
640
|
-
stage
|
|
641
|
-
});
|
|
642
|
-
state.currentExecutingStepId = stepId;
|
|
643
|
-
state.lastEventType = eventType;
|
|
644
|
-
break;
|
|
645
|
-
}
|
|
646
|
-
case "STAGE_COMPLETED": {
|
|
647
|
-
const stage = getEventText(event, "stage");
|
|
648
|
-
if (!stage) {
|
|
649
|
-
state.lastEventType = eventType;
|
|
650
|
-
break;
|
|
651
|
-
}
|
|
652
|
-
const serverMessage = getEventText(event, "message");
|
|
653
|
-
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
654
|
-
const s2 = state.steps[i];
|
|
655
|
-
if (s2.eventType === "STAGE_STARTED" && s2.stage === stage && s2.status === "in_progress") {
|
|
656
|
-
if (serverMessage) s2.message = serverMessage;
|
|
657
|
-
s2.status = "completed";
|
|
658
|
-
const durationMs = Number(event.durationMs);
|
|
659
|
-
if (Number.isFinite(durationMs)) s2.elapsedMs = durationMs;
|
|
660
|
-
if (s2.id === state.currentExecutingStepId) {
|
|
661
|
-
state.currentExecutingStepId = void 0;
|
|
662
|
-
}
|
|
663
|
-
break;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
state.lastEventType = eventType;
|
|
667
|
-
break;
|
|
668
|
-
}
|
|
669
|
-
case "STAGE_FAILED": {
|
|
670
|
-
const stage = getEventText(event, "stage");
|
|
671
|
-
if (!stage) {
|
|
672
|
-
state.lastEventType = eventType;
|
|
673
|
-
break;
|
|
674
|
-
}
|
|
675
|
-
const serverMessage = getEventText(event, "message");
|
|
676
|
-
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
677
|
-
const s2 = state.steps[i];
|
|
678
|
-
if (s2.eventType === "STAGE_STARTED" && s2.stage === stage && s2.status === "in_progress") {
|
|
679
|
-
if (serverMessage) s2.message = serverMessage;
|
|
680
|
-
s2.status = "error";
|
|
681
|
-
const durationMs = Number(event.durationMs);
|
|
682
|
-
if (Number.isFinite(durationMs)) s2.elapsedMs = durationMs;
|
|
683
|
-
if (s2.id === state.currentExecutingStepId) {
|
|
684
|
-
state.currentExecutingStepId = void 0;
|
|
685
|
-
}
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
state.lastEventType = eventType;
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
case "STAGE_SKIPPED": {
|
|
693
|
-
const stage = getEventText(event, "stage");
|
|
694
|
-
if (!stage) {
|
|
695
|
-
state.lastEventType = eventType;
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
698
|
-
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
699
|
-
const s2 = state.steps[i];
|
|
700
|
-
if (s2.eventType === "STAGE_STARTED" && s2.stage === stage && s2.status === "in_progress") {
|
|
701
|
-
if (s2.id === state.currentExecutingStepId) {
|
|
702
|
-
state.currentExecutingStepId = void 0;
|
|
703
|
-
}
|
|
704
|
-
s2.status = "skipped";
|
|
705
|
-
break;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
state.lastEventType = eventType;
|
|
709
|
-
break;
|
|
710
|
-
}
|
|
711
|
-
default:
|
|
712
|
-
state.lastEventType = eventType;
|
|
713
|
-
break;
|
|
714
|
-
}
|
|
715
|
-
return state;
|
|
716
|
-
}
|
|
717
|
-
var DEFAULT_STREAM_ENDPOINT = "/api/playground/ask/stream";
|
|
718
|
-
function buildRequestBody(config, userMessage, sessionId, options) {
|
|
719
|
-
const sessionOwner = config.sessionParams;
|
|
720
|
-
const sessionOwnerId = sessionOwner?.id?.trim();
|
|
721
|
-
if (!sessionOwnerId) {
|
|
722
|
-
throw new Error("ChatConfig.sessionParams.id is required to send ask requests.");
|
|
723
|
-
}
|
|
724
|
-
const sessionAttributes = sessionOwner?.attributes && Object.keys(sessionOwner.attributes).length > 0 ? sessionOwner.attributes : void 0;
|
|
725
|
-
return {
|
|
726
|
-
agentId: config.agentId,
|
|
727
|
-
userInput: userMessage,
|
|
728
|
-
sessionId,
|
|
729
|
-
sessionOwnerId,
|
|
730
|
-
sessionOwnerLabel: sessionOwner?.name || void 0,
|
|
731
|
-
sessionAttributes,
|
|
732
|
-
analysisMode: options?.analysisMode,
|
|
733
|
-
locale: resolveLocale(config.locale),
|
|
734
|
-
timezone: resolveTimezone(config.timezone)
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
function resolveLocale(configured) {
|
|
738
|
-
if (configured && configured.trim().length > 0) return configured.trim();
|
|
739
|
-
if (typeof navigator !== "undefined") {
|
|
740
|
-
const navLocale = navigator.language;
|
|
741
|
-
if (navLocale && navLocale.length > 0) return navLocale;
|
|
742
|
-
}
|
|
743
|
-
return "en-US";
|
|
744
|
-
}
|
|
745
|
-
function resolveTimezone(configured) {
|
|
746
|
-
if (configured && configured.trim().length > 0) return configured.trim();
|
|
747
|
-
try {
|
|
748
|
-
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
749
|
-
if (zone && zone.length > 0) return zone;
|
|
750
|
-
} catch {
|
|
751
|
-
}
|
|
752
|
-
return "UTC";
|
|
753
|
-
}
|
|
754
|
-
function buildStreamingUrl(config) {
|
|
755
|
-
const endpoint = config.api.streamEndpoint || DEFAULT_STREAM_ENDPOINT;
|
|
756
|
-
const stage = config.stage || "DEV";
|
|
757
|
-
const stageParamName = config.stageQueryParam ?? "stage";
|
|
758
|
-
const queryParams = new URLSearchParams({ [stageParamName]: stage });
|
|
759
|
-
return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
|
|
760
|
-
}
|
|
761
|
-
function buildUserActionUrl(config, userActionId, action) {
|
|
762
|
-
const endpoint = config.api.streamEndpoint || DEFAULT_STREAM_ENDPOINT;
|
|
763
|
-
const [endpointPath] = endpoint.split("?");
|
|
764
|
-
const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
|
|
765
|
-
const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
|
|
766
|
-
const encodedUserActionId = encodeURIComponent(userActionId);
|
|
767
|
-
return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
|
|
768
|
-
}
|
|
769
|
-
function buildResolveImagesUrl(config) {
|
|
770
|
-
if (config.api.resolveImagesEndpoint) {
|
|
771
|
-
return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
|
|
772
|
-
}
|
|
773
|
-
const streamEndpoint = config.api.streamEndpoint || DEFAULT_STREAM_ENDPOINT;
|
|
774
|
-
const [endpointPath] = streamEndpoint.split("?");
|
|
775
|
-
const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
|
|
776
|
-
const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
|
|
777
|
-
return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
|
|
778
|
-
}
|
|
779
|
-
function buildRequestHeaders(config) {
|
|
780
|
-
const headers = {
|
|
781
|
-
...config.api.headers
|
|
782
|
-
};
|
|
783
|
-
if (config.api.authToken) {
|
|
784
|
-
headers.Authorization = `Bearer ${config.api.authToken}`;
|
|
785
|
-
}
|
|
786
|
-
return headers;
|
|
787
|
-
}
|
|
788
|
-
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
789
|
-
function hasRagImages(content) {
|
|
790
|
-
return RAG_IMAGE_REGEX.test(content);
|
|
791
|
-
}
|
|
792
|
-
async function waitForNextPaint(signal) {
|
|
793
|
-
if (signal?.aborted) return;
|
|
794
|
-
await new Promise((resolve) => {
|
|
795
|
-
let isSettled = false;
|
|
796
|
-
const finish = () => {
|
|
797
|
-
if (isSettled) return;
|
|
798
|
-
isSettled = true;
|
|
799
|
-
signal?.removeEventListener("abort", finish);
|
|
800
|
-
resolve();
|
|
801
|
-
};
|
|
802
|
-
signal?.addEventListener("abort", finish, { once: true });
|
|
803
|
-
if (typeof requestAnimationFrame === "function") {
|
|
804
|
-
requestAnimationFrame(() => {
|
|
805
|
-
setTimeout(finish, 0);
|
|
806
|
-
});
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
setTimeout(finish, 0);
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
async function resolveRagImageUrls(config, content, signal) {
|
|
813
|
-
const url = buildResolveImagesUrl(config);
|
|
814
|
-
const baseHeaders = buildRequestHeaders(config);
|
|
815
|
-
const headers = { "Content-Type": "application/json", ...baseHeaders };
|
|
816
|
-
const response = await fetch(url, {
|
|
817
|
-
method: "POST",
|
|
818
|
-
headers,
|
|
819
|
-
body: JSON.stringify({ input: content }),
|
|
820
|
-
signal
|
|
821
|
-
});
|
|
822
|
-
if (!response.ok) {
|
|
823
|
-
const errorText = await response.text();
|
|
824
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
825
|
-
}
|
|
826
|
-
const text = await response.text();
|
|
827
|
-
try {
|
|
828
|
-
const parsed = JSON.parse(text);
|
|
829
|
-
if (typeof parsed === "string") return parsed;
|
|
830
|
-
if (typeof parsed.output === "string") return parsed.output;
|
|
831
|
-
if (typeof parsed.result === "string") return parsed.result;
|
|
832
|
-
} catch {
|
|
833
|
-
}
|
|
834
|
-
return text;
|
|
835
|
-
}
|
|
836
|
-
var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
|
|
837
|
-
function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForResponse) {
|
|
838
|
-
const abortControllerRef = react.useRef(null);
|
|
839
|
-
const configRef = react.useRef(config);
|
|
840
|
-
configRef.current = config;
|
|
841
|
-
const callbacksRef = react.useRef(callbacks);
|
|
842
|
-
callbacksRef.current = callbacks;
|
|
843
|
-
const startStream = react.useCallback(
|
|
844
|
-
async (userMessage, streamingId, sessionId, externalAbortController, options) => {
|
|
845
|
-
abortControllerRef.current?.abort();
|
|
846
|
-
const abortController = externalAbortController ?? new AbortController();
|
|
847
|
-
abortControllerRef.current = abortController;
|
|
848
|
-
const state = createInitialV2State();
|
|
849
|
-
const updateMessage = (update) => {
|
|
850
|
-
if (abortController.signal.aborted) return;
|
|
851
|
-
setMessages(
|
|
852
|
-
(prev) => prev.map(
|
|
853
|
-
(msg) => msg.id === streamingId ? { ...msg, ...update } : msg
|
|
854
|
-
)
|
|
855
|
-
);
|
|
856
|
-
};
|
|
857
|
-
try {
|
|
858
|
-
const currentConfig = configRef.current;
|
|
859
|
-
const requestBody = buildRequestBody(
|
|
860
|
-
currentConfig,
|
|
861
|
-
userMessage,
|
|
862
|
-
sessionId,
|
|
863
|
-
options
|
|
864
|
-
);
|
|
865
|
-
const url = buildStreamingUrl(currentConfig);
|
|
866
|
-
const headers = buildRequestHeaders(currentConfig);
|
|
867
|
-
await streamWorkflowEvents(url, requestBody, headers, {
|
|
868
|
-
signal: abortController.signal,
|
|
869
|
-
onEvent: (event) => {
|
|
870
|
-
if (abortController.signal.aborted) return;
|
|
871
|
-
processStreamEventV2(event, state);
|
|
872
|
-
if (state.lastUserAction) {
|
|
873
|
-
callbacksRef.current.onUserActionRequired?.(state.lastUserAction);
|
|
874
|
-
}
|
|
875
|
-
if (state.lastNotification) {
|
|
876
|
-
callbacksRef.current.onUserNotification?.(state.lastNotification);
|
|
877
|
-
}
|
|
878
|
-
const activeStep = state.steps.find((s2) => s2.id === state.currentExecutingStepId);
|
|
879
|
-
const lastInProgressStep = [...state.steps].reverse().find((s2) => s2.status === "in_progress");
|
|
880
|
-
const useful = (m) => m && !isBlandStatus(m) ? m : void 0;
|
|
881
|
-
const latestUsefulStep = [...state.steps].reverse().find(
|
|
882
|
-
(s2) => s2.message && !isBlandStatus(s2.message)
|
|
883
|
-
);
|
|
884
|
-
const currentMessage = useful(activeStep?.message) ?? useful(lastInProgressStep?.message) ?? latestUsefulStep?.message ?? useful(getEventMessage(event)) ?? // Last-resort: every candidate is bland (very first event,
|
|
885
|
-
// nothing useful seen yet). Render the bland label so the
|
|
886
|
-
// bubble isn't blank.
|
|
887
|
-
activeStep?.message ?? lastInProgressStep?.message ?? getEventMessage(event);
|
|
888
|
-
if (currentMessage) {
|
|
889
|
-
callbacksRef.current.onStatusMessage?.(currentMessage);
|
|
890
|
-
}
|
|
891
|
-
callbacksRef.current.onStepsUpdate?.([...state.steps]);
|
|
892
|
-
if (state.hasError) {
|
|
893
|
-
updateMessage({
|
|
894
|
-
streamingContent: FRIENDLY_ERROR_MESSAGE,
|
|
895
|
-
content: FRIENDLY_ERROR_MESSAGE,
|
|
896
|
-
streamProgress: "error",
|
|
897
|
-
isError: true,
|
|
898
|
-
errorDetails: state.errorMessage,
|
|
899
|
-
steps: [...state.steps],
|
|
900
|
-
currentExecutingStepId: void 0,
|
|
901
|
-
executionId: state.executionId,
|
|
902
|
-
sessionId: state.sessionId
|
|
903
|
-
});
|
|
904
|
-
} else {
|
|
905
|
-
updateMessage({
|
|
906
|
-
streamingContent: state.finalResponse,
|
|
907
|
-
content: "",
|
|
908
|
-
currentMessage,
|
|
909
|
-
streamProgress: "processing",
|
|
910
|
-
isError: false,
|
|
911
|
-
steps: [...state.steps],
|
|
912
|
-
currentExecutingStepId: state.currentExecutingStepId,
|
|
913
|
-
executionId: state.executionId,
|
|
914
|
-
sessionId: state.sessionId,
|
|
915
|
-
isCancelled: false
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
},
|
|
919
|
-
onError: (error) => {
|
|
920
|
-
setIsWaitingForResponse(false);
|
|
921
|
-
callbacksRef.current.onStatusMessage?.(null);
|
|
922
|
-
if (error.name !== "AbortError") {
|
|
923
|
-
callbacksRef.current.onError?.(error);
|
|
924
|
-
}
|
|
925
|
-
const isAborted = error.name === "AbortError";
|
|
926
|
-
setMessages(
|
|
927
|
-
(prev) => prev.map(
|
|
928
|
-
(msg) => msg.id === streamingId ? {
|
|
929
|
-
...msg,
|
|
930
|
-
isStreaming: false,
|
|
931
|
-
streamProgress: isAborted ? "processing" : "error",
|
|
932
|
-
isError: !isAborted,
|
|
933
|
-
isCancelled: isAborted,
|
|
934
|
-
errorDetails: isAborted ? void 0 : error.message,
|
|
935
|
-
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
936
|
-
currentMessage: isAborted ? "Thinking..." : void 0,
|
|
937
|
-
steps: [...state.steps].map((step) => {
|
|
938
|
-
if (step.status === "in_progress" && isAborted) {
|
|
939
|
-
return { ...step, status: "pending" };
|
|
940
|
-
}
|
|
941
|
-
return step;
|
|
942
|
-
}),
|
|
943
|
-
currentExecutingStepId: void 0
|
|
944
|
-
} : msg
|
|
945
|
-
)
|
|
946
|
-
);
|
|
947
|
-
},
|
|
948
|
-
onComplete: () => {
|
|
949
|
-
setIsWaitingForResponse(false);
|
|
950
|
-
callbacksRef.current.onStatusMessage?.(null);
|
|
951
|
-
callbacksRef.current.onStepsUpdate?.([]);
|
|
952
|
-
if (state.sessionId && state.sessionId !== sessionId) {
|
|
953
|
-
callbacksRef.current.onSessionIdChange?.(state.sessionId);
|
|
954
|
-
}
|
|
955
|
-
const needsImageResolve = !state.hasError && !abortController.signal.aborted && hasRagImages(state.finalResponse);
|
|
956
|
-
const finalMessage = {
|
|
957
|
-
id: streamingId,
|
|
958
|
-
sessionId: state.sessionId || sessionId,
|
|
959
|
-
role: "assistant",
|
|
960
|
-
content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.finalResponse || "",
|
|
961
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
962
|
-
isStreaming: false,
|
|
963
|
-
streamProgress: state.hasError ? "error" : "completed",
|
|
964
|
-
isError: state.hasError,
|
|
965
|
-
errorDetails: state.hasError ? state.errorMessage : void 0,
|
|
966
|
-
executionId: state.executionId,
|
|
967
|
-
// Defensive: tracingData must be an object (or undefined)
|
|
968
|
-
// so the JSON viewer doesn't char-iterate a string. The
|
|
969
|
-
// event processor already drops bare strings; this is
|
|
970
|
-
// the last stop before the message leaves the SDK.
|
|
971
|
-
tracingData: state.finalData != null && typeof state.finalData === "object" ? state.finalData : void 0,
|
|
972
|
-
steps: state.hasError ? [] : [...state.steps],
|
|
973
|
-
isCancelled: false,
|
|
974
|
-
currentExecutingStepId: void 0,
|
|
975
|
-
isResolvingImages: needsImageResolve,
|
|
976
|
-
totalElapsedMs: state.hasError ? void 0 : state.totalElapsedMs
|
|
977
|
-
};
|
|
978
|
-
setMessages(
|
|
979
|
-
(prev) => prev.map(
|
|
980
|
-
(msg) => msg.id === streamingId ? finalMessage : msg
|
|
981
|
-
)
|
|
982
|
-
);
|
|
983
|
-
callbacksRef.current.onStreamComplete?.(finalMessage);
|
|
984
|
-
}
|
|
985
|
-
});
|
|
986
|
-
const shouldResolveImages = !abortController.signal.aborted && !state.hasError && hasRagImages(state.finalResponse);
|
|
987
|
-
if (shouldResolveImages) {
|
|
988
|
-
await waitForNextPaint(abortController.signal);
|
|
989
|
-
}
|
|
990
|
-
if (shouldResolveImages && !abortController.signal.aborted) {
|
|
991
|
-
try {
|
|
992
|
-
const resolvedContent = await resolveRagImageUrls(
|
|
993
|
-
currentConfig,
|
|
994
|
-
state.finalResponse,
|
|
995
|
-
abortController.signal
|
|
996
|
-
);
|
|
997
|
-
setMessages(
|
|
998
|
-
(prev) => prev.map(
|
|
999
|
-
(msg) => msg.id === streamingId ? { ...msg, content: resolvedContent, isResolvingImages: false } : msg
|
|
1000
|
-
)
|
|
1001
|
-
);
|
|
1002
|
-
} catch {
|
|
1003
|
-
setMessages(
|
|
1004
|
-
(prev) => prev.map(
|
|
1005
|
-
(msg) => msg.id === streamingId ? { ...msg, isResolvingImages: false } : msg
|
|
1006
|
-
)
|
|
1007
|
-
);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
return state.sessionId;
|
|
1011
|
-
} catch (error) {
|
|
1012
|
-
setIsWaitingForResponse(false);
|
|
1013
|
-
if (error.name !== "AbortError") {
|
|
1014
|
-
callbacksRef.current.onError?.(error);
|
|
1015
|
-
}
|
|
1016
|
-
const isAborted = error.name === "AbortError";
|
|
1017
|
-
setMessages(
|
|
1018
|
-
(prev) => prev.map(
|
|
1019
|
-
(msg) => msg.id === streamingId ? {
|
|
1020
|
-
...msg,
|
|
1021
|
-
isStreaming: false,
|
|
1022
|
-
streamProgress: isAborted ? "processing" : "error",
|
|
1023
|
-
isError: !isAborted,
|
|
1024
|
-
isCancelled: isAborted,
|
|
1025
|
-
errorDetails: isAborted ? void 0 : error.message,
|
|
1026
|
-
content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
|
|
1027
|
-
steps: [...state.steps].map((step) => {
|
|
1028
|
-
if (step.status === "in_progress" && isAborted) {
|
|
1029
|
-
return { ...step, status: "pending" };
|
|
1030
|
-
}
|
|
1031
|
-
return step;
|
|
1032
|
-
}),
|
|
1033
|
-
currentExecutingStepId: void 0
|
|
1034
|
-
} : msg
|
|
1035
|
-
)
|
|
1036
|
-
);
|
|
1037
|
-
return state.sessionId;
|
|
1038
|
-
}
|
|
1039
|
-
},
|
|
1040
|
-
[setMessages, setIsWaitingForResponse]
|
|
1041
|
-
);
|
|
1042
|
-
const cancelStream = react.useCallback(() => {
|
|
1043
|
-
abortControllerRef.current?.abort();
|
|
1044
|
-
}, []);
|
|
1045
|
-
return {
|
|
1046
|
-
startStream,
|
|
1047
|
-
cancelStream,
|
|
1048
|
-
abortControllerRef
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
function createCancelledMessageUpdate(steps, currentMessage) {
|
|
1052
|
-
const updatedSteps = steps.map((step) => {
|
|
1053
|
-
if (step.status === "in_progress") {
|
|
1054
|
-
return { ...step, status: "pending" };
|
|
1055
|
-
}
|
|
1056
|
-
return step;
|
|
1057
|
-
});
|
|
1058
|
-
return {
|
|
1059
|
-
isStreaming: false,
|
|
1060
|
-
isCancelled: true,
|
|
1061
|
-
steps: updatedSteps,
|
|
1062
|
-
currentExecutingStepId: void 0,
|
|
1063
|
-
currentMessage: currentMessage || "Thinking..."
|
|
1064
|
-
};
|
|
1065
|
-
}
|
|
1066
|
-
var UserActionStaleError = class extends Error {
|
|
1067
|
-
constructor(userActionId, message = "User action is no longer actionable") {
|
|
1068
|
-
super(message);
|
|
1069
|
-
__publicField(this, "userActionId");
|
|
1070
|
-
this.name = "UserActionStaleError";
|
|
1071
|
-
this.userActionId = userActionId;
|
|
1072
|
-
}
|
|
1073
|
-
};
|
|
1074
|
-
async function sendUserActionRequest(config, userActionId, action, data) {
|
|
1075
|
-
const url = buildUserActionUrl(config, userActionId, action);
|
|
1076
|
-
const baseHeaders = buildRequestHeaders(config);
|
|
1077
|
-
const hasBody = data !== void 0;
|
|
1078
|
-
const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
|
|
1079
|
-
const response = await fetch(url, {
|
|
1080
|
-
method: "POST",
|
|
1081
|
-
headers,
|
|
1082
|
-
body: hasBody ? JSON.stringify(data) : void 0
|
|
1083
|
-
});
|
|
1084
|
-
if (response.status === 404) {
|
|
1085
|
-
throw new UserActionStaleError(userActionId);
|
|
1086
|
-
}
|
|
1087
|
-
if (!response.ok) {
|
|
1088
|
-
const errorText = await response.text();
|
|
1089
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1090
|
-
}
|
|
1091
|
-
return await response.json();
|
|
1092
|
-
}
|
|
1093
|
-
async function submitUserAction(config, userActionId, content) {
|
|
1094
|
-
return sendUserActionRequest(config, userActionId, "submit", content ?? {});
|
|
1095
|
-
}
|
|
1096
|
-
async function cancelUserAction(config, userActionId) {
|
|
1097
|
-
return sendUserActionRequest(config, userActionId, "cancel");
|
|
1098
|
-
}
|
|
1099
|
-
async function resendUserAction(config, userActionId) {
|
|
1100
|
-
return sendUserActionRequest(config, userActionId, "resend");
|
|
1101
|
-
}
|
|
1102
|
-
async function expireUserAction(config, userActionId) {
|
|
1103
|
-
return sendUserActionRequest(config, userActionId, "expired");
|
|
1104
|
-
}
|
|
1105
|
-
var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
|
|
1106
|
-
function upsertPrompt(prompts, req) {
|
|
1107
|
-
const active = { ...req, status: "pending" };
|
|
1108
|
-
const idx = prompts.findIndex(
|
|
1109
|
-
(p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
|
|
1110
|
-
);
|
|
1111
|
-
if (idx >= 0) {
|
|
1112
|
-
const next = prompts.slice();
|
|
1113
|
-
next[idx] = active;
|
|
1114
|
-
return next;
|
|
1115
|
-
}
|
|
1116
|
-
return [...prompts, active];
|
|
1117
|
-
}
|
|
1118
|
-
function getStoredOrInitialMessages(config) {
|
|
1119
|
-
if (!config.userId) return config.initialMessages ?? [];
|
|
1120
|
-
const activeStream = activeStreamStore.get(config.userId);
|
|
1121
|
-
if (activeStream) return activeStream.messages;
|
|
1122
|
-
const stored = chatStore.get(config.userId);
|
|
1123
|
-
if (stored.length > 0) return stored;
|
|
1124
|
-
if (config.initialMessages?.length) {
|
|
1125
|
-
chatStore.set(config.userId, config.initialMessages);
|
|
1126
|
-
return config.initialMessages;
|
|
1127
|
-
}
|
|
1128
|
-
return [];
|
|
1129
|
-
}
|
|
1130
|
-
function getSessionIdFromMessages(messages) {
|
|
1131
|
-
return messages.find((message) => message.sessionId)?.sessionId;
|
|
1132
|
-
}
|
|
1133
|
-
function useChatV2(config, callbacks = {}) {
|
|
1134
|
-
const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
|
|
1135
|
-
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
|
|
1136
|
-
if (!config.userId) return false;
|
|
1137
|
-
return activeStreamStore.get(config.userId)?.isWaiting ?? false;
|
|
1138
|
-
});
|
|
1139
|
-
const sessionIdRef = react.useRef(
|
|
1140
|
-
getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
|
|
1141
|
-
);
|
|
1142
|
-
const prevUserIdRef = react.useRef(config.userId);
|
|
1143
|
-
const streamUserIdRef = react.useRef(void 0);
|
|
1144
|
-
const subscriptionPrevUserIdRef = react.useRef(config.userId);
|
|
1145
|
-
const callbacksRef = react.useRef(callbacks);
|
|
1146
|
-
callbacksRef.current = callbacks;
|
|
1147
|
-
const configRef = react.useRef(config);
|
|
1148
|
-
configRef.current = config;
|
|
1149
|
-
const messagesRef = react.useRef(messages);
|
|
1150
|
-
messagesRef.current = messages;
|
|
1151
|
-
const storeAwareSetMessages = react.useCallback(
|
|
1152
|
-
(updater) => {
|
|
1153
|
-
const streamUserId = streamUserIdRef.current;
|
|
1154
|
-
const currentUserId = configRef.current.userId;
|
|
1155
|
-
const storeKey = streamUserId ?? currentUserId;
|
|
1156
|
-
if (storeKey && activeStreamStore.has(storeKey)) {
|
|
1157
|
-
activeStreamStore.applyMessages(storeKey, updater);
|
|
1158
|
-
}
|
|
1159
|
-
if (!streamUserId || streamUserId === currentUserId) {
|
|
1160
|
-
setMessages(updater);
|
|
1161
|
-
}
|
|
1162
|
-
},
|
|
1163
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1164
|
-
[]
|
|
1165
|
-
);
|
|
1166
|
-
const storeAwareSetIsWaiting = react.useCallback(
|
|
1167
|
-
(waiting) => {
|
|
1168
|
-
const streamUserId = streamUserIdRef.current;
|
|
1169
|
-
const currentUserId = configRef.current.userId;
|
|
1170
|
-
const storeKey = streamUserId ?? currentUserId;
|
|
1171
|
-
if (storeKey && activeStreamStore.has(storeKey)) {
|
|
1172
|
-
activeStreamStore.setWaiting(storeKey, waiting);
|
|
1173
|
-
}
|
|
1174
|
-
if (!streamUserId || streamUserId === currentUserId) {
|
|
1175
|
-
setIsWaitingForResponse(waiting);
|
|
1176
|
-
}
|
|
1177
|
-
},
|
|
1178
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1179
|
-
[]
|
|
1180
|
-
);
|
|
1181
|
-
const [userActionState, setUserActionState] = react.useState(EMPTY_USER_ACTION_STATE);
|
|
1182
|
-
const wrappedCallbacks = react.useMemo(() => ({
|
|
1183
|
-
...callbacksRef.current,
|
|
1184
|
-
onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
|
|
1185
|
-
onStreamStart: () => callbacksRef.current.onStreamStart?.(),
|
|
1186
|
-
onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
|
|
1187
|
-
onError: (error) => callbacksRef.current.onError?.(error),
|
|
1188
|
-
onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
|
|
1189
|
-
onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
|
|
1190
|
-
onUserActionRequired: (request) => {
|
|
1191
|
-
setUserActionState((prev) => ({
|
|
1192
|
-
...prev,
|
|
1193
|
-
prompts: upsertPrompt(prev.prompts, request)
|
|
1194
|
-
}));
|
|
1195
|
-
callbacksRef.current.onUserActionRequired?.(request);
|
|
1196
|
-
},
|
|
1197
|
-
onUserNotification: (notification) => {
|
|
1198
|
-
setUserActionState(
|
|
1199
|
-
(prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
|
|
1200
|
-
);
|
|
1201
|
-
callbacksRef.current.onUserNotification?.(notification);
|
|
1202
|
-
}
|
|
1203
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1204
|
-
}), []);
|
|
1205
|
-
const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
|
|
1206
|
-
config,
|
|
1207
|
-
wrappedCallbacks,
|
|
1208
|
-
storeAwareSetMessages,
|
|
1209
|
-
storeAwareSetIsWaiting
|
|
1210
|
-
);
|
|
1211
|
-
const sendMessage = react.useCallback(
|
|
1212
|
-
async (userMessage, options) => {
|
|
1213
|
-
if (!userMessage.trim()) return;
|
|
1214
|
-
if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
|
|
1215
|
-
sessionIdRef.current = generateId();
|
|
1216
|
-
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
1217
|
-
}
|
|
1218
|
-
const userMessageId = `user-${Date.now()}`;
|
|
1219
|
-
const userMsg = {
|
|
1220
|
-
id: userMessageId,
|
|
1221
|
-
sessionId: sessionIdRef.current,
|
|
1222
|
-
role: "user",
|
|
1223
|
-
content: userMessage,
|
|
1224
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1225
|
-
};
|
|
1226
|
-
setMessages((prev) => [...prev, userMsg]);
|
|
1227
|
-
callbacksRef.current.onMessageSent?.(userMessage);
|
|
1228
|
-
setIsWaitingForResponse(true);
|
|
1229
|
-
callbacksRef.current.onStreamStart?.();
|
|
1230
|
-
const streamingId = `assistant-${Date.now()}`;
|
|
1231
|
-
const streamingMsg = {
|
|
1232
|
-
id: streamingId,
|
|
1233
|
-
sessionId: sessionIdRef.current,
|
|
1234
|
-
role: "assistant",
|
|
1235
|
-
content: "",
|
|
1236
|
-
streamingContent: "",
|
|
1237
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1238
|
-
isStreaming: true,
|
|
1239
|
-
streamProgress: "started",
|
|
1240
|
-
steps: [],
|
|
1241
|
-
currentExecutingStepId: void 0,
|
|
1242
|
-
isCancelled: false,
|
|
1243
|
-
currentMessage: void 0
|
|
1244
|
-
};
|
|
1245
|
-
setMessages((prev) => [...prev, streamingMsg]);
|
|
1246
|
-
const abortController = new AbortController();
|
|
1247
|
-
const { userId } = configRef.current;
|
|
1248
|
-
if (userId) {
|
|
1249
|
-
streamUserIdRef.current = userId;
|
|
1250
|
-
const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
|
|
1251
|
-
activeStreamStore.start(userId, abortController, initialMessages);
|
|
1252
|
-
}
|
|
1253
|
-
const newSessionId = await startStream(
|
|
1254
|
-
userMessage,
|
|
1255
|
-
streamingId,
|
|
1256
|
-
sessionIdRef.current,
|
|
1257
|
-
abortController,
|
|
1258
|
-
options
|
|
1259
|
-
);
|
|
1260
|
-
const finalStreamUserId = streamUserIdRef.current ?? userId;
|
|
1261
|
-
if (finalStreamUserId) {
|
|
1262
|
-
activeStreamStore.complete(finalStreamUserId);
|
|
1263
|
-
}
|
|
1264
|
-
streamUserIdRef.current = void 0;
|
|
1265
|
-
if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
|
|
1266
|
-
sessionIdRef.current = newSessionId;
|
|
1267
|
-
}
|
|
1268
|
-
},
|
|
1269
|
-
[startStream]
|
|
1270
|
-
);
|
|
1271
|
-
const clearMessages = react.useCallback(() => {
|
|
1272
|
-
if (configRef.current.userId) {
|
|
1273
|
-
chatStore.delete(configRef.current.userId);
|
|
1274
|
-
}
|
|
1275
|
-
setMessages([]);
|
|
1276
|
-
}, []);
|
|
1277
|
-
const prependMessages = react.useCallback((msgs) => {
|
|
1278
|
-
setMessages((prev) => [...msgs, ...prev]);
|
|
1279
|
-
}, []);
|
|
1280
|
-
const cancelStream = react.useCallback(() => {
|
|
1281
|
-
const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
|
|
1282
|
-
if (streamUserId) {
|
|
1283
|
-
activeStreamStore.abort(streamUserId);
|
|
1284
|
-
}
|
|
1285
|
-
streamUserIdRef.current = void 0;
|
|
1286
|
-
cancelStreamManager();
|
|
1287
|
-
setIsWaitingForResponse(false);
|
|
1288
|
-
setUserActionState((prev) => ({ ...prev, prompts: [] }));
|
|
1289
|
-
setMessages(
|
|
1290
|
-
(prev) => prev.map((msg) => {
|
|
1291
|
-
if (msg.isStreaming) {
|
|
1292
|
-
return {
|
|
1293
|
-
...msg,
|
|
1294
|
-
...createCancelledMessageUpdate(
|
|
1295
|
-
msg.steps || [],
|
|
1296
|
-
msg.currentMessage
|
|
1297
|
-
)
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
return msg;
|
|
1301
|
-
})
|
|
1302
|
-
);
|
|
1303
|
-
}, [cancelStreamManager]);
|
|
1304
|
-
const resetSession = react.useCallback(() => {
|
|
1305
|
-
const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
|
|
1306
|
-
if (streamUserId) {
|
|
1307
|
-
activeStreamStore.abort(streamUserId);
|
|
1308
|
-
}
|
|
1309
|
-
if (configRef.current.userId) {
|
|
1310
|
-
chatStore.delete(configRef.current.userId);
|
|
1311
|
-
}
|
|
1312
|
-
streamUserIdRef.current = void 0;
|
|
1313
|
-
setMessages([]);
|
|
1314
|
-
sessionIdRef.current = void 0;
|
|
1315
|
-
abortControllerRef.current?.abort();
|
|
1316
|
-
setIsWaitingForResponse(false);
|
|
1317
|
-
setUserActionState(EMPTY_USER_ACTION_STATE);
|
|
1318
|
-
}, []);
|
|
1319
|
-
const getSessionId = react.useCallback(() => {
|
|
1320
|
-
return sessionIdRef.current;
|
|
1321
|
-
}, []);
|
|
1322
|
-
const getMessages = react.useCallback(() => {
|
|
1323
|
-
return messages;
|
|
1324
|
-
}, [messages]);
|
|
1325
|
-
const setPromptStatus = react.useCallback(
|
|
1326
|
-
(userActionId, status) => {
|
|
1327
|
-
setUserActionState((prev) => ({
|
|
1328
|
-
...prev,
|
|
1329
|
-
prompts: prev.prompts.map(
|
|
1330
|
-
(p) => p.userActionId === userActionId ? { ...p, status } : p
|
|
1331
|
-
)
|
|
1332
|
-
}));
|
|
1333
|
-
},
|
|
1334
|
-
[]
|
|
1335
|
-
);
|
|
1336
|
-
const removePrompt = react.useCallback((userActionId) => {
|
|
1337
|
-
setUserActionState((prev) => ({
|
|
1338
|
-
...prev,
|
|
1339
|
-
prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
|
|
1340
|
-
}));
|
|
1341
|
-
}, []);
|
|
1342
|
-
const submitUserAction2 = react.useCallback(
|
|
1343
|
-
async (userActionId, content) => {
|
|
1344
|
-
setPromptStatus(userActionId, "submitting");
|
|
1345
|
-
try {
|
|
1346
|
-
await submitUserAction(configRef.current, userActionId, content);
|
|
1347
|
-
removePrompt(userActionId);
|
|
1348
|
-
} catch (error) {
|
|
1349
|
-
if (error instanceof UserActionStaleError) {
|
|
1350
|
-
setPromptStatus(userActionId, "stale");
|
|
1351
|
-
return;
|
|
1352
|
-
}
|
|
1353
|
-
setPromptStatus(userActionId, "pending");
|
|
1354
|
-
callbacksRef.current.onError?.(error);
|
|
1355
|
-
throw error;
|
|
1356
|
-
}
|
|
1357
|
-
},
|
|
1358
|
-
[removePrompt, setPromptStatus]
|
|
1359
|
-
);
|
|
1360
|
-
const cancelUserAction2 = react.useCallback(
|
|
1361
|
-
async (userActionId) => {
|
|
1362
|
-
setPromptStatus(userActionId, "submitting");
|
|
1363
|
-
try {
|
|
1364
|
-
await cancelUserAction(configRef.current, userActionId);
|
|
1365
|
-
removePrompt(userActionId);
|
|
1366
|
-
} catch (error) {
|
|
1367
|
-
if (error instanceof UserActionStaleError) {
|
|
1368
|
-
removePrompt(userActionId);
|
|
1369
|
-
return;
|
|
1370
|
-
}
|
|
1371
|
-
setPromptStatus(userActionId, "pending");
|
|
1372
|
-
callbacksRef.current.onError?.(error);
|
|
1373
|
-
throw error;
|
|
1374
|
-
}
|
|
1375
|
-
},
|
|
1376
|
-
[removePrompt, setPromptStatus]
|
|
1377
|
-
);
|
|
1378
|
-
const resendUserAction2 = react.useCallback(
|
|
1379
|
-
async (userActionId) => {
|
|
1380
|
-
setPromptStatus(userActionId, "submitting");
|
|
1381
|
-
try {
|
|
1382
|
-
await resendUserAction(configRef.current, userActionId);
|
|
1383
|
-
setPromptStatus(userActionId, "pending");
|
|
1384
|
-
} catch (error) {
|
|
1385
|
-
if (error instanceof UserActionStaleError) {
|
|
1386
|
-
setPromptStatus(userActionId, "stale");
|
|
1387
|
-
return;
|
|
1388
|
-
}
|
|
1389
|
-
setPromptStatus(userActionId, "pending");
|
|
1390
|
-
callbacksRef.current.onError?.(error);
|
|
1391
|
-
throw error;
|
|
1392
|
-
}
|
|
1393
|
-
},
|
|
1394
|
-
[setPromptStatus]
|
|
1395
|
-
);
|
|
1396
|
-
const expireUserAction2 = react.useCallback(
|
|
1397
|
-
async (userActionId) => {
|
|
1398
|
-
setPromptStatus(userActionId, "expired");
|
|
1399
|
-
try {
|
|
1400
|
-
await expireUserAction(configRef.current, userActionId);
|
|
1401
|
-
} catch (error) {
|
|
1402
|
-
if (error instanceof UserActionStaleError) return;
|
|
1403
|
-
callbacksRef.current.onError?.(error);
|
|
1404
|
-
throw error;
|
|
1405
|
-
}
|
|
1406
|
-
},
|
|
1407
|
-
[setPromptStatus]
|
|
1408
|
-
);
|
|
1409
|
-
const dismissNotification = react.useCallback((id) => {
|
|
1410
|
-
setUserActionState((prev) => ({
|
|
1411
|
-
...prev,
|
|
1412
|
-
notifications: prev.notifications.filter((n) => n.id !== id)
|
|
1413
|
-
}));
|
|
1414
|
-
}, []);
|
|
1415
|
-
react.useEffect(() => {
|
|
1416
|
-
const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
|
|
1417
|
-
subscriptionPrevUserIdRef.current = config.userId;
|
|
1418
|
-
const { userId } = config;
|
|
1419
|
-
if (!userId) return;
|
|
1420
|
-
if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
|
|
1421
|
-
streamUserIdRef.current = userId;
|
|
1422
|
-
}
|
|
1423
|
-
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1424
|
-
setMessages(msgs);
|
|
1425
|
-
setIsWaitingForResponse(isWaiting);
|
|
1426
|
-
});
|
|
1427
|
-
const active = activeStreamStore.get(userId);
|
|
1428
|
-
if (active) {
|
|
1429
|
-
setMessages(active.messages);
|
|
1430
|
-
setIsWaitingForResponse(active.isWaiting);
|
|
1431
|
-
}
|
|
1432
|
-
return unsubscribe;
|
|
1433
|
-
}, [config.userId]);
|
|
1434
|
-
react.useEffect(() => {
|
|
1435
|
-
if (!config.userId) return;
|
|
1436
|
-
if (prevUserIdRef.current !== config.userId) return;
|
|
1437
|
-
const toSave = messages.filter((m) => !m.isStreaming);
|
|
1438
|
-
if (toSave.length > 0) {
|
|
1439
|
-
chatStore.set(config.userId, toSave);
|
|
1440
|
-
}
|
|
1441
|
-
}, [messages, config.userId]);
|
|
1442
|
-
react.useEffect(() => {
|
|
1443
|
-
if (!config.userId || activeStreamStore.has(config.userId)) return;
|
|
1444
|
-
if (!config.initialMessages?.length || messagesRef.current.length > 0) return;
|
|
1445
|
-
chatStore.set(config.userId, config.initialMessages);
|
|
1446
|
-
setMessages(config.initialMessages);
|
|
1447
|
-
sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
|
|
1448
|
-
}, [config.initialMessages, config.initialSessionId, config.userId]);
|
|
1449
|
-
react.useEffect(() => {
|
|
1450
|
-
const prevUserId = prevUserIdRef.current;
|
|
1451
|
-
prevUserIdRef.current = config.userId;
|
|
1452
|
-
if (prevUserId === config.userId) return;
|
|
1453
|
-
if (prevUserId && !config.userId) {
|
|
1454
|
-
chatStore.delete(prevUserId);
|
|
1455
|
-
setMessages([]);
|
|
1456
|
-
sessionIdRef.current = void 0;
|
|
1457
|
-
setIsWaitingForResponse(false);
|
|
1458
|
-
setUserActionState(EMPTY_USER_ACTION_STATE);
|
|
1459
|
-
} else if (config.userId) {
|
|
1460
|
-
const active = activeStreamStore.get(config.userId);
|
|
1461
|
-
if (active) {
|
|
1462
|
-
setMessages(active.messages);
|
|
1463
|
-
setIsWaitingForResponse(active.isWaiting);
|
|
1464
|
-
sessionIdRef.current = getSessionIdFromMessages(active.messages) ?? config.initialSessionId;
|
|
1465
|
-
return;
|
|
1466
|
-
}
|
|
1467
|
-
const nextMessages = getStoredOrInitialMessages(config);
|
|
1468
|
-
setMessages(nextMessages);
|
|
1469
|
-
sessionIdRef.current = getSessionIdFromMessages(nextMessages) ?? config.initialSessionId;
|
|
1470
|
-
setIsWaitingForResponse(false);
|
|
1471
|
-
}
|
|
1472
|
-
}, [config]);
|
|
1473
|
-
return {
|
|
1474
|
-
messages,
|
|
1475
|
-
sendMessage,
|
|
1476
|
-
clearMessages,
|
|
1477
|
-
prependMessages,
|
|
1478
|
-
cancelStream,
|
|
1479
|
-
resetSession,
|
|
1480
|
-
getSessionId,
|
|
1481
|
-
getMessages,
|
|
1482
|
-
isWaitingForResponse,
|
|
1483
|
-
sessionId: sessionIdRef.current,
|
|
1484
|
-
userActionState,
|
|
1485
|
-
submitUserAction: submitUserAction2,
|
|
1486
|
-
cancelUserAction: cancelUserAction2,
|
|
1487
|
-
resendUserAction: resendUserAction2,
|
|
1488
|
-
expireUserAction: expireUserAction2,
|
|
1489
|
-
dismissNotification
|
|
1490
|
-
};
|
|
1491
|
-
}
|
|
1492
|
-
function getSpeechRecognition() {
|
|
1493
|
-
if (typeof window === "undefined") return null;
|
|
1494
|
-
return window.SpeechRecognition || window.webkitSpeechRecognition || null;
|
|
1495
|
-
}
|
|
1496
|
-
function useVoice(config = {}, callbacks = {}) {
|
|
1497
|
-
const [voiceState, setVoiceState] = react.useState("idle");
|
|
1498
|
-
const [transcribedText, setTranscribedText] = react.useState("");
|
|
1499
|
-
const [isAvailable, setIsAvailable] = react.useState(false);
|
|
1500
|
-
const [isRecording, setIsRecording] = react.useState(false);
|
|
1501
|
-
const recognitionRef = react.useRef(null);
|
|
1502
|
-
const autoStopTimerRef = react.useRef(null);
|
|
1503
|
-
const {
|
|
1504
|
-
lang = "en-US",
|
|
1505
|
-
interimResults = true,
|
|
1506
|
-
continuous = true,
|
|
1507
|
-
maxAlternatives = 1,
|
|
1508
|
-
autoStopAfterSilence
|
|
1509
|
-
} = config;
|
|
1510
|
-
const { onStart, onEnd, onResult, onError, onStateChange } = callbacks;
|
|
1511
|
-
react.useEffect(() => {
|
|
1512
|
-
const SpeechRecognitionAPI = getSpeechRecognition();
|
|
1513
|
-
setIsAvailable(SpeechRecognitionAPI !== null);
|
|
1514
|
-
}, []);
|
|
1515
|
-
react.useEffect(() => {
|
|
1516
|
-
onStateChange?.(voiceState);
|
|
1517
|
-
}, [voiceState, onStateChange]);
|
|
1518
|
-
const requestPermissions = react.useCallback(async () => {
|
|
1519
|
-
try {
|
|
1520
|
-
const result = await navigator.mediaDevices.getUserMedia({
|
|
1521
|
-
audio: true
|
|
1522
|
-
});
|
|
1523
|
-
result.getTracks().forEach((track) => track.stop());
|
|
1524
|
-
return {
|
|
1525
|
-
granted: true,
|
|
1526
|
-
status: "granted"
|
|
1527
|
-
};
|
|
1528
|
-
} catch (error) {
|
|
1529
|
-
return {
|
|
1530
|
-
granted: false,
|
|
1531
|
-
status: "denied"
|
|
1532
|
-
};
|
|
1533
|
-
}
|
|
1534
|
-
}, []);
|
|
1535
|
-
const getPermissions = react.useCallback(async () => {
|
|
1536
|
-
if (typeof navigator === "undefined" || !navigator.permissions) {
|
|
1537
|
-
return {
|
|
1538
|
-
granted: false,
|
|
1539
|
-
status: "undetermined"
|
|
1540
|
-
};
|
|
1541
|
-
}
|
|
1542
|
-
try {
|
|
1543
|
-
const result = await navigator.permissions.query({
|
|
1544
|
-
name: "microphone"
|
|
1545
|
-
});
|
|
1546
|
-
return {
|
|
1547
|
-
granted: result.state === "granted",
|
|
1548
|
-
status: result.state === "granted" ? "granted" : result.state === "denied" ? "denied" : "undetermined"
|
|
1549
|
-
};
|
|
1550
|
-
} catch {
|
|
1551
|
-
return {
|
|
1552
|
-
granted: false,
|
|
1553
|
-
status: "undetermined"
|
|
1554
|
-
};
|
|
1555
|
-
}
|
|
1556
|
-
}, []);
|
|
1557
|
-
const clearAutoStopTimer = react.useCallback(() => {
|
|
1558
|
-
if (autoStopTimerRef.current) {
|
|
1559
|
-
clearTimeout(autoStopTimerRef.current);
|
|
1560
|
-
autoStopTimerRef.current = null;
|
|
1561
|
-
}
|
|
1562
|
-
}, []);
|
|
1563
|
-
const resetAutoStopTimer = react.useCallback(() => {
|
|
1564
|
-
clearAutoStopTimer();
|
|
1565
|
-
if (autoStopAfterSilence && autoStopAfterSilence > 0) {
|
|
1566
|
-
autoStopTimerRef.current = setTimeout(() => {
|
|
1567
|
-
if (recognitionRef.current && isRecording) {
|
|
1568
|
-
recognitionRef.current.stop();
|
|
1569
|
-
}
|
|
1570
|
-
}, autoStopAfterSilence);
|
|
1571
|
-
}
|
|
1572
|
-
}, [autoStopAfterSilence, clearAutoStopTimer, isRecording]);
|
|
1573
|
-
const stopRecording = react.useCallback(() => {
|
|
1574
|
-
if (recognitionRef.current) {
|
|
1575
|
-
try {
|
|
1576
|
-
recognitionRef.current.stop();
|
|
1577
|
-
} catch (error) {
|
|
1578
|
-
console.warn("Error stopping speech recognition:", error);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
clearAutoStopTimer();
|
|
1582
|
-
setIsRecording(false);
|
|
1583
|
-
setVoiceState("idle");
|
|
1584
|
-
}, [clearAutoStopTimer]);
|
|
1585
|
-
const startRecording = react.useCallback(async () => {
|
|
1586
|
-
const SpeechRecognitionAPI = getSpeechRecognition();
|
|
1587
|
-
if (!SpeechRecognitionAPI) {
|
|
1588
|
-
onError?.("Speech recognition not supported in this browser");
|
|
1589
|
-
return;
|
|
1590
|
-
}
|
|
1591
|
-
try {
|
|
1592
|
-
try {
|
|
1593
|
-
await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1594
|
-
} catch (permError) {
|
|
1595
|
-
onError?.("Microphone access denied. Please allow microphone access in your browser settings.");
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
|
-
const recognition = new SpeechRecognitionAPI();
|
|
1599
|
-
recognition.continuous = continuous;
|
|
1600
|
-
recognition.interimResults = interimResults;
|
|
1601
|
-
recognition.lang = lang;
|
|
1602
|
-
recognition.maxAlternatives = maxAlternatives;
|
|
1603
|
-
recognition.onstart = () => {
|
|
1604
|
-
setVoiceState("listening");
|
|
1605
|
-
setIsRecording(true);
|
|
1606
|
-
onStart?.();
|
|
1607
|
-
resetAutoStopTimer();
|
|
1608
|
-
};
|
|
1609
|
-
recognition.onend = () => {
|
|
1610
|
-
setVoiceState("idle");
|
|
1611
|
-
setIsRecording(false);
|
|
1612
|
-
clearAutoStopTimer();
|
|
1613
|
-
onEnd?.();
|
|
1614
|
-
};
|
|
1615
|
-
recognition.onresult = (event) => {
|
|
1616
|
-
const results = event.results;
|
|
1617
|
-
let transcript = "";
|
|
1618
|
-
for (let i = 0; i < results.length; i++) {
|
|
1619
|
-
const result = results[i];
|
|
1620
|
-
if (result && result[0]) {
|
|
1621
|
-
const text = result[0].transcript;
|
|
1622
|
-
if (transcript && !transcript.endsWith(" ") && !text.startsWith(" ")) {
|
|
1623
|
-
transcript += " " + text;
|
|
1624
|
-
} else {
|
|
1625
|
-
transcript += text;
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
transcript = transcript.trim();
|
|
1630
|
-
if (transcript) {
|
|
1631
|
-
setTranscribedText(transcript);
|
|
1632
|
-
onResult?.(transcript);
|
|
1633
|
-
resetAutoStopTimer();
|
|
1634
|
-
}
|
|
1635
|
-
};
|
|
1636
|
-
recognition.onerror = (event) => {
|
|
1637
|
-
setVoiceState("error");
|
|
1638
|
-
setIsRecording(false);
|
|
1639
|
-
clearAutoStopTimer();
|
|
1640
|
-
let errorMessage = event.error;
|
|
1641
|
-
if (event.error === "not-allowed") {
|
|
1642
|
-
errorMessage = "Microphone access denied. Please allow microphone access in your browser settings.";
|
|
1643
|
-
} else if (event.error === "no-speech") {
|
|
1644
|
-
errorMessage = "No speech detected. Please try again.";
|
|
1645
|
-
} else if (event.error === "audio-capture") {
|
|
1646
|
-
errorMessage = "No microphone found or microphone is in use.";
|
|
1647
|
-
} else if (event.error === "network") {
|
|
1648
|
-
errorMessage = "Network error occurred. Please check your connection.";
|
|
1649
|
-
}
|
|
1650
|
-
onError?.(errorMessage);
|
|
1651
|
-
};
|
|
1652
|
-
recognitionRef.current = recognition;
|
|
1653
|
-
setTranscribedText("");
|
|
1654
|
-
recognition.start();
|
|
1655
|
-
} catch (error) {
|
|
1656
|
-
setVoiceState("error");
|
|
1657
|
-
setIsRecording(false);
|
|
1658
|
-
onError?.(
|
|
1659
|
-
error instanceof Error ? error.message : "Failed to start recording"
|
|
1660
|
-
);
|
|
1661
|
-
}
|
|
1662
|
-
}, [
|
|
1663
|
-
lang,
|
|
1664
|
-
interimResults,
|
|
1665
|
-
continuous,
|
|
1666
|
-
maxAlternatives,
|
|
1667
|
-
onStart,
|
|
1668
|
-
onEnd,
|
|
1669
|
-
onResult,
|
|
1670
|
-
onError,
|
|
1671
|
-
getPermissions,
|
|
1672
|
-
resetAutoStopTimer,
|
|
1673
|
-
clearAutoStopTimer
|
|
1674
|
-
]);
|
|
1675
|
-
const clearTranscript = react.useCallback(() => {
|
|
1676
|
-
setTranscribedText("");
|
|
1677
|
-
}, []);
|
|
1678
|
-
const reset = react.useCallback(() => {
|
|
1679
|
-
stopRecording();
|
|
1680
|
-
setTranscribedText("");
|
|
1681
|
-
setVoiceState("idle");
|
|
1682
|
-
}, [stopRecording]);
|
|
1683
|
-
react.useEffect(() => {
|
|
1684
|
-
return () => {
|
|
1685
|
-
if (recognitionRef.current) {
|
|
1686
|
-
try {
|
|
1687
|
-
recognitionRef.current.stop();
|
|
1688
|
-
} catch {
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
clearAutoStopTimer();
|
|
1692
|
-
};
|
|
1693
|
-
}, [clearAutoStopTimer]);
|
|
1694
|
-
return {
|
|
1695
|
-
voiceState,
|
|
1696
|
-
transcribedText,
|
|
1697
|
-
isAvailable,
|
|
1698
|
-
isRecording,
|
|
1699
|
-
startRecording,
|
|
1700
|
-
stopRecording,
|
|
1701
|
-
requestPermissions,
|
|
1702
|
-
getPermissions,
|
|
1703
|
-
clearTranscript,
|
|
1704
|
-
reset
|
|
1705
|
-
};
|
|
1706
|
-
}
|
|
1707
|
-
function classifyField(field) {
|
|
1708
|
-
if (!field) return "text";
|
|
1709
|
-
if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
|
|
1710
|
-
switch (field.type) {
|
|
1711
|
-
case "boolean":
|
|
1712
|
-
return "boolean";
|
|
1713
|
-
case "integer":
|
|
1714
|
-
return "integer";
|
|
1715
|
-
case "number":
|
|
1716
|
-
return "decimal";
|
|
1717
|
-
case "string":
|
|
1718
|
-
return "text";
|
|
1719
|
-
default:
|
|
1720
|
-
return "text";
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
function isNestedOrUnsupported(field) {
|
|
1724
|
-
if (!field) return false;
|
|
1725
|
-
if (field.type === "object" || field.type === "array") return true;
|
|
1726
|
-
if ("properties" in field && field.properties != null) return true;
|
|
1727
|
-
if ("items" in field && field.items != null) return true;
|
|
1728
|
-
return false;
|
|
1729
|
-
}
|
|
1730
|
-
function getOptions(field) {
|
|
1731
|
-
if (!field || !Array.isArray(field.oneOf)) return [];
|
|
1732
|
-
return field.oneOf.filter(
|
|
1733
|
-
(o) => !!o && typeof o === "object" && typeof o.const === "string"
|
|
1734
|
-
);
|
|
1735
|
-
}
|
|
1736
|
-
function isRequired(schema, key) {
|
|
1737
|
-
return Array.isArray(schema?.required) && schema.required.includes(key);
|
|
1738
|
-
}
|
|
1739
|
-
function coerceValue(field, raw) {
|
|
1740
|
-
const widget = classifyField(field);
|
|
1741
|
-
if (widget === "boolean") {
|
|
1742
|
-
if (typeof raw === "boolean") return raw;
|
|
1743
|
-
if (raw === "true") return true;
|
|
1744
|
-
if (raw === "false") return false;
|
|
1745
|
-
return Boolean(raw);
|
|
1746
|
-
}
|
|
1747
|
-
if (widget === "integer" || widget === "decimal") {
|
|
1748
|
-
if (raw === "" || raw == null) return void 0;
|
|
1749
|
-
const num = typeof raw === "number" ? raw : Number(String(raw).trim());
|
|
1750
|
-
if (Number.isNaN(num)) return void 0;
|
|
1751
|
-
return widget === "integer" ? Math.trunc(num) : num;
|
|
1752
|
-
}
|
|
1753
|
-
if (raw == null) return void 0;
|
|
1754
|
-
const str = String(raw);
|
|
1755
|
-
return str === "" ? void 0 : str;
|
|
1756
|
-
}
|
|
1757
|
-
function defaultValueFor(field) {
|
|
1758
|
-
if (!field || field.default === void 0) {
|
|
1759
|
-
return classifyField(field) === "boolean" ? false : "";
|
|
1760
|
-
}
|
|
1761
|
-
return field.default;
|
|
1762
|
-
}
|
|
1763
|
-
function validateField(field, value, required) {
|
|
1764
|
-
const widget = classifyField(field);
|
|
1765
|
-
const label = field?.title || "This field";
|
|
1766
|
-
const isEmpty = value === void 0 || value === null || typeof value === "string" && value.trim() === "";
|
|
1767
|
-
if (isEmpty) {
|
|
1768
|
-
if (required && widget !== "boolean") return `${label} is required.`;
|
|
1769
|
-
return null;
|
|
1770
|
-
}
|
|
1771
|
-
if (widget === "integer" || widget === "decimal") {
|
|
1772
|
-
const num = typeof value === "number" ? value : Number(value);
|
|
1773
|
-
if (Number.isNaN(num)) return `${label} must be a number.`;
|
|
1774
|
-
if (widget === "integer" && !Number.isInteger(num)) {
|
|
1775
|
-
return `${label} must be a whole number.`;
|
|
1776
|
-
}
|
|
1777
|
-
if (typeof field?.minimum === "number" && num < field.minimum) {
|
|
1778
|
-
return `${label} must be at least ${field.minimum}.`;
|
|
1779
|
-
}
|
|
1780
|
-
if (typeof field?.maximum === "number" && num > field.maximum) {
|
|
1781
|
-
return `${label} must be at most ${field.maximum}.`;
|
|
1782
|
-
}
|
|
1783
|
-
return null;
|
|
1784
|
-
}
|
|
1785
|
-
if (widget === "select") {
|
|
1786
|
-
const allowed = getOptions(field).map((o) => o.const);
|
|
1787
|
-
if (allowed.length > 0 && !allowed.includes(String(value))) {
|
|
1788
|
-
return `${label} has an invalid selection.`;
|
|
1789
|
-
}
|
|
1790
|
-
return null;
|
|
1791
|
-
}
|
|
1792
|
-
const str = String(value);
|
|
1793
|
-
if (typeof field?.minLength === "number" && str.length < field.minLength) {
|
|
1794
|
-
return `${label} must be at least ${field.minLength} characters.`;
|
|
1795
|
-
}
|
|
1796
|
-
if (typeof field?.maxLength === "number" && str.length > field.maxLength) {
|
|
1797
|
-
return `${label} must be at most ${field.maxLength} characters.`;
|
|
1798
|
-
}
|
|
1799
|
-
return null;
|
|
1800
|
-
}
|
|
1801
|
-
function renderableFields(schema) {
|
|
1802
|
-
const props = schema?.properties;
|
|
1803
|
-
if (!props) return [];
|
|
1804
|
-
return Object.entries(props).filter(([, field]) => !isNestedOrUnsupported(field));
|
|
1805
|
-
}
|
|
1806
|
-
function validateForm(schema, values) {
|
|
1807
|
-
const errors = {};
|
|
1808
|
-
for (const [key, field] of renderableFields(schema)) {
|
|
1809
|
-
const coerced = coerceValue(field, values[key]);
|
|
1810
|
-
const err = validateField(field, coerced, isRequired(schema, key));
|
|
1811
|
-
if (err) errors[key] = err;
|
|
1812
|
-
}
|
|
1813
|
-
return errors;
|
|
1814
|
-
}
|
|
1815
|
-
function buildContent(schema, values) {
|
|
1816
|
-
const content = {};
|
|
1817
|
-
for (const [key, field] of renderableFields(schema)) {
|
|
1818
|
-
const coerced = coerceValue(field, values[key]);
|
|
1819
|
-
if (coerced !== void 0) content[key] = coerced;
|
|
1820
|
-
}
|
|
1821
|
-
return content;
|
|
1822
|
-
}
|
|
1823
|
-
var PaymanChatContext = react.createContext(void 0);
|
|
1824
|
-
function usePaymanChat() {
|
|
1825
|
-
const context = react.useContext(PaymanChatContext);
|
|
1826
|
-
if (!context) {
|
|
1827
|
-
throw new Error("usePaymanChat must be used within a PaymanChat component");
|
|
1828
|
-
}
|
|
1829
|
-
return context;
|
|
1830
|
-
}
|
|
1831
|
-
if (reactNative.Platform.OS === "android" && reactNative.UIManager.setLayoutAnimationEnabledExperimental) {
|
|
1832
|
-
reactNative.UIManager.setLayoutAnimationEnabledExperimental(true);
|
|
1833
|
-
}
|
|
1834
|
-
var DEFAULT_ACCENT = "#00858d";
|
|
1835
|
-
var VOICE_DOT_COUNT = 24;
|
|
1836
|
-
var VOICE_DOT_MIN_H = 5;
|
|
1837
|
-
var VOICE_DOT_MAX_H = 14;
|
|
1838
|
-
var SPEECH_ACTIVITY_MS = 1400;
|
|
1839
|
-
var VOICE_BAR_BG = "#f0f9f9";
|
|
1840
|
-
var AI_DISCLAIMER = "AI can make mistakes. Please double-check responses.";
|
|
1841
|
-
var VoiceWaveformBar = react.memo(function VoiceWaveformBar2({
|
|
1842
|
-
index,
|
|
1843
|
-
isActive,
|
|
1844
|
-
accent
|
|
1845
|
-
}) {
|
|
1846
|
-
const height = react.useRef(new reactNative.Animated.Value(VOICE_DOT_MIN_H)).current;
|
|
1847
|
-
react.useEffect(() => {
|
|
1848
|
-
if (isActive) {
|
|
1849
|
-
const anim = reactNative.Animated.loop(
|
|
1850
|
-
reactNative.Animated.sequence([
|
|
1851
|
-
reactNative.Animated.timing(height, {
|
|
1852
|
-
toValue: VOICE_DOT_MAX_H,
|
|
1853
|
-
duration: 180 + index % 3 * 40,
|
|
1854
|
-
delay: index * 25,
|
|
1855
|
-
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
1856
|
-
useNativeDriver: false
|
|
1857
|
-
}),
|
|
1858
|
-
reactNative.Animated.timing(height, {
|
|
1859
|
-
toValue: VOICE_DOT_MIN_H,
|
|
1860
|
-
duration: 180 + index % 3 * 40,
|
|
1861
|
-
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
1862
|
-
useNativeDriver: false
|
|
1863
|
-
})
|
|
1864
|
-
])
|
|
1865
|
-
);
|
|
1866
|
-
anim.start();
|
|
1867
|
-
return () => anim.stop();
|
|
1868
|
-
}
|
|
1869
|
-
reactNative.Animated.timing(height, { toValue: VOICE_DOT_MIN_H, duration: 200, useNativeDriver: false }).start();
|
|
1870
|
-
}, [isActive, index, height]);
|
|
1871
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1872
|
-
reactNative.Animated.View,
|
|
1873
|
-
{
|
|
1874
|
-
style: [s.voiceDot, { height, backgroundColor: accent + "99" }]
|
|
1875
|
-
}
|
|
1876
|
-
);
|
|
1877
|
-
});
|
|
1878
|
-
function formatDuration(sec) {
|
|
1879
|
-
const m = Math.floor(sec / 60);
|
|
1880
|
-
const s2 = sec % 60;
|
|
1881
|
-
return `${m}:${s2.toString().padStart(2, "0")}`;
|
|
1882
|
-
}
|
|
1883
|
-
function InputBar({
|
|
1884
|
-
value,
|
|
1885
|
-
onChange,
|
|
1886
|
-
onSend,
|
|
1887
|
-
onCancel,
|
|
1888
|
-
disabled,
|
|
1889
|
-
placeholder,
|
|
1890
|
-
isStreaming,
|
|
1891
|
-
accent,
|
|
1892
|
-
enableVoice,
|
|
1893
|
-
voiceAvailable,
|
|
1894
|
-
isRecording,
|
|
1895
|
-
recordingSeconds,
|
|
1896
|
-
transcribedText,
|
|
1897
|
-
onVoicePress,
|
|
1898
|
-
onConfirmRecording,
|
|
1899
|
-
onCancelRecording,
|
|
1900
|
-
onFocus,
|
|
1901
|
-
insetBottom
|
|
1902
|
-
}) {
|
|
1903
|
-
const canSend = !disabled && !isStreaming && value.trim().length > 0;
|
|
1904
|
-
const voiceDisabled = !voiceAvailable || disabled || isStreaming;
|
|
1905
|
-
const showVoiceBar = enableVoice && isRecording;
|
|
1906
|
-
const showVoiceBtn = enableVoice && !isRecording;
|
|
1907
|
-
const [keyboardOpen, setKeyboardOpen] = react.useState(false);
|
|
1908
|
-
react.useEffect(() => {
|
|
1909
|
-
const show = reactNative.Keyboard.addListener("keyboardWillShow", () => setKeyboardOpen(true));
|
|
1910
|
-
const hide = reactNative.Keyboard.addListener("keyboardWillHide", () => setKeyboardOpen(false));
|
|
1911
|
-
return () => {
|
|
1912
|
-
show.remove();
|
|
1913
|
-
hide.remove();
|
|
1914
|
-
};
|
|
1915
|
-
}, []);
|
|
1916
|
-
const [isSpeaking, setIsSpeaking] = react.useState(false);
|
|
1917
|
-
const prevTranscript = react.useRef("");
|
|
1918
|
-
const speechTimeout = react.useRef(null);
|
|
1919
|
-
react.useEffect(() => {
|
|
1920
|
-
if (!showVoiceBar) {
|
|
1921
|
-
setIsSpeaking(false);
|
|
1922
|
-
prevTranscript.current = "";
|
|
1923
|
-
if (speechTimeout.current) clearTimeout(speechTimeout.current);
|
|
1924
|
-
return;
|
|
1925
|
-
}
|
|
1926
|
-
const changed = transcribedText !== prevTranscript.current;
|
|
1927
|
-
prevTranscript.current = transcribedText;
|
|
1928
|
-
if (changed) {
|
|
1929
|
-
setIsSpeaking(true);
|
|
1930
|
-
if (speechTimeout.current) clearTimeout(speechTimeout.current);
|
|
1931
|
-
speechTimeout.current = setTimeout(() => setIsSpeaking(false), SPEECH_ACTIVITY_MS);
|
|
1932
|
-
}
|
|
1933
|
-
return () => {
|
|
1934
|
-
if (speechTimeout.current) clearTimeout(speechTimeout.current);
|
|
1935
|
-
};
|
|
1936
|
-
}, [showVoiceBar, transcribedText]);
|
|
1937
|
-
const animOpacity = react.useRef(new reactNative.Animated.Value(1)).current;
|
|
1938
|
-
const animScale = react.useRef(new reactNative.Animated.Value(1)).current;
|
|
1939
|
-
react.useEffect(() => {
|
|
1940
|
-
reactNative.LayoutAnimation.configureNext(reactNative.LayoutAnimation.create(200, "easeInEaseOut", "opacity"));
|
|
1941
|
-
if (showVoiceBar) {
|
|
1942
|
-
animOpacity.setValue(0.92);
|
|
1943
|
-
animScale.setValue(0.98);
|
|
1944
|
-
reactNative.Animated.parallel([
|
|
1945
|
-
reactNative.Animated.timing(animOpacity, { toValue: 1, duration: 200, useNativeDriver: true }),
|
|
1946
|
-
reactNative.Animated.spring(animScale, { toValue: 1, useNativeDriver: true, speed: 12, bounciness: 4 })
|
|
1947
|
-
]).start();
|
|
1948
|
-
} else {
|
|
1949
|
-
animOpacity.setValue(1);
|
|
1950
|
-
animScale.setValue(1);
|
|
1951
|
-
}
|
|
1952
|
-
}, [showVoiceBar, animOpacity, animScale]);
|
|
1953
|
-
const accentLight = accent + "33";
|
|
1954
|
-
const barBorderColor = accent + "59";
|
|
1955
|
-
const bottomPad = keyboardOpen ? 8 : Math.max(insetBottom, 10);
|
|
1956
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [s.outerBar, showVoiceBar && s.outerBarVoice, { paddingBottom: bottomPad }], children: [
|
|
1957
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s.column, children: [
|
|
1958
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [s.inputWrap, showVoiceBar && s.inputWrapRelative], children: [
|
|
1959
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1960
|
-
reactNative.TextInput,
|
|
1961
|
-
{
|
|
1962
|
-
value,
|
|
1963
|
-
onChangeText: onChange,
|
|
1964
|
-
onFocus,
|
|
1965
|
-
editable: !disabled,
|
|
1966
|
-
placeholder,
|
|
1967
|
-
placeholderTextColor: "#9CA3AF",
|
|
1968
|
-
multiline: true,
|
|
1969
|
-
style: [s.input, disabled && s.inputDisabled, showVoiceBar && s.inputHidden],
|
|
1970
|
-
returnKeyType: "default",
|
|
1971
|
-
blurOnSubmit: false,
|
|
1972
|
-
onSubmitEditing: onSend
|
|
1973
|
-
}
|
|
1974
|
-
),
|
|
1975
|
-
showVoiceBar && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1976
|
-
reactNative.Animated.View,
|
|
1977
|
-
{
|
|
1978
|
-
pointerEvents: "box-none",
|
|
1979
|
-
style: [s.voiceBarContainer, { opacity: animOpacity, transform: [{ scale: animScale }] }],
|
|
1980
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [s.voiceBar, { backgroundColor: VOICE_BAR_BG, borderColor: barBorderColor }], children: [
|
|
1981
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1982
|
-
reactNative.Pressable,
|
|
1983
|
-
{
|
|
1984
|
-
onPress: onCancelRecording,
|
|
1985
|
-
style: ({ pressed }) => [s.voiceBarCircle, { backgroundColor: accentLight }, pressed && s.pressed],
|
|
1986
|
-
accessibilityLabel: "Cancel recording",
|
|
1987
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.X, { size: 20, color: accent, strokeWidth: 2.5 })
|
|
1988
|
-
}
|
|
1989
|
-
),
|
|
1990
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s.voiceBarCenter, children: [
|
|
1991
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.voiceDots, children: Array.from({ length: VOICE_DOT_COUNT }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(VoiceWaveformBar, { index: i, isActive: isSpeaking, accent }, i)) }),
|
|
1992
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s.voiceTimer, { color: accent }], children: formatDuration(recordingSeconds) })
|
|
1993
|
-
] }),
|
|
1994
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1995
|
-
reactNative.Pressable,
|
|
1996
|
-
{
|
|
1997
|
-
onPress: onConfirmRecording,
|
|
1998
|
-
style: ({ pressed }) => [s.voiceBarCircle, s.voiceConfirm, { borderColor: accent }, pressed && s.pressed],
|
|
1999
|
-
accessibilityLabel: "Send recording",
|
|
2000
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Check, { size: 22, color: accent, strokeWidth: 2.5 })
|
|
2001
|
-
}
|
|
2002
|
-
)
|
|
2003
|
-
] })
|
|
2004
|
-
}
|
|
2005
|
-
)
|
|
2006
|
-
] }),
|
|
2007
|
-
!showVoiceBar && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.actionsRow, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s.rightActions, children: [
|
|
2008
|
-
showVoiceBtn && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2009
|
-
reactNative.Pressable,
|
|
2010
|
-
{
|
|
2011
|
-
onPress: onVoicePress,
|
|
2012
|
-
disabled: voiceDisabled,
|
|
2013
|
-
style: [s.iconBtn, voiceDisabled && s.btnDisabled],
|
|
2014
|
-
accessibilityLabel: "Voice input",
|
|
2015
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.iconBtnInner, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Mic, { size: 16, color: "#6B7280", strokeWidth: 2 }) })
|
|
2016
|
-
}
|
|
2017
|
-
),
|
|
2018
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2019
|
-
reactNative.Pressable,
|
|
2020
|
-
{
|
|
2021
|
-
onPress: onSend,
|
|
2022
|
-
disabled: !canSend,
|
|
2023
|
-
style: [s.sendBtn, !canSend && s.btnDisabled],
|
|
2024
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [s.sendBtnInner, { backgroundColor: accent }], children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Send, { size: 14, color: "#FFFFFF" }) })
|
|
2025
|
-
}
|
|
2026
|
-
)
|
|
2027
|
-
] }) })
|
|
2028
|
-
] }),
|
|
2029
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.disclaimerWrap, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s.disclaimerText, children: AI_DISCLAIMER }) })
|
|
2030
|
-
] });
|
|
2031
|
-
}
|
|
2032
|
-
var RESPONSE_SPEED = {
|
|
2033
|
-
normal: [2, 4],
|
|
2034
|
-
fast: 1,
|
|
2035
|
-
punctuation: [8, 12],
|
|
2036
|
-
newline: [4, 6],
|
|
2037
|
-
idle: 15
|
|
2038
|
-
};
|
|
2039
|
-
function charDelay(char, speed, multiplier) {
|
|
2040
|
-
const raw = (() => {
|
|
2041
|
-
if (char === "*") return speed.fast;
|
|
2042
|
-
if (char === "\n") return speed.newline[0] + Math.random() * speed.newline[1];
|
|
2043
|
-
if (".!?,;:".includes(char)) return speed.punctuation[0] + Math.random() * speed.punctuation[1];
|
|
2044
|
-
return speed.normal[0] + Math.random() * speed.normal[1];
|
|
2045
|
-
})();
|
|
2046
|
-
return raw / multiplier;
|
|
2047
|
-
}
|
|
2048
|
-
var TYPE_CHUNK = 2;
|
|
2049
|
-
function blockJumpLength(remaining, isStreaming) {
|
|
2050
|
-
if (remaining.startsWith("```")) {
|
|
2051
|
-
const closeIdx = remaining.indexOf("\n```", 3);
|
|
2052
|
-
if (closeIdx === -1) return 0;
|
|
2053
|
-
const afterFence = remaining.indexOf("\n", closeIdx + 1);
|
|
2054
|
-
return afterFence === -1 ? remaining.length : afterFence + 1;
|
|
2055
|
-
}
|
|
2056
|
-
const lines = remaining.split("\n");
|
|
2057
|
-
if (!/^\s*\|/.test(lines[0])) return 0;
|
|
2058
|
-
let consumed = 0;
|
|
2059
|
-
let tableLines = 0;
|
|
2060
|
-
for (let i = 0; i < lines.length; i++) {
|
|
2061
|
-
if (/^\s*\|/.test(lines[i])) {
|
|
2062
|
-
tableLines++;
|
|
2063
|
-
consumed += lines[i].length + 1;
|
|
2064
|
-
} else {
|
|
2065
|
-
break;
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
|
-
if (tableLines < 2) return 0;
|
|
2069
|
-
if (consumed >= remaining.length && isStreaming) return 0;
|
|
2070
|
-
return Math.min(consumed, remaining.length);
|
|
2071
|
-
}
|
|
2072
|
-
function useTypingEffect(targetText, enabled, isStreaming, speedMultiplier = 6) {
|
|
2073
|
-
const instant = speedMultiplier === 0;
|
|
2074
|
-
const multiplier = instant ? 1 : Math.max(speedMultiplier, 0.1);
|
|
2075
|
-
const [displayedText, setDisplayedText] = react.useState("");
|
|
2076
|
-
const displayedRef = react.useRef("");
|
|
2077
|
-
const targetRef = react.useRef(targetText);
|
|
2078
|
-
const enabledRef = react.useRef(enabled);
|
|
2079
|
-
const streamingRef = react.useRef(isStreaming);
|
|
2080
|
-
const timerRef = react.useRef(null);
|
|
2081
|
-
const runningRef = react.useRef(false);
|
|
2082
|
-
targetRef.current = targetText;
|
|
2083
|
-
enabledRef.current = enabled;
|
|
2084
|
-
streamingRef.current = isStreaming;
|
|
2085
|
-
react.useEffect(() => {
|
|
2086
|
-
if (!enabled || instant) {
|
|
2087
|
-
if (timerRef.current) {
|
|
2088
|
-
clearTimeout(timerRef.current);
|
|
2089
|
-
timerRef.current = null;
|
|
2090
|
-
}
|
|
2091
|
-
runningRef.current = false;
|
|
2092
|
-
displayedRef.current = targetText;
|
|
2093
|
-
setDisplayedText(targetText);
|
|
2094
|
-
return;
|
|
2095
|
-
}
|
|
2096
|
-
if (displayedRef.current && !targetRef.current.startsWith(displayedRef.current)) {
|
|
2097
|
-
displayedRef.current = targetRef.current;
|
|
2098
|
-
setDisplayedText(targetRef.current);
|
|
2099
|
-
}
|
|
2100
|
-
if (runningRef.current) return;
|
|
2101
|
-
runningRef.current = true;
|
|
2102
|
-
const tick = () => {
|
|
2103
|
-
if (!enabledRef.current) {
|
|
2104
|
-
runningRef.current = false;
|
|
2105
|
-
return;
|
|
2106
|
-
}
|
|
2107
|
-
if (displayedRef.current && !targetRef.current.startsWith(displayedRef.current)) {
|
|
2108
|
-
displayedRef.current = targetRef.current;
|
|
2109
|
-
setDisplayedText(targetRef.current);
|
|
2110
|
-
timerRef.current = setTimeout(tick, RESPONSE_SPEED.idle / multiplier);
|
|
2111
|
-
return;
|
|
2112
|
-
}
|
|
2113
|
-
if (displayedRef.current.length < targetRef.current.length) {
|
|
2114
|
-
const remaining = targetRef.current.slice(displayedRef.current.length);
|
|
2115
|
-
const atLineStart = displayedRef.current.length === 0 || targetRef.current[displayedRef.current.length - 1] === "\n";
|
|
2116
|
-
if (atLineStart) {
|
|
2117
|
-
const jump = blockJumpLength(remaining, streamingRef.current);
|
|
2118
|
-
if (jump > 0) {
|
|
2119
|
-
displayedRef.current += remaining.slice(0, jump);
|
|
2120
|
-
setDisplayedText(displayedRef.current);
|
|
2121
|
-
timerRef.current = setTimeout(tick, 0);
|
|
2122
|
-
return;
|
|
2123
|
-
}
|
|
2124
|
-
if (streamingRef.current && /^(```|\s*\|)/.test(remaining)) {
|
|
2125
|
-
timerRef.current = setTimeout(tick, RESPONSE_SPEED.idle / multiplier);
|
|
2126
|
-
return;
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
let chunk = remaining.slice(0, TYPE_CHUNK);
|
|
2130
|
-
const nl = chunk.indexOf("\n");
|
|
2131
|
-
if (nl !== -1) chunk = chunk.slice(0, nl + 1);
|
|
2132
|
-
displayedRef.current += chunk;
|
|
2133
|
-
setDisplayedText(displayedRef.current);
|
|
2134
|
-
const lastChar = chunk[chunk.length - 1];
|
|
2135
|
-
timerRef.current = setTimeout(tick, charDelay(lastChar, RESPONSE_SPEED, multiplier));
|
|
2136
|
-
} else {
|
|
2137
|
-
timerRef.current = setTimeout(tick, RESPONSE_SPEED.idle / multiplier);
|
|
2138
|
-
}
|
|
2139
|
-
};
|
|
2140
|
-
tick();
|
|
2141
|
-
return () => {
|
|
2142
|
-
if (timerRef.current) {
|
|
2143
|
-
clearTimeout(timerRef.current);
|
|
2144
|
-
timerRef.current = null;
|
|
2145
|
-
}
|
|
2146
|
-
runningRef.current = false;
|
|
2147
|
-
};
|
|
2148
|
-
}, [enabled, instant]);
|
|
2149
|
-
const isTyping = enabled && !instant && displayedRef.current.length < targetRef.current.length;
|
|
2150
|
-
return { displayedText: enabled || instant ? displayedText : targetText, isTyping };
|
|
2151
|
-
}
|
|
2152
|
-
var PENDING_LABEL = "Thinking";
|
|
2153
|
-
var SHIMMER_DURATION = 2800;
|
|
2154
|
-
var SHIMMER_BAND = 0.55;
|
|
2155
|
-
var SHIMMER_LIGHT = { dim: "#b4b4b4", bright: "#1f2937" };
|
|
2156
|
-
var SHIMMER_DARK = { dim: "#5b6b6e", bright: "#eaf2f3" };
|
|
2157
|
-
function ShimmerChar({
|
|
2158
|
-
ch,
|
|
2159
|
-
pos,
|
|
2160
|
-
progress,
|
|
2161
|
-
dim,
|
|
2162
|
-
bright
|
|
2163
|
-
}) {
|
|
2164
|
-
const center = (pos + 0.4) / 1.8;
|
|
2165
|
-
const half = SHIMMER_BAND / 1.8;
|
|
2166
|
-
const color = progress.interpolate({
|
|
2167
|
-
inputRange: [center - half, center, center + half],
|
|
2168
|
-
outputRange: [dim, bright, dim],
|
|
2169
|
-
extrapolate: "clamp"
|
|
2170
|
-
});
|
|
2171
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.Text, { style: [ts.cursorLabel, { color }], children: ch });
|
|
2172
|
-
}
|
|
2173
|
-
function ShimmerText({ text, isDark }) {
|
|
2174
|
-
const progress = react.useRef(new reactNative.Animated.Value(0)).current;
|
|
2175
|
-
const { dim, bright } = isDark ? SHIMMER_DARK : SHIMMER_LIGHT;
|
|
2176
|
-
react.useEffect(() => {
|
|
2177
|
-
const loop = reactNative.Animated.loop(
|
|
2178
|
-
reactNative.Animated.timing(progress, {
|
|
2179
|
-
toValue: 1,
|
|
2180
|
-
duration: SHIMMER_DURATION,
|
|
2181
|
-
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
2182
|
-
// color interpolation isn't supported by the native driver
|
|
2183
|
-
useNativeDriver: false
|
|
2184
|
-
})
|
|
2185
|
-
);
|
|
2186
|
-
loop.start();
|
|
2187
|
-
return () => loop.stop();
|
|
2188
|
-
}, [progress]);
|
|
2189
|
-
const chars = Array.from(text);
|
|
2190
|
-
const n = Math.max(chars.length - 1, 1);
|
|
2191
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ts.cursorLabel, { color: dim }], children: chars.map((ch, i) => /* @__PURE__ */ jsxRuntime.jsx(ShimmerChar, { ch, pos: i / n, progress, dim, bright }, i)) });
|
|
2192
|
-
}
|
|
2193
|
-
function FlipLabel({ label, isDark }) {
|
|
2194
|
-
const opacity = react.useRef(new reactNative.Animated.Value(0)).current;
|
|
2195
|
-
const translateY = react.useRef(new reactNative.Animated.Value(6)).current;
|
|
2196
|
-
react.useEffect(() => {
|
|
2197
|
-
opacity.setValue(0);
|
|
2198
|
-
translateY.setValue(6);
|
|
2199
|
-
reactNative.Animated.parallel([
|
|
2200
|
-
reactNative.Animated.timing(opacity, { toValue: 1, duration: 300, easing: reactNative.Easing.out(reactNative.Easing.ease), useNativeDriver: true }),
|
|
2201
|
-
reactNative.Animated.timing(translateY, { toValue: 0, duration: 320, easing: reactNative.Easing.out(reactNative.Easing.ease), useNativeDriver: true })
|
|
2202
|
-
]).start();
|
|
2203
|
-
}, [label, opacity, translateY]);
|
|
2204
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: { opacity, transform: [{ translateY }] }, children: /* @__PURE__ */ jsxRuntime.jsx(ShimmerText, { text: label, isDark }) });
|
|
2205
|
-
}
|
|
2206
|
-
function StickyLabel({ label, isDark }) {
|
|
2207
|
-
const opacity = react.useRef(new reactNative.Animated.Value(0)).current;
|
|
2208
|
-
const translateY = react.useRef(new reactNative.Animated.Value(4)).current;
|
|
2209
|
-
react.useEffect(() => {
|
|
2210
|
-
reactNative.Animated.parallel([
|
|
2211
|
-
reactNative.Animated.timing(opacity, { toValue: 1, duration: 400, easing: reactNative.Easing.out(reactNative.Easing.ease), useNativeDriver: true }),
|
|
2212
|
-
reactNative.Animated.timing(translateY, { toValue: 0, duration: 400, easing: reactNative.Easing.out(reactNative.Easing.ease), useNativeDriver: true })
|
|
2213
|
-
]).start();
|
|
2214
|
-
}, [opacity, translateY]);
|
|
2215
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2216
|
-
reactNative.Animated.Text,
|
|
2217
|
-
{
|
|
2218
|
-
style: [
|
|
2219
|
-
ts.stickyLabel,
|
|
2220
|
-
{ color: isDark ? "rgba(255,255,255,0.62)" : "#4b5563", opacity, transform: [{ translateY }] }
|
|
2221
|
-
],
|
|
2222
|
-
children: label
|
|
2223
|
-
}
|
|
2224
|
-
);
|
|
2225
|
-
}
|
|
2226
|
-
function ThinkingBlock({
|
|
2227
|
-
stickyLabel,
|
|
2228
|
-
currentStepLabel,
|
|
2229
|
-
isDark
|
|
2230
|
-
}) {
|
|
2231
|
-
const label = currentStepLabel || PENDING_LABEL;
|
|
2232
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ts.container, children: [
|
|
2233
|
-
stickyLabel ? /* @__PURE__ */ jsxRuntime.jsx(StickyLabel, { label: stickyLabel, isDark }, stickyLabel) : null,
|
|
2234
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ts.cursorRow, children: /* @__PURE__ */ jsxRuntime.jsx(FlipLabel, { label, isDark }, label) })
|
|
2235
|
-
] });
|
|
2236
|
-
}
|
|
2237
|
-
function deriveStickyLabel(steps) {
|
|
2238
|
-
if (!steps || steps.length === 0) return void 0;
|
|
2239
|
-
for (let i = steps.length - 1; i >= 0; i--) {
|
|
2240
|
-
const s2 = steps[i];
|
|
2241
|
-
if (s2.eventType === "STAGE_STARTED" && s2.stage === "analyzer" && s2.status === "completed" && s2.message) {
|
|
2242
|
-
return s2.message;
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
return void 0;
|
|
2246
|
-
}
|
|
2247
|
-
function deriveCurrentStepLabel(steps, stickyLabel) {
|
|
2248
|
-
if (!steps || steps.length === 0) return void 0;
|
|
2249
|
-
for (let i = steps.length - 1; i >= 0; i--) {
|
|
2250
|
-
const s2 = steps[i];
|
|
2251
|
-
if (s2.status === "in_progress" && s2.message) {
|
|
2252
|
-
if (s2.stage === "analyzer" && s2.message === stickyLabel) continue;
|
|
2253
|
-
if (s2.message === stickyLabel) continue;
|
|
2254
|
-
return s2.message;
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
for (let i = steps.length - 1; i >= 0; i--) {
|
|
2258
|
-
const s2 = steps[i];
|
|
2259
|
-
if (s2.message && s2.message !== stickyLabel) return s2.message;
|
|
2260
|
-
}
|
|
2261
|
-
return void 0;
|
|
2262
|
-
}
|
|
2263
|
-
var ts = reactNative.StyleSheet.create({
|
|
2264
|
-
container: { paddingVertical: 2 },
|
|
2265
|
-
stickyLabel: {
|
|
2266
|
-
fontSize: 14,
|
|
2267
|
-
lineHeight: 21,
|
|
2268
|
-
color: "#4b5563",
|
|
2269
|
-
marginBottom: 6
|
|
2270
|
-
},
|
|
2271
|
-
cursorRow: { overflow: "hidden" },
|
|
2272
|
-
cursorLabel: {
|
|
2273
|
-
fontSize: 14,
|
|
2274
|
-
lineHeight: 21,
|
|
2275
|
-
fontWeight: "500"
|
|
2276
|
-
}
|
|
2277
|
-
});
|
|
2278
|
-
function UserBubble({ message, accent }) {
|
|
2279
|
-
const text = message.content ?? "";
|
|
2280
|
-
if (!text) return null;
|
|
2281
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sd.bubbleRow, sd.rowUser], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sd.bubble, sd.bubbleUser, { backgroundColor: accent }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [sd.bubbleText, sd.textUser], children: text }) }) });
|
|
2282
|
-
}
|
|
2283
|
-
function stripIncompleteImageToken(text) {
|
|
2284
|
-
const lastBang = text.lastIndexOf("![");
|
|
2285
|
-
if (lastBang === -1) return text;
|
|
2286
|
-
const after = text.slice(lastBang);
|
|
2287
|
-
if (/^!\[[^\]]*\]\([^)]*\)/.test(after)) return text;
|
|
2288
|
-
return text.slice(0, lastBang);
|
|
2289
|
-
}
|
|
2290
|
-
function stripDuplicatePromptText(text, promptText) {
|
|
2291
|
-
if (!promptText) return text;
|
|
2292
|
-
const pm = promptText.replace(/\\n/g, "\n").trim();
|
|
2293
|
-
if (!pm) return text;
|
|
2294
|
-
const idx = text.indexOf(pm);
|
|
2295
|
-
if (idx !== -1) {
|
|
2296
|
-
return (text.slice(0, idx) + text.slice(idx + pm.length)).trim();
|
|
2297
|
-
}
|
|
2298
|
-
for (let cut = Math.min(text.length, pm.length); cut >= 24; cut--) {
|
|
2299
|
-
if (text.endsWith(pm.slice(0, cut))) {
|
|
2300
|
-
return text.slice(0, text.length - cut).trim();
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
return text;
|
|
2304
|
-
}
|
|
2305
|
-
function AssistantBubble({
|
|
2306
|
-
message,
|
|
2307
|
-
shouldType,
|
|
2308
|
-
isDark,
|
|
2309
|
-
accent,
|
|
2310
|
-
suppressText
|
|
2311
|
-
}) {
|
|
2312
|
-
const isCurrentlyStreaming = !!message.isStreaming && !message.isCancelled;
|
|
2313
|
-
const mdStyles = isDark ? MD_STYLES_DARK : MD_STYLES_LIGHT;
|
|
2314
|
-
const mdRules = react.useMemo(() => makeMdRules(isDark, accent), [isDark, accent]);
|
|
2315
|
-
const bubbleBg = isDark ? "rgba(255,255,255,0.07)" : "#f3f4f6";
|
|
2316
|
-
const errorBg = isDark ? "rgba(220,38,38,0.14)" : "#fef2f2";
|
|
2317
|
-
const rawResponseContent = (() => {
|
|
2318
|
-
const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
|
|
2319
|
-
if (!raw) return "";
|
|
2320
|
-
const normalized = raw.replace(/\\n/g, "\n");
|
|
2321
|
-
const cleaned = message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
|
|
2322
|
-
return stripDuplicatePromptText(cleaned, suppressText);
|
|
2323
|
-
})();
|
|
2324
|
-
const isThinkingStreaming = isCurrentlyStreaming && !rawResponseContent && !message.isError;
|
|
2325
|
-
const hasEverStreamed = react.useRef(!!message.isStreaming);
|
|
2326
|
-
if (message.isStreaming) hasEverStreamed.current = true;
|
|
2327
|
-
const typingEnabled = shouldType && hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
|
|
2328
|
-
const { displayedText, isTyping } = useTypingEffect(
|
|
2329
|
-
rawResponseContent,
|
|
2330
|
-
typingEnabled,
|
|
2331
|
-
isCurrentlyStreaming,
|
|
2332
|
-
3
|
|
2333
|
-
);
|
|
2334
|
-
const stickyLabel = deriveStickyLabel(message.steps);
|
|
2335
|
-
const currentStepLabel = deriveCurrentStepLabel(message.steps, stickyLabel);
|
|
2336
|
-
if (message.isError && !rawResponseContent && !displayedText) {
|
|
2337
|
-
const detail = message.errorDetails || "Something went wrong. Please try again.";
|
|
2338
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sd.bubbleRow, sd.rowAssistant], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sd.bubble, sd.bubbleAssistant, { backgroundColor: errorBg }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sd.errorText, children: detail }) }) });
|
|
2339
|
-
}
|
|
2340
|
-
if (isThinkingStreaming) {
|
|
2341
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sd.bubbleRow, sd.rowAssistant], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sd.bubble, sd.bubbleAssistant, { backgroundColor: bubbleBg }], children: /* @__PURE__ */ jsxRuntime.jsx(ThinkingBlock, { stickyLabel, currentStepLabel, isDark }) }) });
|
|
2342
|
-
}
|
|
2343
|
-
const showCursor = (isCurrentlyStreaming || isTyping) && !message.isCancelled;
|
|
2344
|
-
const markdownContent = showCursor ? displayedText + "\u258B" : displayedText;
|
|
2345
|
-
if (!markdownContent) return null;
|
|
2346
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sd.bubbleRow, sd.rowAssistant], children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [sd.bubble, sd.bubbleAssistant, sd.bubbleAssistantWide, { backgroundColor: bubbleBg }], children: [
|
|
2347
|
-
/* @__PURE__ */ jsxRuntime.jsx(Markdown__default.default, { style: mdStyles, rules: mdRules, children: markdownContent }),
|
|
2348
|
-
message.isCancelled && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sd.cancelledText, children: "Response stopped" }),
|
|
2349
|
-
message.isError && displayedText && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sd.partialErrorText, children: message.errorDetails || "The response was interrupted." })
|
|
2350
|
-
] }) });
|
|
2351
|
-
}
|
|
2352
|
-
function MessageBubble({ message, accent, shouldType, isDark, suppressText }) {
|
|
2353
|
-
if (message.role === "user") return /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { message, accent });
|
|
2354
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AssistantBubble, { message, shouldType, isDark, accent, suppressText });
|
|
2355
|
-
}
|
|
2356
|
-
var RESEND_COOLDOWN_S = 30;
|
|
2357
|
-
var DEFAULT_CODE_LEN = 6;
|
|
2358
|
-
function uaPalette(isDark) {
|
|
2359
|
-
return isDark ? {
|
|
2360
|
-
sheetBg: "#13201f",
|
|
2361
|
-
text: "rgba(255,255,255,0.95)",
|
|
2362
|
-
muted: "rgba(255,255,255,0.55)",
|
|
2363
|
-
border: "rgba(255,255,255,0.10)",
|
|
2364
|
-
fieldBg: "rgba(255,255,255,0.06)",
|
|
2365
|
-
fieldBorder: "rgba(255,255,255,0.18)",
|
|
2366
|
-
overlay: "rgba(0,0,0,0.62)",
|
|
2367
|
-
grabber: "rgba(255,255,255,0.22)"
|
|
2368
|
-
} : {
|
|
2369
|
-
sheetBg: "#ffffff",
|
|
2370
|
-
text: "#111827",
|
|
2371
|
-
muted: "#6b7280",
|
|
2372
|
-
border: "#eceef1",
|
|
2373
|
-
fieldBg: "#f9fafb",
|
|
2374
|
-
fieldBorder: "#d1d5db",
|
|
2375
|
-
overlay: "rgba(15,23,42,0.45)",
|
|
2376
|
-
grabber: "rgba(15,23,42,0.18)"
|
|
2377
|
-
};
|
|
2378
|
-
}
|
|
2379
|
-
function promptInitialSeconds(prompt) {
|
|
2380
|
-
return prompt && typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Math.floor(prompt.expirySeconds) : void 0;
|
|
2381
|
-
}
|
|
2382
|
-
function promptTimerKey(prompt) {
|
|
2383
|
-
return prompt ? `${prompt.userActionId}|${prompt.subAction ?? ""}` : "";
|
|
2384
|
-
}
|
|
2385
|
-
function useExpiredFlag(prompt) {
|
|
2386
|
-
const initial = promptInitialSeconds(prompt);
|
|
2387
|
-
const key = promptTimerKey(prompt);
|
|
2388
|
-
const [expired, setExpired] = react.useState(false);
|
|
2389
|
-
react.useEffect(() => {
|
|
2390
|
-
setExpired(false);
|
|
2391
|
-
if (initial === void 0) return;
|
|
2392
|
-
const t = setTimeout(() => setExpired(true), initial * 1e3);
|
|
2393
|
-
return () => clearTimeout(t);
|
|
2394
|
-
}, [key, initial]);
|
|
2395
|
-
return expired;
|
|
2396
|
-
}
|
|
2397
|
-
function useSecondsLeft(expirySeconds, restartKey) {
|
|
2398
|
-
const initial = typeof expirySeconds === "number" && expirySeconds > 0 ? Math.floor(expirySeconds) : void 0;
|
|
2399
|
-
const [left, setLeft] = react.useState(initial);
|
|
2400
|
-
react.useEffect(() => {
|
|
2401
|
-
setLeft(initial);
|
|
2402
|
-
if (initial === void 0) return;
|
|
2403
|
-
const id = setInterval(() => {
|
|
2404
|
-
setLeft((s2) => s2 === void 0 ? s2 : s2 <= 1 ? 0 : s2 - 1);
|
|
2405
|
-
}, 1e3);
|
|
2406
|
-
return () => clearInterval(id);
|
|
2407
|
-
}, [restartKey, initial]);
|
|
2408
|
-
return left;
|
|
2409
|
-
}
|
|
2410
|
-
function SheetTimerPill({
|
|
2411
|
-
prompt,
|
|
2412
|
-
accent,
|
|
2413
|
-
isDark
|
|
2414
|
-
}) {
|
|
2415
|
-
const left = useSecondsLeft(prompt.expirySeconds, promptTimerKey(prompt));
|
|
2416
|
-
if (left === void 0 || prompt.status === "stale") return null;
|
|
2417
|
-
const expired = left <= 0;
|
|
2418
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2419
|
-
reactNative.View,
|
|
2420
|
-
{
|
|
2421
|
-
style: [
|
|
2422
|
-
sht.timer,
|
|
2423
|
-
{
|
|
2424
|
-
borderColor: expired ? "#ef4444" : accent + "40",
|
|
2425
|
-
backgroundColor: expired ? "rgba(239,68,68,0.10)" : accent + (isDark ? "1F" : "12")
|
|
2426
|
-
}
|
|
2427
|
-
],
|
|
2428
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: expired ? "#ef4444" : accent, fontSize: 12.5, fontWeight: "700", letterSpacing: 0.2 }, children: expired ? "Expired" : `${left}s` })
|
|
2429
|
-
}
|
|
2430
|
-
);
|
|
2431
|
-
}
|
|
2432
|
-
function CardTimerPill({
|
|
2433
|
-
prompt,
|
|
2434
|
-
accent,
|
|
2435
|
-
isDark
|
|
2436
|
-
}) {
|
|
2437
|
-
const left = useSecondsLeft(prompt.expirySeconds, promptTimerKey(prompt));
|
|
2438
|
-
if (left === void 0 || left <= 0) return null;
|
|
2439
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [rc.timer, { backgroundColor: accent + (isDark ? "26" : "14") }], children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: { color: accent, fontSize: 12, fontWeight: "700" }, children: [
|
|
2440
|
-
left,
|
|
2441
|
-
"s"
|
|
2442
|
-
] }) });
|
|
2443
|
-
}
|
|
2444
|
-
function CodeInput({
|
|
2445
|
-
value,
|
|
2446
|
-
onChange,
|
|
2447
|
-
length,
|
|
2448
|
-
disabled,
|
|
2449
|
-
error,
|
|
2450
|
-
accent,
|
|
2451
|
-
pal
|
|
2452
|
-
}) {
|
|
2453
|
-
const ref = react.useRef(null);
|
|
2454
|
-
react.useEffect(() => {
|
|
2455
|
-
const t = setTimeout(() => ref.current?.focus(), 380);
|
|
2456
|
-
return () => clearTimeout(t);
|
|
2457
|
-
}, []);
|
|
2458
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress: () => ref.current?.focus(), style: ub.codeRow, children: [
|
|
2459
|
-
Array.from({ length }).map((_, i) => {
|
|
2460
|
-
const ch = value[i] ?? "";
|
|
2461
|
-
const isCursor = i === value.length && !disabled;
|
|
2462
|
-
const bc = error ? "#ef4444" : isCursor ? accent : pal.fieldBorder;
|
|
2463
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [ub.codeBox, { borderColor: bc, backgroundColor: pal.fieldBg }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.codeChar, { color: pal.text }], children: ch }) }, i);
|
|
2464
|
-
}),
|
|
2465
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2466
|
-
reactNative.TextInput,
|
|
2467
|
-
{
|
|
2468
|
-
ref,
|
|
2469
|
-
value,
|
|
2470
|
-
onChangeText: (t) => onChange(t.replace(/[^0-9]/g, "").slice(0, length)),
|
|
2471
|
-
keyboardType: "number-pad",
|
|
2472
|
-
maxLength: length,
|
|
2473
|
-
editable: !disabled,
|
|
2474
|
-
caretHidden: true,
|
|
2475
|
-
style: ub.codeHidden
|
|
2476
|
-
}
|
|
2477
|
-
)
|
|
2478
|
-
] });
|
|
2479
|
-
}
|
|
2480
|
-
function PrimaryButton({
|
|
2481
|
-
label,
|
|
2482
|
-
onPress,
|
|
2483
|
-
disabled,
|
|
2484
|
-
accent
|
|
2485
|
-
}) {
|
|
2486
|
-
const bg = accent || DEFAULT_ACCENT;
|
|
2487
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2488
|
-
reactNative.Pressable,
|
|
2489
|
-
{
|
|
2490
|
-
onPress,
|
|
2491
|
-
disabled,
|
|
2492
|
-
android_ripple: { color: "rgba(255,255,255,0.22)" },
|
|
2493
|
-
style: [ub.primaryBtn, { backgroundColor: bg, borderColor: bg, opacity: disabled ? 0.5 : 1 }],
|
|
2494
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: ub.primaryBtnText, children: label })
|
|
2495
|
-
}
|
|
2496
|
-
);
|
|
2497
|
-
}
|
|
2498
|
-
function VerificationBody({
|
|
2499
|
-
prompt,
|
|
2500
|
-
expired,
|
|
2501
|
-
pal,
|
|
2502
|
-
accent,
|
|
2503
|
-
onSubmit,
|
|
2504
|
-
onCancel,
|
|
2505
|
-
onResend
|
|
2506
|
-
}) {
|
|
2507
|
-
const isNumeric = prompt.verificationType !== "ALPHANUMERIC_CODE";
|
|
2508
|
-
const field = prompt.requestedSchema?.properties?.verificationCode;
|
|
2509
|
-
const codeLen = (typeof field?.maxLength === "number" ? field.maxLength : void 0) ?? (typeof field?.minLength === "number" ? field.minLength : void 0) ?? DEFAULT_CODE_LEN;
|
|
2510
|
-
const [code, setCode] = react.useState("");
|
|
2511
|
-
const [errored, setErrored] = react.useState(false);
|
|
2512
|
-
const [resendSec, setResendSec] = react.useState(0);
|
|
2513
|
-
const lastSubmitted = react.useRef(null);
|
|
2514
|
-
const resendTimer = react.useRef(null);
|
|
2515
|
-
const busy = prompt.status === "submitting";
|
|
2516
|
-
const stale = prompt.status === "stale";
|
|
2517
|
-
const locked = busy || stale || expired;
|
|
2518
|
-
react.useEffect(() => {
|
|
2519
|
-
if (prompt.subAction === "SubmissionInvalid") {
|
|
2520
|
-
setErrored(true);
|
|
2521
|
-
setCode("");
|
|
2522
|
-
lastSubmitted.current = null;
|
|
2523
|
-
}
|
|
2524
|
-
}, [prompt.subAction, prompt.userActionId]);
|
|
2525
|
-
const doSubmit = react.useCallback(
|
|
2526
|
-
(value) => {
|
|
2527
|
-
if (locked || !value) return;
|
|
2528
|
-
if (lastSubmitted.current === value) return;
|
|
2529
|
-
lastSubmitted.current = value;
|
|
2530
|
-
void onSubmit(prompt.userActionId, { verificationCode: value }).catch(() => {
|
|
2531
|
-
lastSubmitted.current = null;
|
|
2532
|
-
setErrored(true);
|
|
2533
|
-
});
|
|
2534
|
-
},
|
|
2535
|
-
[locked, onSubmit, prompt.userActionId]
|
|
2536
|
-
);
|
|
2537
|
-
react.useEffect(() => {
|
|
2538
|
-
if (!isNumeric || locked) return;
|
|
2539
|
-
if (code.length === codeLen && /^\d+$/.test(code)) doSubmit(code);
|
|
2540
|
-
}, [code, codeLen, doSubmit, isNumeric, locked]);
|
|
2541
|
-
react.useEffect(() => () => {
|
|
2542
|
-
if (resendTimer.current) clearInterval(resendTimer.current);
|
|
2543
|
-
}, []);
|
|
2544
|
-
const handleResend = react.useCallback(async () => {
|
|
2545
|
-
if (locked || resendSec > 0) return;
|
|
2546
|
-
setErrored(false);
|
|
2547
|
-
setCode("");
|
|
2548
|
-
lastSubmitted.current = null;
|
|
2549
|
-
try {
|
|
2550
|
-
await onResend(prompt.userActionId);
|
|
2551
|
-
setResendSec(RESEND_COOLDOWN_S);
|
|
2552
|
-
if (resendTimer.current) clearInterval(resendTimer.current);
|
|
2553
|
-
resendTimer.current = setInterval(() => {
|
|
2554
|
-
setResendSec((s2) => {
|
|
2555
|
-
if (s2 <= 1) {
|
|
2556
|
-
if (resendTimer.current) clearInterval(resendTimer.current);
|
|
2557
|
-
return 0;
|
|
2558
|
-
}
|
|
2559
|
-
return s2 - 1;
|
|
2560
|
-
});
|
|
2561
|
-
}, 1e3);
|
|
2562
|
-
} catch {
|
|
2563
|
-
}
|
|
2564
|
-
}, [locked, onResend, prompt.userActionId, resendSec]);
|
|
2565
|
-
const description = prompt.message?.trim() || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
|
|
2566
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.bodyWrap, children: [
|
|
2567
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2568
|
-
reactNative.ScrollView,
|
|
2569
|
-
{
|
|
2570
|
-
style: ub.fieldsScroll,
|
|
2571
|
-
contentContainerStyle: ub.fieldsContent,
|
|
2572
|
-
keyboardShouldPersistTaps: "handled",
|
|
2573
|
-
showsVerticalScrollIndicator: false,
|
|
2574
|
-
children: [
|
|
2575
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.desc, { color: pal.muted }], children: description }),
|
|
2576
|
-
isNumeric ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2577
|
-
CodeInput,
|
|
2578
|
-
{
|
|
2579
|
-
value: code,
|
|
2580
|
-
onChange: (v) => {
|
|
2581
|
-
setErrored(false);
|
|
2582
|
-
setCode(v);
|
|
2583
|
-
},
|
|
2584
|
-
length: codeLen,
|
|
2585
|
-
disabled: locked,
|
|
2586
|
-
error: errored,
|
|
2587
|
-
accent,
|
|
2588
|
-
pal
|
|
2589
|
-
}
|
|
2590
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2591
|
-
reactNative.TextInput,
|
|
2592
|
-
{
|
|
2593
|
-
value: code,
|
|
2594
|
-
onChangeText: (t) => {
|
|
2595
|
-
setErrored(false);
|
|
2596
|
-
setCode(t);
|
|
2597
|
-
},
|
|
2598
|
-
editable: !locked,
|
|
2599
|
-
placeholder: "Verification code",
|
|
2600
|
-
placeholderTextColor: pal.muted,
|
|
2601
|
-
autoComplete: "one-time-code",
|
|
2602
|
-
style: [
|
|
2603
|
-
ub.input,
|
|
2604
|
-
{ color: pal.text, backgroundColor: pal.fieldBg, borderColor: errored ? "#ef4444" : pal.fieldBorder }
|
|
2605
|
-
]
|
|
2606
|
-
}
|
|
2607
|
-
)
|
|
2608
|
-
]
|
|
2609
|
-
}
|
|
2610
|
-
),
|
|
2611
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.actions, children: [
|
|
2612
|
-
!isNumeric && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2613
|
-
PrimaryButton,
|
|
2614
|
-
{
|
|
2615
|
-
label: busy ? "Verifying\u2026" : "Verify",
|
|
2616
|
-
onPress: () => doSubmit(code.trim()),
|
|
2617
|
-
disabled: locked || !code.trim(),
|
|
2618
|
-
accent
|
|
2619
|
-
}
|
|
2620
|
-
),
|
|
2621
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.linkRow, children: [
|
|
2622
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { disabled: locked || resendSec > 0, onPress: () => void handleResend(), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.link, { color: accent, opacity: locked || resendSec > 0 ? 0.4 : 1 }], children: resendSec > 0 ? `Resend (${resendSec}s)` : "Resend" }) }),
|
|
2623
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { disabled: busy, onPress: () => void onCancel(prompt.userActionId), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.link, ub.linkDanger, { opacity: busy ? 0.4 : 1 }], children: "Cancel" }) })
|
|
2624
|
-
] })
|
|
2625
|
-
] })
|
|
2626
|
-
] });
|
|
2627
|
-
}
|
|
2628
|
-
function FormBody({
|
|
2629
|
-
prompt,
|
|
2630
|
-
pal,
|
|
2631
|
-
accent,
|
|
2632
|
-
isDark,
|
|
2633
|
-
onSubmit,
|
|
2634
|
-
onCancel
|
|
2635
|
-
}) {
|
|
2636
|
-
const schema = prompt.requestedSchema;
|
|
2637
|
-
const fields = react.useMemo(() => renderableFields(schema), [schema]);
|
|
2638
|
-
const [values, setValues] = react.useState(() => {
|
|
2639
|
-
const init = {};
|
|
2640
|
-
for (const [key, field] of fields) init[key] = defaultValueFor(field);
|
|
2641
|
-
return init;
|
|
2642
|
-
});
|
|
2643
|
-
const [errors, setErrors] = react.useState({});
|
|
2644
|
-
const busy = prompt.status === "submitting";
|
|
2645
|
-
const stale = prompt.status === "stale";
|
|
2646
|
-
const locked = busy || stale;
|
|
2647
|
-
const isConfirm = prompt.subAction === "UserConfirmation";
|
|
2648
|
-
const setValue = (key, v) => {
|
|
2649
|
-
setValues((prev) => ({ ...prev, [key]: v }));
|
|
2650
|
-
setErrors((prev) => {
|
|
2651
|
-
if (!prev[key]) return prev;
|
|
2652
|
-
const next = { ...prev };
|
|
2653
|
-
delete next[key];
|
|
2654
|
-
return next;
|
|
2655
|
-
});
|
|
2656
|
-
};
|
|
2657
|
-
const handleSubmit = () => {
|
|
2658
|
-
if (locked) return;
|
|
2659
|
-
const validation = validateForm(schema, values);
|
|
2660
|
-
if (Object.keys(validation).length > 0) {
|
|
2661
|
-
setErrors(validation);
|
|
2662
|
-
return;
|
|
2663
|
-
}
|
|
2664
|
-
void onSubmit(prompt.userActionId, buildContent(schema, values)).catch(() => {
|
|
2665
|
-
});
|
|
2666
|
-
};
|
|
2667
|
-
const mdStyles = isDark ? MD_STYLES_DARK : MD_STYLES_LIGHT;
|
|
2668
|
-
const mdRules = react.useMemo(() => makeMdRules(isDark, accent), [isDark, accent]);
|
|
2669
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.bodyWrap, children: [
|
|
2670
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2671
|
-
reactNative.ScrollView,
|
|
2672
|
-
{
|
|
2673
|
-
style: ub.fieldsScroll,
|
|
2674
|
-
contentContainerStyle: ub.fieldsContent,
|
|
2675
|
-
keyboardShouldPersistTaps: "handled",
|
|
2676
|
-
showsVerticalScrollIndicator: true,
|
|
2677
|
-
children: [
|
|
2678
|
-
prompt.message?.trim() ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.messageBlock, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown__default.default, { style: mdStyles, rules: mdRules, children: prompt.message.replace(/\\n/g, "\n") }) }) : null,
|
|
2679
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.form, children: fields.map(([key, field]) => {
|
|
2680
|
-
const widget = classifyField(field);
|
|
2681
|
-
const label = field.title || key;
|
|
2682
|
-
const required = isRequired(schema, key);
|
|
2683
|
-
const err = errors[key];
|
|
2684
|
-
if (widget === "boolean") {
|
|
2685
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.switchRow, children: [
|
|
2686
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [ub.label, { color: pal.text, flex: 1 }], children: [
|
|
2687
|
-
label,
|
|
2688
|
-
required ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: "#ef4444" }, children: " *" }) : null
|
|
2689
|
-
] }),
|
|
2690
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2691
|
-
reactNative.Switch,
|
|
2692
|
-
{
|
|
2693
|
-
value: Boolean(values[key]),
|
|
2694
|
-
disabled: locked,
|
|
2695
|
-
onValueChange: (v) => setValue(key, v),
|
|
2696
|
-
trackColor: { true: accent }
|
|
2697
|
-
}
|
|
2698
|
-
)
|
|
2699
|
-
] }, key);
|
|
2700
|
-
}
|
|
2701
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.field, children: [
|
|
2702
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [ub.label, { color: pal.text }], children: [
|
|
2703
|
-
label,
|
|
2704
|
-
required ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: "#ef4444" }, children: " *" }) : null
|
|
2705
|
-
] }),
|
|
2706
|
-
field.description ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.hint, { color: pal.muted }], children: field.description }) : null,
|
|
2707
|
-
widget === "select" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.optionList, children: getOptions(field).map((opt) => {
|
|
2708
|
-
const selected = String(values[key] ?? "") === opt.const;
|
|
2709
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2710
|
-
reactNative.Pressable,
|
|
2711
|
-
{
|
|
2712
|
-
disabled: locked,
|
|
2713
|
-
onPress: () => setValue(key, opt.const),
|
|
2714
|
-
android_ripple: { color: accent + "22" },
|
|
2715
|
-
style: [
|
|
2716
|
-
ub.optionRow,
|
|
2717
|
-
{
|
|
2718
|
-
borderColor: selected ? accent : pal.fieldBorder,
|
|
2719
|
-
backgroundColor: selected ? accent + (isDark ? "26" : "14") : pal.fieldBg
|
|
2720
|
-
}
|
|
2721
|
-
],
|
|
2722
|
-
children: [
|
|
2723
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2724
|
-
reactNative.View,
|
|
2725
|
-
{
|
|
2726
|
-
style: [
|
|
2727
|
-
ub.radio,
|
|
2728
|
-
{ borderColor: selected ? accent : pal.fieldBorder }
|
|
2729
|
-
],
|
|
2730
|
-
children: selected ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [ub.radioDot, { backgroundColor: accent }] }) : null
|
|
2731
|
-
}
|
|
2732
|
-
),
|
|
2733
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2734
|
-
reactNative.Text,
|
|
2735
|
-
{
|
|
2736
|
-
style: [
|
|
2737
|
-
ub.optionLabel,
|
|
2738
|
-
{ color: pal.text, fontWeight: selected ? "600" : "500" }
|
|
2739
|
-
],
|
|
2740
|
-
children: opt.title || opt.const
|
|
2741
|
-
}
|
|
2742
|
-
)
|
|
2743
|
-
]
|
|
2744
|
-
},
|
|
2745
|
-
opt.const
|
|
2746
|
-
);
|
|
2747
|
-
}) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2748
|
-
reactNative.TextInput,
|
|
2749
|
-
{
|
|
2750
|
-
value: String(values[key] ?? ""),
|
|
2751
|
-
editable: !locked,
|
|
2752
|
-
onChangeText: (t) => setValue(key, t),
|
|
2753
|
-
placeholder: field.description ? void 0 : label,
|
|
2754
|
-
placeholderTextColor: pal.muted,
|
|
2755
|
-
keyboardType: widget === "integer" ? "number-pad" : widget === "decimal" ? "decimal-pad" : "default",
|
|
2756
|
-
style: [
|
|
2757
|
-
ub.input,
|
|
2758
|
-
{ color: pal.text, backgroundColor: pal.fieldBg, borderColor: err ? "#ef4444" : pal.fieldBorder }
|
|
2759
|
-
]
|
|
2760
|
-
}
|
|
2761
|
-
),
|
|
2762
|
-
err ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: ub.error, children: err }) : null
|
|
2763
|
-
] }, key);
|
|
2764
|
-
}) })
|
|
2765
|
-
]
|
|
2766
|
-
}
|
|
2767
|
-
),
|
|
2768
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.actions, children: [
|
|
2769
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2770
|
-
PrimaryButton,
|
|
2771
|
-
{
|
|
2772
|
-
label: busy ? "Submitting\u2026" : isConfirm ? "Confirm" : "Next",
|
|
2773
|
-
onPress: handleSubmit,
|
|
2774
|
-
disabled: locked,
|
|
2775
|
-
accent
|
|
2776
|
-
}
|
|
2777
|
-
),
|
|
2778
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.linkRow, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { disabled: busy, onPress: () => void onCancel(prompt.userActionId), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.link, ub.linkDanger, { opacity: busy ? 0.4 : 1 }], children: "Cancel" }) }) })
|
|
2779
|
-
] })
|
|
2780
|
-
] });
|
|
2781
|
-
}
|
|
2782
|
-
function UserActionSheet({
|
|
2783
|
-
open,
|
|
2784
|
-
active,
|
|
2785
|
-
expired,
|
|
2786
|
-
isDark,
|
|
2787
|
-
accent,
|
|
2788
|
-
insetBottom,
|
|
2789
|
-
onSubmit,
|
|
2790
|
-
onCancel,
|
|
2791
|
-
onResend,
|
|
2792
|
-
onMinimize
|
|
2793
|
-
}) {
|
|
2794
|
-
const pal = uaPalette(isDark);
|
|
2795
|
-
const screenH = reactNative.Dimensions.get("window").height;
|
|
2796
|
-
const [shown, setShown] = react.useState(active);
|
|
2797
|
-
react.useEffect(() => {
|
|
2798
|
-
if (active) setShown(active);
|
|
2799
|
-
}, [active]);
|
|
2800
|
-
const translateY = react.useRef(new reactNative.Animated.Value(screenH)).current;
|
|
2801
|
-
const backdrop = react.useRef(new reactNative.Animated.Value(0)).current;
|
|
2802
|
-
const minimizeRef = react.useRef(onMinimize);
|
|
2803
|
-
react.useEffect(() => {
|
|
2804
|
-
minimizeRef.current = onMinimize;
|
|
2805
|
-
});
|
|
2806
|
-
const [kbHeight, setKbHeight] = react.useState(0);
|
|
2807
|
-
react.useEffect(() => {
|
|
2808
|
-
const showEvt = reactNative.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
|
|
2809
|
-
const hideEvt = reactNative.Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
|
|
2810
|
-
const s1 = reactNative.Keyboard.addListener(showEvt, (e) => setKbHeight(e?.endCoordinates?.height ?? 0));
|
|
2811
|
-
const s2 = reactNative.Keyboard.addListener(hideEvt, () => setKbHeight(0));
|
|
2812
|
-
return () => {
|
|
2813
|
-
s1.remove();
|
|
2814
|
-
s2.remove();
|
|
2815
|
-
};
|
|
2816
|
-
}, []);
|
|
2817
|
-
react.useEffect(() => {
|
|
2818
|
-
if (open) {
|
|
2819
|
-
reactNative.Animated.parallel([
|
|
2820
|
-
reactNative.Animated.spring(translateY, { toValue: 0, useNativeDriver: true, damping: 30, stiffness: 360, mass: 0.85 }),
|
|
2821
|
-
reactNative.Animated.timing(backdrop, { toValue: 1, duration: 190, useNativeDriver: true })
|
|
2822
|
-
]).start();
|
|
2823
|
-
} else {
|
|
2824
|
-
reactNative.Animated.parallel([
|
|
2825
|
-
reactNative.Animated.timing(translateY, { toValue: screenH, duration: 210, easing: reactNative.Easing.in(reactNative.Easing.cubic), useNativeDriver: true }),
|
|
2826
|
-
reactNative.Animated.timing(backdrop, { toValue: 0, duration: 170, useNativeDriver: true })
|
|
2827
|
-
]).start();
|
|
2828
|
-
}
|
|
2829
|
-
}, [open, translateY, backdrop, screenH]);
|
|
2830
|
-
const pan = react.useRef(
|
|
2831
|
-
reactNative.PanResponder.create({
|
|
2832
|
-
onMoveShouldSetPanResponder: (_evt, g) => g.dy > 6 && Math.abs(g.dy) > Math.abs(g.dx) * 1.4,
|
|
2833
|
-
onPanResponderMove: (_evt, g) => {
|
|
2834
|
-
if (g.dy > 0) translateY.setValue(g.dy);
|
|
2835
|
-
},
|
|
2836
|
-
onPanResponderRelease: (_evt, g) => {
|
|
2837
|
-
if (g.dy > 110 || g.vy > 0.8) {
|
|
2838
|
-
minimizeRef.current();
|
|
2839
|
-
} else {
|
|
2840
|
-
reactNative.Animated.spring(translateY, { toValue: 0, useNativeDriver: true, damping: 30, stiffness: 360 }).start();
|
|
2841
|
-
}
|
|
2842
|
-
}
|
|
2843
|
-
})
|
|
2844
|
-
).current;
|
|
2845
|
-
const a = active ?? shown;
|
|
2846
|
-
if (!a) return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { pointerEvents: "none" });
|
|
2847
|
-
const isVerification = a.kind === "verification";
|
|
2848
|
-
const title = isVerification ? "Verification required" : "Action required";
|
|
2849
|
-
const subtitle = isVerification ? "Confirm the one-time code to continue" : "Review the details and confirm to continue";
|
|
2850
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [reactNative.StyleSheet.absoluteFill, sht.overlay], pointerEvents: open ? "auto" : "none", children: [
|
|
2851
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2852
|
-
reactNative.Animated.View,
|
|
2853
|
-
{
|
|
2854
|
-
pointerEvents: "none",
|
|
2855
|
-
style: [sht.backdrop, { backgroundColor: pal.overlay, opacity: backdrop }]
|
|
2856
|
-
}
|
|
2857
|
-
),
|
|
2858
|
-
open ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: reactNative.StyleSheet.absoluteFill, onPress: () => minimizeRef.current(), accessibilityLabel: "Dismiss" }) : null,
|
|
2859
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sht.kav, { paddingBottom: kbHeight }], pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2860
|
-
reactNative.Animated.View,
|
|
2861
|
-
{
|
|
2862
|
-
style: [
|
|
2863
|
-
sht.sheet,
|
|
2864
|
-
{
|
|
2865
|
-
backgroundColor: pal.sheetBg,
|
|
2866
|
-
borderColor: pal.border,
|
|
2867
|
-
// Cap to the space above the keyboard (and a top margin) so the
|
|
2868
|
-
// header stays visible and the footer sits above the keyboard;
|
|
2869
|
-
// the field area scrolls within whatever's left.
|
|
2870
|
-
maxHeight: Math.min(screenH * 0.9, screenH - kbHeight - 72),
|
|
2871
|
-
paddingBottom: kbHeight > 0 ? 16 : Math.max(insetBottom, 16) + 6,
|
|
2872
|
-
transform: [{ translateY }]
|
|
2873
|
-
}
|
|
2874
|
-
],
|
|
2875
|
-
children: [
|
|
2876
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ...pan.panHandlers, style: sht.handleZone, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sht.grabber, { backgroundColor: pal.grabber }] }) }),
|
|
2877
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: sht.header, children: [
|
|
2878
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sht.iconCircle, { backgroundColor: accent + (isDark ? "26" : "14") }], children: isVerification ? /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.ShieldCheck, { size: 20, color: accent, strokeWidth: 2 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Pencil, { size: 18, color: accent, strokeWidth: 2 }) }),
|
|
2879
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: sht.headerText, children: [
|
|
2880
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [sht.title, { color: pal.text }], children: title }),
|
|
2881
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [sht.subtitle, { color: pal.muted }], numberOfLines: 1, children: subtitle })
|
|
2882
|
-
] }),
|
|
2883
|
-
/* @__PURE__ */ jsxRuntime.jsx(SheetTimerPill, { prompt: a, accent, isDark }),
|
|
2884
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => minimizeRef.current(), hitSlop: 10, style: [sht.closeBtn, { backgroundColor: pal.fieldBg }], children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.X, { size: 16, color: pal.muted, strokeWidth: 2.2 }) })
|
|
2885
|
-
] }),
|
|
2886
|
-
a.status === "stale" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: sht.simpleBody, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.desc, { color: pal.muted }], children: "This request is no longer available." }) }) : expired ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: sht.simpleBody, children: [
|
|
2887
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.desc, { color: pal.muted }], children: isVerification ? "This verification request expired." : "This request expired." }),
|
|
2888
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.actions, children: /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { label: "Close", onPress: () => void onCancel(a.userActionId), accent }) })
|
|
2889
|
-
] }) : isVerification ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2890
|
-
VerificationBody,
|
|
2891
|
-
{
|
|
2892
|
-
prompt: a,
|
|
2893
|
-
expired,
|
|
2894
|
-
pal,
|
|
2895
|
-
accent,
|
|
2896
|
-
onSubmit,
|
|
2897
|
-
onCancel,
|
|
2898
|
-
onResend
|
|
2899
|
-
},
|
|
2900
|
-
a.userActionId
|
|
2901
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(FormBody, { prompt: a, pal, accent, isDark, onSubmit, onCancel }, a.userActionId)
|
|
2902
|
-
]
|
|
2903
|
-
}
|
|
2904
|
-
) })
|
|
2905
|
-
] });
|
|
2906
|
-
}
|
|
2907
|
-
function UserActionReopenCard({
|
|
2908
|
-
active,
|
|
2909
|
-
expired,
|
|
2910
|
-
isDark,
|
|
2911
|
-
accent,
|
|
2912
|
-
onReopen
|
|
2913
|
-
}) {
|
|
2914
|
-
if (expired || active.status === "stale") return null;
|
|
2915
|
-
const pal = uaPalette(isDark);
|
|
2916
|
-
const isVerification = active.kind === "verification";
|
|
2917
|
-
const title = isVerification ? "Verification required" : "Action required";
|
|
2918
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2919
|
-
reactNative.Pressable,
|
|
2920
|
-
{
|
|
2921
|
-
onPress: onReopen,
|
|
2922
|
-
accessibilityRole: "button",
|
|
2923
|
-
accessibilityLabel: `${title}. Tap to respond.`,
|
|
2924
|
-
android_ripple: { color: accent + "22" },
|
|
2925
|
-
style: [
|
|
2926
|
-
rc.card,
|
|
2927
|
-
{
|
|
2928
|
-
backgroundColor: pal.sheetBg,
|
|
2929
|
-
borderColor: accent + (isDark ? "59" : "40")
|
|
2930
|
-
}
|
|
2931
|
-
],
|
|
2932
|
-
children: [
|
|
2933
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [rc.iconCircle, { backgroundColor: accent + (isDark ? "26" : "14") }], children: isVerification ? /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.ShieldCheck, { size: 18, color: accent, strokeWidth: 2 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Pencil, { size: 16, color: accent, strokeWidth: 2 }) }),
|
|
2934
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: rc.textWrap, children: [
|
|
2935
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [rc.title, { color: pal.text }], numberOfLines: 1, children: title }),
|
|
2936
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [rc.subtitle, { color: pal.muted }], numberOfLines: 1, children: "Tap to respond and continue" })
|
|
2937
|
-
] }),
|
|
2938
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardTimerPill, { prompt: active, accent, isDark }),
|
|
2939
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.ChevronRight, { size: 18, color: pal.muted, strokeWidth: 2 })
|
|
2940
|
-
]
|
|
2941
|
-
}
|
|
2942
|
-
);
|
|
2943
|
-
}
|
|
2944
|
-
function NotificationStack({
|
|
2945
|
-
notifications,
|
|
2946
|
-
isDark,
|
|
2947
|
-
onDismiss
|
|
2948
|
-
}) {
|
|
2949
|
-
if (notifications.length === 0) return null;
|
|
2950
|
-
const pal = uaPalette(isDark);
|
|
2951
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.noteStack, children: notifications.map((n) => /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [ub.note, { backgroundColor: pal.fieldBg, borderColor: pal.border }], children: [
|
|
2952
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Info, { size: 14, color: pal.muted, strokeWidth: 1.9 }),
|
|
2953
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.noteText, { color: pal.text }], children: n.message }),
|
|
2954
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => onDismiss(n.id), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.X, { size: 14, color: pal.muted, strokeWidth: 2 }) })
|
|
2955
|
-
] }, n.id)) });
|
|
2956
|
-
}
|
|
2957
|
-
var ub = reactNative.StyleSheet.create({
|
|
2958
|
-
// Body wrapper can shrink within the sheet's (bounded) maxHeight so the inner
|
|
2959
|
-
// field scroll takes over for tall forms while the footer stays pinned.
|
|
2960
|
-
bodyWrap: { flexShrink: 1 },
|
|
2961
|
-
// Field scroll region. flexShrink:1 + no flexGrow → hugs content for short
|
|
2962
|
-
// forms (no scroll), shrinks and scrolls only when the form exceeds the sheet.
|
|
2963
|
-
fieldsScroll: { flexShrink: 1 },
|
|
2964
|
-
fieldsContent: { paddingBottom: 6 },
|
|
2965
|
-
messageBlock: { marginBottom: 18, marginTop: 2 },
|
|
2966
|
-
desc: { fontSize: 14.5, lineHeight: 21, marginBottom: 16 },
|
|
2967
|
-
form: { gap: 18 },
|
|
2968
|
-
field: { gap: 7 },
|
|
2969
|
-
label: { fontSize: 14, fontWeight: "600" },
|
|
2970
|
-
hint: { fontSize: 12.5, lineHeight: 17 },
|
|
2971
|
-
input: {
|
|
2972
|
-
borderWidth: 1,
|
|
2973
|
-
borderRadius: 12,
|
|
2974
|
-
paddingHorizontal: 14,
|
|
2975
|
-
paddingVertical: 13,
|
|
2976
|
-
fontSize: 16
|
|
2977
|
-
},
|
|
2978
|
-
error: { color: "#ef4444", fontSize: 12.5, marginTop: 2 },
|
|
2979
|
-
switchRow: { flexDirection: "row", alignItems: "center", gap: 12 },
|
|
2980
|
-
// Selectable option "boxes" for oneOf / enum single-select fields.
|
|
2981
|
-
optionList: { gap: 10 },
|
|
2982
|
-
optionRow: {
|
|
2983
|
-
flexDirection: "row",
|
|
2984
|
-
alignItems: "center",
|
|
2985
|
-
gap: 12,
|
|
2986
|
-
borderWidth: 1.5,
|
|
2987
|
-
borderRadius: 14,
|
|
2988
|
-
paddingHorizontal: 14,
|
|
2989
|
-
paddingVertical: 14
|
|
2990
|
-
},
|
|
2991
|
-
radio: {
|
|
2992
|
-
width: 20,
|
|
2993
|
-
height: 20,
|
|
2994
|
-
borderRadius: 10,
|
|
2995
|
-
borderWidth: 2,
|
|
2996
|
-
alignItems: "center",
|
|
2997
|
-
justifyContent: "center"
|
|
2998
|
-
},
|
|
2999
|
-
radioDot: { width: 10, height: 10, borderRadius: 5 },
|
|
3000
|
-
optionLabel: { flex: 1, fontSize: 15, letterSpacing: -0.1 },
|
|
3001
|
-
// Pinned footer (outside the fields scroll) — always visible.
|
|
3002
|
-
actions: { paddingTop: 16, gap: 14 },
|
|
3003
|
-
primaryBtn: { minHeight: 52, borderRadius: 14, borderWidth: 1, paddingVertical: 15, alignItems: "center", justifyContent: "center" },
|
|
3004
|
-
primaryBtnText: { color: "#fff", fontSize: 15.5, fontWeight: "600", letterSpacing: -0.1 },
|
|
3005
|
-
linkRow: { flexDirection: "row", justifyContent: "center", gap: 24 },
|
|
3006
|
-
link: { fontSize: 14, fontWeight: "500" },
|
|
3007
|
-
linkDanger: { color: "#dc2626" },
|
|
3008
|
-
codeRow: { flexDirection: "row", justifyContent: "center", gap: 9, position: "relative" },
|
|
3009
|
-
codeBox: {
|
|
3010
|
-
width: 46,
|
|
3011
|
-
height: 56,
|
|
3012
|
-
borderWidth: 1.5,
|
|
3013
|
-
borderRadius: 12,
|
|
3014
|
-
alignItems: "center",
|
|
3015
|
-
justifyContent: "center"
|
|
3016
|
-
},
|
|
3017
|
-
codeChar: { fontSize: 24, fontWeight: "600" },
|
|
3018
|
-
codeHidden: { ...reactNative.StyleSheet.absoluteFillObject, opacity: 0 },
|
|
3019
|
-
noteStack: { paddingHorizontal: 16, paddingBottom: 6, gap: 6 },
|
|
3020
|
-
note: {
|
|
3021
|
-
flexDirection: "row",
|
|
3022
|
-
alignItems: "center",
|
|
3023
|
-
gap: 8,
|
|
3024
|
-
borderWidth: 1,
|
|
3025
|
-
borderRadius: 12,
|
|
3026
|
-
paddingHorizontal: 12,
|
|
3027
|
-
paddingVertical: 10
|
|
3028
|
-
},
|
|
3029
|
-
noteText: { flex: 1, fontSize: 13.5, lineHeight: 19 }
|
|
3030
|
-
});
|
|
3031
|
-
var sht = reactNative.StyleSheet.create({
|
|
3032
|
-
// High stacking so the sheet sits above the chat input bar (which carries its
|
|
3033
|
-
// own zIndex/elevation) and everything else in the chat.
|
|
3034
|
-
overlay: { zIndex: 100, ...reactNative.Platform.select({ android: { elevation: 100 }, default: {} }) },
|
|
3035
|
-
backdrop: { ...reactNative.StyleSheet.absoluteFillObject },
|
|
3036
|
-
kav: { flex: 1, justifyContent: "flex-end" },
|
|
3037
|
-
sheet: {
|
|
3038
|
-
borderTopLeftRadius: 28,
|
|
3039
|
-
borderTopRightRadius: 28,
|
|
3040
|
-
borderWidth: reactNative.StyleSheet.hairlineWidth,
|
|
3041
|
-
borderBottomWidth: 0,
|
|
3042
|
-
paddingHorizontal: 22,
|
|
3043
|
-
paddingTop: 6,
|
|
3044
|
-
maxWidth: 640,
|
|
3045
|
-
width: "100%",
|
|
3046
|
-
alignSelf: "center",
|
|
3047
|
-
...reactNative.Platform.select({
|
|
3048
|
-
ios: {
|
|
3049
|
-
shadowColor: "#000",
|
|
3050
|
-
shadowOffset: { width: 0, height: -6 },
|
|
3051
|
-
shadowOpacity: 0.18,
|
|
3052
|
-
shadowRadius: 24
|
|
3053
|
-
},
|
|
3054
|
-
android: { elevation: 24 }
|
|
3055
|
-
})
|
|
3056
|
-
},
|
|
3057
|
-
handleZone: { alignItems: "center", paddingVertical: 10 },
|
|
3058
|
-
grabber: { width: 40, height: 5, borderRadius: 3 },
|
|
3059
|
-
header: { flexDirection: "row", alignItems: "center", gap: 12, marginTop: 2, marginBottom: 18 },
|
|
3060
|
-
iconCircle: { width: 40, height: 40, borderRadius: 20, alignItems: "center", justifyContent: "center" },
|
|
3061
|
-
headerText: { flex: 1, gap: 2 },
|
|
3062
|
-
title: { fontSize: 18, fontWeight: "700", letterSpacing: -0.3 },
|
|
3063
|
-
subtitle: { fontSize: 13, letterSpacing: -0.1 },
|
|
3064
|
-
timer: { borderWidth: 1, borderRadius: 999, paddingHorizontal: 11, paddingVertical: 5, minWidth: 46, alignItems: "center" },
|
|
3065
|
-
closeBtn: { width: 30, height: 30, borderRadius: 15, alignItems: "center", justifyContent: "center" },
|
|
3066
|
-
// Short, non-scrolling bodies (stale / expired notices).
|
|
3067
|
-
simpleBody: { paddingBottom: 8 }
|
|
3068
|
-
});
|
|
3069
|
-
var rc = reactNative.StyleSheet.create({
|
|
3070
|
-
card: {
|
|
3071
|
-
flexDirection: "row",
|
|
3072
|
-
alignItems: "center",
|
|
3073
|
-
gap: 12,
|
|
3074
|
-
borderWidth: 1.5,
|
|
3075
|
-
borderRadius: 18,
|
|
3076
|
-
paddingHorizontal: 14,
|
|
3077
|
-
paddingVertical: 13,
|
|
3078
|
-
marginVertical: 6
|
|
3079
|
-
},
|
|
3080
|
-
iconCircle: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center" },
|
|
3081
|
-
textWrap: { flex: 1, gap: 1 },
|
|
3082
|
-
title: { fontSize: 14.5, fontWeight: "700", letterSpacing: -0.2 },
|
|
3083
|
-
subtitle: { fontSize: 12.5, letterSpacing: -0.1 },
|
|
3084
|
-
timer: { borderRadius: 999, paddingHorizontal: 9, paddingVertical: 4, minWidth: 40, alignItems: "center" }
|
|
3085
|
-
});
|
|
3086
|
-
var PaymanChat = react.forwardRef(
|
|
3087
|
-
function PaymanChat2({ config, callbacks, children, onLoadMoreMessages, isLoadingMoreMessages = false, hasMoreMessages = false }, ref) {
|
|
3088
|
-
const accent = config.accent ?? DEFAULT_ACCENT;
|
|
3089
|
-
const systemScheme = reactNative.useColorScheme();
|
|
3090
|
-
const isDark = (config.colorScheme ?? systemScheme) === "dark";
|
|
3091
|
-
const ui = isDark ? {
|
|
3092
|
-
listBg: "#0d1719",
|
|
3093
|
-
emptyText: "rgba(255,255,255,0.55)"
|
|
3094
|
-
} : {
|
|
3095
|
-
listBg: "#ffffff",
|
|
3096
|
-
emptyText: "#6b7280"
|
|
3097
|
-
};
|
|
3098
|
-
const callbacksRef = react.useRef(callbacks);
|
|
3099
|
-
react.useEffect(() => {
|
|
3100
|
-
callbacksRef.current = callbacks;
|
|
3101
|
-
});
|
|
3102
|
-
const stableCallbacks = react.useMemo(() => ({
|
|
3103
|
-
onSessionIdChange: (id) => callbacksRef.current?.onSessionIdChange?.(id),
|
|
3104
|
-
onStreamStart: () => callbacksRef.current?.onStreamStart?.(),
|
|
3105
|
-
onStreamEnd: () => callbacksRef.current?.onStreamEnd?.(),
|
|
3106
|
-
onError: (e) => {
|
|
3107
|
-
console.error("[PaymanChat] stream error:", e.message, e);
|
|
3108
|
-
callbacksRef.current?.onError?.(e);
|
|
3109
|
-
},
|
|
3110
|
-
onStatusMessage: (m) => callbacksRef.current?.onStatusMessage?.(m),
|
|
3111
|
-
onStepsUpdate: (steps) => callbacksRef.current?.onStepsUpdate?.(steps),
|
|
3112
|
-
onUserActionRequired: (r) => {
|
|
3113
|
-
console.log("[PaymanChat] USER_ACTION_REQUIRED:", JSON.stringify({ kind: r?.kind, rawAction: r?.rawAction, userActionId: r?.userActionId, hasSchema: !!r?.requestedSchema }));
|
|
3114
|
-
callbacksRef.current?.onUserActionRequired?.(r);
|
|
3115
|
-
},
|
|
3116
|
-
onUserNotification: (n) => {
|
|
3117
|
-
console.log("[PaymanChat] USER_NOTIFICATION:", JSON.stringify({ id: n?.id, message: n?.message }));
|
|
3118
|
-
callbacksRef.current?.onUserNotification?.(n);
|
|
3119
|
-
}
|
|
3120
|
-
}), []);
|
|
3121
|
-
const {
|
|
3122
|
-
messages,
|
|
3123
|
-
sendMessage,
|
|
3124
|
-
isWaitingForResponse,
|
|
3125
|
-
resetSession,
|
|
3126
|
-
clearMessages,
|
|
3127
|
-
prependMessages,
|
|
3128
|
-
cancelStream,
|
|
3129
|
-
getSessionId,
|
|
3130
|
-
getMessages,
|
|
3131
|
-
userActionState,
|
|
3132
|
-
submitUserAction: submitUserAction2,
|
|
3133
|
-
cancelUserAction: cancelUserAction2,
|
|
3134
|
-
resendUserAction: resendUserAction2,
|
|
3135
|
-
dismissNotification
|
|
3136
|
-
} = useChatV2(config, stableCallbacks);
|
|
3137
|
-
react.useEffect(() => {
|
|
3138
|
-
console.log(
|
|
3139
|
-
"[PaymanChat] userActionState ->",
|
|
3140
|
-
"prompts:",
|
|
3141
|
-
userActionState.prompts.length,
|
|
3142
|
-
userActionState.prompts.map((p) => `${p.kind}:${p.status}`).join(","),
|
|
3143
|
-
"| notifications:",
|
|
3144
|
-
userActionState.notifications.length
|
|
3145
|
-
);
|
|
3146
|
-
}, [userActionState]);
|
|
3147
|
-
const activePrompt = react.useMemo(
|
|
3148
|
-
() => userActionState.prompts.find((p) => p.kind !== "notification"),
|
|
3149
|
-
[userActionState.prompts]
|
|
3150
|
-
);
|
|
3151
|
-
const expired = useExpiredFlag(activePrompt);
|
|
3152
|
-
const [dismissedActionId, setDismissedActionId] = react.useState(null);
|
|
3153
|
-
react.useEffect(() => {
|
|
3154
|
-
if (activePrompt) setDismissedActionId(null);
|
|
3155
|
-
}, [activePrompt?.userActionId, activePrompt?.subAction]);
|
|
3156
|
-
const sheetOpen = !!activePrompt && dismissedActionId !== activePrompt.userActionId;
|
|
3157
|
-
const showReopenCard = !!activePrompt && !sheetOpen && !expired && activePrompt.status !== "stale";
|
|
3158
|
-
const minimizeActivePrompt = react.useCallback(() => {
|
|
3159
|
-
setDismissedActionId((prev) => activePrompt?.userActionId ?? prev);
|
|
3160
|
-
}, [activePrompt?.userActionId]);
|
|
3161
|
-
const {
|
|
3162
|
-
transcribedText,
|
|
3163
|
-
isAvailable: voiceAvailable,
|
|
3164
|
-
isRecording,
|
|
3165
|
-
startRecording,
|
|
3166
|
-
stopRecording,
|
|
3167
|
-
clearTranscript
|
|
3168
|
-
} = useVoice(
|
|
3169
|
-
{ lang: "en-US", interimResults: true, continuous: true },
|
|
3170
|
-
{ onError: (e) => console.error("Voice error:", e) }
|
|
3171
|
-
);
|
|
3172
|
-
const [inputValue, setInputValue] = react.useState("");
|
|
3173
|
-
const [recordingSeconds, setRecordingSeconds] = react.useState(0);
|
|
3174
|
-
const recordingStartRef = react.useRef(null);
|
|
3175
|
-
const recordingTimerRef = react.useRef(null);
|
|
3176
|
-
react.useImperativeHandle(ref, () => ({ resetSession, clearMessages, cancelStream, getSessionId, getMessages }));
|
|
3177
|
-
const contextValue = react.useMemo(() => ({
|
|
3178
|
-
resetSession,
|
|
3179
|
-
clearMessages,
|
|
3180
|
-
prependMessages,
|
|
3181
|
-
cancelStream,
|
|
3182
|
-
getSessionId,
|
|
3183
|
-
getMessages,
|
|
3184
|
-
isWaitingForResponse
|
|
3185
|
-
}), [resetSession, clearMessages, prependMessages, cancelStream, getSessionId, getMessages, isWaitingForResponse]);
|
|
3186
|
-
react.useEffect(() => {
|
|
3187
|
-
if (isRecording) {
|
|
3188
|
-
recordingStartRef.current = Date.now();
|
|
3189
|
-
setRecordingSeconds(0);
|
|
3190
|
-
recordingTimerRef.current = setInterval(() => {
|
|
3191
|
-
if (recordingStartRef.current != null)
|
|
3192
|
-
setRecordingSeconds(Math.floor((Date.now() - recordingStartRef.current) / 1e3));
|
|
3193
|
-
}, 1e3);
|
|
3194
|
-
} else {
|
|
3195
|
-
recordingStartRef.current = null;
|
|
3196
|
-
if (recordingTimerRef.current) {
|
|
3197
|
-
clearInterval(recordingTimerRef.current);
|
|
3198
|
-
recordingTimerRef.current = null;
|
|
3199
|
-
}
|
|
3200
|
-
setRecordingSeconds(0);
|
|
3201
|
-
}
|
|
3202
|
-
return () => {
|
|
3203
|
-
if (recordingTimerRef.current) clearInterval(recordingTimerRef.current);
|
|
3204
|
-
};
|
|
3205
|
-
}, [isRecording]);
|
|
3206
|
-
const prevInput = react.useRef(inputValue);
|
|
3207
|
-
react.useEffect(() => {
|
|
3208
|
-
const wasEmpty = prevInput.current.trim() === "";
|
|
3209
|
-
const isEmpty2 = inputValue.trim() === "";
|
|
3210
|
-
prevInput.current = inputValue;
|
|
3211
|
-
if (!wasEmpty && isEmpty2) {
|
|
3212
|
-
clearTranscript();
|
|
3213
|
-
if (isRecording) stopRecording();
|
|
3214
|
-
}
|
|
3215
|
-
}, [inputValue, clearTranscript, isRecording, stopRecording]);
|
|
3216
|
-
const handleSend = react.useCallback(() => {
|
|
3217
|
-
if (isRecording) stopRecording();
|
|
3218
|
-
const text = inputValue.trim();
|
|
3219
|
-
if (!text || isWaitingForResponse) return;
|
|
3220
|
-
console.log("[PaymanChat] sending. baseUrl:", config.api?.baseUrl, "agentId:", config.agentId, "stage:", config.stage, "sessionOwnerId:", config.sessionParams?.id);
|
|
3221
|
-
setInputValue("");
|
|
3222
|
-
void sendMessage(text);
|
|
3223
|
-
}, [inputValue, isWaitingForResponse, sendMessage, isRecording, stopRecording, config]);
|
|
3224
|
-
const handleVoicePress = react.useCallback(async () => {
|
|
3225
|
-
if (!voiceAvailable) return;
|
|
3226
|
-
clearTranscript();
|
|
3227
|
-
await startRecording();
|
|
3228
|
-
}, [voiceAvailable, startRecording, clearTranscript]);
|
|
3229
|
-
const handleConfirmRecording = react.useCallback(() => {
|
|
3230
|
-
stopRecording();
|
|
3231
|
-
if (transcribedText.trim()) setInputValue(transcribedText);
|
|
3232
|
-
}, [stopRecording, transcribedText]);
|
|
3233
|
-
const handleCancelRecording = react.useCallback(() => {
|
|
3234
|
-
stopRecording();
|
|
3235
|
-
clearTranscript();
|
|
3236
|
-
}, [stopRecording, clearTranscript]);
|
|
3237
|
-
const isInputDisabled = !config.sessionParams?.id?.trim();
|
|
3238
|
-
const enableVoice = config.enableVoice !== false;
|
|
3239
|
-
const isEmpty = messages.length === 0;
|
|
3240
|
-
const prevMessageCountRef = react.useRef(messages.length);
|
|
3241
|
-
const typingMessageIds = react.useRef(/* @__PURE__ */ new Set());
|
|
3242
|
-
react.useEffect(() => {
|
|
3243
|
-
if (messages.length > prevMessageCountRef.current) {
|
|
3244
|
-
const newMsgs = messages.slice(prevMessageCountRef.current);
|
|
3245
|
-
newMsgs.forEach((m) => {
|
|
3246
|
-
if (m.role === "assistant") typingMessageIds.current.add(m.id);
|
|
3247
|
-
});
|
|
3248
|
-
}
|
|
3249
|
-
prevMessageCountRef.current = messages.length;
|
|
3250
|
-
}, [messages]);
|
|
3251
|
-
const scrollRef = react.useRef(null);
|
|
3252
|
-
const scrollToBottom = react.useCallback((animated = true) => {
|
|
3253
|
-
scrollRef.current?.scrollToEnd({ animated });
|
|
3254
|
-
}, []);
|
|
3255
|
-
react.useEffect(() => {
|
|
3256
|
-
const t = setTimeout(() => scrollToBottom(true), 60);
|
|
3257
|
-
return () => clearTimeout(t);
|
|
3258
|
-
}, [messages, scrollToBottom]);
|
|
3259
|
-
return /* @__PURE__ */ jsxRuntime.jsx(PaymanChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3260
|
-
reactNative.KeyboardAvoidingView,
|
|
3261
|
-
{
|
|
3262
|
-
style: s.container,
|
|
3263
|
-
behavior: reactNative.Platform.OS === "ios" ? "padding" : "height",
|
|
3264
|
-
keyboardVerticalOffset: 0,
|
|
3265
|
-
children: [
|
|
3266
|
-
children,
|
|
3267
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [s.messageListWrap, { backgroundColor: ui.listBg }], children: isEmpty && !isWaitingForResponse ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s.emptyState, children: [
|
|
3268
|
-
config.emptyStateComponent,
|
|
3269
|
-
config.emptyStateComponent != null && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { height: 16 } }),
|
|
3270
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s.emptyText, { color: ui.emptyText }], children: config.emptyStateText ?? "How can I help you today?" })
|
|
3271
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3272
|
-
reactNative.ScrollView,
|
|
3273
|
-
{
|
|
3274
|
-
ref: scrollRef,
|
|
3275
|
-
style: s.scrollList,
|
|
3276
|
-
contentContainerStyle: s.listContent,
|
|
3277
|
-
showsVerticalScrollIndicator: false,
|
|
3278
|
-
keyboardShouldPersistTaps: "handled",
|
|
3279
|
-
onContentSizeChange: () => scrollToBottom(false),
|
|
3280
|
-
children: [
|
|
3281
|
-
messages.map((msg) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3282
|
-
MessageBubble,
|
|
3283
|
-
{
|
|
3284
|
-
message: msg,
|
|
3285
|
-
accent,
|
|
3286
|
-
shouldType: typingMessageIds.current.has(msg.id),
|
|
3287
|
-
isDark,
|
|
3288
|
-
suppressText: activePrompt?.message && msg.role === "assistant" && msg.id === messages[messages.length - 1]?.id ? activePrompt.message : void 0
|
|
3289
|
-
},
|
|
3290
|
-
msg.id
|
|
3291
|
-
)),
|
|
3292
|
-
showReopenCard && activePrompt ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3293
|
-
UserActionReopenCard,
|
|
3294
|
-
{
|
|
3295
|
-
active: activePrompt,
|
|
3296
|
-
expired,
|
|
3297
|
-
isDark,
|
|
3298
|
-
accent,
|
|
3299
|
-
onReopen: () => setDismissedActionId(null)
|
|
3300
|
-
}
|
|
3301
|
-
) : null,
|
|
3302
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { height: 4 } })
|
|
3303
|
-
]
|
|
3304
|
-
}
|
|
3305
|
-
) }),
|
|
3306
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3307
|
-
NotificationStack,
|
|
3308
|
-
{
|
|
3309
|
-
notifications: userActionState.notifications,
|
|
3310
|
-
isDark,
|
|
3311
|
-
onDismiss: dismissNotification
|
|
3312
|
-
}
|
|
3313
|
-
),
|
|
3314
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.inputBarWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3315
|
-
InputBar,
|
|
3316
|
-
{
|
|
3317
|
-
value: inputValue,
|
|
3318
|
-
onChange: setInputValue,
|
|
3319
|
-
onSend: handleSend,
|
|
3320
|
-
onCancel: cancelStream,
|
|
3321
|
-
disabled: isInputDisabled,
|
|
3322
|
-
placeholder: config.placeholder ?? "Type your message...",
|
|
3323
|
-
isStreaming: isWaitingForResponse,
|
|
3324
|
-
accent,
|
|
3325
|
-
enableVoice,
|
|
3326
|
-
voiceAvailable: enableVoice && voiceAvailable,
|
|
3327
|
-
isRecording: enableVoice && isRecording,
|
|
3328
|
-
recordingSeconds,
|
|
3329
|
-
transcribedText,
|
|
3330
|
-
onVoicePress: handleVoicePress,
|
|
3331
|
-
onConfirmRecording: handleConfirmRecording,
|
|
3332
|
-
onCancelRecording: handleCancelRecording,
|
|
3333
|
-
onFocus: () => {
|
|
3334
|
-
},
|
|
3335
|
-
insetBottom: config.contentInsetBottom ?? (reactNative.Platform.OS === "ios" ? 20 : 8)
|
|
3336
|
-
}
|
|
3337
|
-
) }),
|
|
3338
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3339
|
-
UserActionSheet,
|
|
3340
|
-
{
|
|
3341
|
-
open: sheetOpen,
|
|
3342
|
-
active: activePrompt,
|
|
3343
|
-
expired,
|
|
3344
|
-
isDark,
|
|
3345
|
-
accent,
|
|
3346
|
-
insetBottom: config.contentInsetBottom ?? (reactNative.Platform.OS === "ios" ? 20 : 8),
|
|
3347
|
-
onSubmit: submitUserAction2,
|
|
3348
|
-
onCancel: cancelUserAction2,
|
|
3349
|
-
onResend: resendUserAction2,
|
|
3350
|
-
onMinimize: minimizeActivePrompt
|
|
3351
|
-
}
|
|
3352
|
-
)
|
|
3353
|
-
]
|
|
3354
|
-
}
|
|
3355
|
-
) });
|
|
3356
|
-
}
|
|
3357
|
-
);
|
|
3358
|
-
var s = reactNative.StyleSheet.create({
|
|
3359
|
-
container: { flex: 1, backgroundColor: "#f1f5f9" },
|
|
3360
|
-
messageListWrap: { flex: 1, minHeight: 0, backgroundColor: "#ffffff" },
|
|
3361
|
-
inputBarWrap: {
|
|
3362
|
-
width: "100%",
|
|
3363
|
-
zIndex: 2,
|
|
3364
|
-
backgroundColor: "#ffffff",
|
|
3365
|
-
...reactNative.Platform.select({ android: { elevation: 8 }, default: {} })
|
|
3366
|
-
},
|
|
3367
|
-
// ── Input bar ──
|
|
3368
|
-
outerBar: {
|
|
3369
|
-
width: "100%",
|
|
3370
|
-
backgroundColor: "#f1f5f9",
|
|
3371
|
-
borderTopWidth: 1,
|
|
3372
|
-
borderTopColor: "rgba(0,0,0,0.08)",
|
|
3373
|
-
paddingTop: 6,
|
|
3374
|
-
paddingHorizontal: 12,
|
|
3375
|
-
borderTopLeftRadius: 20,
|
|
3376
|
-
borderTopRightRadius: 20
|
|
3377
|
-
},
|
|
3378
|
-
outerBarVoice: {},
|
|
3379
|
-
column: { maxWidth: 672, width: "100%", alignSelf: "center", gap: 2 },
|
|
3380
|
-
inputWrap: { width: "100%", justifyContent: "center", paddingHorizontal: 4 },
|
|
3381
|
-
inputWrapRelative: { position: "relative" },
|
|
3382
|
-
input: {
|
|
3383
|
-
minHeight: 36,
|
|
3384
|
-
maxHeight: 110,
|
|
3385
|
-
paddingVertical: 8,
|
|
3386
|
-
paddingHorizontal: 16,
|
|
3387
|
-
fontSize: 16,
|
|
3388
|
-
color: "#1F2937",
|
|
3389
|
-
lineHeight: 22
|
|
3390
|
-
},
|
|
3391
|
-
inputDisabled: { opacity: 0.5 },
|
|
3392
|
-
inputHidden: { position: "absolute", left: 0, right: 0, top: 0, opacity: 0, zIndex: 0 },
|
|
3393
|
-
actionsRow: { flexDirection: "row", justifyContent: "flex-end", alignItems: "center", marginBottom: 2 },
|
|
3394
|
-
rightActions: { flexDirection: "row", alignItems: "center", gap: 6 },
|
|
3395
|
-
iconBtn: { width: 28, height: 28, justifyContent: "center", alignItems: "center", borderRadius: 14, overflow: "hidden" },
|
|
3396
|
-
iconBtnInner: { width: 28, height: 28, justifyContent: "center", alignItems: "center", backgroundColor: "rgba(0,0,0,0.1)", borderRadius: 14 },
|
|
3397
|
-
sendBtn: { width: 28, height: 28, borderRadius: 14, overflow: "hidden", justifyContent: "center", alignItems: "center" },
|
|
3398
|
-
sendBtnInner: { width: 28, height: 28, borderRadius: 14, justifyContent: "center", alignItems: "center" },
|
|
3399
|
-
btnDisabled: { opacity: 0.35 },
|
|
3400
|
-
pressed: { opacity: 0.7 },
|
|
3401
|
-
disclaimerWrap: { maxWidth: 672, width: "100%", alignSelf: "center", paddingHorizontal: 12, marginTop: 2 },
|
|
3402
|
-
disclaimerText: { fontSize: 10, lineHeight: 13, color: "#9CA3AF", textAlign: "center" },
|
|
3403
|
-
// ── Voice bar ──
|
|
3404
|
-
voiceBarContainer: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0, justifyContent: "center", zIndex: 1 },
|
|
3405
|
-
voiceBar: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", borderWidth: 1, borderRadius: 28, minHeight: 56, paddingHorizontal: 12, paddingVertical: 10, gap: 12 },
|
|
3406
|
-
voiceBarCircle: { width: 40, height: 40, borderRadius: 20, alignItems: "center", justifyContent: "center" },
|
|
3407
|
-
voiceConfirm: { backgroundColor: "#FFFFFF", borderWidth: 1 },
|
|
3408
|
-
voiceBarCenter: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: 10, marginLeft: 8 },
|
|
3409
|
-
voiceDots: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: 4 },
|
|
3410
|
-
voiceDot: { width: 4, borderRadius: 2 },
|
|
3411
|
-
voiceTimer: { fontSize: 15, fontWeight: "600", minWidth: 36, textAlign: "right" },
|
|
3412
|
-
// ── Messages ──
|
|
3413
|
-
scrollList: { flex: 1 },
|
|
3414
|
-
listContent: { paddingHorizontal: 16, paddingTop: 8, paddingBottom: 4, flexGrow: 1 },
|
|
3415
|
-
emptyState: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 32, paddingBottom: 60 },
|
|
3416
|
-
emptyText: { fontSize: 16, color: "#6b7280", textAlign: "center" }
|
|
3417
|
-
});
|
|
3418
|
-
var sd = reactNative.StyleSheet.create({
|
|
3419
|
-
bubbleRow: { flexDirection: "row", marginVertical: 3 },
|
|
3420
|
-
rowUser: { justifyContent: "flex-end" },
|
|
3421
|
-
rowAssistant: { justifyContent: "flex-start" },
|
|
3422
|
-
bubble: { maxWidth: "82%", paddingHorizontal: 14, paddingVertical: 10, borderRadius: 18 },
|
|
3423
|
-
bubbleUser: { borderBottomRightRadius: 4 },
|
|
3424
|
-
bubbleAssistant: { backgroundColor: "#f3f4f6", borderBottomLeftRadius: 4 },
|
|
3425
|
-
// Assistant bubbles stretch wider so tables and code blocks have room
|
|
3426
|
-
bubbleAssistantWide: { maxWidth: "97%" },
|
|
3427
|
-
bubbleError: { backgroundColor: "#fef2f2" },
|
|
3428
|
-
bubbleText: { fontSize: 15, lineHeight: 22 },
|
|
3429
|
-
textUser: { color: "#ffffff" },
|
|
3430
|
-
textAssistant: { color: "#111827" },
|
|
3431
|
-
errorText: { fontSize: 14, lineHeight: 20, color: "#dc2626" },
|
|
3432
|
-
partialErrorText: { fontSize: 12, color: "#dc2626", marginTop: 4 },
|
|
3433
|
-
cancelledText: { fontSize: 12, color: "#9ca3af", marginTop: 4, fontStyle: "italic" },
|
|
3434
|
-
thinkingRow: { flexDirection: "row", alignItems: "flex-start", gap: 6, marginBottom: 3 },
|
|
3435
|
-
thinkingIcon: { fontSize: 12, lineHeight: 20, width: 14 },
|
|
3436
|
-
thinkingStepText: { flex: 1, fontSize: 14, lineHeight: 20, color: "#374151", fontStyle: "italic" },
|
|
3437
|
-
thinkingStepDone: { color: "#9ca3af" }
|
|
3438
|
-
});
|
|
3439
|
-
function mdPalette(isDark) {
|
|
3440
|
-
return isDark ? {
|
|
3441
|
-
text: "rgba(255,255,255,0.92)",
|
|
3442
|
-
heading: "#ffffff",
|
|
3443
|
-
muted: "rgba(255,255,255,0.55)",
|
|
3444
|
-
border: "rgba(255,255,255,0.16)",
|
|
3445
|
-
borderSoft: "rgba(255,255,255,0.10)",
|
|
3446
|
-
headerBg: "rgba(255,255,255,0.07)",
|
|
3447
|
-
codeInlineBg: "rgba(255,255,255,0.12)",
|
|
3448
|
-
codeInlineText: "#e8eaed",
|
|
3449
|
-
codeBlockBg: "#0b1416",
|
|
3450
|
-
codeBlockText: "#f3f4f6",
|
|
3451
|
-
blockquoteBar: "rgba(255,255,255,0.22)",
|
|
3452
|
-
hr: "rgba(255,255,255,0.14)",
|
|
3453
|
-
link: "#34c3cc"
|
|
3454
|
-
} : {
|
|
3455
|
-
text: "#1f2937",
|
|
3456
|
-
heading: "#111827",
|
|
3457
|
-
muted: "#4b5563",
|
|
3458
|
-
border: "#d1d5db",
|
|
3459
|
-
borderSoft: "#e5e7eb",
|
|
3460
|
-
headerBg: "#f3f4f6",
|
|
3461
|
-
codeInlineBg: "#e5e7eb",
|
|
3462
|
-
codeInlineText: "#1f2937",
|
|
3463
|
-
codeBlockBg: "#1f2937",
|
|
3464
|
-
codeBlockText: "#f9fafb",
|
|
3465
|
-
blockquoteBar: "#d1d5db",
|
|
3466
|
-
hr: "#e5e7eb",
|
|
3467
|
-
link: "#00858d"
|
|
3468
|
-
};
|
|
3469
|
-
}
|
|
3470
|
-
var TABLE_CHAR_W = 7.6;
|
|
3471
|
-
var TABLE_CELL_PAD = 26;
|
|
3472
|
-
var TABLE_MIN_W = 44;
|
|
3473
|
-
var TABLE_MAX_W = 220;
|
|
3474
|
-
function nodeText(node) {
|
|
3475
|
-
if (!node) return "";
|
|
3476
|
-
if (Array.isArray(node.children) && node.children.length) {
|
|
3477
|
-
return node.children.map(nodeText).join("");
|
|
3478
|
-
}
|
|
3479
|
-
return typeof node.content === "string" ? node.content : "";
|
|
3480
|
-
}
|
|
3481
|
-
function tableMatrix(tableNode) {
|
|
3482
|
-
const rows = [];
|
|
3483
|
-
const walk = (n) => {
|
|
3484
|
-
if (!n) return;
|
|
3485
|
-
if (n.type === "tr") {
|
|
3486
|
-
rows.push((n.children || []).map(nodeText));
|
|
3487
|
-
return;
|
|
3488
|
-
}
|
|
3489
|
-
(n.children || []).forEach(walk);
|
|
3490
|
-
};
|
|
3491
|
-
walk(tableNode);
|
|
3492
|
-
return rows;
|
|
3493
|
-
}
|
|
3494
|
-
function makeMdRules(isDark, accent) {
|
|
3495
|
-
const c = mdPalette(isDark);
|
|
3496
|
-
const headerBg = isDark ? accent + "2E" : accent + "14";
|
|
3497
|
-
const headerText = isDark ? "#8fe3e9" : accent;
|
|
3498
|
-
return {
|
|
3499
|
-
table: (node) => {
|
|
3500
|
-
const rows = tableMatrix(node);
|
|
3501
|
-
if (rows.length === 0) return null;
|
|
3502
|
-
const ncols = Math.max(...rows.map((r) => r.length));
|
|
3503
|
-
const colW = [];
|
|
3504
|
-
for (let ci = 0; ci < ncols; ci++) {
|
|
3505
|
-
let maxLen = 0;
|
|
3506
|
-
for (const r of rows) maxLen = Math.max(maxLen, (r[ci] || "").length);
|
|
3507
|
-
colW[ci] = Math.min(
|
|
3508
|
-
TABLE_MAX_W,
|
|
3509
|
-
Math.max(TABLE_MIN_W, Math.round(maxLen * TABLE_CHAR_W) + TABLE_CELL_PAD)
|
|
3510
|
-
);
|
|
3511
|
-
}
|
|
3512
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3513
|
-
reactNative.ScrollView,
|
|
3514
|
-
{
|
|
3515
|
-
horizontal: true,
|
|
3516
|
-
showsHorizontalScrollIndicator: true,
|
|
3517
|
-
style: { marginVertical: 8 },
|
|
3518
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { borderWidth: 1, borderColor: c.border, borderRadius: 8, overflow: "hidden" }, children: rows.map((cells, ri) => {
|
|
3519
|
-
const isHeader = ri === 0;
|
|
3520
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3521
|
-
reactNative.View,
|
|
3522
|
-
{
|
|
3523
|
-
style: {
|
|
3524
|
-
flexDirection: "row",
|
|
3525
|
-
backgroundColor: isHeader ? headerBg : "transparent",
|
|
3526
|
-
borderTopWidth: ri === 0 ? 0 : 1,
|
|
3527
|
-
borderTopColor: c.borderSoft
|
|
3528
|
-
},
|
|
3529
|
-
children: Array.from({ length: ncols }).map((_, ci) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3530
|
-
reactNative.View,
|
|
3531
|
-
{
|
|
3532
|
-
style: {
|
|
3533
|
-
width: colW[ci],
|
|
3534
|
-
paddingHorizontal: 12,
|
|
3535
|
-
paddingVertical: 9,
|
|
3536
|
-
borderRightWidth: ci === ncols - 1 ? 0 : 1,
|
|
3537
|
-
borderRightColor: c.borderSoft,
|
|
3538
|
-
justifyContent: "center"
|
|
3539
|
-
},
|
|
3540
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3541
|
-
reactNative.Text,
|
|
3542
|
-
{
|
|
3543
|
-
style: {
|
|
3544
|
-
fontSize: 13,
|
|
3545
|
-
lineHeight: 18,
|
|
3546
|
-
color: isHeader ? headerText : c.text,
|
|
3547
|
-
fontWeight: isHeader ? "700" : "400"
|
|
3548
|
-
},
|
|
3549
|
-
children: cells[ci] ?? ""
|
|
3550
|
-
}
|
|
3551
|
-
)
|
|
3552
|
-
},
|
|
3553
|
-
ci
|
|
3554
|
-
))
|
|
3555
|
-
},
|
|
3556
|
-
ri
|
|
3557
|
-
);
|
|
3558
|
-
}) })
|
|
3559
|
-
},
|
|
3560
|
-
node.key
|
|
3561
|
-
);
|
|
3562
|
-
}
|
|
3563
|
-
};
|
|
3564
|
-
}
|
|
3565
|
-
function makeMdStyles(isDark) {
|
|
3566
|
-
const c = mdPalette(isDark);
|
|
3567
|
-
const mono = reactNative.Platform.OS === "ios" ? "Menlo" : "monospace";
|
|
3568
|
-
return {
|
|
3569
|
-
// Body
|
|
3570
|
-
body: { color: c.text, fontSize: 14, lineHeight: 21 },
|
|
3571
|
-
paragraph: { marginTop: 0, marginBottom: 4 },
|
|
3572
|
-
// Emphasis
|
|
3573
|
-
strong: { fontWeight: "700", color: c.text },
|
|
3574
|
-
em: { fontStyle: "italic", color: c.text },
|
|
3575
|
-
// Links
|
|
3576
|
-
link: { color: c.link, textDecorationLine: "underline" },
|
|
3577
|
-
// Inline code
|
|
3578
|
-
code_inline: {
|
|
3579
|
-
backgroundColor: c.codeInlineBg,
|
|
3580
|
-
color: c.codeInlineText,
|
|
3581
|
-
fontFamily: mono,
|
|
3582
|
-
fontSize: 12,
|
|
3583
|
-
paddingHorizontal: 4,
|
|
3584
|
-
borderRadius: 4
|
|
3585
|
-
},
|
|
3586
|
-
code_inline_container: { borderRadius: 4 },
|
|
3587
|
-
// Code blocks
|
|
3588
|
-
fence: { backgroundColor: c.codeBlockBg, borderRadius: 8, padding: 12, marginVertical: 6 },
|
|
3589
|
-
code_block: { backgroundColor: c.codeBlockBg, borderRadius: 8, padding: 12, marginVertical: 6 },
|
|
3590
|
-
fence_code: { color: c.codeBlockText, fontFamily: mono, fontSize: 12 },
|
|
3591
|
-
// Lists
|
|
3592
|
-
bullet_list: { marginVertical: 4 },
|
|
3593
|
-
ordered_list: { marginVertical: 4 },
|
|
3594
|
-
list_item: { marginVertical: 1 },
|
|
3595
|
-
bullet_list_icon: { color: c.muted, marginTop: 6 },
|
|
3596
|
-
ordered_list_icon: { color: c.muted },
|
|
3597
|
-
list_item_text: { fontSize: 14, color: c.text },
|
|
3598
|
-
// Headings
|
|
3599
|
-
heading1: { fontSize: 18, fontWeight: "700", color: c.heading, marginBottom: 6, marginTop: 8 },
|
|
3600
|
-
heading2: { fontSize: 16, fontWeight: "700", color: c.heading, marginBottom: 4, marginTop: 6 },
|
|
3601
|
-
heading3: { fontSize: 15, fontWeight: "600", color: c.heading, marginBottom: 4, marginTop: 4 },
|
|
3602
|
-
// Blockquote / HR
|
|
3603
|
-
blockquote: { borderLeftWidth: 3, borderLeftColor: c.blockquoteBar, paddingLeft: 10, marginVertical: 4 },
|
|
3604
|
-
hr: { backgroundColor: c.hr, height: 1, marginVertical: 8 }
|
|
3605
|
-
// Tables are rendered by a fully custom `table` rule (see makeMdRules) —
|
|
3606
|
-
// no per-cell styles needed here.
|
|
3607
|
-
};
|
|
3608
|
-
}
|
|
3609
|
-
var MD_STYLES_LIGHT = makeMdStyles(false);
|
|
3610
|
-
var MD_STYLES_DARK = makeMdStyles(true);
|
|
3611
|
-
function cn(...inputs) {
|
|
3612
|
-
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
3613
|
-
}
|
|
3614
|
-
|
|
3615
|
-
// src/utils/formatDate.ts
|
|
3616
|
-
function formatDate(timestamp) {
|
|
3617
|
-
const date = typeof timestamp === "string" ? new Date(timestamp) : timestamp;
|
|
3618
|
-
const now = /* @__PURE__ */ new Date();
|
|
3619
|
-
const diffMs = now.getTime() - date.getTime();
|
|
3620
|
-
const diffMins = Math.floor(diffMs / 6e4);
|
|
3621
|
-
const diffHours = Math.floor(diffMs / 36e5);
|
|
3622
|
-
const diffDays = Math.floor(diffMs / 864e5);
|
|
3623
|
-
if (diffMins < 1) {
|
|
3624
|
-
return "Just now";
|
|
3625
|
-
}
|
|
3626
|
-
if (diffMins < 60) {
|
|
3627
|
-
return `${diffMins}m ago`;
|
|
3628
|
-
}
|
|
3629
|
-
if (diffHours < 24) {
|
|
3630
|
-
return `${diffHours}h ago`;
|
|
3631
|
-
}
|
|
3632
|
-
if (diffDays < 7) {
|
|
3633
|
-
return `${diffDays}d ago`;
|
|
3634
|
-
}
|
|
3635
|
-
return date.toLocaleDateString("en-US", {
|
|
3636
|
-
month: "short",
|
|
3637
|
-
day: "numeric",
|
|
3638
|
-
year: date.getFullYear() !== now.getFullYear() ? "numeric" : void 0
|
|
3639
|
-
});
|
|
3640
|
-
}
|
|
3641
|
-
function captureSentryError(error, context) {
|
|
3642
|
-
if (!Sentry__namespace.getClient()) return;
|
|
3643
|
-
const tags = {};
|
|
3644
|
-
if (context.executionId) tags.executionId = context.executionId;
|
|
3645
|
-
if (context.sessionId) tags.sessionId = context.sessionId;
|
|
3646
|
-
if (context.route) tags.route = context.route;
|
|
3647
|
-
if (context.cfRay) tags.cfRay = context.cfRay;
|
|
3648
|
-
if (context.customerId) tags.customerId = context.customerId;
|
|
3649
|
-
if (context.customerEmail) tags.customerEmail = context.customerEmail;
|
|
3650
|
-
const contexts = {
|
|
3651
|
-
chat_session: {
|
|
3652
|
-
sessionId: context.sessionId ?? null,
|
|
3653
|
-
sessionOwnerId: context.sessionOwnerId ?? null,
|
|
3654
|
-
executionId: context.executionId ?? null,
|
|
3655
|
-
agentId: context.agentId ?? null,
|
|
3656
|
-
cfRay: context.cfRay ?? null,
|
|
3657
|
-
customerId: context.customerId ?? null,
|
|
3658
|
-
customerEmail: context.customerEmail ?? null
|
|
3659
|
-
}
|
|
3660
|
-
};
|
|
3661
|
-
if (typeof error === "string") {
|
|
3662
|
-
Sentry__namespace.captureMessage(error, { level: "error", tags, contexts });
|
|
3663
|
-
} else {
|
|
3664
|
-
Sentry__namespace.captureException(error, { tags, contexts });
|
|
3665
|
-
}
|
|
3666
|
-
}
|
|
3667
|
-
|
|
3668
|
-
exports.PaymanChat = PaymanChat;
|
|
3669
|
-
exports.PaymanChatContext = PaymanChatContext;
|
|
3670
|
-
exports.UserActionStaleError = UserActionStaleError;
|
|
3671
|
-
exports.cancelUserAction = cancelUserAction;
|
|
3672
|
-
exports.captureSentryError = captureSentryError;
|
|
3673
|
-
exports.cn = cn;
|
|
3674
|
-
exports.formatDate = formatDate;
|
|
3675
|
-
exports.resendUserAction = resendUserAction;
|
|
3676
|
-
exports.submitUserAction = submitUserAction;
|
|
3677
|
-
exports.useChatV2 = useChatV2;
|
|
3678
|
-
exports.usePaymanChat = usePaymanChat;
|
|
3679
|
-
exports.useVoice = useVoice;
|
|
3680
|
-
//# sourceMappingURL=index.native.js.map
|
|
3681
|
-
//# sourceMappingURL=index.native.js.map
|