@paymanai/payman-ask-sdk 4.0.26 → 4.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1155,6 +1155,9 @@ function getUserActionSecondsLeft(prompt) {
1155
1155
  return void 0;
1156
1156
  }
1157
1157
  var EMPTY_USER_ACTION_STATE2 = { prompts: [], notifications: [] };
1158
+ function promptSlotKey(p) {
1159
+ return p.toolCallId || p.userActionId;
1160
+ }
1158
1161
  function upsertPrompt(prompts, req) {
1159
1162
  const idx = prompts.findIndex(
1160
1163
  (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
@@ -1201,6 +1204,7 @@ function useChatV2(config, callbacks = {}) {
1201
1204
  configRef.current = config;
1202
1205
  const messagesRef = React.useRef(messages);
1203
1206
  messagesRef.current = messages;
1207
+ const pendingSubmissionsRef = React.useRef(/* @__PURE__ */ new Map());
1204
1208
  const storeAwareSetMessages = React.useCallback(
1205
1209
  (updater) => {
1206
1210
  const streamUserId = streamUserIdRef.current;
@@ -1235,6 +1239,8 @@ function useChatV2(config, callbacks = {}) {
1235
1239
  if (!config.userId) return EMPTY_USER_ACTION_STATE2;
1236
1240
  return activeStreamStore.get(config.userId)?.userActionState ?? EMPTY_USER_ACTION_STATE2;
1237
1241
  });
1242
+ const userActionStateRef = React.useRef(userActionState);
1243
+ userActionStateRef.current = userActionState;
1238
1244
  const storeAwareSetUserActionState = React.useCallback(
1239
1245
  (updater) => {
1240
1246
  const streamUserId = streamUserIdRef.current;
@@ -1262,6 +1268,12 @@ function useChatV2(config, callbacks = {}) {
1262
1268
  onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1263
1269
  onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1264
1270
  onUserActionRequired: (request) => {
1271
+ const requestSlotKey = promptSlotKey(request);
1272
+ for (const [pendingId, slotKey] of pendingSubmissionsRef.current) {
1273
+ if (slotKey === requestSlotKey) {
1274
+ pendingSubmissionsRef.current.delete(pendingId);
1275
+ }
1276
+ }
1265
1277
  storeAwareSetUserActionState((prev) => ({
1266
1278
  ...prev,
1267
1279
  prompts: upsertPrompt(prev.prompts, request)
@@ -1273,6 +1285,28 @@ function useChatV2(config, callbacks = {}) {
1273
1285
  (prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
1274
1286
  );
1275
1287
  callbacksRef.current.onUserNotification?.(notification);
1288
+ },
1289
+ // A submitted-but-unconfirmed prompt (see `submitUserAction`) is only
1290
+ // safe to drop once the stream has demonstrably moved on. `onEvent`
1291
+ // in useStreamManagerV2 calls `onStepsUpdate` for *every* processed
1292
+ // stream event, unconditionally — so the first time this fires
1293
+ // after a submission, the event in hand is either the rejection
1294
+ // retry (a fresh `USER_ACTION_REQUIRED`, which the handler above
1295
+ // already deleted this pending entry for, earlier in the very same
1296
+ // event) or genuine forward progress (RUN_IN_PROGRESS, a tool call,
1297
+ // content deltas, RUN_COMPLETED, ...). Anything still pending by
1298
+ // the time we get here therefore means the run resumed normally —
1299
+ // clear it.
1300
+ onStepsUpdate: (steps) => {
1301
+ if (pendingSubmissionsRef.current.size > 0) {
1302
+ const resolved = [...pendingSubmissionsRef.current.keys()];
1303
+ pendingSubmissionsRef.current.clear();
1304
+ storeAwareSetUserActionState((prev) => ({
1305
+ ...prev,
1306
+ prompts: prev.prompts.filter((p) => !resolved.includes(p.userActionId))
1307
+ }));
1308
+ }
1309
+ callbacksRef.current.onStepsUpdate?.(steps);
1276
1310
  }
1277
1311
  // eslint-disable-next-line react-hooks/exhaustive-deps
1278
1312
  }), []);
@@ -1408,6 +1442,7 @@ function useChatV2(config, callbacks = {}) {
1408
1442
  [storeAwareSetUserActionState]
1409
1443
  );
1410
1444
  const removePrompt = React.useCallback((userActionId) => {
1445
+ pendingSubmissionsRef.current.delete(userActionId);
1411
1446
  storeAwareSetUserActionState((prev) => ({
1412
1447
  ...prev,
1413
1448
  prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
@@ -1418,7 +1453,10 @@ function useChatV2(config, callbacks = {}) {
1418
1453
  setPromptStatus(userActionId, "submitting");
1419
1454
  try {
1420
1455
  await submitUserAction(configRef.current, userActionId, content);
1421
- removePrompt(userActionId);
1456
+ const prompt = userActionStateRef.current.prompts.find(
1457
+ (p) => p.userActionId === userActionId
1458
+ );
1459
+ pendingSubmissionsRef.current.set(userActionId, prompt ? promptSlotKey(prompt) : userActionId);
1422
1460
  } catch (error) {
1423
1461
  if (error instanceof UserActionStaleError) {
1424
1462
  setPromptStatus(userActionId, "stale");
@@ -1429,7 +1467,7 @@ function useChatV2(config, callbacks = {}) {
1429
1467
  throw error;
1430
1468
  }
1431
1469
  },
1432
- [removePrompt, setPromptStatus]
1470
+ [setPromptStatus]
1433
1471
  );
1434
1472
  const cancelUserAction2 = React.useCallback(
1435
1473
  async (userActionId) => {
@@ -4109,6 +4147,8 @@ function AssistantMessageV2({
4109
4147
  void 0,
4110
4148
  typingSpeed
4111
4149
  );
4150
+ const displayContentPending = Boolean(rawResponseContent) && !displayContent;
4151
+ const showThinkingBlock = isThinkingStreaming || displayContentPending;
4112
4152
  const stickyLabel = (() => {
4113
4153
  const steps = message.steps;
4114
4154
  if (!steps || steps.length === 0) return void 0;
@@ -4243,7 +4283,7 @@ function AssistantMessageV2({
4243
4283
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4244
4284
  toastPortal,
4245
4285
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-assistant-msg payman-v2-fade-in", children: [
4246
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: isThinkingStreaming && /* @__PURE__ */ jsxRuntime.jsx(
4286
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showThinkingBlock && /* @__PURE__ */ jsxRuntime.jsx(
4247
4287
  framerMotion.motion.div,
4248
4288
  {
4249
4289
  initial: { opacity: 0, height: 0 },
@@ -4273,6 +4313,10 @@ function AssistantMessageV2({
4273
4313
  isResolvingImages: message.isResolvingImages,
4274
4314
  onImageClick
4275
4315
  }
4316
+ ) : displayContentPending ? (
4317
+ // The intent/status line above is still bridging this
4318
+ // gap (see `showThinkingBlock`) — nothing to add here.
4319
+ null
4276
4320
  ) : !isThinkingStreaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-assistant-msg-placeholder", children: "..." }) : null }),
4277
4321
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showTrailingProgress && /* @__PURE__ */ jsxRuntime.jsx(
4278
4322
  framerMotion.motion.div,
@@ -4393,6 +4437,112 @@ function AssistantMessageV2({
4393
4437
  )
4394
4438
  ] });
4395
4439
  }
4440
+ var CHAR_VARIANTS = {
4441
+ fade: {
4442
+ initial: { opacity: 0, scale: 0.7 },
4443
+ animate: { opacity: 1, scale: 1 },
4444
+ exit: { opacity: 0, scale: 0.7 },
4445
+ transition: { duration: 0.14, ease: "easeOut" },
4446
+ overflow: "hidden"
4447
+ },
4448
+ blur: {
4449
+ initial: { opacity: 0, filter: "blur(8px)", y: -8 },
4450
+ animate: { opacity: 1, filter: "blur(0px)", y: 0 },
4451
+ exit: { opacity: 0, filter: "blur(8px)", y: 8 },
4452
+ transition: { duration: 0.18, ease: "easeOut" },
4453
+ overflow: "visible"
4454
+ }
4455
+ };
4456
+ function CharSlot({
4457
+ char,
4458
+ charKey,
4459
+ effect
4460
+ }) {
4461
+ if (!/\d/.test(char)) return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "inline-block" }, children: char });
4462
+ const v = CHAR_VARIANTS[effect];
4463
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { position: "relative", display: "inline-block", overflow: v.overflow }, children: /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "popLayout", initial: false, children: /* @__PURE__ */ jsxRuntime.jsx(
4464
+ framerMotion.motion.span,
4465
+ {
4466
+ initial: v.initial,
4467
+ animate: v.animate,
4468
+ exit: v.exit,
4469
+ transition: v.transition,
4470
+ style: { display: "inline-block" },
4471
+ children: char
4472
+ },
4473
+ charKey
4474
+ ) }) });
4475
+ }
4476
+ function CountUp({
4477
+ to,
4478
+ from = 0,
4479
+ duration = 2,
4480
+ delay = 0,
4481
+ digitEffect = "none",
4482
+ className,
4483
+ startWhen = true,
4484
+ separator = ""
4485
+ }) {
4486
+ const ref = React.useRef(null);
4487
+ const motionValue = framerMotion.useMotionValue(from);
4488
+ const damping = 20 + 40 * (1 / duration);
4489
+ const stiffness = 100 * (1 / duration);
4490
+ const springValue = framerMotion.useSpring(motionValue, { damping, stiffness });
4491
+ const isInView = framerMotion.useInView(ref, { once: true, margin: "0px" });
4492
+ const decimals = React.useMemo(() => {
4493
+ const d = (n) => {
4494
+ const s = n.toString();
4495
+ return s.includes(".") ? s.split(".")[1].length : 0;
4496
+ };
4497
+ return Math.max(d(from), d(to));
4498
+ }, [from, to]);
4499
+ const formatValue = React.useCallback(
4500
+ (latest) => {
4501
+ const formatted = Intl.NumberFormat("en-US", {
4502
+ useGrouping: !!separator,
4503
+ minimumFractionDigits: decimals,
4504
+ maximumFractionDigits: decimals
4505
+ }).format(latest);
4506
+ return separator ? formatted.replace(/,/g, separator) : formatted;
4507
+ },
4508
+ [decimals, separator]
4509
+ );
4510
+ const [chars, setChars] = React.useState(formatValue(from).split(""));
4511
+ React.useEffect(() => {
4512
+ if (isInView && startWhen) {
4513
+ const t = setTimeout(() => motionValue.set(to), delay * 1e3);
4514
+ return () => clearTimeout(t);
4515
+ }
4516
+ }, [isInView, startWhen, motionValue, to, delay]);
4517
+ React.useEffect(() => {
4518
+ const unsub = springValue.on("change", (latest) => {
4519
+ if (digitEffect === "none") {
4520
+ if (ref.current) ref.current.textContent = formatValue(latest);
4521
+ } else {
4522
+ setChars(formatValue(latest).split(""));
4523
+ }
4524
+ });
4525
+ return () => unsub();
4526
+ }, [springValue, formatValue, digitEffect]);
4527
+ if (digitEffect === "none") {
4528
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { ref, className: cn(className), children: formatValue(from) });
4529
+ }
4530
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { ref, className: cn("inline-flex items-center", className), children: chars.map((char, i) => /* @__PURE__ */ jsxRuntime.jsx(CharSlot, { char, charKey: `${i}-${char}`, effect: digitEffect }, i)) });
4531
+ }
4532
+ function AnimatedDuration({ seconds, className }) {
4533
+ const total = Math.max(0, seconds);
4534
+ const m = Math.floor(total / 60);
4535
+ const s = total % 60;
4536
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("tabular-nums", className), children: [
4537
+ m > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4538
+ /* @__PURE__ */ jsxRuntime.jsx(CountUp, { to: m, duration: 0.9, digitEffect: "blur", className: "tabular-nums" }),
4539
+ "m",
4540
+ " "
4541
+ ] }),
4542
+ /* @__PURE__ */ jsxRuntime.jsx(CountUp, { to: s, duration: 0.9, digitEffect: "blur", className: "tabular-nums" }),
4543
+ "s"
4544
+ ] });
4545
+ }
4396
4546
  var DEFAULT_MAX_LENGTH = 6;
4397
4547
  var MAX_SUPPORTED_LENGTH = 12;
4398
4548
  var AUTO_FOCUS_DELAY_MS = 250;
@@ -4519,7 +4669,6 @@ function VerificationInline({
4519
4669
  React.useEffect(() => {
4520
4670
  if (prompt.subAction === "SubmissionInvalid") {
4521
4671
  setErrored(true);
4522
- setCode("");
4523
4672
  lastSubmittedRef.current = null;
4524
4673
  }
4525
4674
  }, [prompt.subAction, prompt.userActionId]);
@@ -4598,7 +4747,7 @@ function VerificationInline({
4598
4747
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-ua-head", children: [
4599
4748
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShieldCheck, { className: "payman-v2-ua-icon", size: 15, strokeWidth: 1.75, "aria-hidden": true }),
4600
4749
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-title", children: "Verification required" }),
4601
- typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
4750
+ typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : /* @__PURE__ */ jsxRuntime.jsx(AnimatedDuration, { seconds: secondsLeft }) })
4602
4751
  ] }),
4603
4752
  renderMarkdown ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-ua-markdown", children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownRendererV2, { content: description }) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-ua-desc", children: description }),
4604
4753
  stale ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -4670,6 +4819,228 @@ function VerificationInline({
4670
4819
  ] })
4671
4820
  ] });
4672
4821
  }
