@paymanai/payman-ask-sdk 4.0.17 → 4.0.18

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.
@@ -868,6 +868,17 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
868
868
  signal: abortController.signal,
869
869
  onEvent: (event) => {
870
870
  if (abortController.signal.aborted) return;
871
+ try {
872
+ const et = event?.eventType;
873
+ if (et === "RUN_IN_PROGRESS" || et === "INTENT_PROGRESS" || et === "THINKING_DELTA") {
874
+ const len = (event.partialText ?? event.text ?? "").length;
875
+ console.log(`[stream] ${et} (+${len} chars)`);
876
+ } else {
877
+ console.log(`[stream] ${et ?? "?"}:`, JSON.stringify(event));
878
+ }
879
+ } catch {
880
+ console.log("[stream] (unserializable event)", event?.eventType);
881
+ }
871
882
  processStreamEventV2(event, state);
872
883
  if (state.lastUserAction) {
873
884
  callbacksRef.current.onUserActionRequired?.(state.lastUserAction);
@@ -1687,6 +1698,122 @@ function useVoice(config = {}, callbacks = {}) {
1687
1698
  reset
1688
1699
  };
1689
1700
  }
1701
+ function classifyField(field) {
1702
+ if (!field) return "text";
1703
+ if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
1704
+ switch (field.type) {
1705
+ case "boolean":
1706
+ return "boolean";
1707
+ case "integer":
1708
+ return "integer";
1709
+ case "number":
1710
+ return "decimal";
1711
+ case "string":
1712
+ return "text";
1713
+ default:
1714
+ return "text";
1715
+ }
1716
+ }
1717
+ function isNestedOrUnsupported(field) {
1718
+ if (!field) return false;
1719
+ if (field.type === "object" || field.type === "array") return true;
1720
+ if ("properties" in field && field.properties != null) return true;
1721
+ if ("items" in field && field.items != null) return true;
1722
+ return false;
1723
+ }
1724
+ function getOptions(field) {
1725
+ if (!field || !Array.isArray(field.oneOf)) return [];
1726
+ return field.oneOf.filter(
1727
+ (o) => !!o && typeof o === "object" && typeof o.const === "string"
1728
+ );
1729
+ }
1730
+ function isRequired(schema, key) {
1731
+ return Array.isArray(schema?.required) && schema.required.includes(key);
1732
+ }
1733
+ function coerceValue(field, raw) {
1734
+ const widget = classifyField(field);
1735
+ if (widget === "boolean") {
1736
+ if (typeof raw === "boolean") return raw;
1737
+ if (raw === "true") return true;
1738
+ if (raw === "false") return false;
1739
+ return Boolean(raw);
1740
+ }
1741
+ if (widget === "integer" || widget === "decimal") {
1742
+ if (raw === "" || raw == null) return void 0;
1743
+ const num = typeof raw === "number" ? raw : Number(String(raw).trim());
1744
+ if (Number.isNaN(num)) return void 0;
1745
+ return widget === "integer" ? Math.trunc(num) : num;
1746
+ }
1747
+ if (raw == null) return void 0;
1748
+ const str = String(raw);
1749
+ return str === "" ? void 0 : str;
1750
+ }
1751
+ function defaultValueFor(field) {
1752
+ if (!field || field.default === void 0) {
1753
+ return classifyField(field) === "boolean" ? false : "";
1754
+ }
1755
+ return field.default;
1756
+ }
1757
+ function validateField(field, value, required) {
1758
+ const widget = classifyField(field);
1759
+ const label = field?.title || "This field";
1760
+ const isEmpty = value === void 0 || value === null || typeof value === "string" && value.trim() === "";
1761
+ if (isEmpty) {
1762
+ if (required && widget !== "boolean") return `${label} is required.`;
1763
+ return null;
1764
+ }
1765
+ if (widget === "integer" || widget === "decimal") {
1766
+ const num = typeof value === "number" ? value : Number(value);
1767
+ if (Number.isNaN(num)) return `${label} must be a number.`;
1768
+ if (widget === "integer" && !Number.isInteger(num)) {
1769
+ return `${label} must be a whole number.`;
1770
+ }
1771
+ if (typeof field?.minimum === "number" && num < field.minimum) {
1772
+ return `${label} must be at least ${field.minimum}.`;
1773
+ }
1774
+ if (typeof field?.maximum === "number" && num > field.maximum) {
1775
+ return `${label} must be at most ${field.maximum}.`;
1776
+ }
1777
+ return null;
1778
+ }
1779
+ if (widget === "select") {
1780
+ const allowed = getOptions(field).map((o) => o.const);
1781
+ if (allowed.length > 0 && !allowed.includes(String(value))) {
1782
+ return `${label} has an invalid selection.`;
1783
+ }
1784
+ return null;
1785
+ }
1786
+ const str = String(value);
1787
+ if (typeof field?.minLength === "number" && str.length < field.minLength) {
1788
+ return `${label} must be at least ${field.minLength} characters.`;
1789
+ }
1790
+ if (typeof field?.maxLength === "number" && str.length > field.maxLength) {
1791
+ return `${label} must be at most ${field.maxLength} characters.`;
1792
+ }
1793
+ return null;
1794
+ }
1795
+ function renderableFields(schema) {
1796
+ const props = schema?.properties;
1797
+ if (!props) return [];
1798
+ return Object.entries(props).filter(([, field]) => !isNestedOrUnsupported(field));
1799
+ }
1800
+ function validateForm(schema, values) {
1801
+ const errors = {};
1802
+ for (const [key, field] of renderableFields(schema)) {
1803
+ const coerced = coerceValue(field, values[key]);
1804
+ const err = validateField(field, coerced, isRequired(schema, key));
1805
+ if (err) errors[key] = err;
1806
+ }
1807
+ return errors;
1808
+ }
1809
+ function buildContent(schema, values) {
1810
+ const content = {};
1811
+ for (const [key, field] of renderableFields(schema)) {
1812
+ const coerced = coerceValue(field, values[key]);
1813
+ if (coerced !== void 0) content[key] = coerced;
1814
+ }
1815
+ return content;
1816
+ }
1690
1817
  var PaymanChatContext = react.createContext(void 0);
1691
1818
  function usePaymanChat() {
1692
1819
  const context = react.useContext(PaymanChatContext);
@@ -1767,7 +1894,8 @@ function InputBar({
1767
1894
  onFocus,
1768
1895
  insetBottom
1769
1896
  }) {
1770
- const canSend = !disabled && value.trim().length > 0;
1897
+ const canSend = !disabled && !isStreaming && value.trim().length > 0;
1898
+ const voiceDisabled = !voiceAvailable || disabled || isStreaming;
1771
1899
  const showVoiceBar = enableVoice && isRecording;
1772
1900
  const showVoiceBtn = enableVoice && !isRecording;
1773
1901
  const [keyboardOpen, setKeyboardOpen] = react.useState(false);
@@ -1875,12 +2003,8 @@ function InputBar({
1875
2003
  reactNative.Pressable,
1876
2004
  {
1877
2005
  onPress: onVoicePress,
1878
- disabled: !voiceAvailable || disabled,
1879
- style: ({ pressed }) => [
1880
- s.iconBtn,
1881
- (!voiceAvailable || disabled) && s.btnDisabled,
1882
- pressed && s.pressed
1883
- ],
2006
+ disabled: voiceDisabled,
2007
+ style: [s.iconBtn, voiceDisabled && s.btnDisabled],
1884
2008
  accessibilityLabel: "Voice input",
1885
2009
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.iconBtnInner, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Mic, { size: 16, color: "#6B7280", strokeWidth: 2 }) })
1886
2010
  }
@@ -1888,13 +2012,9 @@ function InputBar({
1888
2012
  /* @__PURE__ */ jsxRuntime.jsx(
1889
2013
  reactNative.Pressable,
1890
2014
  {
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
- ],
2015
+ onPress: onSend,
2016
+ disabled: !canSend,
2017
+ style: [s.sendBtn, !canSend && s.btnDisabled],
1898
2018
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [s.sendBtnInner, { backgroundColor: accent }], children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Send, { size: 14, color: "#FFFFFF" }) })
1899
2019
  }
1900
2020
  )
