@paymanai/payman-ask-sdk 4.0.25 → 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.mjs CHANGED
@@ -1,5 +1,6 @@
1
+ import * as React from 'react';
1
2
  import { createContext, forwardRef, useCallback, useRef, useState, useMemo, useEffect, useImperativeHandle, useLayoutEffect, useContext } from 'react';
2
- import { AnimatePresence, motion } from 'framer-motion';
3
+ import { AnimatePresence, motion, useMotionValue, useSpring, useInView } from 'framer-motion';
3
4
  import { clsx } from 'clsx';
4
5
  import { twMerge } from 'tailwind-merge';
5
6
  import * as Sentry from '@sentry/react';
@@ -273,6 +274,8 @@ function getEventMessage(event) {
273
274
  }
274
275
  case "RUN_IN_PROGRESS":
275
276
  return event.partialText || "Thinking...";
277
+ case "LLM_CALL_STARTED":
278
+ return event.description || "";
276
279
  case "RUN_COMPLETED":
277
280
  return "Agent run completed";
278
281
  case "RUN_FAILED":
@@ -410,7 +413,8 @@ function createInitialV2State() {
410
413
  finalData: void 0,
411
414
  steps: [],
412
415
  stepCounter: 0,
413
- currentExecutingStepId: void 0
416
+ currentExecutingStepId: void 0,
417
+ trailingActivity: false
414
418
  };
415
419
  }