4822
+ function AmountFieldV2({
4823
+ id,
4824
+ value,
4825
+ onChange,
4826
+ disabled,
4827
+ className,
4828
+ onEnter
4829
+ }) {
4830
+ const inputRef = React.useRef(null);
4831
+ const digits = digitsFromValue(value);
4832
+ const display = formatDigits(digits);
4833
+ React.useLayoutEffect(() => {
4834
+ const el = inputRef.current;
4835
+ if (el && document.activeElement === el) {
4836
+ el.setSelectionRange(el.value.length, el.value.length);
4837
+ }
4838
+ }, [display]);
4839
+ return /* @__PURE__ */ jsxRuntime.jsx(
4840
+ "input",
4841
+ {
4842
+ ref: inputRef,
4843
+ id,
4844
+ type: "text",
4845
+ inputMode: "decimal",
4846
+ autoComplete: "off",
4847
+ className,
4848
+ value: display,
4849
+ placeholder: "0.00",
4850
+ disabled,
4851
+ onChange: (e) => {
4852
+ onChange(valueFromDigits(digitsFromDisplay(e.target.value)));
4853
+ },
4854
+ onKeyDown: (e) => {
4855
+ if (e.key === "Enter") {
4856
+ e.preventDefault();
4857
+ onEnter?.();
4858
+ }
4859
+ },
4860
+ onFocus: (e) => {
4861
+ const el = e.currentTarget;
4862
+ requestAnimationFrame(() => el.setSelectionRange(el.value.length, el.value.length));
4863
+ }
4864
+ }
4865
+ );
4866
+ }
4867
+ function digitsFromDisplay(raw) {
4868
+ return raw.replace(/[^0-9]/g, "").replace(/^0+(?=\d)/, "").slice(-9);
4869
+ }
4870
+ function digitsFromValue(value) {
4871
+ const num = Number(value);
4872
+ if (!value || !Number.isFinite(num) || num <= 0) return "";
4873
+ return String(Math.round(num * 100));
4874
+ }
4875
+ function formatDigits(digits) {
4876
+ if (!digits) return "";
4877
+ const padded = digits.padStart(3, "0");
4878
+ const cents = padded.slice(-2);
4879
+ const whole = padded.slice(0, -2).replace(/^0+(?=\d)/, "") || "0";
4880
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
4881
+ return `${grouped}.${cents}`;
4882
+ }
4883
+ function valueFromDigits(digits) {
4884
+ if (!digits) return "";
4885
+ return (Number(digits) / 100).toString();
4886
+ }
4887
+ function findRootClassName(el) {
4888
+ const root = el?.closest(".payman-v2-root");
4889
+ return root instanceof HTMLElement ? root.className : "";
4890
+ }
4891
+ var MENU_MAX_HEIGHT = 240;
4892
+ var OPTION_HEIGHT = 34;
4893
+ var VIEWPORT_MARGIN = 8;
4894
+ function SelectFieldV2({
4895
+ id,
4896
+ value,
4897
+ options,
4898
+ onChange,
4899
+ disabled,
4900
+ error,
4901
+ placeholder = "Select\u2026"
4902
+ }) {
4903
+ const triggerRef = React.useRef(null);
4904
+ const listRef = React.useRef(null);
4905
+ const [open, setOpen] = React.useState(false);
4906
+ const [rect, setRect] = React.useState(null);
4907
+ const [activeIndex, setActiveIndex] = React.useState(-1);
4908
+ const [rootClassName, setRootClassName] = React.useState("");
4909
+ const selected = options.find((o) => o.const === value);
4910
+ React.useLayoutEffect(() => {
4911
+ if (!open) return;
4912
+ const place = () => {
4913
+ const el = triggerRef.current;
4914
+ if (!el) return;
4915
+ const r = el.getBoundingClientRect();
4916
+ const viewportH = window.innerHeight;
4917
+ const estimatedMenuH = Math.min(
4918
+ options.length * OPTION_HEIGHT + VIEWPORT_MARGIN,
4919
+ MENU_MAX_HEIGHT
4920
+ );
4921
+ const spaceBelow = viewportH - r.bottom;
4922
+ const spaceAbove = r.top;
4923
+ const openUp = spaceBelow < estimatedMenuH && spaceAbove > spaceBelow;
4924
+ setRect({ top: r.bottom, bottom: viewportH - r.top, left: r.left, width: r.width, openUp });
4925
+ setRootClassName(findRootClassName(el));
4926
+ };
4927
+ place();
4928
+ window.addEventListener("resize", place);
4929
+ window.addEventListener("scroll", place, true);
4930
+ return () => {
4931
+ window.removeEventListener("resize", place);
4932
+ window.removeEventListener("scroll", place, true);
4933
+ };
4934
+ }, [open, options.length]);
4935
+ React.useEffect(() => {
4936
+ if (!open) return;
4937
+ setActiveIndex(Math.max(0, options.findIndex((o) => o.const === value)));
4938
+ listRef.current?.focus();
4939
+ }, [open]);
4940
+ React.useEffect(() => {
4941
+ if (!open) return;
4942
+ const onDocDown = (e) => {
4943
+ const target = e.target;
4944
+ if (triggerRef.current?.contains(target) || listRef.current?.contains(target)) return;
4945
+ setOpen(false);
4946
+ };
4947
+ document.addEventListener("mousedown", onDocDown);
4948
+ return () => document.removeEventListener("mousedown", onDocDown);
4949
+ }, [open]);
4950
+ const commit = (opt) => {
4951
+ onChange(opt.const);
4952
+ setOpen(false);
4953
+ triggerRef.current?.focus();
4954
+ };
4955
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4956
+ /* @__PURE__ */ jsxRuntime.jsxs(
4957
+ "button",
4958
+ {
4959
+ ref: triggerRef,
4960
+ id,
4961
+ type: "button",
4962
+ disabled,
4963
+ className: cn("payman-v2-ua-select-trigger", error && "payman-v2-ua-input-error"),
4964
+ "aria-haspopup": "listbox",
4965
+ "aria-expanded": open,
4966
+ onClick: () => setOpen((v) => !v),
4967
+ onKeyDown: (e) => {
4968
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
4969
+ e.preventDefault();
4970
+ setOpen(true);
4971
+ }
4972
+ },
4973
+ children: [
4974
+ /* @__PURE__ */ jsxRuntime.jsx(
4975
+ "span",
4976
+ {
4977
+ className: cn(
4978
+ "payman-v2-ua-select-value",
4979
+ !selected && "payman-v2-ua-select-placeholder"
4980
+ ),
4981
+ children: selected ? selected.title || selected.const : placeholder
4982
+ }
4983
+ ),
4984
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 14, className: "payman-v2-ua-select-chevron", "aria-hidden": true })
4985
+ ]
4986
+ }
4987
+ ),
4988
+ open && rect && typeof document !== "undefined" && reactDom.createPortal(
4989
+ /* @__PURE__ */ jsxRuntime.jsx(
4990
+ "div",
4991
+ {
4992
+ ref: listRef,
4993
+ role: "listbox",
4994
+ className: cn(rootClassName, "payman-v2-ua-select-menu"),
4995
+ tabIndex: -1,
4996
+ style: {
4997
+ position: "fixed",
4998
+ left: rect.left,
4999
+ width: rect.width,
5000
+ height: "fit-content",
5001
+ maxHeight: MENU_MAX_HEIGHT,
5002
+ ...rect.openUp ? { bottom: rect.bottom } : { top: rect.top }
5003
+ },
5004
+ onKeyDown: (e) => {
5005
+ if (e.key === "ArrowDown") {
5006
+ e.preventDefault();
5007
+ setActiveIndex((i) => Math.min(options.length - 1, i + 1));
5008
+ } else if (e.key === "ArrowUp") {
5009
+ e.preventDefault();
5010
+ setActiveIndex((i) => Math.max(0, i - 1));
5011
+ } else if (e.key === "Enter" || e.key === " ") {
5012
+ e.preventDefault();
5013
+ if (activeIndex >= 0) commit(options[activeIndex]);
5014
+ } else if (e.key === "Escape" || e.key === "Tab") {
5015
+ setOpen(false);
5016
+ triggerRef.current?.focus();
5017
+ }
5018
+ },
5019
+ children: options.map((opt, i) => /* @__PURE__ */ jsxRuntime.jsxs(
5020
+ "div",
5021
+ {
5022
+ role: "option",
5023
+ "aria-selected": opt.const === value,
5024
+ className: cn(
5025
+ "payman-v2-ua-select-option",
5026
+ i === activeIndex && "payman-v2-ua-select-option-active",
5027
+ opt.const === value && "payman-v2-ua-select-option-selected"
5028
+ ),
5029
+ onMouseEnter: () => setActiveIndex(i),
5030
+ onClick: () => commit(opt),
5031
+ children: [
5032
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: opt.title || opt.const }),
5033
+ opt.const === value && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 13, "aria-hidden": true })
5034
+ ]
5035
+ },
5036
+ opt.const
5037
+ ))
5038
+ }
5039
+ ),
5040
+ document.body
5041
+ )
5042
+ ] });
5043
+ }
4673
5044
  function SchemaFormInline({
4674
5045
  prompt,
4675
5046
  secondsLeft,
@@ -4715,7 +5086,7 @@ function SchemaFormInline({
4715
5086
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-ua-head", children: [
4716
5087
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "payman-v2-ua-icon", size: 14, strokeWidth: 1.75, "aria-hidden": true }),
4717
5088
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-title", children: "Action required" }),
4718
- typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
5089
+ typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : /* @__PURE__ */ jsxRuntime.jsx(AnimatedDuration, { seconds: secondsLeft }) })
4719
5090
  ] }),