@@ -2161,11 +2281,27 @@ function stripIncompleteImageToken(text) {
2161
2281
  if (/^!\[[^\]]*\]\([^)]*\)/.test(after)) return text;
2162
2282
  return text.slice(0, lastBang);
2163
2283
  }
2284
+ function stripDuplicatePromptText(text, promptText) {
2285
+ if (!promptText) return text;
2286
+ const pm = promptText.replace(/\\n/g, "\n").trim();
2287
+ if (!pm) return text;
2288
+ const idx = text.indexOf(pm);
2289
+ if (idx !== -1) {
2290
+ return (text.slice(0, idx) + text.slice(idx + pm.length)).trim();
2291
+ }
2292
+ for (let cut = Math.min(text.length, pm.length); cut >= 24; cut--) {
2293
+ if (text.endsWith(pm.slice(0, cut))) {
2294
+ return text.slice(0, text.length - cut).trim();
2295
+ }
2296
+ }
2297
+ return text;
2298
+ }
2164
2299
  function AssistantBubble({
2165
2300
  message,
2166
2301
  shouldType,
2167
2302
  isDark,
2168
- accent
2303
+ accent,
2304
+ suppressText
2169
2305
  }) {
2170
2306
  const isCurrentlyStreaming = !!message.isStreaming && !message.isCancelled;
2171
2307
  const mdStyles = isDark ? MD_STYLES_DARK : MD_STYLES_LIGHT;
@@ -2176,7 +2312,8 @@ function AssistantBubble({
2176
2312
  const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
2177
2313
  if (!raw) return "";
2178
2314
  const normalized = raw.replace(/\\n/g, "\n");
2179
- return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
2315
+ const cleaned = message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
2316
+ return stripDuplicatePromptText(cleaned, suppressText);
2180
2317
  })();
2181
2318
  const isThinkingStreaming = isCurrentlyStreaming && !rawResponseContent && !message.isError;
2182
2319
  const hasEverStreamed = react.useRef(!!message.isStreaming);
@@ -2206,10 +2343,740 @@ function AssistantBubble({
2206
2343
  message.isError && displayedText && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sd.partialErrorText, children: message.errorDetails || "The response was interrupted." })
2207
2344
  ] }) });
2208
2345
  }