416
420
  function upsertUserAction(state, req) {
@@ -432,6 +436,7 @@ function processStreamEventV2(rawEvent, state) {
432
436
  if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
433
437
  if (event.executionId) state.executionId = event.executionId;
434
438
  if (event.sessionId) state.sessionId = event.sessionId;
439
+ if (state.finalResponse) state.trailingActivity = true;
435
440
  const description = typeof event.description === "string" ? event.description : "";
436
441
  if (description) {
437
442
  for (let i = state.steps.length - 1; i >= 0; i--) {
@@ -466,7 +471,12 @@ function processStreamEventV2(rawEvent, state) {
466
471
  state.lastEventType = eventType;
467
472
  break;
468
473
  }
474
+ case "LLM_CALL_STARTED":
475
+ if (state.finalResponse) state.trailingActivity = true;
476
+ state.lastEventType = eventType;
477
+ break;
469
478
  case "INTENT_STARTED": {
479
+ if (state.finalResponse) state.trailingActivity = true;
470
480
  const stepId = `step-${state.stepCounter++}`;
471
481
  state.steps.push({
472
482
  id: stepId,
@@ -482,6 +492,7 @@ function processStreamEventV2(rawEvent, state) {
482
492
  break;
483
493
  }
484
494
  case "INTENT_COMPLETED": {
495
+ if (state.finalResponse) state.trailingActivity = true;
485
496
  const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
486
497
  if (intentStep) {
487
498
  intentStep.status = "completed";
@@ -513,6 +524,7 @@ function processStreamEventV2(rawEvent, state) {
513
524
  step2.status = "completed";
514
525
  }
515
526
  });
527
+ state.trailingActivity = false;
516
528
  state.lastEventType = eventType;
517
529
  break;
518
530
  }
@@ -584,6 +596,7 @@ function processStreamEventV2(rawEvent, state) {
584
596
  case "WORKFLOW_ERROR":
585
597
  state.hasError = true;
586
598
  state.errorMessage = event.errorMessage || event.message || "Workflow error";
599
+ state.trailingActivity = false;
587
600
  state.lastEventType = eventType;
588
601
  break;
589
602
  // ---- K2 pipeline stage lifecycle events ----
@@ -872,7 +885,9 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
872
885
  const latestUsefulStep = [...state.steps].reverse().find(
873
886
  (s) => s.message && !isBlandStatus(s.message)
874
887
  );
875
- const currentMessage = useful(activeStep?.message) ?? useful(lastInProgressStep?.message) ?? latestUsefulStep?.message ?? useful(getEventMessage(event)) ?? // Last-resort: every candidate is bland (very first event,
888
+ const eventTypeUpper = typeof event.eventType === "string" ? event.eventType.toUpperCase() : "";
889
+ const liveEventLabel = eventTypeUpper === "KEEP_ALIVE" || eventTypeUpper === "LLM_CALL_STARTED" ? useful(getEventMessage(event)) : void 0;
890
+ const currentMessage = useful(activeStep?.message) ?? useful(lastInProgressStep?.message) ?? liveEventLabel ?? latestUsefulStep?.message ?? useful(getEventMessage(event)) ?? // Last-resort: every candidate is bland (very first event,
876
891
  // nothing useful seen yet). Render the bland label so the
877
892
  // bubble isn't blank.
878
893
  activeStep?.message ?? lastInProgressStep?.message ?? getEventMessage(event);
@@ -897,6 +912,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
897
912
  streamingContent: state.finalResponse,
898
913
  content: "",
899
914
  currentMessage,
915
+ hasTrailingActivity: state.trailingActivity,
900
916
  streamProgress: "processing",
901
917
  isError: false,
902
918
  steps: [...state.steps],
@@ -1112,6 +1128,9 @@ function getUserActionSecondsLeft(prompt) {
1112
1128
  return void 0;
1113
1129
  }
1114
1130
  var EMPTY_USER_ACTION_STATE2 = { prompts: [], notifications: [] };
1131
+ function promptSlotKey(p) {
1132
+ return p.toolCallId || p.userActionId;
1133
+ }
1115
1134
  function upsertPrompt(prompts, req) {
1116
1135
  const idx = prompts.findIndex(
1117
1136
  (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
@@ -1158,6 +1177,7 @@ function useChatV2(config, callbacks = {}) {
1158
1177
  configRef.current = config;
1159
1178
  const messagesRef = useRef(messages);
1160
1179
  messagesRef.current = messages;
1180
+ const pendingSubmissionsRef = useRef(/* @__PURE__ */ new Map());
1161
1181
  const storeAwareSetMessages = useCallback(
1162
1182
  (updater) => {
1163
1183
  const streamUserId = streamUserIdRef.current;
@@ -1192,6 +1212,8 @@ function useChatV2(config, callbacks = {}) {
1192
1212
  if (!config.userId) return EMPTY_USER_ACTION_STATE2;
1193
1213
  return activeStreamStore.get(config.userId)?.userActionState ?? EMPTY_USER_ACTION_STATE2;
1194
1214
  });
1215
+ const userActionStateRef = useRef(userActionState);
1216
+ userActionStateRef.current = userActionState;
1195
1217
  const storeAwareSetUserActionState = useCallback(
1196
1218
  (updater) => {
1197
1219
  const streamUserId = streamUserIdRef.current;
@@ -1219,6 +1241,12 @@ function useChatV2(config, callbacks = {}) {
1219
1241
  onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1220
1242
  onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1221
1243
  onUserActionRequired: (request) => {
1244
+ const requestSlotKey = promptSlotKey(request);
1245
+ for (const [pendingId, slotKey] of pendingSubmissionsRef.current) {
1246
+ if (slotKey === requestSlotKey) {
1247
+ pendingSubmissionsRef.current.delete(pendingId);
1248
+ }
1249
+ }
1222
1250
  storeAwareSetUserActionState((prev) => ({
1223
1251
  ...prev,
1224
1252
  prompts: upsertPrompt(prev.prompts, request)
@@ -1230,6 +1258,28 @@ function useChatV2(config, callbacks = {}) {
1230
1258
  (prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
1231
1259
  );
1232
1260
  callbacksRef.current.onUserNotification?.(notification);
1261
+ },
1262
+ // A submitted-but-unconfirmed prompt (see `submitUserAction`) is only
1263
+ // safe to drop once the stream has demonstrably moved on. `onEvent`
1264
+ // in useStreamManagerV2 calls `onStepsUpdate` for *every* processed
1265
+ // stream event, unconditionally — so the first time this fires
1266
+ // after a submission, the event in hand is either the rejection
1267
+ // retry (a fresh `USER_ACTION_REQUIRED`, which the handler above
1268
+ // already deleted this pending entry for, earlier in the very same
1269
+ // event) or genuine forward progress (RUN_IN_PROGRESS, a tool call,
1270
+ // content deltas, RUN_COMPLETED, ...). Anything still pending by
1271
+ // the time we get here therefore means the run resumed normally —
1272
+ // clear it.
1273
+ onStepsUpdate: (steps) => {
1274
+ if (pendingSubmissionsRef.current.size > 0) {
1275
+ const resolved = [...pendingSubmissionsRef.current.keys()];
1276
+ pendingSubmissionsRef.current.clear();
1277
+ storeAwareSetUserActionState((prev) => ({
1278
+ ...prev,
1279
+ prompts: prev.prompts.filter((p) => !resolved.includes(p.userActionId))
1280
+ }));
1281
+ }
1282
+ callbacksRef.current.onStepsUpdate?.(steps);
1233
1283
  }
1234
1284
  // eslint-disable-next-line react-hooks/exhaustive-deps
1235
1285
  }), []);
@@ -1365,6 +1415,7 @@ function useChatV2(config, callbacks = {}) {
1365
1415
  [storeAwareSetUserActionState]
1366
1416
  );
1367
1417
  const removePrompt = useCallback((userActionId) => {
1418
+ pendingSubmissionsRef.current.delete(userActionId);
1368
1419
  storeAwareSetUserActionState((prev) => ({
1369
1420
  ...prev,
1370
1421
  prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
@@ -1375,7 +1426,10 @@ function useChatV2(config, callbacks = {}) {
1375
1426
  setPromptStatus(userActionId, "submitting");
1376
1427
  try {
1377
1428
  await submitUserAction(configRef.current, userActionId, content);
1378
- removePrompt(userActionId);
1429
+ const prompt = userActionStateRef.current.prompts.find(
1430
+ (p) => p.userActionId === userActionId
1431
+ );
1432
+ pendingSubmissionsRef.current.set(userActionId, prompt ? promptSlotKey(prompt) : userActionId);
1379
1433
  } catch (error) {
1380
1434
  if (error instanceof UserActionStaleError) {
1381
1435
  setPromptStatus(userActionId, "stale");
@@ -1386,7 +1440,7 @@ function useChatV2(config, callbacks = {}) {
1386
1440
  throw error;
1387
1441
  }
1388
1442
  },
1389
- [removePrompt, setPromptStatus]
1443
+ [setPromptStatus]
1390
1444
  );
1391
1445
  const cancelUserAction2 = useCallback(
1392
1446
  async (userActionId) => {
@@ -2055,6 +2109,9 @@ function getConflictErrorMessage(errorDetails) {
2055
2109
  }
2056
2110
  function initSentryIfNeeded(dsn) {
2057
2111
  if (!dsn) return;
2112
+ if (typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1")) {
2113
+ return;
2114
+ }
2058
2115
  if (Sentry.getClient()) return;
2059
2116
  Sentry.init({
2060
2117
  dsn,
@@ -3436,9 +3493,88 @@ function MarkdownImageV2({
3436
3493
  }
3437
3494
  ) });
3438
3495
  }
3439
- function buildComponents(onImageClick, isResolvingRef) {
3496
+ var RAG_SOURCE_CLASS = "payman-v2-rag-source";
3497
+ var RAG_FOLLOW_UP_CLASS = "payman-v2-rag-follow-up";
3498
+ var SOURCE_PATTERN = /^Sources?:\s*\S/i;
3499
+ var MAX_FOLLOW_UP_LENGTH = 320;
3500
+ var HR_PATTERN = /^-{3,}$|^_{3,}$|^\*{3,}$/;
3501
+ function normalizeRagParagraphText(text) {
3502
+ return text.replace(/\s+/g, " ").trim();
3503
+ }
3504
+ function isRagSourceParagraph(text) {
3505
+ return SOURCE_PATTERN.test(normalizeRagParagraphText(text));
3506
+ }
3507
+ function isRagFollowUpCandidate(text) {
3508
+ const normalized = normalizeRagParagraphText(text);
3509
+ return normalized.length > 0 && normalized.length <= MAX_FOLLOW_UP_LENGTH && normalized.endsWith("?");
3510
+ }
3511
+ function extractRagParagraphTexts(content) {
3512
+ const paragraphs = [];
3513
+ const currentParagraph = [];
3514
+ const flushParagraph = () => {
3515
+ const paragraph = normalizeRagParagraphText(currentParagraph.join(" "));
3516
+ currentParagraph.length = 0;
3517
+ if (paragraph) paragraphs.push(paragraph);
3518
+ };
3519
+ content.replace(/\\n/g, "\n").split(/\r?\n/).forEach((line) => {
3520
+ const trimmedLine = line.trim();
3521
+ if (!trimmedLine) {
3522
+ flushParagraph();
3523
+ return;
3524
+ }
3525
+ if (HR_PATTERN.test(trimmedLine)) {
3526
+ flushParagraph();
3527
+ return;
3528
+ }
3529
+ currentParagraph.push(trimmedLine);
3530
+ });
3531
+ flushParagraph();
3532
+ return paragraphs;
3533
+ }
3534
+ function getRagAnswerParagraphRoles(paragraphs) {
3535
+ const normalized = paragraphs.map(normalizeRagParagraphText);
3536
+ const roles = normalized.map(() => null);
3537
+ const sourceIndexes = normalized.map((paragraph, index) => isRagSourceParagraph(paragraph) ? index : -1).filter((index) => index >= 0);
3538
+ for (const sourceIndex of sourceIndexes) {
3539
+ roles[sourceIndex] = "source";
3540
+ for (const candidateIndex of [sourceIndex - 1, sourceIndex + 1]) {
3541
+ if (candidateIndex >= 0 && candidateIndex < normalized.length && roles[candidateIndex] == null && isRagFollowUpCandidate(normalized[candidateIndex] ?? "")) {
3542
+ roles[candidateIndex] = "follow-up";
3543
+ }
3544
+ }
3545
+ }
3546
+ return roles;
3547
+ }
3548
+ function getRagAnswerParagraphRole(paragraph, paragraphs) {
3549
+ const normalizedParagraph = normalizeRagParagraphText(paragraph);
3550
+ if (isRagSourceParagraph(normalizedParagraph)) return "source";
3551
+ const roles = getRagAnswerParagraphRoles(paragraphs);
3552
+ return paragraphs.map(normalizeRagParagraphText).some((candidate, index) => {
3553
+ return candidate === normalizedParagraph && roles[index] === "follow-up";
3554
+ }) ? "follow-up" : null;
3555
+ }
3556
+ function ragAnswerRoleClassName(role) {
3557
+ if (role === "source") return RAG_SOURCE_CLASS;
3558
+ if (role === "follow-up") return RAG_FOLLOW_UP_CLASS;
3559
+ return void 0;
3560
+ }
3561
+ function reactNodeText(node) {
3562
+ if (typeof node === "string" || typeof node === "number") return String(node);
3563
+ if (Array.isArray(node)) return node.map(reactNodeText).join("");
3564
+ if (React.isValidElement(node)) {
3565
+ return reactNodeText(node.props.children);
3566
+ }
3567
+ return "";
3568
+ }
3569
+ function buildComponents(onImageClick, isResolvingRef, ragParagraphsRef) {
3440
3570
  return {
3441
- p: ({ children }) => /* @__PURE__ */ jsx("p", { children }),
3571
+ p: ({ children }) => {
3572
+ const role = getRagAnswerParagraphRole(
3573
+ reactNodeText(children),
3574
+ ragParagraphsRef?.current ?? []
3575
+ );
3576
+ return /* @__PURE__ */ jsx("p", { className: ragAnswerRoleClassName(role), children });
3577
+ },
3442
3578
  code: ({ children }) => /* @__PURE__ */ jsx("code", { children }),
3443
3579
  pre: ({ children }) => /* @__PURE__ */ jsx("div", { className: "payman-v2-markdown-pre", children: /* @__PURE__ */ jsx("pre", { children }) }),
3444
3580
  ul: ({ children }) => /* @__PURE__ */ jsx("ul", { children }),
@@ -3476,8 +3612,13 @@ function MarkdownRendererV2({
3476
3612
  }) {
3477
3613
  const isResolvingRef = useRef(isResolvingImages);
3478
3614
  isResolvingRef.current = isResolvingImages;
3615
+ const ragParagraphsRef = useRef([]);
3616
+ ragParagraphsRef.current = useMemo(
3617
+ () => extractRagParagraphTexts(content),
3618
+ [content]
3619
+ );
3479
3620
  const components = useMemo(
3480
- () => buildComponents(onImageClick, isResolvingRef),
3621
+ () => buildComponents(onImageClick, isResolvingRef, ragParagraphsRef),
3481
3622
  [onImageClick]
3482
3623
  );
3483
3624
  return /* @__PURE__ */ jsx(
@@ -3970,6 +4111,7 @@ function AssistantMessageV2({
3970
4111
  return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
3971
4112
  })();
3972
4113
  const isThinkingStreaming = !!message.isStreaming && !rawResponseContent && !message.isError;
4114
+ const showTrailingProgress = !!message.isStreaming && !!rawResponseContent && !message.isError && !message.isCancelled && !!message.hasTrailingActivity;
3973
4115
  const responseTypingEnabled = hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
3974
4116
  const { displayedText: displayContent, isTyping: isResponseTyping } = useTypingEffect(
3975
4117
  rawResponseContent,
@@ -3978,6 +4120,8 @@ function AssistantMessageV2({
3978
4120
  void 0,
3979
4121
  typingSpeed
3980
4122
  );
4123
+ const displayContentPending = Boolean(rawResponseContent) && !displayContent;
4124
+ const showThinkingBlock = isThinkingStreaming || displayContentPending;
3981
4125
  const stickyLabel = (() => {
3982
4126
  const steps = message.steps;
3983
4127
  if (!steps || steps.length === 0) return void 0;
@@ -4112,7 +4256,7 @@ function AssistantMessageV2({
4112
4256
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4113
4257
  toastPortal,
4114
4258
  /* @__PURE__ */ jsxs("div", { className: "payman-v2-assistant-msg payman-v2-fade-in", children: [
4115
- /* @__PURE__ */ jsx(AnimatePresence, { children: isThinkingStreaming && /* @__PURE__ */ jsx(
4259
+ /* @__PURE__ */ jsx(AnimatePresence, { children: showThinkingBlock && /* @__PURE__ */ jsx(
4116
4260
  motion.div,
4117
4261
  {
4118
4262
  initial: { opacity: 0, height: 0 },
@@ -4128,7 +4272,7 @@ function AssistantMessageV2({
4128
4272
  {
4129
4273
  isStreaming: isThinkingStreaming,
4130
4274
  stickyLabel,
4131
- currentStepLabel
4275
+ currentStepLabel: currentStepLabel ?? message.currentMessage
4132
4276
  }
4133
4277
  )
4134
4278
  },
@@ -4142,7 +4286,32 @@ function AssistantMessageV2({
4142
4286
  isResolvingImages: message.isResolvingImages,
4143
4287
  onImageClick
4144
4288
  }
4289
+ ) : displayContentPending ? (
4290
+ // The intent/status line above is still bridging this
4291
+ // gap (see `showThinkingBlock`) — nothing to add here.
4292
+ null
4145
4293
  ) : !isThinkingStreaming ? /* @__PURE__ */ jsx("span", { className: "payman-v2-assistant-msg-placeholder", children: "..." }) : null }),
4294
+ /* @__PURE__ */ jsx(AnimatePresence, { children: showTrailingProgress && /* @__PURE__ */ jsx(
4295
+ motion.div,
4296
+ {
4297
+ initial: { opacity: 0, height: 0 },
4298
+ animate: { opacity: 1, height: "auto" },
4299
+ exit: { opacity: 0, height: 0 },
4300
+ transition: {
4301
+ duration: 0.32,
4302
+ ease: [0.2, 0, 0, 1]
4303
+ },
4304
+ style: { overflow: "hidden" },
4305
+ children: /* @__PURE__ */ jsx(
4306
+ ThinkingBlockV2,
4307
+ {
4308
+ isStreaming: true,
4309
+ currentStepLabel: message.currentMessage ?? currentStepLabel
4310
+ }
4311
+ )
4312
+ },
4313
+ "trailing-progress"
4314
+ ) }),
4146
4315
  isCancelled && message.isStreaming && /* @__PURE__ */ jsxs("div", { className: "payman-v2-assistant-msg-paused", children: [
4147
4316
  /* @__PURE__ */ jsx(
4148
4317
  WifiOff,
@@ -4241,6 +4410,112 @@ function AssistantMessageV2({
4241
4410
  )
4242
4411
  ] });
4243
4412
  }
4413
+ var CHAR_VARIANTS = {
4414
+ fade: {
4415
+ initial: { opacity: 0, scale: 0.7 },
4416
+ animate: { opacity: 1, scale: 1 },
4417
+ exit: { opacity: 0, scale: 0.7 },
4418
+ transition: { duration: 0.14, ease: "easeOut" },
4419
+ overflow: "hidden"
4420
+ },
4421
+ blur: {
4422
+ initial: { opacity: 0, filter: "blur(8px)", y: -8 },
4423
+ animate: { opacity: 1, filter: "blur(0px)", y: 0 },
4424
+ exit: { opacity: 0, filter: "blur(8px)", y: 8 },
4425
+ transition: { duration: 0.18, ease: "easeOut" },
4426
+ overflow: "visible"
4427
+ }
4428
+ };
4429
+ function CharSlot({
4430
+ char,
4431
+ charKey,
4432
+ effect
4433
+ }) {
4434
+ if (!/\d/.test(char)) return /* @__PURE__ */ jsx("span", { style: { display: "inline-block" }, children: char });
4435
+ const v = CHAR_VARIANTS[effect];
4436
+ return /* @__PURE__ */ jsx("span", { style: { position: "relative", display: "inline-block", overflow: v.overflow }, children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", initial: false, children: /* @__PURE__ */ jsx(
4437
+ motion.span,
4438
+ {
4439
+ initial: v.initial,
4440
+ animate: v.animate,
4441
+ exit: v.exit,
4442
+ transition: v.transition,
4443
+ style: { display: "inline-block" },
4444
+ children: char
4445
+ },
4446
+ charKey
4447
+ ) }) });
4448
+ }
4449
+ function CountUp({
4450
+ to,
4451
+ from = 0,
4452
+ duration = 2,
4453
+ delay = 0,
4454
+ digitEffect = "none",
4455
+ className,
4456
+ startWhen = true,
4457
+ separator = ""
4458
+ }) {
4459
+ const ref = useRef(null);
4460
+ const motionValue = useMotionValue(from);
4461
+ const damping = 20 + 40 * (1 / duration);
4462
+ const stiffness = 100 * (1 / duration);
4463
+ const springValue = useSpring(motionValue, { damping, stiffness });
4464
+ const isInView = useInView(ref, { once: true, margin: "0px" });
4465
+ const decimals = useMemo(() => {
4466
+ const d = (n) => {
4467
+ const s = n.toString();
4468
+ return s.includes(".") ? s.split(".")[1].length : 0;
4469
+ };
4470
+ return Math.max(d(from), d(to));
4471
+ }, [from, to]);
4472
+ const formatValue = useCallback(
4473
+ (latest) => {
4474
+ const formatted = Intl.NumberFormat("en-US", {
4475
+ useGrouping: !!separator,
4476
+ minimumFractionDigits: decimals,
4477
+ maximumFractionDigits: decimals
4478
+ }).format(latest);
4479
+ return separator ? formatted.replace(/,/g, separator) : formatted;
4480
+ },
4481
+ [decimals, separator]
4482
+ );
4483
+ const [chars, setChars] = useState(formatValue(from).split(""));
4484
+ useEffect(() => {
4485
+ if (isInView && startWhen) {
4486
+ const t = setTimeout(() => motionValue.set(to), delay * 1e3);
4487
+ return () => clearTimeout(t);
4488
+ }
4489
+ }, [isInView, startWhen, motionValue, to, delay]);
4490
+ useEffect(() => {
4491
+ const unsub = springValue.on("change", (latest) => {
4492
+ if (digitEffect === "none") {
4493
+ if (ref.current) ref.current.textContent = formatValue(latest);
4494
+ } else {
4495
+ setChars(formatValue(latest).split(""));
4496
+ }
4497
+ });
4498
+ return () => unsub();
4499
+ }, [springValue, formatValue, digitEffect]);
4500
+ if (digitEffect === "none") {
4501
+ return /* @__PURE__ */ jsx("span", { ref, className: cn(className), children: formatValue(from) });
4502
+ }
4503
+ return /* @__PURE__ */ jsx("span", { ref, className: cn("inline-flex items-center", className), children: chars.map((char, i) => /* @__PURE__ */ jsx(CharSlot, { char, charKey: `${i}-${char}`, effect: digitEffect }, i)) });
4504
+ }
4505
+ function AnimatedDuration({ seconds, className }) {
4506
+ const total = Math.max(0, seconds);
4507
+ const m = Math.floor(total / 60);
4508
+ const s = total % 60;
4509
+ return /* @__PURE__ */ jsxs("span", { className: cn("tabular-nums", className), children: [
4510
+ m > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
4511
+ /* @__PURE__ */ jsx(CountUp, { to: m, duration: 0.9, digitEffect: "blur", className: "tabular-nums" }),
4512
+ "m",
4513
+ " "
4514
+ ] }),
4515
+ /* @__PURE__ */ jsx(CountUp, { to: s, duration: 0.9, digitEffect: "blur", className: "tabular-nums" }),
4516
+ "s"
4517
+ ] });
4518
+ }
4244
4519
  var DEFAULT_MAX_LENGTH = 6;
4245
4520
  var MAX_SUPPORTED_LENGTH = 12;
4246
4521
  var AUTO_FOCUS_DELAY_MS = 250;
@@ -4367,7 +4642,6 @@ function VerificationInline({
4367
4642
  useEffect(() => {
4368
4643
  if (prompt.subAction === "SubmissionInvalid") {
4369
4644
  setErrored(true);
4370
- setCode("");
4371
4645
  lastSubmittedRef.current = null;
4372
4646
  }
4373
4647
  }, [prompt.subAction, prompt.userActionId]);
@@ -4437,15 +4711,18 @@ function VerificationInline({
4437
4711
  if (busy) return;
4438
4712
  void onCancel(prompt.userActionId);
4439
4713
  }, [busy, onCancel, prompt.userActionId]);
4440
- const description = prompt.message?.trim() || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
4714
+ const promptMessage = prompt.message?.trim();
4715
+ const description = promptMessage || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
4716
+ const messageFormat = prompt.metadata?.["payman/messageFormat"];
4717
+ const renderMarkdown = !!promptMessage && messageFormat === "markdown";
4441
4718
  if (hiddenAfterResend) return null;
4442
4719
  return /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua", role: "group", "aria-label": "Verification required", children: [
4443
4720
  /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-head", children: [
4444
4721
  /* @__PURE__ */ jsx(ShieldCheck, { className: "payman-v2-ua-icon", size: 15, strokeWidth: 1.75, "aria-hidden": true }),
4445
4722
  /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-title", children: "Verification required" }),
4446
- typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
4723
+ typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : /* @__PURE__ */ jsx(AnimatedDuration, { seconds: secondsLeft }) })
4447
4724
  ] }),
4448
- /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: description }),
4725
+ renderMarkdown ? /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-markdown", children: /* @__PURE__ */ jsx(MarkdownRendererV2, { content: description }) }) : /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: description }),
4449
4726
  stale ? /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4450
4727
  isNumeric ? /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-field", children: /* @__PURE__ */ jsx(
4451
4728
  OtpInputV2,
@@ -4515,6 +4792,228 @@ function VerificationInline({
4515
4792
  ] })
4516
4793
  ] });
4517
4794
  }
4795
+ function AmountFieldV2({
4796
+ id,
4797
+ value,
4798
+ onChange,
4799
+ disabled,
4800
+ className,
4801
+ onEnter
4802
+ }) {
4803
+ const inputRef = useRef(null);
4804
+ const digits = digitsFromValue(value);
4805
+ const display = formatDigits(digits);
4806
+ useLayoutEffect(() => {
4807
+ const el = inputRef.current;
4808
+ if (el && document.activeElement === el) {
4809
+ el.setSelectionRange(el.value.length, el.value.length);
4810
+ }
4811
+ }, [display]);
4812
+ return /* @__PURE__ */ jsx(
4813
+ "input",
4814
+ {
4815
+ ref: inputRef,
4816
+ id,
4817
+ type: "text",
4818
+ inputMode: "decimal",
4819
+ autoComplete: "off",
4820
+ className,
4821
+ value: display,
4822
+ placeholder: "0.00",
4823
+ disabled,
4824
+ onChange: (e) => {
4825
+ onChange(valueFromDigits(digitsFromDisplay(e.target.value)));
4826
+ },
4827
+ onKeyDown: (e) => {
4828
+ if (e.key === "Enter") {
4829
+ e.preventDefault();
4830
+ onEnter?.();
4831
+ }
4832
+ },
4833
+ onFocus: (e) => {
4834
+ const el = e.currentTarget;
4835
+ requestAnimationFrame(() => el.setSelectionRange(el.value.length, el.value.length));
4836
+ }
4837
+ }
4838
+ );
4839
+ }
4840
+ function digitsFromDisplay(raw) {
4841
+ return raw.replace(/[^0-9]/g, "").replace(/^0+(?=\d)/, "").slice(-9);
4842
+ }
4843
+ function digitsFromValue(value) {
4844
+ const num = Number(value);
4845
+ if (!value || !Number.isFinite(num) || num <= 0) return "";
4846
+ return String(Math.round(num * 100));
4847
+ }
4848
+ function formatDigits(digits) {
4849
+ if (!digits) return "";
4850
+ const padded = digits.padStart(3, "0");
4851
+ const cents = padded.slice(-2);
4852
+ const whole = padded.slice(0, -2).replace(/^0+(?=\d)/, "") || "0";
4853
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
4854
+ return `${grouped}.${cents}`;
4855
+ }
4856
+ function valueFromDigits(digits) {
4857
+ if (!digits) return "";
4858
+ return (Number(digits) / 100).toString();
4859
+ }
4860
+ function findRootClassName(el) {
4861
+ const root = el?.closest(".payman-v2-root");
4862
+ return root instanceof HTMLElement ? root.className : "";
4863
+ }
4864
+ var MENU_MAX_HEIGHT = 240;
4865
+ var OPTION_HEIGHT = 34;
4866
+ var VIEWPORT_MARGIN = 8;
4867
+ function SelectFieldV2({
4868
+ id,
4869
+ value,
4870
+ options,
4871
+ onChange,
4872
+ disabled,
4873
+ error,
4874
+ placeholder = "Select\u2026"
4875
+ }) {
4876
+ const triggerRef = useRef(null);
4877
+ const listRef = useRef(null);
4878
+ const [open, setOpen] = useState(false);
4879
+ const [rect, setRect] = useState(null);
4880
+ const [activeIndex, setActiveIndex] = useState(-1);
4881
+ const [rootClassName, setRootClassName] = useState("");
4882
+ const selected = options.find((o) => o.const === value);
4883
+ useLayoutEffect(() => {
4884
+ if (!open) return;
4885
+ const place = () => {
4886
+ const el = triggerRef.current;
4887
+ if (!el) return;
4888
+ const r = el.getBoundingClientRect();
4889
+ const viewportH = window.innerHeight;
4890
+ const estimatedMenuH = Math.min(
4891
+ options.length * OPTION_HEIGHT + VIEWPORT_MARGIN,
4892
+ MENU_MAX_HEIGHT
4893
+ );
4894
+ const spaceBelow = viewportH - r.bottom;
4895
+ const spaceAbove = r.top;
4896
+ const openUp = spaceBelow < estimatedMenuH && spaceAbove > spaceBelow;
4897
+ setRect({ top: r.bottom, bottom: viewportH - r.top, left: r.left, width: r.width, openUp });
4898
+ setRootClassName(findRootClassName(el));
4899
+ };
4900
+ place();
4901
+ window.addEventListener("resize", place);
4902
+ window.addEventListener("scroll", place, true);
4903
+ return () => {
4904
+ window.removeEventListener("resize", place);
4905
+ window.removeEventListener("scroll", place, true);
4906
+ };
4907
+ }, [open, options.length]);
4908
+ useEffect(() => {
4909
+ if (!open) return;
4910
+ setActiveIndex(Math.max(0, options.findIndex((o) => o.const === value)));
4911
+ listRef.current?.focus();
4912
+ }, [open]);
4913
+ useEffect(() => {
4914
+ if (!open) return;
4915
+ const onDocDown = (e) => {
4916
+ const target = e.target;
4917
+ if (triggerRef.current?.contains(target) || listRef.current?.contains(target)) return;
4918
+ setOpen(false);
4919
+ };
4920
+ document.addEventListener("mousedown", onDocDown);
4921
+ return () => document.removeEventListener("mousedown", onDocDown);
4922
+ }, [open]);
4923
+ const commit = (opt) => {
4924
+ onChange(opt.const);
4925
+ setOpen(false);
4926
+ triggerRef.current?.focus();
4927
+ };
4928
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4929
+ /* @__PURE__ */ jsxs(
4930
+ "button",
4931
+ {
4932
+ ref: triggerRef,
4933
+ id,
4934
+ type: "button",
4935
+ disabled,
4936
+ className: cn("payman-v2-ua-select-trigger", error && "payman-v2-ua-input-error"),
4937
+ "aria-haspopup": "listbox",
4938
+ "aria-expanded": open,
4939
+ onClick: () => setOpen((v) => !v),
4940
+ onKeyDown: (e) => {
4941
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
4942
+ e.preventDefault();
4943
+ setOpen(true);
4944
+ }
4945
+ },
4946
+ children: [
4947
+ /* @__PURE__ */ jsx(
4948
+ "span",
4949
+ {
4950
+ className: cn(
4951
+ "payman-v2-ua-select-value",
4952
+ !selected && "payman-v2-ua-select-placeholder"
4953
+ ),
4954
+ children: selected ? selected.title || selected.const : placeholder
4955
+ }
4956
+ ),
4957
+ /* @__PURE__ */ jsx(ChevronDown, { size: 14, className: "payman-v2-ua-select-chevron", "aria-hidden": true })
4958
+ ]
4959
+ }
4960
+ ),
4961
+ open && rect && typeof document !== "undefined" && createPortal(
4962
+ /* @__PURE__ */ jsx(
4963
+ "div",
4964
+ {
4965
+ ref: listRef,
4966
+ role: "listbox",
4967
+ className: cn(rootClassName, "payman-v2-ua-select-menu"),
4968
+ tabIndex: -1,
4969
+ style: {
4970
+ position: "fixed",
4971
+ left: rect.left,
4972
+ width: rect.width,
4973
+ height: "fit-content",
4974
+ maxHeight: MENU_MAX_HEIGHT,
4975
+ ...rect.openUp ? { bottom: rect.bottom } : { top: rect.top }
4976
+ },
4977
+ onKeyDown: (e) => {
4978
+ if (e.key === "ArrowDown") {
4979
+ e.preventDefault();
4980
+ setActiveIndex((i) => Math.min(options.length - 1, i + 1));
4981
+ } else if (e.key === "ArrowUp") {
4982
+ e.preventDefault();
4983
+ setActiveIndex((i) => Math.max(0, i - 1));
4984
+ } else if (e.key === "Enter" || e.key === " ") {
4985
+ e.preventDefault();
4986
+ if (activeIndex >= 0) commit(options[activeIndex]);
4987
+ } else if (e.key === "Escape" || e.key === "Tab") {
4988
+ setOpen(false);
4989
+ triggerRef.current?.focus();
4990
+ }
4991
+ },
4992
+ children: options.map((opt, i) => /* @__PURE__ */ jsxs(
4993
+ "div",
4994
+ {
4995
+ role: "option",
4996
+ "aria-selected": opt.const === value,
4997
+ className: cn(
4998
+ "payman-v2-ua-select-option",
4999
+ i === activeIndex && "payman-v2-ua-select-option-active",
5000
+ opt.const === value && "payman-v2-ua-select-option-selected"
5001
+ ),
5002
+ onMouseEnter: () => setActiveIndex(i),
5003
+ onClick: () => commit(opt),
5004
+ children: [
5005
+ /* @__PURE__ */ jsx("span", { children: opt.title || opt.const }),
5006
+ opt.const === value && /* @__PURE__ */ jsx(Check, { size: 13, "aria-hidden": true })
5007
+ ]
5008
+ },
5009
+ opt.const
5010
+ ))
5011
+ }
5012
+ ),
5013
+ document.body
5014
+ )
5015
+ ] });
5016
+ }
4518
5017
  function SchemaFormInline({
4519
5018
  prompt,
4520
5019
  secondsLeft,
@@ -4560,7 +5059,7 @@ function SchemaFormInline({
4560
5059
  /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-head", children: [
4561
5060
  /* @__PURE__ */ jsx(Pencil, { className: "payman-v2-ua-icon", size: 14, strokeWidth: 1.75, "aria-hidden": true }),
4562
5061
  /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-title", children: "Action required" }),
4563
- typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
5062
+ typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : /* @__PURE__ */ jsx(AnimatedDuration, { seconds: secondsLeft }) })
4564
5063
  ] }),
4565
5064
  prompt.message?.trim() && (renderMarkdown ? /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-markdown", children: /* @__PURE__ */ jsx(MarkdownRendererV2, { content: prompt.message }) }) : /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: prompt.message })),
4566
5065
  stale ? /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : fields.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-form", children: fields.map(([key, field]) => {
@@ -4592,26 +5091,33 @@ function SchemaFormInline({
4592
5091
  required && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-req", children: "*" })
4593
5092
  ] }),
4594
5093
  field.description && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-hint", children: field.description }),
4595
- widget === "select" ? /* @__PURE__ */ jsxs(
4596
- "select",
5094
+ widget === "select" ? /* @__PURE__ */ jsx(
5095
+ SelectFieldV2,
4597
5096
  {
4598
5097
  id: fieldId,
4599
- className: cn("payman-v2-ua-input", err && "payman-v2-ua-input-error"),
4600
5098
  value: String(values[key] ?? ""),
5099
+ options: getOptions(field),
4601
5100
  disabled: locked,
4602
- onChange: (e) => setValue(key, e.target.value),
4603
- children: [
4604
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select\u2026" }),
4605
- getOptions(field).map((opt) => /* @__PURE__ */ jsx("option", { value: opt.const, children: opt.title || opt.const }, opt.const))
4606
- ]
5101
+ error: Boolean(err),
5102
+ onChange: (v) => setValue(key, v)
5103
+ }
5104
+ ) : widget === "decimal" ? /* @__PURE__ */ jsx(
5105
+ AmountFieldV2,
5106
+ {
5107
+ id: fieldId,
5108
+ value: String(values[key] ?? ""),
5109
+ disabled: locked,
5110
+ className: cn("payman-v2-ua-input", err && "payman-v2-ua-input-error"),
5111
+ onChange: (v) => setValue(key, v),
5112
+ onEnter: handleSubmit
4607
5113
  }
4608
5114
  ) : /* @__PURE__ */ jsx(
4609
5115
  "input",
4610
5116
  {
4611
5117
  id: fieldId,
4612
- type: widget === "integer" || widget === "decimal" ? "number" : "text",
4613
- inputMode: widget === "integer" ? "numeric" : widget === "decimal" ? "decimal" : void 0,
4614
- step: widget === "decimal" ? "any" : widget === "integer" ? "1" : void 0,
5118
+ type: widget === "integer" ? "number" : "text",
5119
+ inputMode: widget === "integer" ? "numeric" : void 0,
5120
+ step: widget === "integer" ? "1" : void 0,
4615
5121
  className: cn("payman-v2-ua-input", err && "payman-v2-ua-input-error"),
4616
5122
  value: String(values[key] ?? ""),
4617
5123
  disabled: locked,
@@ -4708,12 +5214,16 @@ function UserActionInline({
4708
5214
  onSubmit,
4709
5215
  onCancel,
4710
5216
  onResend,
4711
- onExpired
5217
+ onExpired,
5218
+ suppressExpiredNote
4712
5219
  }) {
4713
5220
  const [secondsLeft, expired] = useExpiryCountdown(prompt);
4714
5221
  useEffect(() => {
4715
5222
  if (expired && prompt.kind !== "notification") onExpired?.();
4716
5223
  }, [expired, onExpired, prompt.kind]);
5224
+ if (expired && prompt.kind !== "notification" && suppressExpiredNote) {
5225
+ return null;
5226
+ }
4717
5227
  let body;
4718
5228
  if (expired && prompt.kind !== "notification") {
4719
5229
  const note = {
@@ -4774,6 +5284,9 @@ function getPromptViewKey(prompt) {
4774
5284
  prompt.message ?? ""
4775
5285
  ].join(PROMPT_KEY_SEPARATOR);
4776
5286
  }
5287
+ function getPromptMountKey(prompt) {
5288
+ return [getPromptSlotKey(prompt), prompt.userActionId].join(PROMPT_KEY_SEPARATOR);
5289
+ }
4777
5290
  var MessageListV2 = forwardRef(
4778
5291
  function MessageListV22({
4779
5292
  messages,
@@ -4785,6 +5298,7 @@ var MessageListV2 = forwardRef(
4785
5298
  messageActions,
4786
5299
  retryDisabled = false,
4787
5300
  userActionPrompts,
5301
+ dockedUserActionId,
4788
5302
  notifications,
4789
5303
  onSubmitUserAction,
4790
5304
  onCancelUserAction,
@@ -4893,10 +5407,11 @@ var MessageListV2 = forwardRef(
4893
5407
  }, [userActionPrompts]);
4894
5408
  const visibleUserActionPrompts = useMemo(
4895
5409
  () => userActionPrompts?.filter((prompt) => {
5410
+ if (prompt.userActionId === dockedUserActionId) return false;
4896
5411
  const promptKey = getPromptViewKey(prompt);
4897
5412
  return !expiredPromptViewState[promptKey]?.hidden;
4898
5413
  }),
4899
- [expiredPromptViewState, userActionPrompts]
5414
+ [dockedUserActionId, expiredPromptViewState, userActionPrompts]
4900
5415
  );
4901
5416
  const pinToBottom = useCallback((behavior = "instant") => {
4902
5417
  const el = scrollRef.current;
@@ -5045,7 +5560,7 @@ var MessageListV2 = forwardRef(
5045
5560
  onResend: onResendUserAction ?? noop,
5046
5561
  onExpired: () => handleUserActionExpired(promptKey, prompt.userActionId)
5047
5562
  },
5048
- promptKey
5563
+ getPromptMountKey(prompt)
5049
5564
  );
5050
5565
  })
5051
5566
  ]
@@ -5591,10 +6106,455 @@ var ChatInputV2 = forwardRef(
5591
6106
  ] });
5592
6107
  }
5593
6108
  );
5594
-
5595
- // src/assets/thinking-atom.lottie
5596
- 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=";
5597
- var DEFAULT_SIZE = 36;
6109
+ function dockKey(prompt) {
6110
+ return `${prompt.toolCallId || prompt.userActionId}:${prompt.userActionId}`;
6111
+ }
6112
+ function UserActionDock({
6113
+ prompt,
6114
+ onSubmit,
6115
+ onCancel,
6116
+ onResend,
6117
+ onExpired,
6118
+ children
6119
+ }) {
6120
+ return /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-dock", children: /* @__PURE__ */ jsx(motion.div, { layout: "size", transition: { duration: 0.3, ease: [0.2, 0, 0, 1] }, children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: prompt ? /* @__PURE__ */ jsx(
6121
+ motion.div,
6122
+ {
6123
+ initial: { opacity: 0 },
6124
+ animate: { opacity: 1 },
6125
+ exit: { opacity: 0 },
6126
+ transition: { duration: 0.15 },
6127
+ className: "payman-v2-ua-dock-panel",
6128
+ children: /* @__PURE__ */ jsx(
6129
+ UserActionInline,
6130
+ {
6131
+ prompt,
6132
+ onSubmit,
6133
+ onCancel,
6134
+ onResend,
6135
+ onExpired: () => onExpired?.(prompt.userActionId),
6136
+ suppressExpiredNote: true
6137
+ }
6138
+ )
6139
+ },
6140
+ dockKey(prompt)
6141
+ ) : /* @__PURE__ */ jsx(
6142
+ motion.div,
6143
+ {
6144
+ initial: { opacity: 0 },
6145
+ animate: { opacity: 1 },
6146
+ exit: { opacity: 0 },
6147
+ transition: { duration: 0.15 },
6148
+ children
6149
+ },
6150
+ "composer"
6151
+ ) }) }) });
6152
+ }
6153
+ function clamp(value) {
6154
+ return Math.max(0, Math.min(1, value));
6155
+ }
6156
+ function ensureFrameSize(frame, rows, cols) {
6157
+ const result = [];
6158
+ for (let r = 0; r < rows; r++) {
6159
+ const row = frame[r] || [];
6160
+ result.push([]);
6161
+ for (let c = 0; c < cols; c++) {
6162
+ result[r][c] = row[c] ?? 0;
6163
+ }
6164
+ }
6165
+ return result;
6166
+ }
6167
+ function useAnimation(frames, options) {
6168
+ const [frameIndex, setFrameIndex] = useState(0);
6169
+ const [isPlaying, setIsPlaying] = useState(options.autoplay);
6170
+ const frameIdRef = useRef(void 0);
6171
+ const lastTimeRef = useRef(0);
6172
+ const accumulatorRef = useRef(0);
6173
+ useEffect(() => {
6174
+ if (!frames || frames.length === 0 || !isPlaying) {
6175
+ return;
6176
+ }
6177
+ const frameInterval = 1e3 / options.fps;
6178
+ const animate = (currentTime) => {
6179
+ if (lastTimeRef.current === 0) {
6180
+ lastTimeRef.current = currentTime;
6181
+ }
6182
+ const deltaTime = currentTime - lastTimeRef.current;
6183
+ lastTimeRef.current = currentTime;
6184
+ accumulatorRef.current += deltaTime;
6185
+ if (accumulatorRef.current >= frameInterval) {
6186
+ accumulatorRef.current -= frameInterval;
6187
+ setFrameIndex((prev) => {
6188
+ const next = prev + 1;
6189
+ if (next >= frames.length) {
6190
+ if (options.loop) {
6191
+ options.onFrame?.(0);
6192
+ return 0;
6193
+ } else {
6194
+ setIsPlaying(false);
6195
+ return prev;
6196
+ }
6197
+ }
6198
+ options.onFrame?.(next);
6199
+ return next;
6200
+ });
6201
+ }
6202
+ frameIdRef.current = requestAnimationFrame(animate);
6203
+ };
6204
+ frameIdRef.current = requestAnimationFrame(animate);
6205
+ return () => {
6206
+ if (frameIdRef.current) {
6207
+ cancelAnimationFrame(frameIdRef.current);
6208
+ }
6209
+ };
6210
+ }, [frames, isPlaying, options.fps, options.loop, options.onFrame]);
6211
+ useEffect(() => {
6212
+ setFrameIndex(0);
6213
+ setIsPlaying(options.autoplay);
6214
+ lastTimeRef.current = 0;
6215
+ accumulatorRef.current = 0;
6216
+ }, [frames, options.autoplay]);
6217
+ return { frameIndex, isPlaying };
6218
+ }
6219
+ function emptyFrame(rows, cols) {
6220
+ return Array.from({ length: rows }, () => Array(cols).fill(0));
6221
+ }
6222
+ function setPixel(frame, row, col, value) {
6223
+ if (row >= 0 && row < frame.length && col >= 0 && col < frame[0].length) {
6224
+ frame[row][col] = value;
6225
+ }
6226
+ }
6227
+ var loader = (() => {
6228
+ const frames = [];
6229
+ const size = 7;
6230
+ const center = 3;
6231
+ const radius = 2.5;
6232
+ for (let frame = 0; frame < 12; frame++) {
6233
+ const f = emptyFrame(size, size);
6234
+ for (let i = 0; i < 8; i++) {
6235
+ const angle = frame / 12 * Math.PI * 2 + i / 8 * Math.PI * 2;
6236
+ const x = Math.round(center + Math.cos(angle) * radius);
6237
+ const y = Math.round(center + Math.sin(angle) * radius);
6238
+ const brightness = 1 - i / 10;
6239
+ setPixel(f, y, x, Math.max(0.2, brightness));
6240
+ }
6241
+ frames.push(f);
6242
+ }
6243
+ return frames;
6244
+ })();
6245
+ var pulse = (() => {
6246
+ const frames = [];
6247
+ const size = 7;
6248
+ const center = 3;
6249
+ for (let frame = 0; frame < 16; frame++) {
6250
+ const f = emptyFrame(size, size);
6251
+ const phase = frame / 16 * Math.PI * 2;
6252
+ const intensity = (Math.sin(phase) + 1) / 2;
6253
+ setPixel(f, center, center, 1);
6254
+ const radius = Math.floor((1 - intensity) * 3) + 1;
6255
+ for (let dy = -radius; dy <= radius; dy++) {
6256
+ for (let dx = -radius; dx <= radius; dx++) {
6257
+ const dist = Math.sqrt(dx * dx + dy * dy);
6258
+ if (Math.abs(dist - radius) < 0.7) {
6259
+ setPixel(f, center + dy, center + dx, intensity * 0.6);
6260
+ }
6261
+ }
6262
+ }
6263
+ frames.push(f);
6264
+ }
6265
+ return frames;
6266
+ })();
6267
+ function vu(columns, levels) {
6268
+ const rows = 7;
6269
+ const frame = emptyFrame(rows, columns);
6270
+ for (let col = 0; col < Math.min(columns, levels.length); col++) {
6271
+ const level = Math.max(0, Math.min(1, levels[col]));
6272
+ const height = Math.floor(level * rows);
6273
+ for (let row = 0; row < rows; row++) {
6274
+ const rowFromBottom = rows - 1 - row;
6275
+ if (rowFromBottom < height) {
6276
+ let brightness = 1;
6277
+ if (row < rows * 0.3) {
6278
+ brightness = 1;
6279
+ } else if (row < rows * 0.6) {
6280
+ brightness = 0.8;
6281
+ } else {
6282
+ brightness = 0.6;
6283
+ }
6284
+ frame[row][col] = brightness;
6285
+ }
6286
+ }
6287
+ }
6288
+ return frame;
6289
+ }
6290
+ var wave = (() => {
6291
+ const frames = [];
6292
+ const rows = 7;
6293
+ const cols = 7;
6294
+ for (let frame = 0; frame < 24; frame++) {
6295
+ const f = emptyFrame(rows, cols);
6296
+ const phase = frame / 24 * Math.PI * 2;
6297
+ for (let col = 0; col < cols; col++) {
6298
+ const colPhase = col / cols * Math.PI * 2;
6299
+ const height = Math.sin(phase + colPhase) * 2.5 + 3.5;
6300
+ const row = Math.floor(height);
6301
+ if (row >= 0 && row < rows) {
6302
+ setPixel(f, row, col, 1);
6303
+ const frac = height - row;
6304
+ if (row > 0) setPixel(f, row - 1, col, 1 - frac);
6305
+ if (row < rows - 1) setPixel(f, row + 1, col, frac);
6306
+ }
6307
+ }
6308
+ frames.push(f);
6309
+ }
6310
+ return frames;
6311
+ })();
6312
+ var snake = (() => {
6313
+ const frames = [];
6314
+ const rows = 7;
6315
+ const cols = 7;
6316
+ const path = [];
6317
+ let x = 0;
6318
+ let y = 0;
6319
+ let dx = 1;
6320
+ let dy = 0;
6321
+ const visited = /* @__PURE__ */ new Set();
6322
+ while (path.length < rows * cols) {
6323
+ path.push([y, x]);
6324
+ visited.add(`${y},${x}`);
6325
+ const nextX = x + dx;
6326
+ const nextY = y + dy;
6327
+ if (nextX >= 0 && nextX < cols && nextY >= 0 && nextY < rows && !visited.has(`${nextY},${nextX}`)) {
6328
+ x = nextX;
6329
+ y = nextY;
6330
+ } else {
6331
+ const newDx = -dy;
6332
+ const newDy = dx;
6333
+ dx = newDx;
6334
+ dy = newDy;
6335
+ const nextX2 = x + dx;
6336
+ const nextY2 = y + dy;
6337
+ if (nextX2 >= 0 && nextX2 < cols && nextY2 >= 0 && nextY2 < rows && !visited.has(`${nextY2},${nextX2}`)) {
6338
+ x = nextX2;
6339
+ y = nextY2;
6340
+ } else {
6341
+ break;
6342
+ }
6343
+ }
6344
+ }
6345
+ const snakeLength = 5;
6346
+ for (let frame = 0; frame < path.length; frame++) {
6347
+ const f = emptyFrame(rows, cols);
6348
+ for (let i = 0; i < snakeLength; i++) {
6349
+ const idx = frame - i;
6350
+ if (idx >= 0 && idx < path.length) {
6351
+ const [y2, x2] = path[idx];
6352
+ const brightness = 1 - i / snakeLength;
6353
+ setPixel(f, y2, x2, brightness);
6354
+ }
6355
+ }
6356
+ frames.push(f);
6357
+ }
6358
+ return frames;
6359
+ })();
6360
+ var Matrix = React.forwardRef(
6361
+ ({
6362
+ rows,
6363
+ cols,
6364
+ pattern,
6365
+ frames,
6366
+ fps = 12,
6367
+ autoplay = true,
6368
+ loop = true,
6369
+ size = 10,
6370
+ gap = 2,
6371
+ palette = {
6372
+ on: "currentColor",
6373
+ off: "var(--muted-foreground)"
6374
+ },
6375
+ brightness = 1,
6376
+ ariaLabel,
6377
+ onFrame,
6378
+ mode = "default",
6379
+ levels,
6380
+ className,
6381
+ ...props
6382
+ }, ref) => {
6383
+ const { frameIndex } = useAnimation(frames, {
6384
+ fps,
6385
+ autoplay: autoplay && !pattern,
6386
+ loop,
6387
+ onFrame
6388
+ });
6389
+ const currentFrame = useMemo(() => {
6390
+ if (mode === "vu" && levels && levels.length > 0) {
6391
+ return ensureFrameSize(vu(cols, levels), rows, cols);
6392
+ }
6393
+ if (pattern) {
6394
+ return ensureFrameSize(pattern, rows, cols);
6395
+ }
6396
+ if (frames && frames.length > 0) {
6397
+ return ensureFrameSize(frames[frameIndex] || frames[0], rows, cols);
6398
+ }
6399
+ return ensureFrameSize([], rows, cols);
6400
+ }, [pattern, frames, frameIndex, rows, cols, mode, levels]);
6401
+ const cellPositions = useMemo(() => {
6402
+ const positions = [];
6403
+ for (let row = 0; row < rows; row++) {
6404
+ positions[row] = [];
6405
+ for (let col = 0; col < cols; col++) {
6406
+ positions[row][col] = {
6407
+ x: col * (size + gap),
6408
+ y: row * (size + gap)
6409
+ };
6410
+ }
6411
+ }
6412
+ return positions;
6413
+ }, [rows, cols, size, gap]);
6414
+ const svgDimensions = useMemo(() => {
6415
+ return {
6416
+ width: cols * (size + gap) - gap,
6417
+ height: rows * (size + gap) - gap
6418
+ };
6419
+ }, [rows, cols, size, gap]);
6420
+ const isAnimating = !pattern && frames && frames.length > 0;
6421
+ return /* @__PURE__ */ jsx(
6422
+ "div",
6423
+ {
6424
+ ref,
6425
+ role: "img",
6426
+ "aria-label": ariaLabel ?? "matrix display",
6427
+ "aria-live": isAnimating ? "polite" : void 0,
6428
+ className: cn("relative inline-block", className),
6429
+ style: {
6430
+ "--matrix-on": palette.on,
6431
+ "--matrix-off": palette.off,
6432
+ "--matrix-gap": `${gap}px`,
6433
+ "--matrix-size": `${size}px`
6434
+ },
6435
+ ...props,
6436
+ children: /* @__PURE__ */ jsxs(
6437
+ "svg",
6438
+ {
6439
+ width: svgDimensions.width,
6440
+ height: svgDimensions.height,
6441
+ viewBox: `0 0 ${svgDimensions.width} ${svgDimensions.height}`,
6442
+ xmlns: "http://www.w3.org/2000/svg",
6443
+ className: "block",
6444
+ style: { overflow: "visible" },
6445
+ children: [
6446
+ /* @__PURE__ */ jsxs("defs", { children: [
6447
+ /* @__PURE__ */ jsxs("radialGradient", { id: "matrix-pixel-on", cx: "50%", cy: "50%", r: "50%", children: [
6448
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "var(--matrix-on)", stopOpacity: "1" }),
6449
+ /* @__PURE__ */ jsx(
6450
+ "stop",
6451
+ {
6452
+ offset: "70%",
6453
+ stopColor: "var(--matrix-on)",
6454
+ stopOpacity: "0.85"
6455
+ }
6456
+ ),
6457
+ /* @__PURE__ */ jsx(
6458
+ "stop",
6459
+ {
6460
+ offset: "100%",
6461
+ stopColor: "var(--matrix-on)",
6462
+ stopOpacity: "0.6"
6463
+ }
6464
+ )
6465
+ ] }),
6466
+ /* @__PURE__ */ jsxs("radialGradient", { id: "matrix-pixel-off", cx: "50%", cy: "50%", r: "50%", children: [
6467
+ /* @__PURE__ */ jsx(
6468
+ "stop",
6469
+ {
6470
+ offset: "0%",
6471
+ stopColor: "var(--muted-foreground)",
6472
+ stopOpacity: "1"
6473
+ }
6474
+ ),
6475
+ /* @__PURE__ */ jsx(
6476
+ "stop",
6477
+ {
6478
+ offset: "100%",
6479
+ stopColor: "var(--muted-foreground)",
6480
+ stopOpacity: "0.7"
6481
+ }
6482
+ )
6483
+ ] }),
6484
+ /* @__PURE__ */ jsxs(
6485
+ "filter",
6486
+ {
6487
+ id: "matrix-glow",
6488
+ x: "-50%",
6489
+ y: "-50%",
6490
+ width: "200%",
6491
+ height: "200%",
6492
+ children: [
6493
+ /* @__PURE__ */ jsx("feGaussianBlur", { stdDeviation: "2", result: "blur" }),
6494
+ /* @__PURE__ */ jsx("feComposite", { in: "SourceGraphic", in2: "blur", operator: "over" })
6495
+ ]
6496
+ }
6497
+ )
6498
+ ] }),
6499
+ /* @__PURE__ */ jsx("style", { children: `
6500
+ .matrix-pixel {
6501
+ transition: opacity 300ms ease-out, transform 150ms ease-out;
6502
+ transform-origin: center;
6503
+ transform-box: fill-box;
6504
+ }
6505
+ .matrix-pixel-active {
6506
+ filter: url(#matrix-glow);
6507
+ }
6508
+ ` }),
6509
+ currentFrame.map(
6510
+ (row, rowIndex) => row.map((value, colIndex) => {
6511
+ const pos = cellPositions[rowIndex]?.[colIndex];
6512
+ if (!pos) return null;
6513
+ const opacity = clamp(brightness * value);
6514
+ const isActive = opacity > 0.5;
6515
+ const isOn = opacity > 0.05;
6516
+ const fill = isOn ? "url(#matrix-pixel-on)" : "url(#matrix-pixel-off)";
6517
+ const scale = isActive ? 1.1 : 1;
6518
+ const radius = size / 2 * 0.9;
6519
+ return /* @__PURE__ */ jsx(
6520
+ "circle",
6521
+ {
6522
+ className: cn(
6523
+ "matrix-pixel",
6524
+ isActive && "matrix-pixel-active",
6525
+ !isOn && "opacity-20 dark:opacity-[0.1]"
6526
+ ),
6527
+ cx: pos.x + size / 2,
6528
+ cy: pos.y + size / 2,
6529
+ r: radius,
6530
+ fill,
6531
+ opacity: isOn ? opacity : 0.1,
6532
+ style: {
6533
+ transform: `scale(${scale})`
6534
+ }
6535
+ },
6536
+ `${rowIndex}-${colIndex}`
6537
+ );
6538
+ })
6539
+ )
6540
+ ]
6541
+ }
6542
+ )
6543
+ }
6544
+ );
6545
+ }
6546
+ );
6547
+ Matrix.displayName = "Matrix";
6548
+ var DEFAULT_SIZE = 22;
6549
+ var MATRIX_GRID = 7;
6550
+ var MATRIX_ON = "var(--payman-v2-matrix-color)";
6551
+ var MATRIX_PRESETS = [
6552
+ { frames: loader, fps: 8 },
6553
+ { frames: wave, fps: 14 },
6554
+ { frames: snake, fps: 16 },
6555
+ { frames: pulse, fps: 12 }
6556
+ ];
6557
+ var PRESET_ROTATE_MS = 1e4;
5598
6558
  function useElapsedSeconds(isStreaming) {
5599
6559
  const [elapsed, setElapsed] = useState(0);
5600
6560
  const startRef = useRef(null);
@@ -5615,12 +6575,37 @@ function useElapsedSeconds(isStreaming) {
5615
6575
  }, [isStreaming]);
5616
6576
  return elapsed;
5617
6577
  }
6578
+ function useMatrixPresetIndex(isStreaming) {
6579
+ const [index, setIndex] = useState(0);
6580
+ useEffect(() => {
6581
+ if (!isStreaming) return;
6582
+ setIndex(0);
6583
+ const id = setInterval(() => {
6584
+ setIndex((prev) => (prev + 1) % MATRIX_PRESETS.length);
6585
+ }, PRESET_ROTATE_MS);
6586
+ return () => clearInterval(id);
6587
+ }, [isStreaming]);
6588
+ return index;
6589
+ }
6590
+ function formatElapsed(totalSeconds) {
6591
+ if (totalSeconds < 60) return `${totalSeconds}s`;
6592
+ const minutes = Math.floor(totalSeconds / 60);
6593
+ const seconds = totalSeconds % 60;
6594
+ return `${minutes}m ${seconds}s`;
6595
+ }
5618
6596
  function StreamingIndicatorV2({
5619
6597
  isStreaming,
5620
6598
  loadingAnimation
5621
6599
  }) {
5622
6600
  const size = loadingAnimation?.size ?? DEFAULT_SIZE;
5623
6601
  const elapsed = useElapsedSeconds(isStreaming);
6602
+ const presetIndex = useMatrixPresetIndex(isStreaming);
6603
+ const preset = MATRIX_PRESETS[presetIndex];
6604
+ const gap = 1;
6605
+ const cell = useMemo(
6606
+ () => Math.max(2, Math.round((size - (MATRIX_GRID - 1) * gap) / MATRIX_GRID)),
6607
+ [size]
6608
+ );
5624
6609
  return /* @__PURE__ */ jsx(AnimatePresence, { children: isStreaming && /* @__PURE__ */ jsxs(
5625
6610
  motion.div,
5626
6611
  {
@@ -5636,21 +6621,62 @@ function StreamingIndicatorV2({
5636
6621
  {
5637
6622
  className: "payman-v2-streaming-indicator-glyph",
5638
6623
  style: { width: size, height: size },
5639
- children: /* @__PURE__ */ jsx(
5640
- DotLottieReact,
5641
- {
5642
- src: loadingAnimation?.src ?? thinking_atom_default,
5643
- loop: true,
5644
- autoplay: true,
5645
- style: { width: "100%", height: "100%" }
5646
- }
6624
+ children: loadingAnimation?.src ? (
6625
+ // Consumer override: render their Lottie animation.
6626
+ /* @__PURE__ */ jsx(
6627
+ DotLottieReact,
6628
+ {
6629
+ src: loadingAnimation.src,
6630
+ loop: true,
6631
+ autoplay: true,
6632
+ style: { width: "100%", height: "100%" }
6633
+ }
6634
+ )
6635
+ ) : (
6636
+ // Default: dot-matrix glyph, cycling presets +
6637
+ // Payman brand tint. Each preset lives in its own
6638
+ // keyed motion layer so swapping presets crossfades
6639
+ // (old scales/fades out while the new scales/fades
6640
+ // in) instead of hard-cutting. Keying the layer on
6641
+ // `presetIndex` also remounts the Matrix, restarting
6642
+ // its frame counter cleanly at frame 0.
6643
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: /* @__PURE__ */ jsx(
6644
+ motion.div,
6645
+ {
6646
+ className: "payman-v2-streaming-indicator-matrix",
6647
+ initial: { opacity: 0, scale: 0.8 },
6648
+ animate: { opacity: 1, scale: 1 },
6649
+ exit: { opacity: 0, scale: 0.8 },
6650
+ transition: { duration: 0.45, ease: [0.2, 0, 0, 1] },
6651
+ children: /* @__PURE__ */ jsx(
6652
+ Matrix,
6653
+ {
6654
+ rows: MATRIX_GRID,
6655
+ cols: MATRIX_GRID,
6656
+ frames: preset.frames,
6657
+ fps: preset.fps,
6658
+ size: cell,
6659
+ gap,
6660
+ palette: {
6661
+ on: MATRIX_ON,
6662
+ // Unlit dots use the v2 theme's own muted-text
6663
+ // token (light/dark-aware), not the bare
6664
+ // `--muted-foreground` the vendored Matrix
6665
+ // component falls back to internally — that
6666
+ // variable isn't defined under `.payman-v2-root`
6667
+ // and would otherwise resolve to black.
6668
+ off: "var(--payman-v2-matrix-dot-off)"
6669
+ },
6670
+ ariaLabel: "Working\u2026"
6671
+ }
6672
+ )
6673
+ },
6674
+ presetIndex
6675
+ ) })
5647
6676
  )
5648
6677
  }
5649
6678
  ),
5650
- elapsed > 0 && /* @__PURE__ */ jsxs("span", { className: "payman-v2-streaming-indicator-elapsed", children: [
5651
- elapsed,
5652
- "s"
5653
- ] })
6679
+ elapsed > 0 && /* @__PURE__ */ jsx("span", { className: "payman-v2-streaming-indicator-elapsed", children: formatElapsed(elapsed) })
5654
6680
  ]
5655
6681
  },
5656
6682
  "streaming-indicator"
@@ -6258,6 +7284,15 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6258
7284
  const expireUserAction2 = chat.expireUserAction ?? NOOP_ASYNC;
6259
7285
  const dismissNotification = chat.dismissNotification ?? NOOP;
6260
7286
  const isUserActionSupported = typeof chat.submitUserAction === "function" && typeof chat.cancelUserAction === "function" && typeof chat.resendUserAction === "function";
7287
+ const expiredUserActionIdsRef = useRef(/* @__PURE__ */ new Set());
7288
+ const expireUserActionOnce = useCallback(
7289
+ (userActionId) => {
7290
+ if (expiredUserActionIdsRef.current.has(userActionId)) return;
7291
+ expiredUserActionIdsRef.current.add(userActionId);
7292
+ void expireUserAction2(userActionId);
7293
+ },
7294
+ [expireUserAction2]
7295
+ );
6261
7296
  const {
6262
7297
  transcribedText,
6263
7298
  isAvailable: voiceAvailable,
@@ -6500,6 +7535,9 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6500
7535
  };
6501
7536
  const userActionPrompts = isUserActionSupported ? userActionState.prompts : void 0;
6502
7537
  const notifications = userActionState.notifications;
7538
+ const dockedPrompt = userActionPrompts?.find(
7539
+ (prompt) => prompt.kind !== "notification" && (prompt.status === "pending" || prompt.status === "submitting" || prompt.status === "stale")
7540
+ );
6503
7541
  const handleV2Send = (text) => {
6504
7542
  if (isRecording) stopRecording();
6505
7543
  if (text.trim() && !disableInput && isSessionParamsConfigured) {
@@ -6652,11 +7690,12 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6652
7690
  retryDisabled: isWaitingForResponse,
6653
7691
  typingSpeed: config.typingSpeed ?? 4,
6654
7692
  userActionPrompts,
7693
+ dockedUserActionId: dockedPrompt?.userActionId,
6655
7694
  notifications,
6656
7695
  onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
6657
7696
  onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
6658
7697
  onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
6659
- onExpireUserAction: isUserActionSupported ? expireUserAction2 : void 0,
7698
+ onExpireUserAction: isUserActionSupported ? async (id) => expireUserActionOnce(id) : void 0,
6660
7699
  onDismissNotification: dismissNotification,
6661
7700
  onSubmitFeedback: handleSubmitFeedback
6662
7701
  }
@@ -6669,33 +7708,43 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6669
7708
  }
6670
7709
  ),
6671
7710
  hasAskPermission && /* @__PURE__ */ jsx(
6672
- ChatInputV2,
7711
+ UserActionDock,
6673
7712
  {
6674
- ref: chatInputV2Ref,
6675
- onSend: handleV2Send,
6676
- onCancel: cancelStream,
6677
- disabled: isV2InputDisabled,
6678
- isStreaming: isWaitingForResponse,
6679
- placeholder: isRecording ? "Listening..." : placeholder,
6680
- enableVoice: config.enableVoice === true,
6681
- transcribedText: config.enableVoice === true ? transcribedText : "",
6682
- voiceAvailable: config.enableVoice === true && voiceAvailable,
6683
- isRecording,
6684
- onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
6685
- onCancelRecording: handleCancelRecording,
6686
- onConfirmRecording: handleConfirmRecording,
6687
- showResetSession,
6688
- onResetSession: requestResetSession,
6689
- showAttachmentButton,
6690
- showUploadImageButton,
6691
- showAttachFileButton,
6692
- onUploadImageClick,
6693
- onAttachFileClick,
6694
- editingMessageId,
6695
- onClearEditing: handleClearEditing,
6696
- analysisMode: enableDeepModeToggle ? analysisMode : void 0,
6697
- onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
6698
- slashCommands
7713
+ prompt: isUserActionSupported ? dockedPrompt : void 0,
7714
+ onSubmit: submitUserAction2,
7715
+ onCancel: cancelUserAction2,
7716
+ onResend: resendUserAction2,
7717
+ onExpired: expireUserActionOnce,
7718
+ children: /* @__PURE__ */ jsx(
7719
+ ChatInputV2,
7720
+ {
7721
+ ref: chatInputV2Ref,
7722
+ onSend: handleV2Send,
7723
+ onCancel: cancelStream,
7724
+ disabled: isV2InputDisabled,
7725
+ isStreaming: isWaitingForResponse,
7726
+ placeholder: isRecording ? "Listening..." : placeholder,
7727
+ enableVoice: config.enableVoice === true,
7728
+ transcribedText: config.enableVoice === true ? transcribedText : "",
7729
+ voiceAvailable: config.enableVoice === true && voiceAvailable,
7730
+ isRecording,
7731
+ onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
7732
+ onCancelRecording: handleCancelRecording,
7733
+ onConfirmRecording: handleConfirmRecording,
7734
+ showResetSession,
7735
+ onResetSession: requestResetSession,
7736
+ showAttachmentButton,
7737
+ showUploadImageButton,
7738
+ showAttachFileButton,
7739
+ onUploadImageClick,
7740
+ onAttachFileClick,
7741
+ editingMessageId,
7742
+ onClearEditing: handleClearEditing,
7743
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
7744
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
7745
+ slashCommands
7746
+ }
7747
+ )
6699
7748
  }
6700
7749
  )
6701
7750
  ]