@paymanai/payman-ask-sdk 4.0.17 → 4.0.19

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.
@@ -1099,6 +1099,9 @@ async function cancelUserAction(config, userActionId) {
1099
1099
  async function resendUserAction(config, userActionId) {
1100
1100
  return sendUserActionRequest(config, userActionId, "resend");
1101
1101
  }
1102
+ async function expireUserAction(config, userActionId) {
1103
+ return sendUserActionRequest(config, userActionId, "expired");
1104
+ }
1102
1105
  var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1103
1106
  function upsertPrompt(prompts, req) {
1104
1107
  const active = { ...req, status: "pending" };
@@ -1390,6 +1393,19 @@ function useChatV2(config, callbacks = {}) {
1390
1393
  },
1391
1394
  [setPromptStatus]
1392
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
+ );
1393
1409
  const dismissNotification = react.useCallback((id) => {
1394
1410
  setUserActionState((prev) => ({
1395
1411
  ...prev,
@@ -1469,6 +1485,7 @@ function useChatV2(config, callbacks = {}) {
1469
1485
  submitUserAction: submitUserAction2,
1470
1486
  cancelUserAction: cancelUserAction2,
1471
1487
  resendUserAction: resendUserAction2,
1488
+ expireUserAction: expireUserAction2,
1472
1489
  dismissNotification
1473
1490
  };
1474
1491
  }
@@ -1687,6 +1704,122 @@ function useVoice(config = {}, callbacks = {}) {
1687
1704
  reset
1688
1705
  };
1689
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
+ }
1690
1823
  var PaymanChatContext = react.createContext(void 0);
1691
1824
  function usePaymanChat() {
1692
1825
  const context = react.useContext(PaymanChatContext);
@@ -1767,7 +1900,8 @@ function InputBar({
1767
1900
  onFocus,
1768
1901
  insetBottom
1769
1902
  }) {
1770
- const canSend = !disabled && value.trim().length > 0;
1903
+ const canSend = !disabled && !isStreaming && value.trim().length > 0;
1904
+ const voiceDisabled = !voiceAvailable || disabled || isStreaming;
1771
1905
  const showVoiceBar = enableVoice && isRecording;
1772
1906
  const showVoiceBtn = enableVoice && !isRecording;
1773
1907
  const [keyboardOpen, setKeyboardOpen] = react.useState(false);
@@ -1875,12 +2009,8 @@ function InputBar({
1875
2009
  reactNative.Pressable,
1876
2010
  {
1877
2011
  onPress: onVoicePress,
1878
- disabled: !voiceAvailable || disabled,
1879
- style: ({ pressed }) => [
1880
- s.iconBtn,
1881
- (!voiceAvailable || disabled) && s.btnDisabled,
1882
- pressed && s.pressed
1883
- ],
2012
+ disabled: voiceDisabled,
2013
+ style: [s.iconBtn, voiceDisabled && s.btnDisabled],
1884
2014
  accessibilityLabel: "Voice input",
1885
2015
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.iconBtnInner, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Mic, { size: 16, color: "#6B7280", strokeWidth: 2 }) })
1886
2016
  }
@@ -1888,13 +2018,9 @@ function InputBar({
1888
2018
  /* @__PURE__ */ jsxRuntime.jsx(
1889
2019
  reactNative.Pressable,
1890
2020
  {
1891
- onPress: isStreaming ? onCancel : onSend,
1892
- disabled: !isStreaming && !canSend,
1893
- style: ({ pressed }) => [
1894
- s.sendBtn,
1895
- !isStreaming && !canSend && s.btnDisabled,
1896
- pressed && s.pressed
1897
- ],
2021
+ onPress: onSend,
2022
+ disabled: !canSend,
2023
+ style: [s.sendBtn, !canSend && s.btnDisabled],
1898
2024
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [s.sendBtnInner, { backgroundColor: accent }], children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Send, { size: 14, color: "#FFFFFF" }) })
1899
2025
  }
1900
2026
  )
@@ -2161,11 +2287,27 @@ function stripIncompleteImageToken(text) {
2161
2287
  if (/^!\[[^\]]*\]\([^)]*\)/.test(after)) return text;
2162
2288
  return text.slice(0, lastBang);
2163
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
+ }
2164
2305
  function AssistantBubble({
2165
2306
  message,
2166
2307
  shouldType,
2167
2308
  isDark,
2168
- accent
2309
+ accent,
2310
+ suppressText
2169
2311
  }) {
2170
2312
  const isCurrentlyStreaming = !!message.isStreaming && !message.isCancelled;
2171
2313
  const mdStyles = isDark ? MD_STYLES_DARK : MD_STYLES_LIGHT;
@@ -2176,7 +2318,8 @@ function AssistantBubble({
2176
2318
  const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
2177
2319
  if (!raw) return "";
2178
2320
  const normalized = raw.replace(/\\n/g, "\n");
2179
- return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
2321
+ const cleaned = message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
2322
+ return stripDuplicatePromptText(cleaned, suppressText);
2180
2323
  })();
2181
2324
  const isThinkingStreaming = isCurrentlyStreaming && !rawResponseContent && !message.isError;
2182
2325
  const hasEverStreamed = react.useRef(!!message.isStreaming);
@@ -2206,10 +2349,740 @@ function AssistantBubble({
2206
2349
  message.isError && displayedText && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sd.partialErrorText, children: message.errorDetails || "The response was interrupted." })
2207
2350
  ] }) });