2209
- function MessageBubble({ message, accent, shouldType, isDark }) {
2346
+ function MessageBubble({ message, accent, shouldType, isDark, suppressText }) {
2210
2347
  if (message.role === "user") return /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { message, accent });
2211
- return /* @__PURE__ */ jsxRuntime.jsx(AssistantBubble, { message, shouldType, isDark, accent });
2348
+ return /* @__PURE__ */ jsxRuntime.jsx(AssistantBubble, { message, shouldType, isDark, accent, suppressText });
2349
+ }
2350
+ var RESEND_COOLDOWN_S = 30;
2351
+ var DEFAULT_CODE_LEN = 6;
2352
+ function uaPalette(isDark) {
2353
+ return isDark ? {
2354
+ sheetBg: "#13201f",
2355
+ text: "rgba(255,255,255,0.95)",
2356
+ muted: "rgba(255,255,255,0.55)",
2357
+ border: "rgba(255,255,255,0.10)",
2358
+ fieldBg: "rgba(255,255,255,0.06)",
2359
+ fieldBorder: "rgba(255,255,255,0.18)",
2360
+ overlay: "rgba(0,0,0,0.62)",
2361
+ grabber: "rgba(255,255,255,0.22)"
2362
+ } : {
2363
+ sheetBg: "#ffffff",
2364
+ text: "#111827",
2365
+ muted: "#6b7280",
2366
+ border: "#eceef1",
2367
+ fieldBg: "#f9fafb",
2368
+ fieldBorder: "#d1d5db",
2369
+ overlay: "rgba(15,23,42,0.45)",
2370
+ grabber: "rgba(15,23,42,0.18)"
2371
+ };
2372
+ }
2373
+ function promptInitialSeconds(prompt) {
2374
+ return prompt && typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Math.floor(prompt.expirySeconds) : void 0;
2375
+ }
2376
+ function promptTimerKey(prompt) {
2377
+ return prompt ? `${prompt.userActionId}|${prompt.subAction ?? ""}` : "";
2378
+ }
2379
+ function useExpiredFlag(prompt) {
2380
+ const initial = promptInitialSeconds(prompt);
2381
+ const key = promptTimerKey(prompt);
2382
+ const [expired, setExpired] = react.useState(false);
2383
+ react.useEffect(() => {
2384
+ setExpired(false);
2385
+ if (initial === void 0) return;
2386
+ const t = setTimeout(() => setExpired(true), initial * 1e3);
2387
+ return () => clearTimeout(t);
2388
+ }, [key, initial]);
2389
+ return expired;
2390
+ }
2391
+ function useSecondsLeft(expirySeconds, restartKey) {
2392
+ const initial = typeof expirySeconds === "number" && expirySeconds > 0 ? Math.floor(expirySeconds) : void 0;
2393
+ const [left, setLeft] = react.useState(initial);
2394
+ react.useEffect(() => {
2395
+ setLeft(initial);
2396
+ if (initial === void 0) return;
2397
+ const id = setInterval(() => {
2398
+ setLeft((s2) => s2 === void 0 ? s2 : s2 <= 1 ? 0 : s2 - 1);
2399
+ }, 1e3);
2400
+ return () => clearInterval(id);
2401
+ }, [restartKey, initial]);
2402
+ return left;
2403
+ }
2404
+ function SheetTimerPill({
2405
+ prompt,
2406
+ accent,
2407
+ isDark
2408
+ }) {
2409
+ const left = useSecondsLeft(prompt.expirySeconds, promptTimerKey(prompt));
2410
+ if (left === void 0 || prompt.status === "stale") return null;
2411
+ const expired = left <= 0;
2412
+ return /* @__PURE__ */ jsxRuntime.jsx(
2413
+ reactNative.View,
2414
+ {
2415
+ style: [
2416
+ sht.timer,
2417
+ {
2418
+ borderColor: expired ? "#ef4444" : accent + "40",
2419
+ backgroundColor: expired ? "rgba(239,68,68,0.10)" : accent + (isDark ? "1F" : "12")
2420
+ }
2421
+ ],
2422
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: expired ? "#ef4444" : accent, fontSize: 12.5, fontWeight: "700", letterSpacing: 0.2 }, children: expired ? "Expired" : `${left}s` })
2423
+ }
2424
+ );
2425
+ }
2426
+ function CardTimerPill({
2427
+ prompt,
2428
+ accent,
2429
+ isDark
2430
+ }) {
2431
+ const left = useSecondsLeft(prompt.expirySeconds, promptTimerKey(prompt));
2432
+ if (left === void 0 || left <= 0) return null;
2433
+ 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: [
2434
+ left,
2435
+ "s"
2436
+ ] }) });
2437
+ }
2438
+ function CodeInput({
2439
+ value,
2440
+ onChange,
2441
+ length,
2442
+ disabled,
2443
+ error,
2444
+ accent,
2445
+ pal
2446
+ }) {
2447
+ const ref = react.useRef(null);
2448
+ react.useEffect(() => {
2449
+ const t = setTimeout(() => ref.current?.focus(), 380);
2450
+ return () => clearTimeout(t);
2451
+ }, []);
2452
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress: () => ref.current?.focus(), style: ub.codeRow, children: [
2453
+ Array.from({ length }).map((_, i) => {
2454
+ const ch = value[i] ?? "";
2455
+ const isCursor = i === value.length && !disabled;
2456
+ const bc = error ? "#ef4444" : isCursor ? accent : pal.fieldBorder;
2457
+ 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);
2458
+ }),
2459
+ /* @__PURE__ */ jsxRuntime.jsx(
2460
+ reactNative.TextInput,
2461
+ {
2462
+ ref,
2463
+ value,
2464
+ onChangeText: (t) => onChange(t.replace(/[^0-9]/g, "").slice(0, length)),
2465
+ keyboardType: "number-pad",
2466
+ maxLength: length,
2467
+ editable: !disabled,
2468
+ caretHidden: true,
2469
+ style: ub.codeHidden
2470
+ }
2471
+ )
2472
+ ] });
2473
+ }
2474
+ function PrimaryButton({
2475
+ label,
2476
+ onPress,
2477
+ disabled,
2478
+ accent
2479
+ }) {
2480
+ const bg = accent || DEFAULT_ACCENT;
2481
+ return /* @__PURE__ */ jsxRuntime.jsx(
2482
+ reactNative.Pressable,
2483
+ {
2484
+ onPress,
2485
+ disabled,
2486
+ android_ripple: { color: "rgba(255,255,255,0.22)" },
2487
+ style: [ub.primaryBtn, { backgroundColor: bg, borderColor: bg, opacity: disabled ? 0.5 : 1 }],
2488
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: ub.primaryBtnText, children: label })
2489
+ }
2490
+ );
2491
+ }
2492
+ function VerificationBody({
2493
+ prompt,
2494
+ expired,
2495
+ pal,
2496
+ accent,
2497
+ onSubmit,
2498
+ onCancel,
2499
+ onResend
2500
+ }) {
2501
+ const isNumeric = prompt.verificationType !== "ALPHANUMERIC_CODE";
2502
+ const field = prompt.requestedSchema?.properties?.verificationCode;
2503
+ const codeLen = (typeof field?.maxLength === "number" ? field.maxLength : void 0) ?? (typeof field?.minLength === "number" ? field.minLength : void 0) ?? DEFAULT_CODE_LEN;
2504
+ const [code, setCode] = react.useState("");
2505
+ const [errored, setErrored] = react.useState(false);
2506
+ const [resendSec, setResendSec] = react.useState(0);
2507
+ const lastSubmitted = react.useRef(null);
2508
+ const resendTimer = react.useRef(null);
2509
+ const busy = prompt.status === "submitting";
2510
+ const stale = prompt.status === "stale";
2511
+ const locked = busy || stale || expired;
2512
+ react.useEffect(() => {
2513
+ if (prompt.subAction === "SubmissionInvalid") {
2514
+ setErrored(true);
2515
+ setCode("");
2516
+ lastSubmitted.current = null;
2517
+ }
2518
+ }, [prompt.subAction, prompt.userActionId]);
2519
+ const doSubmit = react.useCallback(
2520
+ (value) => {
2521
+ if (locked || !value) return;
2522
+ if (lastSubmitted.current === value) return;
2523
+ lastSubmitted.current = value;
2524
+ void onSubmit(prompt.userActionId, { verificationCode: value }).catch(() => {
2525
+ lastSubmitted.current = null;
2526
+ setErrored(true);
2527
+ });
2528
+ },
2529
+ [locked, onSubmit, prompt.userActionId]
2530
+ );
2531
+ react.useEffect(() => {
2532
+ if (!isNumeric || locked) return;
2533
+ if (code.length === codeLen && /^\d+$/.test(code)) doSubmit(code);
2534
+ }, [code, codeLen, doSubmit, isNumeric, locked]);
2535
+ react.useEffect(() => () => {
2536
+ if (resendTimer.current) clearInterval(resendTimer.current);
2537
+ }, []);
2538
+ const handleResend = react.useCallback(async () => {
2539
+ if (locked || resendSec > 0) return;
2540
+ setErrored(false);
2541
+ setCode("");
2542
+ lastSubmitted.current = null;
2543
+ try {
2544
+ await onResend(prompt.userActionId);
2545
+ setResendSec(RESEND_COOLDOWN_S);
2546
+ if (resendTimer.current) clearInterval(resendTimer.current);
2547
+ resendTimer.current = setInterval(() => {
2548
+ setResendSec((s2) => {
2549
+ if (s2 <= 1) {
2550
+ if (resendTimer.current) clearInterval(resendTimer.current);
2551
+ return 0;
2552
+ }
2553
+ return s2 - 1;
2554
+ });
2555
+ }, 1e3);
2556
+ } catch {
2557
+ }
2558
+ }, [locked, onResend, prompt.userActionId, resendSec]);
2559
+ const description = prompt.message?.trim() || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
2560
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.bodyWrap, children: [
2561
+ /* @__PURE__ */ jsxRuntime.jsxs(
2562
+ reactNative.ScrollView,
2563
+ {
2564
+ style: ub.fieldsScroll,
2565
+ contentContainerStyle: ub.fieldsContent,
2566
+ keyboardShouldPersistTaps: "handled",
2567
+ showsVerticalScrollIndicator: false,
2568
+ children: [
2569
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.desc, { color: pal.muted }], children: description }),
2570
+ isNumeric ? /* @__PURE__ */ jsxRuntime.jsx(
2571
+ CodeInput,
2572
+ {
2573
+ value: code,
2574
+ onChange: (v) => {
2575
+ setErrored(false);
2576
+ setCode(v);
2577
+ },
2578
+ length: codeLen,
2579
+ disabled: locked,
2580
+ error: errored,
2581
+ accent,
2582
+ pal
2583
+ }
2584
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2585
+ reactNative.TextInput,
2586
+ {
2587
+ value: code,
2588
+ onChangeText: (t) => {
2589
+ setErrored(false);
2590
+ setCode(t);
2591
+ },
2592
+ editable: !locked,
2593
+ placeholder: "Verification code",
2594
+ placeholderTextColor: pal.muted,
2595
+ autoComplete: "one-time-code",
2596
+ style: [
2597
+ ub.input,
2598
+ { color: pal.text, backgroundColor: pal.fieldBg, borderColor: errored ? "#ef4444" : pal.fieldBorder }
2599
+ ]
2600
+ }
2601
+ )
2602
+ ]
2603
+ }
2604
+ ),
2605
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.actions, children: [
2606
+ !isNumeric && /* @__PURE__ */ jsxRuntime.jsx(
2607
+ PrimaryButton,
2608
+ {
2609
+ label: busy ? "Verifying\u2026" : "Verify",
2610
+ onPress: () => doSubmit(code.trim()),
2611
+ disabled: locked || !code.trim(),
2612
+ accent
2613
+ }
2614
+ ),
2615
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.linkRow, children: [
2616
+ /* @__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" }) }),
2617
+ /* @__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" }) })
2618
+ ] })
2619
+ ] })
2620
+ ] });
2212
2621
  }
2622
+ function FormBody({
2623
+ prompt,
2624
+ pal,
2625
+ accent,
2626
+ isDark,
2627
+ onSubmit,
2628
+ onCancel
2629
+ }) {
2630
+ const schema = prompt.requestedSchema;
2631
+ const fields = react.useMemo(() => renderableFields(schema), [schema]);
2632
+ const [values, setValues] = react.useState(() => {
2633
+ const init = {};
2634
+ for (const [key, field] of fields) init[key] = defaultValueFor(field);
2635
+ return init;
2636
+ });
2637
+ const [errors, setErrors] = react.useState({});
2638
+ const busy = prompt.status === "submitting";
2639
+ const stale = prompt.status === "stale";
2640
+ const locked = busy || stale;
2641
+ const isConfirm = prompt.subAction === "UserConfirmation";
2642
+ const setValue = (key, v) => {
2643
+ setValues((prev) => ({ ...prev, [key]: v }));
2644
+ setErrors((prev) => {
2645
+ if (!prev[key]) return prev;
2646
+ const next = { ...prev };
2647
+ delete next[key];
2648
+ return next;
2649
+ });
2650
+ };
2651
+ const handleSubmit = () => {
2652
+ if (locked) return;
2653
+ const validation = validateForm(schema, values);
2654
+ if (Object.keys(validation).length > 0) {
2655
+ setErrors(validation);
2656
+ return;
2657
+ }
2658
+ void onSubmit(prompt.userActionId, buildContent(schema, values)).catch(() => {
2659
+ });
2660
+ };
2661
+ const mdStyles = isDark ? MD_STYLES_DARK : MD_STYLES_LIGHT;
2662
+ const mdRules = react.useMemo(() => makeMdRules(isDark, accent), [isDark, accent]);
2663
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.bodyWrap, children: [
2664
+ /* @__PURE__ */ jsxRuntime.jsxs(
2665
+ reactNative.ScrollView,
2666
+ {
2667
+ style: ub.fieldsScroll,
2668
+ contentContainerStyle: ub.fieldsContent,
2669
+ keyboardShouldPersistTaps: "handled",
2670
+ showsVerticalScrollIndicator: true,
2671
+ children: [
2672
+ 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,
2673
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.form, children: fields.map(([key, field]) => {
2674
+ const widget = classifyField(field);
2675
+ const label = field.title || key;
2676
+ const required = isRequired(schema, key);
2677
+ const err = errors[key];
2678
+ if (widget === "boolean") {
2679
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.switchRow, children: [
2680
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [ub.label, { color: pal.text, flex: 1 }], children: [
2681
+ label,
2682
+ required ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: "#ef4444" }, children: " *" }) : null
2683
+ ] }),
2684
+ /* @__PURE__ */ jsxRuntime.jsx(
2685
+ reactNative.Switch,
2686
+ {
2687
+ value: Boolean(values[key]),
2688
+ disabled: locked,
2689
+ onValueChange: (v) => setValue(key, v),
2690
+ trackColor: { true: accent }
2691
+ }
2692
+ )
2693
+ ] }, key);
2694
+ }
2695
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.field, children: [
2696
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [ub.label, { color: pal.text }], children: [
2697
+ label,
2698
+ required ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: "#ef4444" }, children: " *" }) : null
2699
+ ] }),
2700
+ field.description ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.hint, { color: pal.muted }], children: field.description }) : null,
2701
+ widget === "select" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.optionList, children: getOptions(field).map((opt) => {
2702
+ const selected = String(values[key] ?? "") === opt.const;
2703
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2704
+ reactNative.Pressable,
2705
+ {
2706
+ disabled: locked,
2707
+ onPress: () => setValue(key, opt.const),
2708
+ android_ripple: { color: accent + "22" },
2709
+ style: [
2710
+ ub.optionRow,
2711
+ {
2712
+ borderColor: selected ? accent : pal.fieldBorder,
2713
+ backgroundColor: selected ? accent + (isDark ? "26" : "14") : pal.fieldBg
2714
+ }
2715
+ ],
2716
+ children: [
2717
+ /* @__PURE__ */ jsxRuntime.jsx(
2718
+ reactNative.View,
2719
+ {
2720
+ style: [
2721
+ ub.radio,
2722
+ { borderColor: selected ? accent : pal.fieldBorder }
2723
+ ],
2724
+ children: selected ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [ub.radioDot, { backgroundColor: accent }] }) : null
2725
+ }
2726
+ ),
2727
+ /* @__PURE__ */ jsxRuntime.jsx(
2728
+ reactNative.Text,
2729
+ {
2730
+ style: [
2731
+ ub.optionLabel,
2732
+ { color: pal.text, fontWeight: selected ? "600" : "500" }
2733
+ ],
2734
+ children: opt.title || opt.const
2735
+ }
2736
+ )
2737
+ ]
2738
+ },
2739
+ opt.const
2740
+ );
2741
+ }) }) : /* @__PURE__ */ jsxRuntime.jsx(
2742
+ reactNative.TextInput,
2743
+ {
2744
+ value: String(values[key] ?? ""),
2745
+ editable: !locked,
2746
+ onChangeText: (t) => setValue(key, t),
2747
+ placeholder: field.description ? void 0 : label,
2748
+ placeholderTextColor: pal.muted,
2749
+ keyboardType: widget === "integer" ? "number-pad" : widget === "decimal" ? "decimal-pad" : "default",
2750
+ style: [
2751
+ ub.input,
2752
+ { color: pal.text, backgroundColor: pal.fieldBg, borderColor: err ? "#ef4444" : pal.fieldBorder }
2753
+ ]
2754
+ }
2755
+ ),
2756
+ err ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: ub.error, children: err }) : null
2757
+ ] }, key);
2758
+ }) })
2759
+ ]
2760
+ }
2761
+ ),
2762
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.actions, children: [
2763
+ /* @__PURE__ */ jsxRuntime.jsx(
2764
+ PrimaryButton,
2765
+ {
2766
+ label: busy ? "Submitting\u2026" : isConfirm ? "Confirm" : "Next",
2767
+ onPress: handleSubmit,
2768
+ disabled: locked,
2769
+ accent
2770
+ }
2771
+ ),
2772
+ /* @__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" }) }) })
2773
+ ] })
2774
+ ] });
2775
+ }
2776
+ function UserActionSheet({
2777
+ open,
2778
+ active,
2779
+ expired,
2780
+ isDark,
2781
+ accent,
2782
+ insetBottom,
2783
+ onSubmit,
2784
+ onCancel,
2785
+ onResend,
2786
+ onMinimize
2787
+ }) {
2788
+ const pal = uaPalette(isDark);
2789
+ const screenH = reactNative.Dimensions.get("window").height;
2790
+ const [shown, setShown] = react.useState(active);
2791
+ react.useEffect(() => {
2792
+ if (active) setShown(active);
2793
+ }, [active]);
2794
+ const translateY = react.useRef(new reactNative.Animated.Value(screenH)).current;
2795
+ const backdrop = react.useRef(new reactNative.Animated.Value(0)).current;
2796
+ const minimizeRef = react.useRef(onMinimize);
2797
+ react.useEffect(() => {
2798
+ minimizeRef.current = onMinimize;
2799
+ });
2800
+ const [kbHeight, setKbHeight] = react.useState(0);
2801
+ react.useEffect(() => {
2802
+ const showEvt = reactNative.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
2803
+ const hideEvt = reactNative.Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
2804
+ const s1 = reactNative.Keyboard.addListener(showEvt, (e) => setKbHeight(e?.endCoordinates?.height ?? 0));
2805
+ const s2 = reactNative.Keyboard.addListener(hideEvt, () => setKbHeight(0));
2806
+ return () => {
2807
+ s1.remove();
2808
+ s2.remove();
2809
+ };
2810
+ }, []);
2811
+ react.useEffect(() => {
2812
+ if (open) {
2813
+ reactNative.Animated.parallel([
2814
+ reactNative.Animated.spring(translateY, { toValue: 0, useNativeDriver: true, damping: 30, stiffness: 360, mass: 0.85 }),
2815
+ reactNative.Animated.timing(backdrop, { toValue: 1, duration: 190, useNativeDriver: true })
2816
+ ]).start();
2817
+ } else {
2818
+ reactNative.Animated.parallel([
2819
+ reactNative.Animated.timing(translateY, { toValue: screenH, duration: 210, easing: reactNative.Easing.in(reactNative.Easing.cubic), useNativeDriver: true }),
2820
+ reactNative.Animated.timing(backdrop, { toValue: 0, duration: 170, useNativeDriver: true })
2821
+ ]).start();
2822
+ }
2823
+ }, [open, translateY, backdrop, screenH]);
2824
+ const pan = react.useRef(
2825
+ reactNative.PanResponder.create({
2826
+ onMoveShouldSetPanResponder: (_evt, g) => g.dy > 6 && Math.abs(g.dy) > Math.abs(g.dx) * 1.4,
2827
+ onPanResponderMove: (_evt, g) => {
2828
+ if (g.dy > 0) translateY.setValue(g.dy);
2829
+ },
2830
+ onPanResponderRelease: (_evt, g) => {
2831
+ if (g.dy > 110 || g.vy > 0.8) {
2832
+ minimizeRef.current();
2833
+ } else {
2834
+ reactNative.Animated.spring(translateY, { toValue: 0, useNativeDriver: true, damping: 30, stiffness: 360 }).start();
2835
+ }
2836
+ }
2837
+ })
2838
+ ).current;
2839
+ const a = active ?? shown;
2840
+ if (!a) return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { pointerEvents: "none" });
2841
+ const isVerification = a.kind === "verification";
2842
+ const title = isVerification ? "Verification required" : "Action required";
2843
+ const subtitle = isVerification ? "Confirm the one-time code to continue" : "Review the details and confirm to continue";
2844
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [reactNative.StyleSheet.absoluteFill, sht.overlay], pointerEvents: open ? "auto" : "none", children: [
2845
+ /* @__PURE__ */ jsxRuntime.jsx(
2846
+ reactNative.Animated.View,
2847
+ {
2848
+ pointerEvents: "none",
2849
+ style: [sht.backdrop, { backgroundColor: pal.overlay, opacity: backdrop }]
2850
+ }
2851
+ ),
2852
+ open ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: reactNative.StyleSheet.absoluteFill, onPress: () => minimizeRef.current(), accessibilityLabel: "Dismiss" }) : null,
2853
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sht.kav, { paddingBottom: kbHeight }], pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsxs(
2854
+ reactNative.Animated.View,
2855
+ {
2856
+ style: [
2857
+ sht.sheet,
2858
+ {
2859
+ backgroundColor: pal.sheetBg,
2860
+ borderColor: pal.border,
2861
+ // Cap to the space above the keyboard (and a top margin) so the
2862
+ // header stays visible and the footer sits above the keyboard;
2863
+ // the field area scrolls within whatever's left.
2864
+ maxHeight: Math.min(screenH * 0.9, screenH - kbHeight - 72),
2865
+ paddingBottom: kbHeight > 0 ? 16 : Math.max(insetBottom, 16) + 6,
2866
+ transform: [{ translateY }]
2867
+ }
2868
+ ],
2869
+ children: [
2870
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ...pan.panHandlers, style: sht.handleZone, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sht.grabber, { backgroundColor: pal.grabber }] }) }),
2871
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: sht.header, children: [
2872
+ /* @__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 }) }),
2873
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: sht.headerText, children: [
2874
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [sht.title, { color: pal.text }], children: title }),
2875
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [sht.subtitle, { color: pal.muted }], numberOfLines: 1, children: subtitle })
2876
+ ] }),
2877
+ /* @__PURE__ */ jsxRuntime.jsx(SheetTimerPill, { prompt: a, accent, isDark }),
2878
+ /* @__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 }) })
2879
+ ] }),
2880
+ 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: [
2881
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.desc, { color: pal.muted }], children: isVerification ? "This verification request expired." : "This request expired." }),
2882
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.actions, children: /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { label: "Close", onPress: () => void onCancel(a.userActionId), accent }) })
2883
+ ] }) : isVerification ? /* @__PURE__ */ jsxRuntime.jsx(
2884
+ VerificationBody,
2885
+ {
2886
+ prompt: a,
2887
+ expired,
2888
+ pal,
2889
+ accent,
2890
+ onSubmit,
2891
+ onCancel,
2892
+ onResend
2893
+ },
2894
+ a.userActionId
2895
+ ) : /* @__PURE__ */ jsxRuntime.jsx(FormBody, { prompt: a, pal, accent, isDark, onSubmit, onCancel }, a.userActionId)
2896
+ ]
2897
+ }
2898
+ ) })
2899
+ ] });
2900
+ }
2901
+ function UserActionReopenCard({
2902
+ active,
2903
+ expired,
2904
+ isDark,
2905
+ accent,
2906
+ onReopen
2907
+ }) {
2908
+ if (expired || active.status === "stale") return null;
2909
+ const pal = uaPalette(isDark);
2910
+ const isVerification = active.kind === "verification";
2911
+ const title = isVerification ? "Verification required" : "Action required";
2912
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2913
+ reactNative.Pressable,
2914
+ {
2915
+ onPress: onReopen,
2916
+ accessibilityRole: "button",
2917
+ accessibilityLabel: `${title}. Tap to respond.`,
2918
+ android_ripple: { color: accent + "22" },
2919
+ style: [
2920
+ rc.card,
2921
+ {
2922
+ backgroundColor: pal.sheetBg,
2923
+ borderColor: accent + (isDark ? "59" : "40")
2924
+ }
2925
+ ],
2926
+ children: [
2927
+ /* @__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 }) }),
2928
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: rc.textWrap, children: [
2929
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [rc.title, { color: pal.text }], numberOfLines: 1, children: title }),
2930
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [rc.subtitle, { color: pal.muted }], numberOfLines: 1, children: "Tap to respond and continue" })
2931
+ ] }),
2932
+ /* @__PURE__ */ jsxRuntime.jsx(CardTimerPill, { prompt: active, accent, isDark }),
2933
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.ChevronRight, { size: 18, color: pal.muted, strokeWidth: 2 })
2934
+ ]
2935
+ }
2936
+ );
2937
+ }
2938
+ function NotificationStack({
2939
+ notifications,
2940
+ isDark,
2941
+ onDismiss
2942
+ }) {
2943
+ if (notifications.length === 0) return null;
2944
+ const pal = uaPalette(isDark);
2945
+ 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: [
2946
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Info, { size: 14, color: pal.muted, strokeWidth: 1.9 }),
2947
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.noteText, { color: pal.text }], children: n.message }),
2948
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => onDismiss(n.id), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.X, { size: 14, color: pal.muted, strokeWidth: 2 }) })
2949
+ ] }, n.id)) });
2950
+ }
2951
+ var ub = reactNative.StyleSheet.create({
2952
+ // Body wrapper can shrink within the sheet's (bounded) maxHeight so the inner
2953
+ // field scroll takes over for tall forms while the footer stays pinned.
2954
+ bodyWrap: { flexShrink: 1 },
2955
+ // Field scroll region. flexShrink:1 + no flexGrow → hugs content for short
2956
+ // forms (no scroll), shrinks and scrolls only when the form exceeds the sheet.
2957
+ fieldsScroll: { flexShrink: 1 },
2958
+ fieldsContent: { paddingBottom: 6 },
2959
+ messageBlock: { marginBottom: 18, marginTop: 2 },
2960
+ desc: { fontSize: 14.5, lineHeight: 21, marginBottom: 16 },
2961
+ form: { gap: 18 },
2962
+ field: { gap: 7 },
2963
+ label: { fontSize: 14, fontWeight: "600" },
2964
+ hint: { fontSize: 12.5, lineHeight: 17 },
2965
+ input: {
2966
+ borderWidth: 1,
2967
+ borderRadius: 12,
2968
+ paddingHorizontal: 14,
2969
+ paddingVertical: 13,
2970
+ fontSize: 16
2971
+ },
2972
+ error: { color: "#ef4444", fontSize: 12.5, marginTop: 2 },
2973
+ switchRow: { flexDirection: "row", alignItems: "center", gap: 12 },
2974
+ // Selectable option "boxes" for oneOf / enum single-select fields.
2975
+ optionList: { gap: 10 },
2976
+ optionRow: {
2977
+ flexDirection: "row",
2978
+ alignItems: "center",
2979
+ gap: 12,
2980
+ borderWidth: 1.5,
2981
+ borderRadius: 14,
2982
+ paddingHorizontal: 14,
2983
+ paddingVertical: 14
2984
+ },
2985
+ radio: {
2986
+ width: 20,
2987
+ height: 20,
2988
+ borderRadius: 10,
2989
+ borderWidth: 2,
2990
+ alignItems: "center",
2991
+ justifyContent: "center"
2992
+ },
2993
+ radioDot: { width: 10, height: 10, borderRadius: 5 },
2994
+ optionLabel: { flex: 1, fontSize: 15, letterSpacing: -0.1 },
2995
+ // Pinned footer (outside the fields scroll) — always visible.
2996
+ actions: { paddingTop: 16, gap: 14 },
2997
+ primaryBtn: { minHeight: 52, borderRadius: 14, borderWidth: 1, paddingVertical: 15, alignItems: "center", justifyContent: "center" },
2998
+ primaryBtnText: { color: "#fff", fontSize: 15.5, fontWeight: "600", letterSpacing: -0.1 },
2999
+ linkRow: { flexDirection: "row", justifyContent: "center", gap: 24 },
3000
+ link: { fontSize: 14, fontWeight: "500" },
3001
+ linkDanger: { color: "#dc2626" },
3002
+ codeRow: { flexDirection: "row", justifyContent: "center", gap: 9, position: "relative" },
3003
+ codeBox: {
3004
+ width: 46,
3005
+ height: 56,
3006
+ borderWidth: 1.5,
3007
+ borderRadius: 12,
3008
+ alignItems: "center",
3009
+ justifyContent: "center"
3010
+ },
3011
+ codeChar: { fontSize: 24, fontWeight: "600" },
3012
+ codeHidden: { ...reactNative.StyleSheet.absoluteFillObject, opacity: 0 },
3013
+ noteStack: { paddingHorizontal: 16, paddingBottom: 6, gap: 6 },
3014
+ note: {
3015
+ flexDirection: "row",
3016
+ alignItems: "center",
3017
+ gap: 8,
3018
+ borderWidth: 1,
3019
+ borderRadius: 12,
3020
+ paddingHorizontal: 12,
3021
+ paddingVertical: 10
3022
+ },
3023
+ noteText: { flex: 1, fontSize: 13.5, lineHeight: 19 }
3024
+ });
3025
+ var sht = reactNative.StyleSheet.create({
3026
+ // High stacking so the sheet sits above the chat input bar (which carries its
3027
+ // own zIndex/elevation) and everything else in the chat.
3028
+ overlay: { zIndex: 100, ...reactNative.Platform.select({ android: { elevation: 100 }, default: {} }) },
3029
+ backdrop: { ...reactNative.StyleSheet.absoluteFillObject },
3030
+ kav: { flex: 1, justifyContent: "flex-end" },
3031
+ sheet: {
3032
+ borderTopLeftRadius: 28,
3033
+ borderTopRightRadius: 28,
3034
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
3035
+ borderBottomWidth: 0,
3036
+ paddingHorizontal: 22,
3037
+ paddingTop: 6,
3038
+ maxWidth: 640,
3039
+ width: "100%",
3040
+ alignSelf: "center",
3041
+ ...reactNative.Platform.select({
3042
+ ios: {
3043
+ shadowColor: "#000",
3044
+ shadowOffset: { width: 0, height: -6 },
3045
+ shadowOpacity: 0.18,
3046
+ shadowRadius: 24
3047
+ },
3048
+ android: { elevation: 24 }
3049
+ })
3050
+ },
3051
+ handleZone: { alignItems: "center", paddingVertical: 10 },
3052
+ grabber: { width: 40, height: 5, borderRadius: 3 },
3053
+ header: { flexDirection: "row", alignItems: "center", gap: 12, marginTop: 2, marginBottom: 18 },
3054
+ iconCircle: { width: 40, height: 40, borderRadius: 20, alignItems: "center", justifyContent: "center" },
3055
+ headerText: { flex: 1, gap: 2 },
3056
+ title: { fontSize: 18, fontWeight: "700", letterSpacing: -0.3 },
3057
+ subtitle: { fontSize: 13, letterSpacing: -0.1 },
3058
+ timer: { borderWidth: 1, borderRadius: 999, paddingHorizontal: 11, paddingVertical: 5, minWidth: 46, alignItems: "center" },
3059
+ closeBtn: { width: 30, height: 30, borderRadius: 15, alignItems: "center", justifyContent: "center" },
3060
+ // Short, non-scrolling bodies (stale / expired notices).
3061
+ simpleBody: { paddingBottom: 8 }
3062
+ });
3063
+ var rc = reactNative.StyleSheet.create({
3064
+ card: {
3065
+ flexDirection: "row",
3066
+ alignItems: "center",
3067
+ gap: 12,
3068
+ borderWidth: 1.5,
3069
+ borderRadius: 18,
3070
+ paddingHorizontal: 14,
3071
+ paddingVertical: 13,
3072
+ marginVertical: 6
3073
+ },
3074
+ iconCircle: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center" },
3075
+ textWrap: { flex: 1, gap: 1 },
3076
+ title: { fontSize: 14.5, fontWeight: "700", letterSpacing: -0.2 },
3077
+ subtitle: { fontSize: 12.5, letterSpacing: -0.1 },
3078
+ timer: { borderRadius: 999, paddingHorizontal: 9, paddingVertical: 4, minWidth: 40, alignItems: "center" }
3079
+ });
2213
3080
  var PaymanChat = react.forwardRef(
2214
3081
  function PaymanChat2({ config, callbacks, children, onLoadMoreMessages, isLoadingMoreMessages = false, hasMoreMessages = false }, ref) {
2215
3082
  const accent = config.accent ?? DEFAULT_ACCENT;
@@ -2236,8 +3103,14 @@ var PaymanChat = react.forwardRef(
2236
3103
  },
2237
3104
  onStatusMessage: (m) => callbacksRef.current?.onStatusMessage?.(m),
2238
3105
  onStepsUpdate: (steps) => callbacksRef.current?.onStepsUpdate?.(steps),
2239
- onUserActionRequired: (r) => callbacksRef.current?.onUserActionRequired?.(r),
2240
- onUserNotification: (n) => callbacksRef.current?.onUserNotification?.(n)
3106
+ onUserActionRequired: (r) => {
3107
+ console.log("[PaymanChat] USER_ACTION_REQUIRED:", JSON.stringify({ kind: r?.kind, rawAction: r?.rawAction, userActionId: r?.userActionId, hasSchema: !!r?.requestedSchema }));
3108
+ callbacksRef.current?.onUserActionRequired?.(r);
3109
+ },
3110
+ onUserNotification: (n) => {
3111
+ console.log("[PaymanChat] USER_NOTIFICATION:", JSON.stringify({ id: n?.id, message: n?.message }));
3112
+ callbacksRef.current?.onUserNotification?.(n);
3113
+ }
2241
3114
  }), []);