4720
5091
  prompt.message?.trim() && (renderMarkdown ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-ua-markdown", children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownRendererV2, { content: prompt.message }) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-ua-desc", children: prompt.message })),
4721
5092
  stale ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : fields.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-ua-form", children: fields.map(([key, field]) => {
@@ -4747,26 +5118,33 @@ function SchemaFormInline({
4747
5118
  required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-req", children: "*" })
4748
5119
  ] }),
4749
5120
  field.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-hint", children: field.description }),
4750
- widget === "select" ? /* @__PURE__ */ jsxRuntime.jsxs(
4751
- "select",
5121
+ widget === "select" ? /* @__PURE__ */ jsxRuntime.jsx(
5122
+ SelectFieldV2,
4752
5123
  {
4753
5124
  id: fieldId,
4754
- className: cn("payman-v2-ua-input", err && "payman-v2-ua-input-error"),
4755
5125
  value: String(values[key] ?? ""),
5126
+ options: getOptions(field),
4756
5127
  disabled: locked,
4757
- onChange: (e) => setValue(key, e.target.value),
4758
- children: [
4759
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Select\u2026" }),
4760
- getOptions(field).map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.const, children: opt.title || opt.const }, opt.const))
4761
- ]
5128
+ error: Boolean(err),
5129
+ onChange: (v) => setValue(key, v)
5130
+ }
5131
+ ) : widget === "decimal" ? /* @__PURE__ */ jsxRuntime.jsx(
5132
+ AmountFieldV2,
5133
+ {
5134
+ id: fieldId,
5135
+ value: String(values[key] ?? ""),
5136
+ disabled: locked,
5137
+ className: cn("payman-v2-ua-input", err && "payman-v2-ua-input-error"),
5138
+ onChange: (v) => setValue(key, v),
5139
+ onEnter: handleSubmit
4762
5140
  }
4763
5141
  ) : /* @__PURE__ */ jsxRuntime.jsx(
4764
5142
  "input",
4765
5143
  {
4766
5144
  id: fieldId,
4767
- type: widget === "integer" || widget === "decimal" ? "number" : "text",
4768
- inputMode: widget === "integer" ? "numeric" : widget === "decimal" ? "decimal" : void 0,
4769
- step: widget === "decimal" ? "any" : widget === "integer" ? "1" : void 0,
5145
+ type: widget === "integer" ? "number" : "text",
5146
+ inputMode: widget === "integer" ? "numeric" : void 0,
5147
+ step: widget === "integer" ? "1" : void 0,
4770
5148
  className: cn("payman-v2-ua-input", err && "payman-v2-ua-input-error"),
4771
5149
  value: String(values[key] ?? ""),
4772
5150
  disabled: locked,
@@ -4863,12 +5241,16 @@ function UserActionInline({
4863
5241
  onSubmit,
4864
5242
  onCancel,
4865
5243
  onResend,
4866
- onExpired
5244
+ onExpired,
5245
+ suppressExpiredNote
4867
5246
  }) {
4868
5247
  const [secondsLeft, expired] = useExpiryCountdown(prompt);
4869
5248
  React.useEffect(() => {
4870
5249
  if (expired && prompt.kind !== "notification") onExpired?.();
4871
5250
  }, [expired, onExpired, prompt.kind]);
5251
+ if (expired && prompt.kind !== "notification" && suppressExpiredNote) {
5252
+ return null;
5253
+ }
4872
5254
  let body;
4873
5255
  if (expired && prompt.kind !== "notification") {
4874
5256
  const note = {
@@ -4929,6 +5311,9 @@ function getPromptViewKey(prompt) {
4929
5311
  prompt.message ?? ""
4930
5312
  ].join(PROMPT_KEY_SEPARATOR);
4931
5313
  }
5314
+ function getPromptMountKey(prompt) {
5315
+ return [getPromptSlotKey(prompt), prompt.userActionId].join(PROMPT_KEY_SEPARATOR);
5316
+ }
4932
5317
  var MessageListV2 = React.forwardRef(
4933
5318
  function MessageListV22({
4934
5319
  messages,
@@ -4940,6 +5325,7 @@ var MessageListV2 = React.forwardRef(
4940
5325
  messageActions,
4941
5326
  retryDisabled = false,
4942
5327
  userActionPrompts,
5328
+ dockedUserActionId,
4943
5329
  notifications,
4944
5330
  onSubmitUserAction,
4945
5331
  onCancelUserAction,
@@ -5048,10 +5434,11 @@ var MessageListV2 = React.forwardRef(
5048
5434
  }, [userActionPrompts]);
5049
5435
  const visibleUserActionPrompts = React.useMemo(
5050
5436
  () => userActionPrompts?.filter((prompt) => {
5437
+ if (prompt.userActionId === dockedUserActionId) return false;
5051
5438
  const promptKey = getPromptViewKey(prompt);
5052
5439
  return !expiredPromptViewState[promptKey]?.hidden;
5053
5440
  }),
5054
- [expiredPromptViewState, userActionPrompts]
5441
+ [dockedUserActionId, expiredPromptViewState, userActionPrompts]
5055
5442
  );
5056
5443
  const pinToBottom = React.useCallback((behavior = "instant") => {
5057
5444
  const el = scrollRef.current;
@@ -5200,7 +5587,7 @@ var MessageListV2 = React.forwardRef(
5200
5587
  onResend: onResendUserAction ?? noop,
5201
5588
  onExpired: () => handleUserActionExpired(promptKey, prompt.userActionId)
5202
5589
  },
5203
- promptKey
5590
+ getPromptMountKey(prompt)
5204
5591
  );
5205
5592
  })
5206
5593
  ]
@@ -5746,10 +6133,455 @@ var ChatInputV2 = React.forwardRef(
5746
6133
  ] });
5747
6134
  }
5748
6135
  );
5749
-
5750
- // src/assets/thinking-atom.lottie
5751
- var thinking_atom_default = "data:application/zip;base64,UEsDBBQAAAAIAMWrkFzFrMWzUgAAAF4AAAANAAAAbWFuaWZlc3QuanNvbqtWKkstKs7Mz1OyUjJS0lFKT81LLUosyS8C8h1S8kty8ktKMlP14SzdrGIHQz0zPZDaxLzM3MQSoN5iJavoaqXMFKAe38TMPIXgZKApSrWxtQBQSwMEFAAAAAgAxauQXLRghFpXBAAA1RwAABEAAABhL01haW4gU2NlbmUuanNvbu1YWW/jNhD+KwafJVWkqMtPbdEDfdiiQIq+GMZCcei1GvmApLRdGP7v/YYiddqbGNumR4wcomZGHM7B76N0ZLstm7N3Wb6b3a3UTjGHPTw8sLnvsA2bCx/X3811q+qMzY/sAx74stjXda7WeaGqL1alyup9OeNeEnmcnRxWZB9VWbH54sjqj2weOI2bH5+KYibhoirZnONSa0f7AzykGOQYkGP4X2dFpbq13ONx0mTVu6x6bNXZXosf4erI9OLwjwRw7Tv4WWLSP+ALa7I2Rs0RkvkzRhEZQdWz8iHCms48Z54RMEAs2gABkcGRYVFHBqXv8Sh2GBKgRzDNW00SIClag1GzugVNShk5OUd9zxMj4cI/2UhoSdUwVBJpn53EGPMTOd0hh8hAUwuodC1UoVZ1ud+9UjWog+jvb6hIv9D9egxT8aK8kfte5tD5kiTYCfpaqvUPyABb7beH9/57zlUi4zBwE+FnrsyUdDOfC/c+iNVqzVOVKoH06vwLLDsr1Y6q+VZLgX7+dxQDW29SDGmKsVNPr1iLSyB1Bkui0CDGAEdkEmmpeXghQwe/Td0slnRzyDQ5NwdH8L05htXHLGFqIWk0fQ+YPhc9P3+3VpvsoDrSYR9KFNHWpKvWdgfdV998/e3sF2w70Nb35f7pAFNdfn0z49QpNLHDVnqh0FL9IUXU1kG1eZmDO1rYzB06+imrN30/hNG9JmkiPLIVm9flE2ZFuRZoFzf0hIyXzkJfqXcgtLJGCSHyi1SR/URF9pNJYP8b2adeaEwwoqc6AQ2XS+obXTi7bdi6eGmWs8MmXyEN3+UFPaOzQGOdBcQ52BeeDGXqgB3jRNIlirBe7Vs2TYNkXeiGsFtcTS0w3XLPAp8x0MQ86uluEVPse66t9R4ZdbVd88Vw4tNpqTcZIZekEUrFQi/2fAS3hqPIwBMXSIqBp6yqVN1shjHH5NejpvUyOtdZzNyX93lNVfwPn+vGrHjDpiuxSXAvFIHDhSdFRNghvcSPnMQLhKRbo3dbg0bvNgYWsZ4xcz7txuIYrGOfOzz0/JiTUZB4acwdVwiNa43abfVGrbVnMA79fCXG3WGvPdLLlM5nc6czWiBpKFXxq07ptqBOudg7hDI49vRUOAJYuJggZiQDrqFSxA6CMgj2/8PCNqDty6oCjqlViaL8XOb0iC4JjWfU51Xb6CiLunz8CiPop0cnnIb6Ryd7YGpf4bqTUmhPSpSSYXxuSsBtk3ztGkQwPAJS7QevkWdOa2SLNLSn378smfqMTZF0yRzV8Ez4SToNf4i5drmWByfHdk1B5PxGQW+Xgix5jMlhTB5jchlS0CeNOiK74GREQROOGVLQhKFuFHSjoH+CgkSELwM3CrqCgjDJGQoKbhT0pikIH0oE+CAMgOmpF3HAof580grxHaWRdt9opjr9wWUyj6UWHnvSD4yZ1N9pBiIXshuF3Cjktd9izmHo61CIxeSAPpWd/gRQSwECFAAUAAAACADFq5BcxazFs1IAAABeAAAADQAAAAAAAAAAAAAAAAAAAAAAbWFuaWZlc3QuanNvblBLAQIUABQAAAAIAMWrkFy0YIRaVwQAANUcAAARAAAAAAAAAAAAAAAAAH0AAABhL01haW4gU2NlbmUuanNvblBLBQYAAAAAAgACAHoAAAADBQAAAAA=";
5752
- var DEFAULT_SIZE = 36;
6136
+ function dockKey(prompt) {
6137
+ return `${prompt.toolCallId || prompt.userActionId}:${prompt.userActionId}`;
6138
+ }
6139
+ function UserActionDock({
6140
+ prompt,
6141
+ onSubmit,
6142
+ onCancel,
6143
+ onResend,
6144
+ onExpired,
6145
+ children
6146
+ }) {
6147
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-ua-dock", children: /* @__PURE__ */ jsxRuntime.jsx(framerMotion.motion.div, { layout: "size", transition: { duration: 0.3, ease: [0.2, 0, 0, 1] }, children: /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", initial: false, children: prompt ? /* @__PURE__ */ jsxRuntime.jsx(
6148
+ framerMotion.motion.div,
6149
+ {
6150
+ initial: { opacity: 0 },
6151
+ animate: { opacity: 1 },
6152
+ exit: { opacity: 0 },
6153
+ transition: { duration: 0.15 },
6154
+ className: "payman-v2-ua-dock-panel",
6155
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6156
+ UserActionInline,
6157
+ {
6158
+ prompt,
6159
+ onSubmit,
6160
+ onCancel,
6161
+ onResend,
6162
+ onExpired: () => onExpired?.(prompt.userActionId),
6163
+ suppressExpiredNote: true
6164
+ }
6165
+ )
6166
+ },
6167
+ dockKey(prompt)
6168
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
6169
+ framerMotion.motion.div,
6170
+ {
6171
+ initial: { opacity: 0 },
6172
+ animate: { opacity: 1 },
6173
+ exit: { opacity: 0 },
6174
+ transition: { duration: 0.15 },
6175
+ children
6176
+ },
6177
+ "composer"
6178
+ ) }) }) });
6179
+ }
6180
+ function clamp(value) {
6181
+ return Math.max(0, Math.min(1, value));
6182
+ }
6183
+ function ensureFrameSize(frame, rows, cols) {
6184
+ const result = [];
6185
+ for (let r = 0; r < rows; r++) {
6186
+ const row = frame[r] || [];
6187
+ result.push([]);
6188
+ for (let c = 0; c < cols; c++) {
6189
+ result[r][c] = row[c] ?? 0;
6190
+ }
6191
+ }
6192
+ return result;
6193
+ }
6194
+ function useAnimation(frames, options) {
6195
+ const [frameIndex, setFrameIndex] = React.useState(0);
6196
+ const [isPlaying, setIsPlaying] = React.useState(options.autoplay);
6197
+ const frameIdRef = React.useRef(void 0);
6198
+ const lastTimeRef = React.useRef(0);
6199
+ const accumulatorRef = React.useRef(0);
6200
+ React.useEffect(() => {
6201
+ if (!frames || frames.length === 0 || !isPlaying) {
6202
+ return;
6203
+ }
6204
+ const frameInterval = 1e3 / options.fps;
6205
+ const animate = (currentTime) => {
6206
+ if (lastTimeRef.current === 0) {
6207
+ lastTimeRef.current = currentTime;
6208
+ }
6209
+ const deltaTime = currentTime - lastTimeRef.current;
6210
+ lastTimeRef.current = currentTime;
6211
+ accumulatorRef.current += deltaTime;
6212
+ if (accumulatorRef.current >= frameInterval) {
6213
+ accumulatorRef.current -= frameInterval;
6214
+ setFrameIndex((prev) => {
6215
+ const next = prev + 1;
6216
+ if (next >= frames.length) {
6217
+ if (options.loop) {
6218
+ options.onFrame?.(0);
6219
+ return 0;
6220
+ } else {
6221
+ setIsPlaying(false);
6222
+ return prev;
6223
+ }
6224
+ }
6225
+ options.onFrame?.(next);
6226
+ return next;
6227
+ });
6228
+ }
6229
+ frameIdRef.current = requestAnimationFrame(animate);
6230
+ };
6231
+ frameIdRef.current = requestAnimationFrame(animate);
6232
+ return () => {
6233
+ if (frameIdRef.current) {
6234
+ cancelAnimationFrame(frameIdRef.current);
6235
+ }
6236
+ };
6237
+ }, [frames, isPlaying, options.fps, options.loop, options.onFrame]);
6238
+ React.useEffect(() => {
6239
+ setFrameIndex(0);
6240
+ setIsPlaying(options.autoplay);
6241
+ lastTimeRef.current = 0;
6242
+ accumulatorRef.current = 0;
6243
+ }, [frames, options.autoplay]);
6244
+ return { frameIndex, isPlaying };
6245
+ }
6246
+ function emptyFrame(rows, cols) {
6247
+ return Array.from({ length: rows }, () => Array(cols).fill(0));
6248
+ }
6249
+ function setPixel(frame, row, col, value) {
6250
+ if (row >= 0 && row < frame.length && col >= 0 && col < frame[0].length) {
6251
+ frame[row][col] = value;
6252
+ }
6253
+ }
6254
+ var loader = (() => {
6255
+ const frames = [];
6256
+ const size = 7;
6257
+ const center = 3;
6258
+ const radius = 2.5;
6259
+ for (let frame = 0; frame < 12; frame++) {
6260
+ const f = emptyFrame(size, size);
6261
+ for (let i = 0; i < 8; i++) {
6262
+ const angle = frame / 12 * Math.PI * 2 + i / 8 * Math.PI * 2;
6263
+ const x = Math.round(center + Math.cos(angle) * radius);
6264
+ const y = Math.round(center + Math.sin(angle) * radius);
6265
+ const brightness = 1 - i / 10;
6266
+ setPixel(f, y, x, Math.max(0.2, brightness));
6267
+ }
6268
+ frames.push(f);
6269
+ }
6270
+ return frames;
6271
+ })();
6272
+ var pulse = (() => {
6273
+ const frames = [];
6274
+ const size = 7;
6275
+ const center = 3;
6276
+ for (let frame = 0; frame < 16; frame++) {
6277
+ const f = emptyFrame(size, size);
6278
+ const phase = frame / 16 * Math.PI * 2;
6279
+ const intensity = (Math.sin(phase) + 1) / 2;
6280
+ setPixel(f, center, center, 1);
6281
+ const radius = Math.floor((1 - intensity) * 3) + 1;
6282
+ for (let dy = -radius; dy <= radius; dy++) {
6283
+ for (let dx = -radius; dx <= radius; dx++) {
6284
+ const dist = Math.sqrt(dx * dx + dy * dy);
6285
+ if (Math.abs(dist - radius) < 0.7) {
6286
+ setPixel(f, center + dy, center + dx, intensity * 0.6);
6287
+ }
6288
+ }
6289
+ }
6290
+ frames.push(f);
6291
+ }
6292
+ return frames;
6293
+ })();
6294
+ function vu(columns, levels) {
6295
+ const rows = 7;
6296
+ const frame = emptyFrame(rows, columns);
6297
+ for (let col = 0; col < Math.min(columns, levels.length); col++) {
6298
+ const level = Math.max(0, Math.min(1, levels[col]));
6299
+ const height = Math.floor(level * rows);
6300
+ for (let row = 0; row < rows; row++) {
6301
+ const rowFromBottom = rows - 1 - row;
6302
+ if (rowFromBottom < height) {
6303
+ let brightness = 1;
6304
+ if (row < rows * 0.3) {
6305
+ brightness = 1;
6306
+ } else if (row < rows * 0.6) {
6307
+ brightness = 0.8;
6308
+ } else {
6309
+ brightness = 0.6;
6310
+ }
6311
+ frame[row][col] = brightness;
6312
+ }
6313
+ }
6314
+ }
6315
+ return frame;
6316
+ }
6317
+ var wave = (() => {
6318
+ const frames = [];
6319
+ const rows = 7;
6320
+ const cols = 7;
6321
+ for (let frame = 0; frame < 24; frame++) {
6322
+ const f = emptyFrame(rows, cols);
6323
+ const phase = frame / 24 * Math.PI * 2;
6324
+ for (let col = 0; col < cols; col++) {
6325
+ const colPhase = col / cols * Math.PI * 2;
6326
+ const height = Math.sin(phase + colPhase) * 2.5 + 3.5;
6327
+ const row = Math.floor(height);
6328
+ if (row >= 0 && row < rows) {
6329
+ setPixel(f, row, col, 1);
6330
+ const frac = height - row;
6331
+ if (row > 0) setPixel(f, row - 1, col, 1 - frac);
6332
+ if (row < rows - 1) setPixel(f, row + 1, col, frac);
6333
+ }
6334
+ }
6335
+ frames.push(f);
6336
+ }
6337
+ return frames;
6338
+ })();
6339
+ var snake = (() => {
6340
+ const frames = [];
6341
+ const rows = 7;
6342
+ const cols = 7;
6343
+ const path = [];
6344
+ let x = 0;
6345
+ let y = 0;
6346
+ let dx = 1;
6347
+ let dy = 0;
6348
+ const visited = /* @__PURE__ */ new Set();
6349
+ while (path.length < rows * cols) {
6350
+ path.push([y, x]);
6351
+ visited.add(`${y},${x}`);
6352
+ const nextX = x + dx;
6353
+ const nextY = y + dy;
6354
+ if (nextX >= 0 && nextX < cols && nextY >= 0 && nextY < rows && !visited.has(`${nextY},${nextX}`)) {
6355
+ x = nextX;
6356
+ y = nextY;
6357
+ } else {
6358
+ const newDx = -dy;
6359
+ const newDy = dx;
6360
+ dx = newDx;
6361
+ dy = newDy;
6362
+ const nextX2 = x + dx;
6363
+ const nextY2 = y + dy;
6364
+ if (nextX2 >= 0 && nextX2 < cols && nextY2 >= 0 && nextY2 < rows && !visited.has(`${nextY2},${nextX2}`)) {
6365
+ x = nextX2;
6366
+ y = nextY2;
6367
+ } else {
6368
+ break;
6369
+ }
6370
+ }
6371
+ }
6372
+ const snakeLength = 5;
6373
+ for (let frame = 0; frame < path.length; frame++) {
6374
+ const f = emptyFrame(rows, cols);
6375
+ for (let i = 0; i < snakeLength; i++) {
6376
+ const idx = frame - i;
6377
+ if (idx >= 0 && idx < path.length) {
6378
+ const [y2, x2] = path[idx];
6379
+ const brightness = 1 - i / snakeLength;
6380
+ setPixel(f, y2, x2, brightness);
6381
+ }
6382
+ }
6383
+ frames.push(f);
6384
+ }
6385
+ return frames;
6386
+ })();
6387
+ var Matrix = React__namespace.forwardRef(
6388
+ ({
6389
+ rows,
6390
+ cols,
6391
+ pattern,
6392
+ frames,
6393
+ fps = 12,
6394
+ autoplay = true,
6395
+ loop = true,
6396
+ size = 10,
6397
+ gap = 2,
6398
+ palette = {
6399
+ on: "currentColor",
6400
+ off: "var(--muted-foreground)"
6401
+ },
6402
+ brightness = 1,
6403
+ ariaLabel,
6404
+ onFrame,
6405
+ mode = "default",
6406
+ levels,
6407
+ className,
6408
+ ...props
6409
+ }, ref) => {
6410
+ const { frameIndex } = useAnimation(frames, {
6411
+ fps,
6412
+ autoplay: autoplay && !pattern,
6413
+ loop,
6414
+ onFrame
6415
+ });
6416
+ const currentFrame = React.useMemo(() => {
6417
+ if (mode === "vu" && levels && levels.length > 0) {
6418
+ return ensureFrameSize(vu(cols, levels), rows, cols);
6419
+ }
6420
+ if (pattern) {
6421
+ return ensureFrameSize(pattern, rows, cols);
6422
+ }
6423
+ if (frames && frames.length > 0) {
6424
+ return ensureFrameSize(frames[frameIndex] || frames[0], rows, cols);
6425
+ }
6426
+ return ensureFrameSize([], rows, cols);
6427
+ }, [pattern, frames, frameIndex, rows, cols, mode, levels]);
6428
+ const cellPositions = React.useMemo(() => {
6429
+ const positions = [];
6430
+ for (let row = 0; row < rows; row++) {
6431
+ positions[row] = [];
6432
+ for (let col = 0; col < cols; col++) {
6433
+ positions[row][col] = {
6434
+ x: col * (size + gap),
6435
+ y: row * (size + gap)
6436
+ };
6437
+ }
6438
+ }
6439
+ return positions;
6440
+ }, [rows, cols, size, gap]);
6441
+ const svgDimensions = React.useMemo(() => {
6442
+ return {
6443
+ width: cols * (size + gap) - gap,
6444
+ height: rows * (size + gap) - gap
6445
+ };
6446
+ }, [rows, cols, size, gap]);
6447
+ const isAnimating = !pattern && frames && frames.length > 0;
6448
+ return /* @__PURE__ */ jsxRuntime.jsx(
6449
+ "div",
6450
+ {
6451
+ ref,
6452
+ role: "img",
6453
+ "aria-label": ariaLabel ?? "matrix display",
6454
+ "aria-live": isAnimating ? "polite" : void 0,
6455
+ className: cn("relative inline-block", className),
6456
+ style: {
6457
+ "--matrix-on": palette.on,
6458
+ "--matrix-off": palette.off,
6459
+ "--matrix-gap": `${gap}px`,
6460
+ "--matrix-size": `${size}px`
6461
+ },
6462
+ ...props,
6463
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
6464
+ "svg",
6465
+ {
6466
+ width: svgDimensions.width,
6467
+ height: svgDimensions.height,
6468
+ viewBox: `0 0 ${svgDimensions.width} ${svgDimensions.height}`,
6469
+ xmlns: "http://www.w3.org/2000/svg",
6470
+ className: "block",
6471
+ style: { overflow: "visible" },
6472
+ children: [
6473
+ /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
6474
+ /* @__PURE__ */ jsxRuntime.jsxs("radialGradient", { id: "matrix-pixel-on", cx: "50%", cy: "50%", r: "50%", children: [
6475
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "var(--matrix-on)", stopOpacity: "1" }),
6476
+ /* @__PURE__ */ jsxRuntime.jsx(
6477
+ "stop",
6478
+ {
6479
+ offset: "70%",
6480
+ stopColor: "var(--matrix-on)",
6481
+ stopOpacity: "0.85"
6482
+ }
6483
+ ),
6484
+ /* @__PURE__ */ jsxRuntime.jsx(
6485
+ "stop",
6486
+ {
6487
+ offset: "100%",
6488
+ stopColor: "var(--matrix-on)",
6489
+ stopOpacity: "0.6"
6490
+ }
6491
+ )
6492
+ ] }),
6493
+ /* @__PURE__ */ jsxRuntime.jsxs("radialGradient", { id: "matrix-pixel-off", cx: "50%", cy: "50%", r: "50%", children: [
6494
+ /* @__PURE__ */ jsxRuntime.jsx(
6495
+ "stop",
6496
+ {
6497
+ offset: "0%",
6498
+ stopColor: "var(--muted-foreground)",
6499
+ stopOpacity: "1"
6500
+ }
6501
+ ),
6502
+ /* @__PURE__ */ jsxRuntime.jsx(
6503
+ "stop",
6504
+ {
6505
+ offset: "100%",
6506
+ stopColor: "var(--muted-foreground)",
6507
+ stopOpacity: "0.7"
6508
+ }
6509
+ )
6510
+ ] }),
6511
+ /* @__PURE__ */ jsxRuntime.jsxs(
6512
+ "filter",
6513
+ {
6514
+ id: "matrix-glow",
6515
+ x: "-50%",
6516
+ y: "-50%",
6517
+ width: "200%",
6518
+ height: "200%",
6519
+ children: [
6520
+ /* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { stdDeviation: "2", result: "blur" }),
6521
+ /* @__PURE__ */ jsxRuntime.jsx("feComposite", { in: "SourceGraphic", in2: "blur", operator: "over" })
6522
+ ]
6523
+ }
6524
+ )
6525
+ ] }),
6526
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
6527
+ .matrix-pixel {
6528
+ transition: opacity 300ms ease-out, transform 150ms ease-out;
6529
+ transform-origin: center;
6530
+ transform-box: fill-box;
6531
+ }
6532
+ .matrix-pixel-active {
6533
+ filter: url(#matrix-glow);
6534
+ }
6535
+ ` }),
6536
+ currentFrame.map(
6537
+ (row, rowIndex) => row.map((value, colIndex) => {
6538
+ const pos = cellPositions[rowIndex]?.[colIndex];
6539
+ if (!pos) return null;
6540
+ const opacity = clamp(brightness * value);
6541
+ const isActive = opacity > 0.5;
6542
+ const isOn = opacity > 0.05;
6543
+ const fill = isOn ? "url(#matrix-pixel-on)" : "url(#matrix-pixel-off)";
6544
+ const scale = isActive ? 1.1 : 1;
6545
+ const radius = size / 2 * 0.9;
6546
+ return /* @__PURE__ */ jsxRuntime.jsx(
6547
+ "circle",
6548
+ {
6549
+ className: cn(
6550
+ "matrix-pixel",
6551
+ isActive && "matrix-pixel-active",
6552
+ !isOn && "opacity-20 dark:opacity-[0.1]"
6553
+ ),
6554
+ cx: pos.x + size / 2,
6555
+ cy: pos.y + size / 2,
6556
+ r: radius,
6557
+ fill,
6558
+ opacity: isOn ? opacity : 0.1,
6559
+ style: {
6560
+ transform: `scale(${scale})`
6561
+ }
6562
+ },
6563
+ `${rowIndex}-${colIndex}`
6564
+ );
6565
+ })
6566
+ )
6567
+ ]
6568
+ }
6569
+ )
6570
+ }
6571
+ );
6572
+ }
6573
+ );
6574
+ Matrix.displayName = "Matrix";
6575
+ var DEFAULT_SIZE = 22;
6576
+ var MATRIX_GRID = 7;
6577
+ var MATRIX_ON = "var(--payman-v2-matrix-color)";
6578
+ var MATRIX_PRESETS = [
6579
+ { frames: loader, fps: 8 },
6580
+ { frames: wave, fps: 14 },
6581
+ { frames: snake, fps: 16 },
6582
+ { frames: pulse, fps: 12 }
6583
+ ];
6584
+ var PRESET_ROTATE_MS = 1e4;
5753
6585
  function useElapsedSeconds(isStreaming) {
5754
6586
  const [elapsed, setElapsed] = React.useState(0);
5755
6587
  const startRef = React.useRef(null);
@@ -5770,12 +6602,37 @@ function useElapsedSeconds(isStreaming) {
5770
6602
  }, [isStreaming]);
5771
6603
  return elapsed;
5772
6604
  }
6605
+ function useMatrixPresetIndex(isStreaming) {
6606
+ const [index, setIndex] = React.useState(0);
6607
+ React.useEffect(() => {
6608
+ if (!isStreaming) return;
6609
+ setIndex(0);
6610
+ const id = setInterval(() => {
6611
+ setIndex((prev) => (prev + 1) % MATRIX_PRESETS.length);
6612
+ }, PRESET_ROTATE_MS);
6613
+ return () => clearInterval(id);
6614
+ }, [isStreaming]);
6615
+ return index;
6616
+ }
6617
+ function formatElapsed(totalSeconds) {
6618
+ if (totalSeconds < 60) return `${totalSeconds}s`;
6619
+ const minutes = Math.floor(totalSeconds / 60);
6620
+ const seconds = totalSeconds % 60;
6621
+ return `${minutes}m ${seconds}s`;
6622
+ }
5773
6623
  function StreamingIndicatorV2({
5774
6624
  isStreaming,
5775
6625
  loadingAnimation
5776
6626
  }) {
5777
6627
  const size = loadingAnimation?.size ?? DEFAULT_SIZE;
5778
6628
  const elapsed = useElapsedSeconds(isStreaming);
6629
+ const presetIndex = useMatrixPresetIndex(isStreaming);
6630
+ const preset = MATRIX_PRESETS[presetIndex];
6631
+ const gap = 1;
6632
+ const cell = React.useMemo(
6633
+ () => Math.max(2, Math.round((size - (MATRIX_GRID - 1) * gap) / MATRIX_GRID)),
6634
+ [size]
6635
+ );
5779
6636
  return /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: isStreaming && /* @__PURE__ */ jsxRuntime.jsxs(
5780
6637
  framerMotion.motion.div,
5781
6638
  {
@@ -5791,21 +6648,62 @@ function StreamingIndicatorV2({
5791
6648
  {
5792
6649
  className: "payman-v2-streaming-indicator-glyph",
5793
6650
  style: { width: size, height: size },
5794
- children: /* @__PURE__ */ jsxRuntime.jsx(
5795
- dotlottieReact.DotLottieReact,
5796
- {
5797
- src: loadingAnimation?.src ?? thinking_atom_default,
5798
- loop: true,
5799
- autoplay: true,
5800
- style: { width: "100%", height: "100%" }
5801
- }
6651
+ children: loadingAnimation?.src ? (
6652
+ // Consumer override: render their Lottie animation.
6653
+ /* @__PURE__ */ jsxRuntime.jsx(
6654
+ dotlottieReact.DotLottieReact,
6655
+ {
6656
+ src: loadingAnimation.src,
6657
+ loop: true,
6658
+ autoplay: true,
6659
+ style: { width: "100%", height: "100%" }
6660
+ }
6661
+ )
6662
+ ) : (
6663
+ // Default: dot-matrix glyph, cycling presets +
6664
+ // Payman brand tint. Each preset lives in its own
6665
+ // keyed motion layer so swapping presets crossfades
6666
+ // (old scales/fades out while the new scales/fades
6667
+ // in) instead of hard-cutting. Keying the layer on
6668
+ // `presetIndex` also remounts the Matrix, restarting
6669
+ // its frame counter cleanly at frame 0.
6670
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { initial: false, children: /* @__PURE__ */ jsxRuntime.jsx(
6671
+ framerMotion.motion.div,
6672
+ {
6673
+ className: "payman-v2-streaming-indicator-matrix",
6674
+ initial: { opacity: 0, scale: 0.8 },
6675
+ animate: { opacity: 1, scale: 1 },
6676
+ exit: { opacity: 0, scale: 0.8 },
6677
+ transition: { duration: 0.45, ease: [0.2, 0, 0, 1] },
6678
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6679
+ Matrix,
6680
+ {
6681
+ rows: MATRIX_GRID,
6682
+ cols: MATRIX_GRID,
6683
+ frames: preset.frames,
6684
+ fps: preset.fps,
6685
+ size: cell,
6686
+ gap,
6687
+ palette: {
6688
+ on: MATRIX_ON,
6689
+ // Unlit dots use the v2 theme's own muted-text
6690
+ // token (light/dark-aware), not the bare
6691
+ // `--muted-foreground` the vendored Matrix
6692
+ // component falls back to internally — that
6693
+ // variable isn't defined under `.payman-v2-root`
6694
+ // and would otherwise resolve to black.
6695
+ off: "var(--payman-v2-matrix-dot-off)"
6696
+ },
6697
+ ariaLabel: "Working\u2026"
6698
+ }
6699
+ )
6700
+ },
6701
+ presetIndex
6702
+ ) })
5802
6703
  )
5803
6704
  }
5804
6705
  ),
5805
- elapsed > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "payman-v2-streaming-indicator-elapsed", children: [
5806
- elapsed,
5807
- "s"
5808
- ] })
6706
+ elapsed > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-streaming-indicator-elapsed", children: formatElapsed(elapsed) })
5809
6707
  ]
5810
6708
  },
5811
6709
  "streaming-indicator"
@@ -6413,6 +7311,15 @@ var PaymanChatInner = React.forwardRef(function PaymanChatInner2({
6413
7311
  const expireUserAction2 = chat.expireUserAction ?? NOOP_ASYNC;
6414
7312
  const dismissNotification = chat.dismissNotification ?? NOOP;
6415
7313
  const isUserActionSupported = typeof chat.submitUserAction === "function" && typeof chat.cancelUserAction === "function" && typeof chat.resendUserAction === "function";
7314
+ const expiredUserActionIdsRef = React.useRef(/* @__PURE__ */ new Set());
7315
+ const expireUserActionOnce = React.useCallback(
7316
+ (userActionId) => {
7317
+ if (expiredUserActionIdsRef.current.has(userActionId)) return;
7318
+ expiredUserActionIdsRef.current.add(userActionId);
7319
+ void expireUserAction2(userActionId);
7320
+ },
7321
+ [expireUserAction2]
7322
+ );
6416
7323
  const {
6417
7324
  transcribedText,
6418
7325
  isAvailable: voiceAvailable,
@@ -6655,6 +7562,9 @@ var PaymanChatInner = React.forwardRef(function PaymanChatInner2({
6655
7562
  };
6656
7563
  const userActionPrompts = isUserActionSupported ? userActionState.prompts : void 0;
6657
7564
  const notifications = userActionState.notifications;
7565
+ const dockedPrompt = userActionPrompts?.find(
7566
+ (prompt) => prompt.kind !== "notification" && (prompt.status === "pending" || prompt.status === "submitting" || prompt.status === "stale")
7567
+ );
6658
7568
  const handleV2Send = (text) => {
6659
7569
  if (isRecording) stopRecording();
6660
7570
  if (text.trim() && !disableInput && isSessionParamsConfigured) {
@@ -6807,11 +7717,12 @@ var PaymanChatInner = React.forwardRef(function PaymanChatInner2({
6807
7717
  retryDisabled: isWaitingForResponse,
6808
7718
  typingSpeed: config.typingSpeed ?? 4,
6809
7719
  userActionPrompts,
7720
+ dockedUserActionId: dockedPrompt?.userActionId,
6810
7721
  notifications,
6811
7722
  onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
6812
7723
  onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
6813
7724
  onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
6814
- onExpireUserAction: isUserActionSupported ? expireUserAction2 : void 0,
7725
+ onExpireUserAction: isUserActionSupported ? async (id) => expireUserActionOnce(id) : void 0,
6815
7726
  onDismissNotification: dismissNotification,
6816
7727
  onSubmitFeedback: handleSubmitFeedback
6817
7728
  }
@@ -6824,33 +7735,43 @@ var PaymanChatInner = React.forwardRef(function PaymanChatInner2({
6824
7735
  }
6825
7736
  ),
6826
7737
  hasAskPermission && /* @__PURE__ */ jsxRuntime.jsx(
6827
- ChatInputV2,
7738
+ UserActionDock,
6828
7739
  {
6829
- ref: chatInputV2Ref,
6830
- onSend: handleV2Send,
6831
- onCancel: cancelStream,
6832
- disabled: isV2InputDisabled,
6833
- isStreaming: isWaitingForResponse,
6834
- placeholder: isRecording ? "Listening..." : placeholder,
6835
- enableVoice: config.enableVoice === true,
6836
- transcribedText: config.enableVoice === true ? transcribedText : "",
6837
- voiceAvailable: config.enableVoice === true && voiceAvailable,
6838
- isRecording,
6839
- onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
6840
- onCancelRecording: handleCancelRecording,
6841
- onConfirmRecording: handleConfirmRecording,
6842
- showResetSession,
6843
- onResetSession: requestResetSession,
6844
- showAttachmentButton,
6845
- showUploadImageButton,
6846
- showAttachFileButton,
6847
- onUploadImageClick,
6848
- onAttachFileClick,
6849
- editingMessageId,
6850
- onClearEditing: handleClearEditing,
6851
- analysisMode: enableDeepModeToggle ? analysisMode : void 0,
6852
- onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
6853
- slashCommands
7740
+ prompt: isUserActionSupported ? dockedPrompt : void 0,
7741
+ onSubmit: submitUserAction2,
7742
+ onCancel: cancelUserAction2,
7743
+ onResend: resendUserAction2,
7744
+ onExpired: expireUserActionOnce,
7745
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7746
+ ChatInputV2,
7747
+ {
7748
+ ref: chatInputV2Ref,
7749
+ onSend: handleV2Send,
7750
+ onCancel: cancelStream,
7751
+ disabled: isV2InputDisabled,
7752
+ isStreaming: isWaitingForResponse,
7753
+ placeholder: isRecording ? "Listening..." : placeholder,
7754
+ enableVoice: config.enableVoice === true,
7755
+ transcribedText: config.enableVoice === true ? transcribedText : "",
7756
+ voiceAvailable: config.enableVoice === true && voiceAvailable,
7757
+ isRecording,
7758
+ onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
7759
+ onCancelRecording: handleCancelRecording,
7760
+ onConfirmRecording: handleConfirmRecording,
7761
+ showResetSession,
7762
+ onResetSession: requestResetSession,
7763
+ showAttachmentButton,
7764
+ showUploadImageButton,
7765
+ showAttachFileButton,
7766
+ onUploadImageClick,
7767
+ onAttachFileClick,
7768
+ editingMessageId,
7769
+ onClearEditing: handleClearEditing,
7770
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
7771
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
7772
+ slashCommands
7773
+ }
7774
+ )
6854
7775
  }
6855
7776
  )
6856
7777
  ]