2208
2351
  }
2209
- function MessageBubble({ message, accent, shouldType, isDark }) {
2352
+ function MessageBubble({ message, accent, shouldType, isDark, suppressText }) {
2210
2353
  if (message.role === "user") return /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { message, accent });
2211
- return /* @__PURE__ */ jsxRuntime.jsx(AssistantBubble, { message, shouldType, isDark, 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
+ );
2212
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
+ });
2213
3086
  var PaymanChat = react.forwardRef(
2214
3087
  function PaymanChat2({ config, callbacks, children, onLoadMoreMessages, isLoadingMoreMessages = false, hasMoreMessages = false }, ref) {
2215
3088
  const accent = config.accent ?? DEFAULT_ACCENT;
@@ -2236,8 +3109,14 @@ var PaymanChat = react.forwardRef(
2236
3109
  },
2237
3110
  onStatusMessage: (m) => callbacksRef.current?.onStatusMessage?.(m),
2238
3111
  onStepsUpdate: (steps) => callbacksRef.current?.onStepsUpdate?.(steps),
2239
- onUserActionRequired: (r) => callbacksRef.current?.onUserActionRequired?.(r),
2240
- onUserNotification: (n) => callbacksRef.current?.onUserNotification?.(n)
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
+ }
2241
3120
  }), []);
2242
3121
  const {
2243
3122
  messages,
@@ -2248,8 +3127,37 @@ var PaymanChat = react.forwardRef(
2248
3127
  prependMessages,
2249
3128
  cancelStream,
2250
3129
  getSessionId,
2251
- getMessages
3130
+ getMessages,
3131
+ userActionState,
3132
+ submitUserAction: submitUserAction2,
3133
+ cancelUserAction: cancelUserAction2,
3134
+ resendUserAction: resendUserAction2,
3135
+ dismissNotification
2252
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]);
2253
3161
  const {
2254
3162
  transcribedText,
2255
3163
  isAvailable: voiceAvailable,
@@ -2326,7 +3234,7 @@ var PaymanChat = react.forwardRef(
2326
3234
  stopRecording();
2327
3235
  clearTranscript();
2328
3236
  }, [stopRecording, clearTranscript]);
2329
- const isInputDisabled = isWaitingForResponse || !config.sessionParams?.id?.trim();
3237
+ const isInputDisabled = !config.sessionParams?.id?.trim();
2330
3238
  const enableVoice = config.enableVoice !== false;
2331
3239
  const isEmpty = messages.length === 0;
2332
3240
  const prevMessageCountRef = react.useRef(messages.length);
@@ -2376,14 +3284,33 @@ var PaymanChat = react.forwardRef(
2376
3284
  message: msg,
2377
3285
  accent,
2378
3286
  shouldType: typingMessageIds.current.has(msg.id),
2379
- isDark
3287
+ isDark,
3288
+ suppressText: activePrompt?.message && msg.role === "assistant" && msg.id === messages[messages.length - 1]?.id ? activePrompt.message : void 0
2380
3289
  },
2381
3290
  msg.id
2382
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,
2383
3302
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { height: 4 } })
2384
3303
  ]
2385
3304
  }
2386
3305
  ) }),
3306
+ /* @__PURE__ */ jsxRuntime.jsx(
3307
+ NotificationStack,
3308
+ {
3309
+ notifications: userActionState.notifications,
3310
+ isDark,
3311
+ onDismiss: dismissNotification
3312
+ }
3313
+ ),
2387
3314
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.inputBarWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
2388
3315
  InputBar,
2389
3316
  {
@@ -2407,7 +3334,22 @@ var PaymanChat = react.forwardRef(
2407
3334
  },
2408
3335
  insetBottom: config.contentInsetBottom ?? (reactNative.Platform.OS === "ios" ? 20 : 8)
2409
3336
  }
2410
- ) })
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
+ )
2411
3353
  ]
2412
3354
  }
2413
3355
  ) });