2242
3115
  const {
2243
3116
  messages,
@@ -2248,8 +3121,37 @@ var PaymanChat = react.forwardRef(
2248
3121
  prependMessages,
2249
3122
  cancelStream,
2250
3123
  getSessionId,
2251
- getMessages
3124
+ getMessages,
3125
+ userActionState,
3126
+ submitUserAction: submitUserAction2,
3127
+ cancelUserAction: cancelUserAction2,
3128
+ resendUserAction: resendUserAction2,
3129
+ dismissNotification
2252
3130
  } = useChatV2(config, stableCallbacks);
3131
+ react.useEffect(() => {
3132
+ console.log(
3133
+ "[PaymanChat] userActionState ->",
3134
+ "prompts:",
3135
+ userActionState.prompts.length,
3136
+ userActionState.prompts.map((p) => `${p.kind}:${p.status}`).join(","),
3137
+ "| notifications:",
3138
+ userActionState.notifications.length
3139
+ );
3140
+ }, [userActionState]);
3141
+ const activePrompt = react.useMemo(
3142
+ () => userActionState.prompts.find((p) => p.kind !== "notification"),
3143
+ [userActionState.prompts]
3144
+ );
3145
+ const expired = useExpiredFlag(activePrompt);
3146
+ const [dismissedActionId, setDismissedActionId] = react.useState(null);
3147
+ react.useEffect(() => {
3148
+ if (activePrompt) setDismissedActionId(null);
3149
+ }, [activePrompt?.userActionId, activePrompt?.subAction]);
3150
+ const sheetOpen = !!activePrompt && dismissedActionId !== activePrompt.userActionId;
3151
+ const showReopenCard = !!activePrompt && !sheetOpen && !expired && activePrompt.status !== "stale";
3152
+ const minimizeActivePrompt = react.useCallback(() => {
3153
+ setDismissedActionId((prev) => activePrompt?.userActionId ?? prev);
3154
+ }, [activePrompt?.userActionId]);
2253
3155
  const {
2254
3156
  transcribedText,
2255
3157
  isAvailable: voiceAvailable,
@@ -2326,7 +3228,7 @@ var PaymanChat = react.forwardRef(
2326
3228
  stopRecording();
2327
3229
  clearTranscript();
2328
3230
  }, [stopRecording, clearTranscript]);
2329
- const isInputDisabled = isWaitingForResponse || !config.sessionParams?.id?.trim();
3231
+ const isInputDisabled = !config.sessionParams?.id?.trim();
2330
3232
  const enableVoice = config.enableVoice !== false;
2331
3233
  const isEmpty = messages.length === 0;
2332
3234
  const prevMessageCountRef = react.useRef(messages.length);
@@ -2376,14 +3278,33 @@ var PaymanChat = react.forwardRef(
2376
3278
  message: msg,
2377
3279
  accent,
2378
3280
  shouldType: typingMessageIds.current.has(msg.id),
2379
- isDark
3281
+ isDark,
3282
+ suppressText: activePrompt?.message && msg.role === "assistant" && msg.id === messages[messages.length - 1]?.id ? activePrompt.message : void 0
2380
3283
  },
2381
3284
  msg.id
2382
3285
  )),
3286
+ showReopenCard && activePrompt ? /* @__PURE__ */ jsxRuntime.jsx(
3287
+ UserActionReopenCard,
3288
+ {
3289
+ active: activePrompt,
3290
+ expired,
3291
+ isDark,
3292
+ accent,
3293
+ onReopen: () => setDismissedActionId(null)
3294
+ }
3295
+ ) : null,
2383
3296
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { height: 4 } })
2384
3297
  ]
2385
3298
  }
2386
3299
  ) }),
3300
+ /* @__PURE__ */ jsxRuntime.jsx(
3301
+ NotificationStack,
3302
+ {
3303
+ notifications: userActionState.notifications,
3304
+ isDark,
3305
+ onDismiss: dismissNotification
3306
+ }
3307
+ ),
2387
3308
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.inputBarWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
2388
3309
  InputBar,
2389
3310
  {
@@ -2407,7 +3328,22 @@ var PaymanChat = react.forwardRef(
2407
3328
  },
2408
3329
  insetBottom: config.contentInsetBottom ?? (reactNative.Platform.OS === "ios" ? 20 : 8)
2409
3330
  }
2410
- ) })
3331
+ ) }),
3332
+ /* @__PURE__ */ jsxRuntime.jsx(
3333
+ UserActionSheet,
3334
+ {
3335
+ open: sheetOpen,
3336
+ active: activePrompt,
3337
+ expired,
3338
+ isDark,
3339
+ accent,
3340
+ insetBottom: config.contentInsetBottom ?? (reactNative.Platform.OS === "ios" ? 20 : 8),
3341
+ onSubmit: submitUserAction2,
3342
+ onCancel: cancelUserAction2,
3343
+ onResend: resendUserAction2,
3344
+ onMinimize: minimizeActivePrompt
3345
+ }
3346
+ )
2411
3347
  ]
2412
3348
  }
2413
3349
  ) });