@cimplify/sdk 0.98.0 → 0.99.0

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/react.mjs CHANGED
@@ -1263,10 +1263,10 @@ function CimplifyProvider({
1263
1263
  }, [resolvedClient, baseCurrency, displayCurrency]);
1264
1264
  const convertPrice = useCallback(
1265
1265
  (amount) => {
1266
- const num = typeof amount === "string" ? parseFloat(amount) : amount;
1267
- if (isNaN(num)) return 0;
1268
- if (!fxRate || displayCurrency === baseCurrency) return num;
1269
- return Math.round(num * fxRate * 100) / 100;
1266
+ const num2 = typeof amount === "string" ? parseFloat(amount) : amount;
1267
+ if (isNaN(num2)) return 0;
1268
+ if (!fxRate || displayCurrency === baseCurrency) return num2;
1269
+ return Math.round(num2 * fxRate * 100) / 100;
1270
1270
  },
1271
1271
  [fxRate, displayCurrency, baseCurrency]
1272
1272
  );
@@ -2714,8 +2714,8 @@ var STATE_TONE = {
2714
2714
  cancelled: "text-destructive",
2715
2715
  default: "text-foreground/55"
2716
2716
  };
2717
- function StateDot({ label, tone = "default" }) {
2718
- return /* @__PURE__ */ jsxs("span", { className: cn("inline-flex items-center gap-1.5 text-xs font-medium", STATE_TONE[tone]), children: [
2717
+ function StateDot({ label, tone: tone2 = "default" }) {
2718
+ return /* @__PURE__ */ jsxs("span", { className: cn("inline-flex items-center gap-1.5 text-xs font-medium", STATE_TONE[tone2]), children: [
2719
2719
  /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-current" }),
2720
2720
  label
2721
2721
  ] });
@@ -17113,11 +17113,11 @@ function installHooks() {
17113
17113
  }
17114
17114
  var CACHE_LIMIT = 300;
17115
17115
  var cache = /* @__PURE__ */ new Map();
17116
- function renderMarkdown(text) {
17117
- const cached = cache.get(text);
17116
+ function renderMarkdown(text2) {
17117
+ const cached = cache.get(text2);
17118
17118
  if (cached !== void 0) return cached;
17119
17119
  installHooks();
17120
- const html = DOMPurify.sanitize(marked.parse(text), {
17120
+ const html = DOMPurify.sanitize(marked.parse(text2), {
17121
17121
  ALLOWED_TAGS,
17122
17122
  ALLOWED_ATTR
17123
17123
  });
@@ -17125,15 +17125,15 @@ function renderMarkdown(text) {
17125
17125
  const oldest = cache.keys().next().value;
17126
17126
  if (oldest !== void 0) cache.delete(oldest);
17127
17127
  }
17128
- cache.set(text, html);
17128
+ cache.set(text2, html);
17129
17129
  return html;
17130
17130
  }
17131
- function SettledMarkdown({ text, className }) {
17132
- const html = useMemo(() => renderMarkdown(text), [text]);
17131
+ function SettledMarkdown({ text: text2, className }) {
17132
+ const html = useMemo(() => renderMarkdown(text2), [text2]);
17133
17133
  return /* @__PURE__ */ jsx("div", { className, dangerouslySetInnerHTML: { __html: html } });
17134
17134
  }
17135
17135
  var ChatMarkdown = memo(function ChatMarkdown2({
17136
- text,
17136
+ text: text2,
17137
17137
  className,
17138
17138
  streaming = false
17139
17139
  }) {
@@ -17141,10 +17141,10 @@ var ChatMarkdown = memo(function ChatMarkdown2({
17141
17141
  return (
17142
17142
  // While the streamdown chunk loads, the settled renderer keeps the
17143
17143
  // text on screen — no blank frame at stream start.
17144
- /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(SettledMarkdown, { text, className }), children: /* @__PURE__ */ jsx(Streamdown, { className, children: text }) })
17144
+ /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(SettledMarkdown, { text: text2, className }), children: /* @__PURE__ */ jsx(Streamdown, { className, children: text2 }) })
17145
17145
  );
17146
17146
  }
17147
- return /* @__PURE__ */ jsx(SettledMarkdown, { text, className });
17147
+ return /* @__PURE__ */ jsx(SettledMarkdown, { text: text2, className });
17148
17148
  });
17149
17149
 
17150
17150
  // src/react/chat-kit/motion.ts
@@ -17719,943 +17719,6 @@ function deliveryState(message, timeline, seenAt, isOptimistic2) {
17719
17719
  const seenCutoff = timeline.firstAgentMessageId ? seenAt : null;
17720
17720
  return seenCutoff && message.created_at <= seenCutoff ? "seen" : "delivered";
17721
17721
  }
17722
- var MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
17723
- var KEEP_DISMISSED_KEY = "cimplify_keep_chat_dismissed";
17724
- var AT_BOTTOM_SLACK_PX = 48;
17725
- function ChatWidget({
17726
- client: clientProp,
17727
- businessName = "Support",
17728
- greeting = "Hi there!",
17729
- subtitle = "Ask us anything \u2014 we typically reply in a few seconds.",
17730
- starters,
17731
- placeholder = "Type a message\u2026",
17732
- position = "bottom-right",
17733
- defaultOpen = false,
17734
- pollInterval,
17735
- className,
17736
- classNames
17737
- }) {
17738
- const context = useOptionalCimplifyClient();
17739
- const client = clientProp ?? context?.client;
17740
- const [open, setOpen] = useState(defaultOpen);
17741
- const [readAt, setReadAt] = useState(null);
17742
- const [attachError, setAttachError] = useState(null);
17743
- const [replyTo, setReplyTo] = useState(null);
17744
- const {
17745
- messages,
17746
- isLoading,
17747
- isSending,
17748
- awaitingReply,
17749
- seenAt,
17750
- error,
17751
- isActive,
17752
- humanRequested,
17753
- claimedAs,
17754
- signOut,
17755
- send,
17756
- react,
17757
- requestHuman,
17758
- startConversation,
17759
- loadOlder,
17760
- mayHaveOlder,
17761
- openIfExisting
17762
- } = useChat({ client, pollInterval });
17763
- const showTyping = isSending || awaitingReply;
17764
- const attachPhoto = useCallback(
17765
- async (file) => {
17766
- if (!client) return;
17767
- setAttachError(null);
17768
- if (!file.type.startsWith("image/")) {
17769
- setAttachError(new CimplifyError("invalid_attachment", "Only photos can be attached."));
17770
- return;
17771
- }
17772
- if (file.size > MAX_ATTACHMENT_BYTES) {
17773
- setAttachError(new CimplifyError("attachment_too_large", "Photos must be under 5MB."));
17774
- return;
17775
- }
17776
- const uploaded = await client.uploads.upload(file);
17777
- if (!uploaded.ok) {
17778
- setAttachError(uploaded.error);
17779
- return;
17780
- }
17781
- const attachment = {
17782
- upload_id: uploaded.value.id,
17783
- url: uploaded.value.url,
17784
- mime_type: uploaded.value.content_type,
17785
- filename: uploaded.value.filename,
17786
- size: uploaded.value.size_bytes
17787
- };
17788
- await send("", { attachments: [attachment] });
17789
- },
17790
- [client, send]
17791
- );
17792
- const handleSend = useCallback(
17793
- (content) => {
17794
- setAttachError(null);
17795
- const target = replyTo;
17796
- setReplyTo(null);
17797
- return send(content, target ? { replyTo: target } : void 0);
17798
- },
17799
- [send, replyTo]
17800
- );
17801
- const unread = open ? 0 : messages.filter(
17802
- (m) => m.sender_type !== "customer" && (!readAt || m.created_at > readAt)
17803
- ).length;
17804
- useState(() => {
17805
- if (defaultOpen) void openIfExisting();
17806
- });
17807
- const handleOpen = useCallback(() => {
17808
- setOpen(true);
17809
- void openIfExisting();
17810
- }, [openIfExisting]);
17811
- const handleClose = useCallback(() => {
17812
- const newest = messages[messages.length - 1];
17813
- setReadAt(newest?.created_at ?? null);
17814
- setOpen(false);
17815
- }, [messages]);
17816
- const isLeft = position === "bottom-left";
17817
- return /* @__PURE__ */ jsxs(
17818
- "div",
17819
- {
17820
- className: cn(
17821
- "fixed bottom-6 z-[9999]",
17822
- isLeft ? "left-6" : "right-6",
17823
- className,
17824
- classNames?.root
17825
- ),
17826
- children: [
17827
- !open && /* @__PURE__ */ jsx(
17828
- Bubble,
17829
- {
17830
- unread,
17831
- onClick: handleOpen,
17832
- className: classNames?.bubble
17833
- }
17834
- ),
17835
- open && /* @__PURE__ */ jsx(
17836
- Panel,
17837
- {
17838
- client,
17839
- businessName,
17840
- greeting,
17841
- subtitle,
17842
- starters,
17843
- placeholder,
17844
- messages,
17845
- isLoading,
17846
- isSending,
17847
- isTyping: showTyping,
17848
- seenAt,
17849
- readAt,
17850
- error: error ?? attachError,
17851
- isActive,
17852
- isLeft,
17853
- humanRequested,
17854
- mayHaveOlder,
17855
- replyTo,
17856
- claimedAs,
17857
- onSignOut: () => void signOut(),
17858
- onClose: handleClose,
17859
- onSend: handleSend,
17860
- onReact: react,
17861
- onReply: setReplyTo,
17862
- onCancelReply: () => setReplyTo(null),
17863
- onRequestHuman: () => void requestHuman(),
17864
- onLoadOlder: () => void loadOlder(),
17865
- onAttach: attachPhoto,
17866
- onStartConversation: startConversation,
17867
- classNames
17868
- }
17869
- )
17870
- ]
17871
- }
17872
- );
17873
- }
17874
- function Bubble({
17875
- unread,
17876
- onClick,
17877
- className
17878
- }) {
17879
- return /* @__PURE__ */ jsxs(
17880
- "button",
17881
- {
17882
- type: "button",
17883
- onClick,
17884
- "aria-label": "Open chat",
17885
- className: cn(
17886
- "relative flex h-14 w-14 items-center justify-center rounded-full",
17887
- "bg-foreground text-background shadow-lg",
17888
- "transition-transform duration-300 ease-out",
17889
- "hover:scale-[1.08] active:scale-95",
17890
- className
17891
- ),
17892
- children: [
17893
- /* @__PURE__ */ jsx(ChatIcon, { className: "h-6 w-6" }),
17894
- unread > 0 && /* @__PURE__ */ jsx(
17895
- "span",
17896
- {
17897
- className: cn(
17898
- "absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center",
17899
- "rounded-full bg-destructive text-[10px] font-bold text-white",
17900
- "ring-2 ring-background"
17901
- ),
17902
- children: unread > 9 ? "9+" : unread
17903
- }
17904
- )
17905
- ]
17906
- }
17907
- );
17908
- }
17909
- function Panel({
17910
- client,
17911
- businessName,
17912
- greeting,
17913
- subtitle,
17914
- starters,
17915
- placeholder,
17916
- messages,
17917
- isLoading,
17918
- isSending,
17919
- isTyping,
17920
- seenAt,
17921
- readAt,
17922
- error,
17923
- isActive,
17924
- isLeft,
17925
- humanRequested,
17926
- mayHaveOlder,
17927
- replyTo,
17928
- claimedAs,
17929
- onSignOut,
17930
- onClose,
17931
- onSend,
17932
- onReact,
17933
- onReply,
17934
- onCancelReply,
17935
- onRequestHuman,
17936
- onLoadOlder,
17937
- onAttach,
17938
- onStartConversation,
17939
- classNames
17940
- }) {
17941
- return /* @__PURE__ */ jsxs(
17942
- "div",
17943
- {
17944
- className: cn(
17945
- "flex flex-col overflow-hidden rounded-2xl bg-background shadow-2xl",
17946
- "w-[400px] max-w-[calc(100vw-3rem)]",
17947
- "h-[min(600px,calc(100vh-6rem))]",
17948
- "animate-in fade-in slide-in-from-bottom-4 duration-300",
17949
- isLeft ? "origin-bottom-left" : "origin-bottom-right",
17950
- classNames?.panel
17951
- ),
17952
- children: [
17953
- /* @__PURE__ */ jsx(
17954
- Header,
17955
- {
17956
- businessName,
17957
- claimedAs,
17958
- onSignOut,
17959
- onClose,
17960
- className: classNames?.header
17961
- }
17962
- ),
17963
- !isActive && !isLoading ? /* @__PURE__ */ jsx(
17964
- Welcome,
17965
- {
17966
- greeting,
17967
- subtitle,
17968
- starters,
17969
- onStarter: onStartConversation,
17970
- className: classNames?.welcome
17971
- }
17972
- ) : /* @__PURE__ */ jsx(
17973
- MessageList,
17974
- {
17975
- messages,
17976
- businessName,
17977
- isLoading,
17978
- isSending: isTyping,
17979
- seenAt,
17980
- readAt,
17981
- humanRequested,
17982
- mayHaveOlder,
17983
- onReact,
17984
- onReply,
17985
- onRequestHuman,
17986
- onLoadOlder,
17987
- className: classNames?.messages
17988
- }
17989
- ),
17990
- isActive && !claimedAs && client && /* @__PURE__ */ jsx(KeepConversation, { client }),
17991
- humanRequested && isActive && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t border-border bg-muted/50 px-4 py-2 text-[11px] text-muted-foreground", children: [
17992
- /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 flex-shrink-0 animate-pulse rounded-full bg-amber-500" }),
17993
- "Waiting for ",
17994
- businessName,
17995
- " \u2014 usually replies in minutes"
17996
- ] }),
17997
- error != null && /* @__PURE__ */ jsx("div", { className: "border-t border-destructive/20 bg-destructive/10 px-4 py-2 text-xs text-destructive", children: error.userMessage || "Something went wrong. Please try again." }),
17998
- replyTo && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 border-t border-border bg-muted/50 px-4 py-2", children: [
17999
- /* @__PURE__ */ jsx("span", { className: "w-[3px] shrink-0 self-stretch rounded-full bg-foreground/40" }),
18000
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
18001
- /* @__PURE__ */ jsxs("div", { className: "text-[11px] font-semibold text-foreground", children: [
18002
- "Replying to ",
18003
- replyTo.sender_type === "customer" ? "yourself" : businessName
18004
- ] }),
18005
- /* @__PURE__ */ jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: proseOf(replyTo.content) || "Photo" })
18006
- ] }),
18007
- /* @__PURE__ */ jsx(
18008
- "button",
18009
- {
18010
- type: "button",
18011
- "aria-label": "Cancel reply",
18012
- onClick: onCancelReply,
18013
- className: "flex-shrink-0 p-0.5 text-muted-foreground hover:text-foreground",
18014
- children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3.5 w-3.5" })
18015
- }
18016
- )
18017
- ] }),
18018
- /* @__PURE__ */ jsx(
18019
- ChatInput,
18020
- {
18021
- placeholder,
18022
- isSending,
18023
- onSend: isActive ? onSend : async (text) => {
18024
- await onStartConversation(text);
18025
- return true;
18026
- },
18027
- onAttach,
18028
- className: classNames?.input
18029
- }
18030
- ),
18031
- /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-border bg-background px-4 py-1.5 text-center text-[10px] text-muted-foreground", children: [
18032
- "Powered by",
18033
- " ",
18034
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Cimplify" })
18035
- ] })
18036
- ]
18037
- }
18038
- );
18039
- }
18040
- function Header({
18041
- businessName,
18042
- claimedAs,
18043
- onSignOut,
18044
- onClose,
18045
- className
18046
- }) {
18047
- return /* @__PURE__ */ jsxs(
18048
- "div",
18049
- {
18050
- className: cn(
18051
- "flex flex-shrink-0 items-center gap-3 bg-foreground px-5 py-4 text-background",
18052
- className
18053
- ),
18054
- children: [
18055
- /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-background/15", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-5 w-5" }) }),
18056
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
18057
- /* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
18058
- claimedAs ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-background/60", children: [
18059
- /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
18060
- "Chatting as ",
18061
- claimedAs
18062
- ] }),
18063
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
18064
- /* @__PURE__ */ jsx(
18065
- "button",
18066
- {
18067
- type: "button",
18068
- onClick: onSignOut,
18069
- className: "flex-shrink-0 underline underline-offset-2 hover:text-background",
18070
- children: "Not you?"
18071
- }
18072
- )
18073
- ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-background/60", children: [
18074
- /* @__PURE__ */ jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
18075
- "Online"
18076
- ] })
18077
- ] }),
18078
- /* @__PURE__ */ jsx(
18079
- "button",
18080
- {
18081
- type: "button",
18082
- onClick: onClose,
18083
- "aria-label": "Close chat",
18084
- className: cn(
18085
- "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg",
18086
- "bg-background/10 text-background/70 transition-colors hover:bg-background/20 hover:text-background"
18087
- ),
18088
- children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-[18px] w-[18px]" })
18089
- }
18090
- )
18091
- ]
18092
- }
18093
- );
18094
- }
18095
- function Welcome({
18096
- greeting,
18097
- subtitle,
18098
- starters,
18099
- onStarter,
18100
- className
18101
- }) {
18102
- const defaultStarters = [
18103
- { icon: "\u{1F50D}", text: "Help me find a product" },
18104
- { icon: "\u{1F6D2}", text: "Check my cart" },
18105
- { icon: "\u{1F4E6}", text: "Track my order" }
18106
- ];
18107
- const items = starters ?? defaultStarters;
18108
- return /* @__PURE__ */ jsxs(
18109
- "div",
18110
- {
18111
- className: cn(
18112
- "flex flex-1 flex-col items-center justify-center overflow-y-auto bg-muted/50 px-8 py-10 text-center",
18113
- className
18114
- ),
18115
- children: [
18116
- /* @__PURE__ */ jsx("div", { className: "mb-5 flex h-16 w-16 items-center justify-center rounded-2xl bg-muted", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-7 w-7 text-muted-foreground" }) }),
18117
- /* @__PURE__ */ jsx("h3", { className: "mb-2 text-xl font-bold text-foreground", children: greeting }),
18118
- /* @__PURE__ */ jsx("p", { className: "mb-6 max-w-[280px] text-sm leading-relaxed text-muted-foreground", children: subtitle }),
18119
- /* @__PURE__ */ jsx("div", { className: "flex w-full max-w-[300px] flex-col gap-2", children: items.map((s) => /* @__PURE__ */ jsxs(
18120
- "button",
18121
- {
18122
- type: "button",
18123
- onClick: () => onStarter(s.text),
18124
- className: cn(
18125
- "flex items-center gap-3 rounded-xl border border-border bg-background px-4 py-3",
18126
- "text-left text-[13px] font-medium text-foreground",
18127
- "transition-all hover:-translate-y-px hover:border-foreground hover:shadow-sm"
18128
- ),
18129
- children: [
18130
- s.icon && /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-muted text-[15px]", children: s.icon }),
18131
- /* @__PURE__ */ jsx("span", { children: s.text })
18132
- ]
18133
- },
18134
- s.text
18135
- )) })
18136
- ]
18137
- }
18138
- );
18139
- }
18140
- var QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F"];
18141
- function LetterAvatar({ name }) {
18142
- return /* @__PURE__ */ jsx("span", { className: "flex size-6 items-center justify-center rounded-full bg-foreground text-[10px] font-bold text-background", children: name.charAt(0).toUpperCase() });
18143
- }
18144
- function MessageList({
18145
- messages,
18146
- businessName,
18147
- isLoading,
18148
- isSending,
18149
- seenAt,
18150
- readAt,
18151
- humanRequested,
18152
- mayHaveOlder,
18153
- onReact,
18154
- onReply,
18155
- onRequestHuman,
18156
- onLoadOlder,
18157
- className
18158
- }) {
18159
- const scrolledUpRef = useRef(false);
18160
- const timeline = chatTimeline(messages, { readAt });
18161
- const { bubbles, reactionsByTarget, firstUnreadId, unreadOnOpen, firstAgentMessageId } = timeline;
18162
- const lastBubble = bubbles[bubbles.length - 1];
18163
- const offerHuman = !humanRequested && lastBubble?.sender_type === "bot" && !isSending;
18164
- const handleScroll = (event) => {
18165
- const node = event.currentTarget;
18166
- scrolledUpRef.current = node.scrollHeight - node.scrollTop - node.clientHeight >= AT_BOTTOM_SLACK_PX;
18167
- };
18168
- return /* @__PURE__ */ jsxs(
18169
- "div",
18170
- {
18171
- onScroll: handleScroll,
18172
- style: { background: "var(--color-chat-canvas, var(--color-muted, #f3f5f3))" },
18173
- className: cn("flex flex-1 flex-col gap-0.5 overflow-y-auto px-3.5 py-3.5", className),
18174
- children: [
18175
- isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
18176
- mayHaveOlder && /* @__PURE__ */ jsx("div", { className: "flex justify-center pb-1", children: /* @__PURE__ */ jsx(
18177
- "button",
18178
- {
18179
- type: "button",
18180
- onClick: onLoadOlder,
18181
- className: "rounded-full border border-foreground/[0.06] bg-background px-3.5 py-[7px] text-xs font-semibold text-muted-foreground shadow-[0_1px_2px_rgba(15,23,42,0.04)]",
18182
- children: "Show earlier messages"
18183
- }
18184
- ) }),
18185
- bubbles.map((msg, i) => {
18186
- const prev = bubbles[i - 1];
18187
- const newDay = !prev || !sameDay(prev.created_at, msg.created_at);
18188
- const group = groupPosition(timeline, i);
18189
- const startsRun = group === "single" || group === "first";
18190
- const delivery = deliveryState(msg, timeline, seenAt, isOptimistic);
18191
- return /* @__PURE__ */ jsxs(
18192
- "div",
18193
- {
18194
- className: cn("flex flex-col gap-1.5", startsRun && i > 0 && "mt-2.5"),
18195
- children: [
18196
- newDay && /* @__PURE__ */ jsx(Seam, { variant: "chip", label: dayLabel(msg.created_at) }),
18197
- msg.id === firstUnreadId && /* @__PURE__ */ jsx(
18198
- Seam,
18199
- {
18200
- variant: "unread",
18201
- label: `${unreadOnOpen} new message${unreadOnOpen === 1 ? "" : "s"}`
18202
- }
18203
- ),
18204
- msg.id === firstAgentMessageId && /* @__PURE__ */ jsx(Seam, { label: "A person joined" }),
18205
- msg.sender_type === "system" ? /* @__PURE__ */ jsx(Seam, { label: "Passing you to the team" }) : /* @__PURE__ */ jsxs("div", { className: "group relative", children: [
18206
- msg.sender_type !== "customer" && !isOptimistic(msg) && /* @__PURE__ */ jsx(HoverRail, { message: msg, onReact, onReply }),
18207
- /* @__PURE__ */ jsx(
18208
- MessageBubble,
18209
- {
18210
- message: msg,
18211
- reactions: reactionsByTarget.get(msg.id),
18212
- businessName,
18213
- group,
18214
- delivery,
18215
- onReply,
18216
- avatar: /* @__PURE__ */ jsx(LetterAvatar, { name: businessName }),
18217
- onQuoteMiss: async () => {
18218
- onLoadOlder();
18219
- return true;
18220
- }
18221
- }
18222
- )
18223
- ] })
18224
- ]
18225
- },
18226
- msg.id
18227
- );
18228
- }),
18229
- isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
18230
- offerHuman && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
18231
- "button",
18232
- {
18233
- type: "button",
18234
- onClick: onRequestHuman,
18235
- className: "rounded-full border border-border bg-background px-3 py-1.5 text-xs font-semibold text-foreground hover:border-foreground",
18236
- children: "Talk to a person"
18237
- }
18238
- ) }),
18239
- /* @__PURE__ */ jsx(
18240
- "div",
18241
- {
18242
- ref: (node) => {
18243
- if (node && !scrolledUpRef.current) node.scrollIntoView({ behavior: "smooth" });
18244
- }
18245
- },
18246
- `${messages.length}:${isSending}`
18247
- )
18248
- ]
18249
- }
18250
- );
18251
- }
18252
- function HoverRail({
18253
- message,
18254
- onReact,
18255
- onReply
18256
- }) {
18257
- return /* @__PURE__ */ jsxs("div", { className: "absolute -top-3 right-0 z-10 hidden gap-0.5 rounded-full border border-border bg-background px-1 py-0.5 shadow-sm group-hover:flex", children: [
18258
- QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsx(
18259
- "button",
18260
- {
18261
- type: "button",
18262
- "aria-label": `React ${emoji}`,
18263
- onClick: () => void onReact(message, emoji),
18264
- className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
18265
- children: emoji
18266
- },
18267
- emoji
18268
- )),
18269
- /* @__PURE__ */ jsx(
18270
- "button",
18271
- {
18272
- type: "button",
18273
- "aria-label": "Reply",
18274
- onClick: () => onReply(message),
18275
- className: "flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground",
18276
- children: /* @__PURE__ */ jsx(ReplyIcon, { className: "h-3.5 w-3.5" })
18277
- }
18278
- )
18279
- ] });
18280
- }
18281
- function readKeepDismissed() {
18282
- try {
18283
- return typeof window !== "undefined" && window.localStorage.getItem(KEEP_DISMISSED_KEY) === "1";
18284
- } catch {
18285
- return false;
18286
- }
18287
- }
18288
- function writeKeepDismissed() {
18289
- try {
18290
- window.localStorage.setItem(KEEP_DISMISSED_KEY, "1");
18291
- } catch {
18292
- }
18293
- }
18294
- function KeepConversation({ client }) {
18295
- const [step, setStep] = useState("row");
18296
- const [dismissed, setDismissed] = useState(readKeepDismissed);
18297
- const [contact, setContact] = useState("");
18298
- const [code, setCode] = useState("");
18299
- const [busy, setBusy] = useState(false);
18300
- const [verifyError, setVerifyError] = useState(null);
18301
- if (dismissed) return null;
18302
- const contactType = contact.includes("@") ? "email" : "phone";
18303
- const requestCode = async () => {
18304
- const trimmed = contact.trim();
18305
- if (!trimmed || busy) return;
18306
- setBusy(true);
18307
- setVerifyError(null);
18308
- const result = await client.link.requestOtp({ contact: trimmed, contact_type: contactType });
18309
- setBusy(false);
18310
- if (result.ok) {
18311
- setCode("");
18312
- setStep("code");
18313
- } else {
18314
- setVerifyError(result.error.userMessage || "Couldn't send a code \u2014 try again.");
18315
- }
18316
- };
18317
- const verify = async (value) => {
18318
- if (value.length !== 6 || busy) return;
18319
- setBusy(true);
18320
- setVerifyError(null);
18321
- const result = await client.link.verifyOtp({
18322
- contact: contact.trim(),
18323
- contact_type: contactType,
18324
- otp_code: value
18325
- });
18326
- setBusy(false);
18327
- if (!result.ok) {
18328
- setCode("");
18329
- setVerifyError(result.error.userMessage || "Invalid or expired code.");
18330
- }
18331
- };
18332
- return /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-border bg-muted/50 px-4 py-2.5", children: [
18333
- step === "row" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
18334
- /* @__PURE__ */ jsxs(
18335
- "button",
18336
- {
18337
- type: "button",
18338
- onClick: () => setStep("contact"),
18339
- className: "flex min-w-0 flex-1 flex-col text-left",
18340
- children: [
18341
- /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-foreground", children: "Keep this conversation" }),
18342
- /* @__PURE__ */ jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: "Verify your number and it follows you \u2014 any device" })
18343
- ]
18344
- }
18345
- ),
18346
- /* @__PURE__ */ jsx(
18347
- "button",
18348
- {
18349
- type: "button",
18350
- "aria-label": "Dismiss",
18351
- onClick: () => {
18352
- writeKeepDismissed();
18353
- setDismissed(true);
18354
- },
18355
- className: "flex-shrink-0 p-1 text-muted-foreground hover:text-foreground",
18356
- children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3.5 w-3.5" })
18357
- }
18358
- )
18359
- ] }),
18360
- step === "contact" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
18361
- /* @__PURE__ */ jsx(
18362
- "input",
18363
- {
18364
- type: "text",
18365
- inputMode: "email",
18366
- value: contact,
18367
- onChange: (e) => setContact(e.target.value),
18368
- onKeyDown: (e) => e.key === "Enter" && void requestCode(),
18369
- placeholder: "Phone or email",
18370
- autoFocus: true,
18371
- className: "h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-3 text-xs text-foreground outline-none placeholder:text-muted-foreground focus:border-foreground"
18372
- }
18373
- ),
18374
- /* @__PURE__ */ jsx(
18375
- "button",
18376
- {
18377
- type: "button",
18378
- onClick: () => void requestCode(),
18379
- disabled: !contact.trim() || busy,
18380
- className: "h-9 flex-shrink-0 rounded-lg bg-foreground px-3 text-xs font-semibold text-background disabled:opacity-50",
18381
- children: busy ? "Sending\u2026" : "Send code"
18382
- }
18383
- )
18384
- ] }),
18385
- step === "code" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
18386
- /* @__PURE__ */ jsx(
18387
- "input",
18388
- {
18389
- type: "text",
18390
- inputMode: "numeric",
18391
- autoComplete: "one-time-code",
18392
- maxLength: 6,
18393
- value: code,
18394
- onChange: (e) => {
18395
- const digits = e.target.value.replace(/\D/g, "").slice(0, 6);
18396
- setCode(digits);
18397
- if (digits.length === 6) void verify(digits);
18398
- },
18399
- placeholder: "6-digit code",
18400
- autoFocus: true,
18401
- className: "h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-3 text-xs tracking-[0.2em] text-foreground outline-none placeholder:tracking-normal placeholder:text-muted-foreground focus:border-foreground"
18402
- }
18403
- ),
18404
- /* @__PURE__ */ jsx(
18405
- "button",
18406
- {
18407
- type: "button",
18408
- onClick: () => void requestCode(),
18409
- disabled: busy,
18410
- className: "h-9 flex-shrink-0 rounded-lg px-2 text-[11px] font-medium text-muted-foreground hover:text-foreground disabled:opacity-50",
18411
- children: "Resend"
18412
- }
18413
- )
18414
- ] }),
18415
- verifyError && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-[11px] text-destructive", children: verifyError })
18416
- ] });
18417
- }
18418
- function ChatInput({
18419
- placeholder,
18420
- isSending,
18421
- onSend,
18422
- onAttach,
18423
- className
18424
- }) {
18425
- const [value, setValue] = useState("");
18426
- const [uploading, setUploading] = useState(false);
18427
- const textareaRef = useRef(null);
18428
- const fileRef = useRef(null);
18429
- const handleAttach = useCallback(
18430
- async (file) => {
18431
- setUploading(true);
18432
- try {
18433
- await onAttach(file);
18434
- } finally {
18435
- setUploading(false);
18436
- }
18437
- },
18438
- [onAttach]
18439
- );
18440
- const handleSend = useCallback(async () => {
18441
- const trimmed = value.trim();
18442
- if (!trimmed || isSending) return;
18443
- setValue("");
18444
- if (textareaRef.current) {
18445
- textareaRef.current.style.height = "auto";
18446
- }
18447
- const sent = await onSend(trimmed);
18448
- if (!sent) setValue((current) => current || trimmed);
18449
- }, [value, isSending, onSend]);
18450
- const handleKeyDown = useCallback(
18451
- (e) => {
18452
- if (e.key === "Enter" && !e.shiftKey) {
18453
- e.preventDefault();
18454
- handleSend();
18455
- }
18456
- },
18457
- [handleSend]
18458
- );
18459
- const handleInput = useCallback(() => {
18460
- const el = textareaRef.current;
18461
- if (!el) return;
18462
- el.style.height = "auto";
18463
- el.style.height = `${Math.min(el.scrollHeight, 100)}px`;
18464
- }, []);
18465
- const hasContent = value.trim().length > 0;
18466
- return /* @__PURE__ */ jsxs(
18467
- "div",
18468
- {
18469
- className: cn(
18470
- "flex flex-shrink-0 items-end gap-2 border-t border-border bg-background px-4 py-3",
18471
- className
18472
- ),
18473
- children: [
18474
- /* @__PURE__ */ jsx(
18475
- "input",
18476
- {
18477
- ref: fileRef,
18478
- type: "file",
18479
- accept: "image/*",
18480
- className: "hidden",
18481
- "aria-hidden": "true",
18482
- tabIndex: -1,
18483
- onChange: (e) => {
18484
- const file = e.target.files?.[0];
18485
- e.target.value = "";
18486
- if (file) void handleAttach(file);
18487
- }
18488
- }
18489
- ),
18490
- /* @__PURE__ */ jsx(
18491
- "button",
18492
- {
18493
- type: "button",
18494
- "aria-label": "Attach a photo",
18495
- disabled: uploading,
18496
- onClick: () => fileRef.current?.click(),
18497
- className: cn(
18498
- "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
18499
- "bg-muted text-muted-foreground transition-colors hover:text-foreground",
18500
- uploading && "animate-pulse"
18501
- ),
18502
- children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-[18px] w-[18px]" })
18503
- }
18504
- ),
18505
- /* @__PURE__ */ jsx(
18506
- "div",
18507
- {
18508
- className: cn(
18509
- "flex flex-1 items-end rounded-xl border-[1.5px] border-transparent bg-muted px-1",
18510
- "transition-colors focus-within:border-foreground focus-within:bg-background"
18511
- ),
18512
- children: /* @__PURE__ */ jsx(
18513
- "textarea",
18514
- {
18515
- ref: textareaRef,
18516
- value,
18517
- onChange: (e) => setValue(e.target.value),
18518
- onKeyDown: handleKeyDown,
18519
- onInput: handleInput,
18520
- placeholder,
18521
- rows: 1,
18522
- className: cn(
18523
- "max-h-[100px] min-h-[20px] flex-1 resize-none bg-transparent px-2.5 py-2",
18524
- "text-sm text-foreground outline-none placeholder:text-muted-foreground"
18525
- )
18526
- }
18527
- )
18528
- }
18529
- ),
18530
- /* @__PURE__ */ jsx(
18531
- "button",
18532
- {
18533
- type: "button",
18534
- onClick: handleSend,
18535
- disabled: !hasContent || isSending,
18536
- "aria-label": "Send message",
18537
- className: cn(
18538
- "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
18539
- "transition-all duration-200",
18540
- hasContent && !isSending ? "bg-foreground text-background hover:scale-105 active:scale-95" : "bg-muted text-muted-foreground"
18541
- ),
18542
- children: /* @__PURE__ */ jsx(SendIcon, { className: "h-[18px] w-[18px]" })
18543
- }
18544
- )
18545
- ]
18546
- }
18547
- );
18548
- }
18549
- function TypingDots() {
18550
- return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 px-4 py-3", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
18551
- "span",
18552
- {
18553
- className: "h-[7px] w-[7px] animate-bounce rounded-full bg-muted-foreground/40",
18554
- style: { animationDelay: `${i * 160}ms` }
18555
- },
18556
- i
18557
- )) });
18558
- }
18559
- function ChatIcon({ className }) {
18560
- return /* @__PURE__ */ jsx(
18561
- "svg",
18562
- {
18563
- className,
18564
- viewBox: "0 0 24 24",
18565
- fill: "none",
18566
- stroke: "currentColor",
18567
- strokeWidth: 2,
18568
- strokeLinecap: "round",
18569
- strokeLinejoin: "round",
18570
- children: /* @__PURE__ */ jsx("path", { d: "M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" })
18571
- }
18572
- );
18573
- }
18574
- function CloseIcon({ className }) {
18575
- return /* @__PURE__ */ jsx(
18576
- "svg",
18577
- {
18578
- className,
18579
- viewBox: "0 0 24 24",
18580
- fill: "none",
18581
- stroke: "currentColor",
18582
- strokeWidth: 2,
18583
- strokeLinecap: "round",
18584
- strokeLinejoin: "round",
18585
- children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
18586
- }
18587
- );
18588
- }
18589
- function SendIcon({ className }) {
18590
- return /* @__PURE__ */ jsxs(
18591
- "svg",
18592
- {
18593
- className,
18594
- viewBox: "0 0 24 24",
18595
- fill: "none",
18596
- stroke: "currentColor",
18597
- strokeWidth: 2,
18598
- strokeLinecap: "round",
18599
- strokeLinejoin: "round",
18600
- children: [
18601
- /* @__PURE__ */ jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
18602
- /* @__PURE__ */ jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
18603
- ]
18604
- }
18605
- );
18606
- }
18607
- function ImageIcon({ className }) {
18608
- return /* @__PURE__ */ jsxs(
18609
- "svg",
18610
- {
18611
- className,
18612
- viewBox: "0 0 24 24",
18613
- fill: "none",
18614
- stroke: "currentColor",
18615
- strokeWidth: 2,
18616
- strokeLinecap: "round",
18617
- strokeLinejoin: "round",
18618
- children: [
18619
- /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
18620
- /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
18621
- /* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
18622
- ]
18623
- }
18624
- );
18625
- }
18626
- function ReplyIcon({ className }) {
18627
- return /* @__PURE__ */ jsxs(
18628
- "svg",
18629
- {
18630
- className,
18631
- viewBox: "0 0 24 24",
18632
- fill: "none",
18633
- stroke: "currentColor",
18634
- strokeWidth: 2,
18635
- strokeLinecap: "round",
18636
- strokeLinejoin: "round",
18637
- children: [
18638
- /* @__PURE__ */ jsx("polyline", { points: "9 17 4 12 9 7" }),
18639
- /* @__PURE__ */ jsx("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
18640
- ]
18641
- }
18642
- );
18643
- }
18644
-
18645
- // src/react/genui/catalog.ts
18646
- function genUICatalog(registry) {
18647
- return new Map(
18648
- registry.components.map((component) => [
18649
- component.name,
18650
- {
18651
- params: component.params.map((param) => ({
18652
- name: param.name,
18653
- required: param.required ?? false
18654
- }))
18655
- }
18656
- ])
18657
- );
18658
- }
18659
17722
 
18660
17723
  // src/genui/lang/parser/ast.ts
18661
17724
  function isRuntimeExpr(node) {
@@ -18858,10 +17921,10 @@ var BUILTINS = {
18858
17921
  signature: "Round(number, decimals?) \u2192 number",
18859
17922
  description: "Round to N decimal places (default 0)",
18860
17923
  fn: (n, decimals) => {
18861
- const num = toNumber2(n);
17924
+ const num2 = toNumber2(n);
18862
17925
  const d = decimals != null ? toNumber2(decimals) : 0;
18863
17926
  const factor = Math.pow(10, d);
18864
- return Math.round(num * factor) / factor;
17927
+ return Math.round(num2 * factor) / factor;
18865
17928
  }
18866
17929
  },
18867
17930
  Abs: {
@@ -19207,1226 +18270,2509 @@ function tokenize(src) {
19207
18270
  }
19208
18271
  continue;
19209
18272
  }
19210
- if (c === "!") {
19211
- if (i + 1 < n && src[i + 1] === "=") {
19212
- tokens.push({ t: 26 /* NotEq */ });
19213
- i += 2;
19214
- } else {
19215
- tokens.push({ t: 33 /* Not */ });
18273
+ if (c === "!") {
18274
+ if (i + 1 < n && src[i + 1] === "=") {
18275
+ tokens.push({ t: 26 /* NotEq */ });
18276
+ i += 2;
18277
+ } else {
18278
+ tokens.push({ t: 33 /* Not */ });
18279
+ i++;
18280
+ }
18281
+ continue;
18282
+ }
18283
+ if (c === ">") {
18284
+ if (i + 1 < n && src[i + 1] === "=") {
18285
+ tokens.push({ t: 29 /* GreaterEq */ });
18286
+ i += 2;
18287
+ } else {
18288
+ tokens.push({ t: 27 /* Greater */ });
18289
+ i++;
18290
+ }
18291
+ continue;
18292
+ }
18293
+ if (c === "<") {
18294
+ if (i + 1 < n && src[i + 1] === "=") {
18295
+ tokens.push({ t: 30 /* LessEq */ });
18296
+ i += 2;
18297
+ } else {
18298
+ tokens.push({ t: 28 /* Less */ });
18299
+ i++;
18300
+ }
18301
+ continue;
18302
+ }
18303
+ if (c === "&") {
18304
+ if (i + 1 < n && src[i + 1] === "&") {
18305
+ tokens.push({ t: 31 /* And */ });
18306
+ i += 2;
18307
+ } else {
18308
+ tokens.push({ t: 31 /* And */ });
18309
+ i++;
18310
+ }
18311
+ continue;
18312
+ }
18313
+ if (c === "|") {
18314
+ if (i + 1 < n && src[i + 1] === "|") {
18315
+ tokens.push({ t: 32 /* Or */ });
18316
+ i += 2;
18317
+ } else {
18318
+ tokens.push({ t: 32 /* Or */ });
18319
+ i++;
18320
+ }
18321
+ continue;
18322
+ }
18323
+ if (c === ".") {
18324
+ tokens.push({ t: 19 /* Dot */ });
18325
+ i++;
18326
+ continue;
18327
+ }
18328
+ if (c === "?") {
18329
+ tokens.push({ t: 34 /* Question */ });
18330
+ i++;
18331
+ continue;
18332
+ }
18333
+ if (c === "+") {
18334
+ tokens.push({ t: 20 /* Plus */ });
18335
+ i++;
18336
+ continue;
18337
+ }
18338
+ if (c === "*") {
18339
+ tokens.push({ t: 22 /* Star */ });
18340
+ i++;
18341
+ continue;
18342
+ }
18343
+ if (c === "/") {
18344
+ tokens.push({ t: 23 /* Slash */ });
18345
+ i++;
18346
+ continue;
18347
+ }
18348
+ if (c === "%") {
18349
+ tokens.push({ t: 24 /* Percent */ });
18350
+ i++;
18351
+ continue;
18352
+ }
18353
+ if (c === '"') {
18354
+ const start = i;
18355
+ i++;
18356
+ let isClosed = false;
18357
+ while (i < n) {
18358
+ if (src[i] === "\\") {
18359
+ i += 2;
18360
+ } else if (src[i] === '"') {
18361
+ i++;
18362
+ isClosed = true;
18363
+ break;
18364
+ } else {
18365
+ i++;
18366
+ }
18367
+ }
18368
+ const rawString = src.slice(start, i);
18369
+ try {
18370
+ const validJsonString = isClosed ? rawString : rawString + '"';
18371
+ tokens.push({ t: 14 /* Str */, v: JSON.parse(validJsonString) });
18372
+ } catch {
18373
+ const stripped = rawString.replace(/^"|"$/g, "");
18374
+ tokens.push({ t: 14 /* Str */, v: stripped });
18375
+ }
18376
+ continue;
18377
+ }
18378
+ if (c === "'") {
18379
+ i++;
18380
+ let result = "";
18381
+ while (i < n) {
18382
+ if (src[i] === "\\") {
18383
+ i++;
18384
+ if (i < n) {
18385
+ const esc = src[i];
18386
+ if (esc === "'") result += "'";
18387
+ else if (esc === "\\") result += "\\";
18388
+ else if (esc === "n") result += "\n";
18389
+ else if (esc === "t") result += " ";
18390
+ else result += esc;
18391
+ i++;
18392
+ }
18393
+ } else if (src[i] === "'") {
18394
+ i++;
18395
+ break;
18396
+ } else {
18397
+ result += src[i];
18398
+ i++;
18399
+ }
18400
+ }
18401
+ tokens.push({ t: 14 /* Str */, v: result });
18402
+ continue;
18403
+ }
18404
+ if (c === "-") {
18405
+ const prev = tokens.length > 0 ? tokens[tokens.length - 1] : null;
18406
+ const afterValue = prev != null && (prev.t === 15 /* Num */ || prev.t === 14 /* Str */ || prev.t === 16 /* Ident */ || prev.t === 17 /* Type */ || prev.t === 2 /* RParen */ || prev.t === 4 /* RBrack */ || prev.t === 10 /* True */ || prev.t === 11 /* False */ || prev.t === 12 /* Null */ || prev.t === 18 /* StateVar */ || prev.t === 35 /* BuiltinCall */);
18407
+ if (!afterValue && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") ; else {
18408
+ tokens.push({ t: 21 /* Minus */ });
18409
+ i++;
18410
+ continue;
18411
+ }
18412
+ }
18413
+ const isDigit = c >= "0" && c <= "9";
18414
+ const isNegDigit = c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9";
18415
+ if (isDigit || isNegDigit) {
18416
+ const start = i;
18417
+ if (src[i] === "-") i++;
18418
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
18419
+ if (i < n && src[i] === "." && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") {
18420
+ i++;
18421
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
18422
+ }
18423
+ if (i < n && (src[i] === "e" || src[i] === "E")) {
18424
+ i++;
18425
+ if (i < n && (src[i] === "+" || src[i] === "-")) i++;
18426
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
18427
+ }
18428
+ tokens.push({ t: 15 /* Num */, v: +src.slice(start, i) });
18429
+ continue;
18430
+ }
18431
+ if (c === "$" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
18432
+ const start = i;
18433
+ i++;
18434
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
18435
+ i++;
18436
+ tokens.push({ t: 18 /* StateVar */, v: src.slice(start, i) });
18437
+ continue;
18438
+ }
18439
+ const isAlpha = c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
18440
+ if (isAlpha) {
18441
+ const start = i;
18442
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
18443
+ i++;
18444
+ const word = src.slice(start, i);
18445
+ if (word === "true") {
18446
+ tokens.push({ t: 10 /* True */ });
18447
+ continue;
18448
+ }
18449
+ if (word === "false") {
18450
+ tokens.push({ t: 11 /* False */ });
18451
+ continue;
18452
+ }
18453
+ if (word === "null") {
18454
+ tokens.push({ t: 12 /* Null */ });
18455
+ continue;
18456
+ }
18457
+ const kind = c >= "A" && c <= "Z" ? 17 /* Type */ : 16 /* Ident */;
18458
+ tokens.push({ t: kind, v: word });
18459
+ continue;
18460
+ }
18461
+ if (c === "@" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
18462
+ i++;
18463
+ const start = i;
18464
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
19216
18465
  i++;
19217
- }
18466
+ tokens.push({ t: 35 /* BuiltinCall */, v: src.slice(start, i) });
19218
18467
  continue;
19219
18468
  }
19220
- if (c === ">") {
19221
- if (i + 1 < n && src[i + 1] === "=") {
19222
- tokens.push({ t: 29 /* GreaterEq */ });
19223
- i += 2;
19224
- } else {
19225
- tokens.push({ t: 27 /* Greater */ });
19226
- i++;
18469
+ i++;
18470
+ }
18471
+ tokens.push({ t: 13 /* EOF */ });
18472
+ return tokens;
18473
+ }
18474
+
18475
+ // src/genui/lang/parser/types.ts
18476
+ function isElementNode(value) {
18477
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
18478
+ const node = value;
18479
+ return node.type === "element" && typeof node.typeName === "string" && typeof node.props === "object" && node.props !== null && typeof node.partial === "boolean";
18480
+ }
18481
+
18482
+ // src/genui/lang/parser/materialize.ts
18483
+ function containsDynamicValue(v) {
18484
+ if (v == null || typeof v !== "object") return false;
18485
+ if (isASTNode(v)) return true;
18486
+ if (Array.isArray(v)) return v.some(containsDynamicValue);
18487
+ if (isElementNode(v)) {
18488
+ return Object.values(v.props).some(containsDynamicValue);
18489
+ }
18490
+ const obj = v;
18491
+ return Object.values(obj).some(containsDynamicValue);
18492
+ }
18493
+ function resolveRef(name, ctx, mode) {
18494
+ if (ctx.visited.has(name)) {
18495
+ ctx.unres.push(name);
18496
+ return mode === "expr" ? { k: "Ph", n: name } : null;
18497
+ }
18498
+ if (!ctx.syms.has(name)) {
18499
+ ctx.unres.push(name);
18500
+ return mode === "expr" ? { k: "Ph", n: name } : null;
18501
+ }
18502
+ const target = ctx.syms.get(name);
18503
+ ctx.unreached?.delete(name);
18504
+ if (target.k === "Comp" && isReservedCall(target.name)) {
18505
+ const refType = target.name === RESERVED_CALLS.Mutation ? "mutation" : "query";
18506
+ return { k: "RuntimeRef", n: name, refType };
18507
+ }
18508
+ ctx.visited.add(name);
18509
+ const prevStatementId = ctx.currentStatementId;
18510
+ ctx.currentStatementId = name;
18511
+ try {
18512
+ const result = mode === "value" ? materializeValue(target, ctx) : materializeExpr(target, ctx);
18513
+ if (mode === "value" && isElementNode(result)) {
18514
+ result.statementId = name;
18515
+ }
18516
+ return result;
18517
+ } finally {
18518
+ ctx.currentStatementId = prevStatementId;
18519
+ ctx.visited.delete(name);
18520
+ }
18521
+ }
18522
+ function materializeLazyBuiltin(node, ctx, scopedRefs) {
18523
+ if (!LAZY_BUILTINS.has(node.name) || node.args.length < 3) return null;
18524
+ const varArg = node.args[1];
18525
+ const varName = varArg.k === "Ref" ? varArg.n : varArg.k === "Str" ? varArg.v : null;
18526
+ if (!varName) return null;
18527
+ const nextScopedRefs = new Set(scopedRefs);
18528
+ nextScopedRefs.add(varName);
18529
+ const recursedArgs = node.args.map(
18530
+ (a, i) => i === 1 ? a : materializeExprInternal(a, ctx, nextScopedRefs)
18531
+ );
18532
+ return { ...node, args: recursedArgs };
18533
+ }
18534
+ function materializeExprInternal(node, ctx, scopedRefs) {
18535
+ switch (node.k) {
18536
+ case "Ref":
18537
+ return scopedRefs.has(node.n) ? node : resolveRef(node.n, ctx, "expr");
18538
+ case "Ph":
18539
+ return node;
18540
+ case "Comp": {
18541
+ const lazy2 = materializeLazyBuiltin(node, ctx, scopedRefs);
18542
+ if (lazy2) return lazy2;
18543
+ const recursedArgs = node.args.map((a) => materializeExprInternal(a, ctx, scopedRefs));
18544
+ if (isBuiltin(node.name) || isReservedCall(node.name)) {
18545
+ return { ...node, args: recursedArgs };
19227
18546
  }
19228
- continue;
18547
+ const def = ctx.cat?.get(node.name);
18548
+ if (def) {
18549
+ const mappedProps = {};
18550
+ for (let i = 0; i < def.params.length && i < recursedArgs.length; i++) {
18551
+ mappedProps[def.params[i].name] = recursedArgs[i];
18552
+ }
18553
+ return { ...node, args: recursedArgs, mappedProps };
18554
+ }
18555
+ ctx.errors.push({
18556
+ code: "unknown-component",
18557
+ component: node.name,
18558
+ path: "",
18559
+ message: `Unknown component "${node.name}" \u2014 not found in catalog or builtins`,
18560
+ statementId: ctx.currentStatementId
18561
+ });
18562
+ return { ...node, args: recursedArgs };
19229
18563
  }
19230
- if (c === "<") {
19231
- if (i + 1 < n && src[i + 1] === "=") {
19232
- tokens.push({ t: 30 /* LessEq */ });
19233
- i += 2;
19234
- } else {
19235
- tokens.push({ t: 28 /* Less */ });
19236
- i++;
18564
+ case "Arr":
18565
+ return { ...node, els: node.els.map((e) => materializeExprInternal(e, ctx, scopedRefs)) };
18566
+ case "Obj":
18567
+ return {
18568
+ ...node,
18569
+ entries: node.entries.map(
18570
+ ([k, v]) => [k, materializeExprInternal(v, ctx, scopedRefs)]
18571
+ )
18572
+ };
18573
+ case "BinOp":
18574
+ return {
18575
+ ...node,
18576
+ left: materializeExprInternal(node.left, ctx, scopedRefs),
18577
+ right: materializeExprInternal(node.right, ctx, scopedRefs)
18578
+ };
18579
+ case "UnaryOp":
18580
+ return { ...node, operand: materializeExprInternal(node.operand, ctx, scopedRefs) };
18581
+ case "Ternary":
18582
+ return {
18583
+ ...node,
18584
+ cond: materializeExprInternal(node.cond, ctx, scopedRefs),
18585
+ then: materializeExprInternal(node.then, ctx, scopedRefs),
18586
+ else: materializeExprInternal(node.else, ctx, scopedRefs)
18587
+ };
18588
+ case "Member":
18589
+ return { ...node, obj: materializeExprInternal(node.obj, ctx, scopedRefs) };
18590
+ case "Index":
18591
+ return {
18592
+ ...node,
18593
+ obj: materializeExprInternal(node.obj, ctx, scopedRefs),
18594
+ index: materializeExprInternal(node.index, ctx, scopedRefs)
18595
+ };
18596
+ case "Assign":
18597
+ return { ...node, value: materializeExprInternal(node.value, ctx, scopedRefs) };
18598
+ // Literals, StateRef, RuntimeRef — pass through unchanged
18599
+ default:
18600
+ return node;
18601
+ }
18602
+ }
18603
+ function materializeExpr(node, ctx) {
18604
+ return materializeExprInternal(node, ctx, /* @__PURE__ */ new Set());
18605
+ }
18606
+ function materializeValue(node, ctx) {
18607
+ switch (node.k) {
18608
+ // ── Ref resolution ───────────────────────────────────────────────────
18609
+ case "Ref":
18610
+ return resolveRef(node.n, ctx, "value");
18611
+ // ── Literals → plain values ──────────────────────────────────────────
18612
+ case "Str":
18613
+ return node.v;
18614
+ case "Num":
18615
+ return node.v;
18616
+ case "Bool":
18617
+ return node.v;
18618
+ case "Null":
18619
+ return null;
18620
+ case "Ph":
18621
+ return null;
18622
+ // ── Collections ──────────────────────────────────────────────────────
18623
+ case "Arr": {
18624
+ const items = [];
18625
+ for (const e of node.els) {
18626
+ if (e.k === "Ph") continue;
18627
+ const value = materializeValue(e, ctx);
18628
+ if (value === null && (e.k === "Comp" || e.k === "Ref")) continue;
18629
+ items.push(value);
19237
18630
  }
19238
- continue;
18631
+ return items;
19239
18632
  }
19240
- if (c === "&") {
19241
- if (i + 1 < n && src[i + 1] === "&") {
19242
- tokens.push({ t: 31 /* And */ });
19243
- i += 2;
19244
- } else {
19245
- tokens.push({ t: 31 /* And */ });
19246
- i++;
18633
+ case "Obj": {
18634
+ const o = {};
18635
+ for (const [k, v] of node.entries) o[k] = materializeValue(v, ctx);
18636
+ return o;
18637
+ }
18638
+ // ── Component nodes ──────────────────────────────────────────────────
18639
+ case "Comp": {
18640
+ const { name, args } = node;
18641
+ if (isBuiltin(name)) {
18642
+ const lazy2 = materializeLazyBuiltin(node, ctx, /* @__PURE__ */ new Set());
18643
+ if (lazy2) return lazy2;
18644
+ return { ...node, args: args.map((a) => materializeExpr(a, ctx)) };
18645
+ }
18646
+ if (isReservedCall(name)) {
18647
+ ctx.errors.push({
18648
+ code: "inline-reserved",
18649
+ component: name,
18650
+ path: "",
18651
+ message: `${name}() must be declared as a top-level statement, not used inline as a value`,
18652
+ statementId: ctx.currentStatementId
18653
+ });
18654
+ return null;
18655
+ }
18656
+ const def = ctx.cat?.get(name);
18657
+ const props = {};
18658
+ if (def) {
18659
+ for (let i = 0; i < def.params.length && i < args.length; i++) {
18660
+ props[def.params[i].name] = materializeValue(args[i], ctx);
18661
+ }
18662
+ if (args.length > def.params.length) {
18663
+ const excessCount = args.length - def.params.length;
18664
+ ctx.errors.push({
18665
+ code: "excess-args",
18666
+ component: name,
18667
+ path: "",
18668
+ message: `${name} takes ${def.params.length} arg(s), got ${args.length} (${excessCount} excess dropped)`,
18669
+ statementId: ctx.currentStatementId
18670
+ });
18671
+ }
18672
+ const missingRequired = def.params.filter(
18673
+ (p) => p.required && (!(p.name in props) || props[p.name] === null)
18674
+ );
18675
+ if (missingRequired.length) {
18676
+ const stillInvalid = missingRequired.filter((p) => {
18677
+ if (p.defaultValue !== void 0) {
18678
+ props[p.name] = p.defaultValue;
18679
+ return false;
18680
+ }
18681
+ return true;
18682
+ });
18683
+ if (stillInvalid.length) {
18684
+ for (const p of stillInvalid) {
18685
+ const isNull = p.name in props;
18686
+ ctx.errors.push({
18687
+ code: isNull ? "null-required" : "missing-required",
18688
+ component: name,
18689
+ path: `/${p.name}`,
18690
+ message: isNull ? `required field "${p.name}" cannot be null` : `missing required field "${p.name}"`,
18691
+ statementId: ctx.currentStatementId
18692
+ });
18693
+ }
18694
+ return null;
18695
+ }
18696
+ }
18697
+ } else if (!isBuiltin(name) && !isReservedCall(name)) {
18698
+ ctx.errors.push({
18699
+ code: "unknown-component",
18700
+ component: name,
18701
+ path: "",
18702
+ message: `Unknown component "${name}" \u2014 not found in catalog or builtins`,
18703
+ statementId: ctx.currentStatementId
18704
+ });
18705
+ return null;
19247
18706
  }
19248
- continue;
18707
+ const hasDynamicProps = Object.values(props).some((v) => containsDynamicValue(v));
18708
+ return { type: "element", typeName: name, props, partial: ctx.partial, hasDynamicProps };
19249
18709
  }
19250
- if (c === "|") {
19251
- if (i + 1 < n && src[i + 1] === "|") {
19252
- tokens.push({ t: 32 /* Or */ });
19253
- i += 2;
19254
- } else {
19255
- tokens.push({ t: 32 /* Or */ });
19256
- i++;
18710
+ // ── Runtime expression nodes → preserve as ASTNode, normalize children ─
18711
+ default: {
18712
+ if (isRuntimeExpr(node)) {
18713
+ return materializeExpr(node, ctx);
19257
18714
  }
19258
- continue;
19259
- }
19260
- if (c === ".") {
19261
- tokens.push({ t: 19 /* Dot */ });
19262
- i++;
19263
- continue;
18715
+ return node;
19264
18716
  }
19265
- if (c === "?") {
19266
- tokens.push({ t: 34 /* Question */ });
19267
- i++;
18717
+ }
18718
+ }
18719
+
18720
+ // src/genui/lang/parser/statements.ts
18721
+ function autoClose(input) {
18722
+ const stack = [];
18723
+ let inStr = false;
18724
+ let esc = false;
18725
+ for (let i = 0; i < input.length; i++) {
18726
+ const c = input[i];
18727
+ if (esc) {
18728
+ esc = false;
19268
18729
  continue;
19269
18730
  }
19270
- if (c === "+") {
19271
- tokens.push({ t: 20 /* Plus */ });
19272
- i++;
18731
+ if (c === "\\" && inStr) {
18732
+ esc = true;
19273
18733
  continue;
19274
18734
  }
19275
- if (c === "*") {
19276
- tokens.push({ t: 22 /* Star */ });
19277
- i++;
18735
+ if (inStr) {
18736
+ if (c === inStr) inStr = false;
19278
18737
  continue;
19279
18738
  }
19280
- if (c === "/") {
19281
- tokens.push({ t: 23 /* Slash */ });
19282
- i++;
18739
+ if (c === '"' || c === "'") {
18740
+ inStr = c;
19283
18741
  continue;
19284
18742
  }
19285
- if (c === "%") {
19286
- tokens.push({ t: 24 /* Percent */ });
19287
- i++;
18743
+ if (c === "(" || c === "[" || c === "{") stack.push(c);
18744
+ else if (c === ")" && stack[stack.length - 1] === "(") stack.pop();
18745
+ else if (c === "]" && stack[stack.length - 1] === "[") stack.pop();
18746
+ else if (c === "}" && stack[stack.length - 1] === "{") stack.pop();
18747
+ }
18748
+ const wasIncomplete = !!inStr || stack.length > 0;
18749
+ if (!wasIncomplete) return { text: input, wasIncomplete: false };
18750
+ let out = input;
18751
+ if (inStr) {
18752
+ if (esc) out += "\\";
18753
+ out += inStr;
18754
+ }
18755
+ for (let j = stack.length - 1; j >= 0; j--)
18756
+ out += stack[j] === "(" ? ")" : stack[j] === "[" ? "]" : "}";
18757
+ return { text: out, wasIncomplete: true };
18758
+ }
18759
+ function split(tokens) {
18760
+ const stmts = [];
18761
+ let pos = 0;
18762
+ while (pos < tokens.length) {
18763
+ while (pos < tokens.length && tokens[pos].t === 0 /* Newline */) pos++;
18764
+ if (pos >= tokens.length || tokens[pos].t === 13 /* EOF */) break;
18765
+ const tok = tokens[pos];
18766
+ if (tok.t !== 16 /* Ident */ && tok.t !== 17 /* Type */ && tok.t !== 18 /* StateVar */) {
18767
+ while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
19288
18768
  continue;
19289
18769
  }
19290
- if (c === '"') {
19291
- const start = i;
19292
- i++;
19293
- let isClosed = false;
19294
- while (i < n) {
19295
- if (src[i] === "\\") {
19296
- i += 2;
19297
- } else if (src[i] === '"') {
19298
- i++;
19299
- isClosed = true;
19300
- break;
19301
- } else {
19302
- i++;
19303
- }
19304
- }
19305
- const rawString = src.slice(start, i);
19306
- try {
19307
- const validJsonString = isClosed ? rawString : rawString + '"';
19308
- tokens.push({ t: 14 /* Str */, v: JSON.parse(validJsonString) });
19309
- } catch {
19310
- const stripped = rawString.replace(/^"|"$/g, "");
19311
- tokens.push({ t: 14 /* Str */, v: stripped });
19312
- }
18770
+ const id = tok.v;
18771
+ const idTokenType = tok.t;
18772
+ pos++;
18773
+ if (pos >= tokens.length || tokens[pos].t !== 9 /* Equals */) {
18774
+ while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
19313
18775
  continue;
19314
18776
  }
19315
- if (c === "'") {
19316
- i++;
19317
- let result = "";
19318
- while (i < n) {
19319
- if (src[i] === "\\") {
19320
- i++;
19321
- if (i < n) {
19322
- const esc = src[i];
19323
- if (esc === "'") result += "'";
19324
- else if (esc === "\\") result += "\\";
19325
- else if (esc === "n") result += "\n";
19326
- else if (esc === "t") result += " ";
19327
- else result += esc;
19328
- i++;
19329
- }
19330
- } else if (src[i] === "'") {
19331
- i++;
19332
- break;
19333
- } else {
19334
- result += src[i];
19335
- i++;
18777
+ pos++;
18778
+ const expr = [];
18779
+ let depth = 0;
18780
+ let ternaryDepth = 0;
18781
+ while (pos < tokens.length && tokens[pos].t !== 13 /* EOF */) {
18782
+ const tt = tokens[pos].t;
18783
+ if (tt === 0 /* Newline */ && depth <= 0 && ternaryDepth <= 0) {
18784
+ let peek = pos + 1;
18785
+ while (peek < tokens.length && tokens[peek].t === 0 /* Newline */) peek++;
18786
+ const nextT = peek < tokens.length ? tokens[peek].t : 13 /* EOF */;
18787
+ if (nextT === 34 /* Question */ || nextT === 8 /* Colon */ && ternaryDepth > 0) {
18788
+ pos++;
18789
+ continue;
19336
18790
  }
18791
+ break;
19337
18792
  }
19338
- tokens.push({ t: 14 /* Str */, v: result });
19339
- continue;
19340
- }
19341
- if (c === "-") {
19342
- const prev = tokens.length > 0 ? tokens[tokens.length - 1] : null;
19343
- const afterValue = prev != null && (prev.t === 15 /* Num */ || prev.t === 14 /* Str */ || prev.t === 16 /* Ident */ || prev.t === 17 /* Type */ || prev.t === 2 /* RParen */ || prev.t === 4 /* RBrack */ || prev.t === 10 /* True */ || prev.t === 11 /* False */ || prev.t === 12 /* Null */ || prev.t === 18 /* StateVar */ || prev.t === 35 /* BuiltinCall */);
19344
- if (!afterValue && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") ; else {
19345
- tokens.push({ t: 21 /* Minus */ });
19346
- i++;
19347
- continue;
19348
- }
19349
- }
19350
- const isDigit = c >= "0" && c <= "9";
19351
- const isNegDigit = c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9";
19352
- if (isDigit || isNegDigit) {
19353
- const start = i;
19354
- if (src[i] === "-") i++;
19355
- while (i < n && src[i] >= "0" && src[i] <= "9") i++;
19356
- if (i < n && src[i] === "." && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") {
19357
- i++;
19358
- while (i < n && src[i] >= "0" && src[i] <= "9") i++;
19359
- }
19360
- if (i < n && (src[i] === "e" || src[i] === "E")) {
19361
- i++;
19362
- if (i < n && (src[i] === "+" || src[i] === "-")) i++;
19363
- while (i < n && src[i] >= "0" && src[i] <= "9") i++;
19364
- }
19365
- tokens.push({ t: 15 /* Num */, v: +src.slice(start, i) });
19366
- continue;
19367
- }
19368
- if (c === "$" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
19369
- const start = i;
19370
- i++;
19371
- while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
19372
- i++;
19373
- tokens.push({ t: 18 /* StateVar */, v: src.slice(start, i) });
19374
- continue;
19375
- }
19376
- const isAlpha = c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
19377
- if (isAlpha) {
19378
- const start = i;
19379
- while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
19380
- i++;
19381
- const word = src.slice(start, i);
19382
- if (word === "true") {
19383
- tokens.push({ t: 10 /* True */ });
19384
- continue;
19385
- }
19386
- if (word === "false") {
19387
- tokens.push({ t: 11 /* False */ });
19388
- continue;
19389
- }
19390
- if (word === "null") {
19391
- tokens.push({ t: 12 /* Null */ });
18793
+ if (tt === 0 /* Newline */) {
18794
+ pos++;
19392
18795
  continue;
19393
18796
  }
19394
- const kind = c >= "A" && c <= "Z" ? 17 /* Type */ : 16 /* Ident */;
19395
- tokens.push({ t: kind, v: word });
19396
- continue;
19397
- }
19398
- if (c === "@" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
19399
- i++;
19400
- const start = i;
19401
- while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
19402
- i++;
19403
- tokens.push({ t: 35 /* BuiltinCall */, v: src.slice(start, i) });
19404
- continue;
18797
+ if (tt === 1 /* LParen */ || tt === 3 /* LBrack */ || tt === 5 /* LBrace */) depth++;
18798
+ else if ((tt === 2 /* RParen */ || tt === 4 /* RBrack */ || tt === 6 /* RBrace */) && depth > 0) depth--;
18799
+ else if (tt === 34 /* Question */ && depth === 0) ternaryDepth++;
18800
+ else if (tt === 8 /* Colon */ && depth === 0 && ternaryDepth > 0) ternaryDepth--;
18801
+ expr.push(tokens[pos++]);
19405
18802
  }
19406
- i++;
18803
+ if (expr.length) stmts.push({ id, idTokenType, tokens: expr });
19407
18804
  }
19408
- tokens.push({ t: 13 /* EOF */ });
19409
- return tokens;
19410
- }
19411
-
19412
- // src/genui/lang/parser/types.ts
19413
- function isElementNode(value) {
19414
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
19415
- const node = value;
19416
- return node.type === "element" && typeof node.typeName === "string" && typeof node.props === "object" && node.props !== null && typeof node.partial === "boolean";
18805
+ return stmts;
19417
18806
  }
19418
18807
 
19419
- // src/genui/lang/parser/materialize.ts
19420
- function containsDynamicValue(v) {
19421
- if (v == null || typeof v !== "object") return false;
19422
- if (isASTNode(v)) return true;
19423
- if (Array.isArray(v)) return v.some(containsDynamicValue);
19424
- if (isElementNode(v)) {
19425
- return Object.values(v.props).some(containsDynamicValue);
19426
- }
19427
- const obj = v;
19428
- return Object.values(obj).some(containsDynamicValue);
18808
+ // src/genui/lang/parser/parser.ts
18809
+ function emptyResult(incomplete = true) {
18810
+ return {
18811
+ root: null,
18812
+ meta: {
18813
+ incomplete,
18814
+ unresolved: [],
18815
+ orphaned: [],
18816
+ statementCount: 0,
18817
+ errors: []
18818
+ },
18819
+ stateDeclarations: {},
18820
+ queryStatements: [],
18821
+ mutationStatements: []
18822
+ };
19429
18823
  }
19430
- function resolveRef(name, ctx, mode) {
19431
- if (ctx.visited.has(name)) {
19432
- ctx.unres.push(name);
19433
- return mode === "expr" ? { k: "Ph", n: name } : null;
18824
+ function collectQueryDeps(node) {
18825
+ if (!isASTNode(node)) return [];
18826
+ const refs = /* @__PURE__ */ new Set();
18827
+ walkAST(node, (current) => {
18828
+ if (current.k === "StateRef") refs.add(current.n);
18829
+ });
18830
+ return [...refs];
18831
+ }
18832
+ function classifyStatement(raw, expr) {
18833
+ if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Query) {
18834
+ const deps = collectQueryDeps(expr.args[1]);
18835
+ return {
18836
+ kind: "query",
18837
+ id: raw.id,
18838
+ call: { callee: RESERVED_CALLS.Query, args: expr.args },
18839
+ expr,
18840
+ deps: deps.length > 0 ? deps : void 0
18841
+ };
19434
18842
  }
19435
- if (!ctx.syms.has(name)) {
19436
- ctx.unres.push(name);
19437
- return mode === "expr" ? { k: "Ph", n: name } : null;
18843
+ if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Mutation) {
18844
+ return {
18845
+ kind: "mutation",
18846
+ id: raw.id,
18847
+ call: { callee: RESERVED_CALLS.Mutation, args: expr.args },
18848
+ expr
18849
+ };
19438
18850
  }
19439
- const target = ctx.syms.get(name);
19440
- ctx.unreached?.delete(name);
19441
- if (target.k === "Comp" && isReservedCall(target.name)) {
19442
- const refType = target.name === RESERVED_CALLS.Mutation ? "mutation" : "query";
19443
- return { k: "RuntimeRef", n: name, refType };
18851
+ if (raw.idTokenType === 18 /* StateVar */) {
18852
+ return { kind: "state", id: raw.id, init: expr };
19444
18853
  }
19445
- ctx.visited.add(name);
19446
- const prevStatementId = ctx.currentStatementId;
19447
- ctx.currentStatementId = name;
19448
- try {
19449
- const result = mode === "value" ? materializeValue(target, ctx) : materializeExpr(target, ctx);
19450
- if (mode === "value" && isElementNode(result)) {
19451
- result.statementId = name;
18854
+ return { kind: "value", id: raw.id, expr };
18855
+ }
18856
+ function extractStatements(stmts, ctx) {
18857
+ const stateDeclarations = {};
18858
+ const queryStatements = [];
18859
+ const mutationStatements = [];
18860
+ for (const stmt of stmts) {
18861
+ switch (stmt.kind) {
18862
+ case "state":
18863
+ stateDeclarations[stmt.id] = materializeValue(stmt.init, ctx);
18864
+ break;
18865
+ case "query":
18866
+ queryStatements.push({
18867
+ statementId: stmt.id,
18868
+ toolAST: stmt.call.args[0] ?? null,
18869
+ argsAST: stmt.call.args[1] ?? null,
18870
+ defaultsAST: stmt.call.args[2] ?? null,
18871
+ refreshAST: stmt.call.args[3] ?? null,
18872
+ deps: stmt.deps,
18873
+ complete: true
18874
+ });
18875
+ break;
18876
+ case "mutation":
18877
+ mutationStatements.push({
18878
+ statementId: stmt.id,
18879
+ toolAST: stmt.call.args[0] ?? null,
18880
+ argsAST: stmt.call.args[1] ?? null
18881
+ });
18882
+ break;
19452
18883
  }
19453
- return result;
19454
- } finally {
19455
- ctx.currentStatementId = prevStatementId;
19456
- ctx.visited.delete(name);
19457
18884
  }
19458
- }
19459
- function materializeLazyBuiltin(node, ctx, scopedRefs) {
19460
- if (!LAZY_BUILTINS.has(node.name) || node.args.length < 3) return null;
19461
- const varArg = node.args[1];
19462
- const varName = varArg.k === "Ref" ? varArg.n : varArg.k === "Str" ? varArg.v : null;
19463
- if (!varName) return null;
19464
- const nextScopedRefs = new Set(scopedRefs);
19465
- nextScopedRefs.add(varName);
19466
- const recursedArgs = node.args.map(
19467
- (a, i) => i === 1 ? a : materializeExprInternal(a, ctx, nextScopedRefs)
19468
- );
19469
- return { ...node, args: recursedArgs };
19470
- }
19471
- function materializeExprInternal(node, ctx, scopedRefs) {
19472
- switch (node.k) {
19473
- case "Ref":
19474
- return scopedRefs.has(node.n) ? node : resolveRef(node.n, ctx, "expr");
19475
- case "Ph":
19476
- return node;
19477
- case "Comp": {
19478
- const lazy2 = materializeLazyBuiltin(node, ctx, scopedRefs);
19479
- if (lazy2) return lazy2;
19480
- const recursedArgs = node.args.map((a) => materializeExprInternal(a, ctx, scopedRefs));
19481
- if (isBuiltin(node.name) || isReservedCall(node.name)) {
19482
- return { ...node, args: recursedArgs };
19483
- }
19484
- const def = ctx.cat?.get(node.name);
19485
- if (def) {
19486
- const mappedProps = {};
19487
- for (let i = 0; i < def.params.length && i < recursedArgs.length; i++) {
19488
- mappedProps[def.params[i].name] = recursedArgs[i];
18885
+ for (const stmt of stmts) {
18886
+ const nodes = stmt.kind === "state" ? [stmt.init] : stmt.kind === "value" ? [stmt.expr] : stmt.kind === "query" || stmt.kind === "mutation" ? stmt.call.args : [];
18887
+ for (const node of nodes) {
18888
+ for (const dep of collectQueryDeps(node)) {
18889
+ if (!(dep in stateDeclarations)) {
18890
+ stateDeclarations[dep] = null;
19489
18891
  }
19490
- return { ...node, args: recursedArgs, mappedProps };
19491
18892
  }
19492
- ctx.errors.push({
19493
- code: "unknown-component",
19494
- component: node.name,
19495
- path: "",
19496
- message: `Unknown component "${node.name}" \u2014 not found in catalog or builtins`,
19497
- statementId: ctx.currentStatementId
19498
- });
19499
- return { ...node, args: recursedArgs };
19500
18893
  }
19501
- case "Arr":
19502
- return { ...node, els: node.els.map((e) => materializeExprInternal(e, ctx, scopedRefs)) };
19503
- case "Obj":
19504
- return {
19505
- ...node,
19506
- entries: node.entries.map(
19507
- ([k, v]) => [k, materializeExprInternal(v, ctx, scopedRefs)]
19508
- )
19509
- };
19510
- case "BinOp":
19511
- return {
19512
- ...node,
19513
- left: materializeExprInternal(node.left, ctx, scopedRefs),
19514
- right: materializeExprInternal(node.right, ctx, scopedRefs)
19515
- };
19516
- case "UnaryOp":
19517
- return { ...node, operand: materializeExprInternal(node.operand, ctx, scopedRefs) };
19518
- case "Ternary":
19519
- return {
19520
- ...node,
19521
- cond: materializeExprInternal(node.cond, ctx, scopedRefs),
19522
- then: materializeExprInternal(node.then, ctx, scopedRefs),
19523
- else: materializeExprInternal(node.else, ctx, scopedRefs)
19524
- };
19525
- case "Member":
19526
- return { ...node, obj: materializeExprInternal(node.obj, ctx, scopedRefs) };
19527
- case "Index":
19528
- return {
19529
- ...node,
19530
- obj: materializeExprInternal(node.obj, ctx, scopedRefs),
19531
- index: materializeExprInternal(node.index, ctx, scopedRefs)
19532
- };
19533
- case "Assign":
19534
- return { ...node, value: materializeExprInternal(node.value, ctx, scopedRefs) };
19535
- // Literals, StateRef, RuntimeRef — pass through unchanged
19536
- default:
19537
- return node;
19538
18894
  }
18895
+ return { stateDeclarations, queryStatements, mutationStatements };
19539
18896
  }
19540
- function materializeExpr(node, ctx) {
19541
- return materializeExprInternal(node, ctx, /* @__PURE__ */ new Set());
18897
+ var DEFAULT_ROOT_STATEMENT_ID = "root";
18898
+ function isComponentStatement(stmt) {
18899
+ return stmt.kind === "value" && stmt.expr.k === "Comp" && !isBuiltin(stmt.expr.name) && stmt.expr.name !== RESERVED_CALLS.Query && stmt.expr.name !== RESERVED_CALLS.Mutation;
19542
18900
  }
19543
- function materializeValue(node, ctx) {
19544
- switch (node.k) {
19545
- // ── Ref resolution ───────────────────────────────────────────────────
19546
- case "Ref":
19547
- return resolveRef(node.n, ctx, "value");
19548
- // ── Literals → plain values ──────────────────────────────────────────
19549
- case "Str":
19550
- return node.v;
19551
- case "Num":
19552
- return node.v;
19553
- case "Bool":
19554
- return node.v;
19555
- case "Null":
19556
- return null;
19557
- case "Ph":
19558
- return null;
19559
- // ── Collections ──────────────────────────────────────────────────────
19560
- case "Arr": {
19561
- const items = [];
19562
- for (const e of node.els) {
19563
- if (e.k === "Ph") continue;
19564
- const value = materializeValue(e, ctx);
19565
- if (value === null && (e.k === "Comp" || e.k === "Ref")) continue;
19566
- items.push(value);
18901
+ function pickEntryId(stmtMap, typedStmts, firstId, rootName) {
18902
+ if (stmtMap.has(DEFAULT_ROOT_STATEMENT_ID)) return DEFAULT_ROOT_STATEMENT_ID;
18903
+ if (rootName && stmtMap.has(rootName)) return rootName;
18904
+ const preferredComponent = rootName ? typedStmts.find((stmt) => isComponentStatement(stmt) && stmt.expr.name === rootName) : void 0;
18905
+ if (preferredComponent) return preferredComponent.id;
18906
+ const firstComponent = typedStmts.find(isComponentStatement);
18907
+ return firstComponent?.id ?? firstId;
18908
+ }
18909
+ function buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtCount, cat, rootName) {
18910
+ const entryId = pickEntryId(stmtMap, typedStmts, firstId, rootName);
18911
+ if (!stmtMap.has(entryId)) return emptyResult(wasIncomplete);
18912
+ const syms = /* @__PURE__ */ new Map();
18913
+ for (const [id, stmt] of stmtMap) {
18914
+ syms.set(id, stmt.kind === "state" ? stmt.init : stmt.expr);
18915
+ }
18916
+ const unres = [];
18917
+ const errors = [];
18918
+ const unreached = /* @__PURE__ */ new Set();
18919
+ for (const [id, stmt] of stmtMap) {
18920
+ if (id === entryId) continue;
18921
+ if (stmt.kind === "state" || stmt.kind === "query" || stmt.kind === "mutation") continue;
18922
+ unreached.add(id);
18923
+ }
18924
+ const ctx = {
18925
+ syms,
18926
+ cat,
18927
+ errors,
18928
+ unres,
18929
+ visited: /* @__PURE__ */ new Set(),
18930
+ partial: wasIncomplete,
18931
+ currentStatementId: entryId,
18932
+ unreached
18933
+ };
18934
+ const materialized = materializeValue(syms.get(entryId), ctx);
18935
+ const root = isElementNode(materialized) ? materialized : null;
18936
+ if (root) root.statementId = entryId;
18937
+ const { stateDeclarations, queryStatements, mutationStatements } = extractStatements(
18938
+ typedStmts,
18939
+ ctx
18940
+ );
18941
+ const orphaned = [...unreached];
18942
+ return {
18943
+ root,
18944
+ meta: {
18945
+ incomplete: wasIncomplete,
18946
+ unresolved: unres,
18947
+ orphaned,
18948
+ statementCount: stmtCount,
18949
+ errors
18950
+ },
18951
+ stateDeclarations,
18952
+ queryStatements,
18953
+ mutationStatements
18954
+ };
18955
+ }
18956
+ function skipString(input, start) {
18957
+ if (input[start] !== '"') return start;
18958
+ let i = start + 1;
18959
+ while (i < input.length) {
18960
+ const c = input[i];
18961
+ if (c === "\\") {
18962
+ i += 2;
18963
+ } else if (c === '"') {
18964
+ return i + 1;
18965
+ } else {
18966
+ i++;
18967
+ }
18968
+ }
18969
+ return i;
18970
+ }
18971
+ function stripFences(input) {
18972
+ const blocks = [];
18973
+ let i = 0;
18974
+ while (i < input.length) {
18975
+ let fenceStart = -1;
18976
+ while (i < input.length) {
18977
+ const nextI = skipString(input, i);
18978
+ if (nextI > i) {
18979
+ i = nextI;
18980
+ continue;
19567
18981
  }
19568
- return items;
18982
+ const c = input[i];
18983
+ if (c === "`" && i + 1 < input.length && input[i + 1] === "`" && i + 2 < input.length && input[i + 2] === "`") {
18984
+ fenceStart = i;
18985
+ break;
18986
+ }
18987
+ i++;
19569
18988
  }
19570
- case "Obj": {
19571
- const o = {};
19572
- for (const [k, v] of node.entries) o[k] = materializeValue(v, ctx);
19573
- return o;
18989
+ if (fenceStart === -1) break;
18990
+ let j = fenceStart + 3;
18991
+ while (j < input.length && input[j] !== "\n") j++;
18992
+ if (j >= input.length) {
18993
+ blocks.push(input.slice(fenceStart + 3).replace(/^[^\n]*\n?/, ""));
18994
+ i = input.length;
18995
+ break;
19574
18996
  }
19575
- // ── Component nodes ──────────────────────────────────────────────────
19576
- case "Comp": {
19577
- const { name, args } = node;
19578
- if (isBuiltin(name)) {
19579
- const lazy2 = materializeLazyBuiltin(node, ctx, /* @__PURE__ */ new Set());
19580
- if (lazy2) return lazy2;
19581
- return { ...node, args: args.map((a) => materializeExpr(a, ctx)) };
18997
+ j++;
18998
+ let closePos = -1;
18999
+ let k = j;
19000
+ while (k < input.length) {
19001
+ const nextK = skipString(input, k);
19002
+ if (nextK > k) {
19003
+ k = nextK;
19004
+ continue;
19582
19005
  }
19583
- if (isReservedCall(name)) {
19584
- ctx.errors.push({
19585
- code: "inline-reserved",
19586
- component: name,
19587
- path: "",
19588
- message: `${name}() must be declared as a top-level statement, not used inline as a value`,
19589
- statementId: ctx.currentStatementId
19590
- });
19591
- return null;
19006
+ const c = input[k];
19007
+ if (c === "`" && k + 1 < input.length && input[k + 1] === "`" && k + 2 < input.length && input[k + 2] === "`") {
19008
+ closePos = k;
19009
+ break;
19592
19010
  }
19593
- const def = ctx.cat?.get(name);
19594
- const props = {};
19595
- if (def) {
19596
- for (let i = 0; i < def.params.length && i < args.length; i++) {
19597
- props[def.params[i].name] = materializeValue(args[i], ctx);
19598
- }
19599
- if (args.length > def.params.length) {
19600
- const excessCount = args.length - def.params.length;
19601
- ctx.errors.push({
19602
- code: "excess-args",
19603
- component: name,
19604
- path: "",
19605
- message: `${name} takes ${def.params.length} arg(s), got ${args.length} (${excessCount} excess dropped)`,
19606
- statementId: ctx.currentStatementId
19607
- });
19608
- }
19609
- const missingRequired = def.params.filter(
19610
- (p) => p.required && (!(p.name in props) || props[p.name] === null)
19611
- );
19612
- if (missingRequired.length) {
19613
- const stillInvalid = missingRequired.filter((p) => {
19614
- if (p.defaultValue !== void 0) {
19615
- props[p.name] = p.defaultValue;
19616
- return false;
19617
- }
19618
- return true;
19619
- });
19620
- if (stillInvalid.length) {
19621
- for (const p of stillInvalid) {
19622
- const isNull = p.name in props;
19623
- ctx.errors.push({
19624
- code: isNull ? "null-required" : "missing-required",
19625
- component: name,
19626
- path: `/${p.name}`,
19627
- message: isNull ? `required field "${p.name}" cannot be null` : `missing required field "${p.name}"`,
19628
- statementId: ctx.currentStatementId
19629
- });
19630
- }
19631
- return null;
19632
- }
19011
+ k++;
19012
+ }
19013
+ if (closePos !== -1) {
19014
+ blocks.push(input.slice(j, closePos));
19015
+ i = closePos + 3;
19016
+ } else {
19017
+ blocks.push(input.slice(j));
19018
+ i = input.length;
19019
+ }
19020
+ }
19021
+ if (blocks.length > 0) return blocks.join("\n");
19022
+ if (input.startsWith("```")) {
19023
+ let j = 3;
19024
+ while (j < input.length && input[j] !== "\n") j++;
19025
+ const start = j < input.length ? j + 1 : 3;
19026
+ const body = input.slice(start);
19027
+ const trailingFence = body.lastIndexOf("```");
19028
+ if (trailingFence !== -1) {
19029
+ return body.slice(0, trailingFence);
19030
+ }
19031
+ return body;
19032
+ }
19033
+ return input;
19034
+ }
19035
+ function stripComments(input) {
19036
+ let inStr = false;
19037
+ return input.split("\n").map((line) => {
19038
+ for (let i = 0; i < line.length; i++) {
19039
+ const c = line[i];
19040
+ if (inStr) {
19041
+ if (c === "\\" && i + 1 < line.length) {
19042
+ i++;
19043
+ continue;
19633
19044
  }
19634
- } else if (!isBuiltin(name) && !isReservedCall(name)) {
19635
- ctx.errors.push({
19636
- code: "unknown-component",
19637
- component: name,
19638
- path: "",
19639
- message: `Unknown component "${name}" \u2014 not found in catalog or builtins`,
19640
- statementId: ctx.currentStatementId
19641
- });
19642
- return null;
19045
+ if (c === inStr) inStr = false;
19046
+ continue;
19643
19047
  }
19644
- const hasDynamicProps = Object.values(props).some((v) => containsDynamicValue(v));
19645
- return { type: "element", typeName: name, props, partial: ctx.partial, hasDynamicProps };
19646
- }
19647
- // ── Runtime expression nodes → preserve as ASTNode, normalize children ─
19648
- default: {
19649
- if (isRuntimeExpr(node)) {
19650
- return materializeExpr(node, ctx);
19048
+ if (c === '"' || c === "'") {
19049
+ inStr = c;
19050
+ continue;
19051
+ }
19052
+ if (c === "/" && line[i + 1] === "/") {
19053
+ return line.substring(0, i).trimEnd();
19054
+ }
19055
+ if (c === "#") {
19056
+ return line.substring(0, i).trimEnd();
19651
19057
  }
19652
- return node;
19653
19058
  }
19059
+ return line;
19060
+ }).join("\n");
19061
+ }
19062
+ function preprocess(input) {
19063
+ return stripComments(stripFences(input.trim())).trim();
19064
+ }
19065
+ function parse(input, cat, rootName) {
19066
+ const trimmed = preprocess(input);
19067
+ if (!trimmed) return emptyResult();
19068
+ const { text: text2, wasIncomplete } = autoClose(trimmed);
19069
+ const stmts = split(tokenize(text2));
19070
+ if (!stmts.length) return emptyResult(wasIncomplete);
19071
+ const stmtMap = /* @__PURE__ */ new Map();
19072
+ let firstId = "";
19073
+ for (const s of stmts) {
19074
+ const expr = parseExpression(s.tokens);
19075
+ const stmt = classifyStatement(s, expr);
19076
+ stmtMap.set(s.id, stmt);
19077
+ if (!firstId) firstId = s.id;
19654
19078
  }
19079
+ const typedStmts = [...stmtMap.values()];
19080
+ return buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtMap.size, cat, rootName);
19655
19081
  }
19656
-
19657
- // src/genui/lang/parser/statements.ts
19658
- function autoClose(input) {
19659
- const stack = [];
19660
- let inStr = false;
19661
- let esc = false;
19662
- for (let i = 0; i < input.length; i++) {
19663
- const c = input[i];
19664
- if (esc) {
19665
- esc = false;
19666
- continue;
19082
+ function createStreamParser(cat, rootName) {
19083
+ let buf = "";
19084
+ let cleaned = "";
19085
+ let completedEnd = 0;
19086
+ const completedStmtMap = /* @__PURE__ */ new Map();
19087
+ let firstId = "";
19088
+ function addStmt(text2) {
19089
+ const t = text2.trim();
19090
+ if (!t) return;
19091
+ for (const s of split(tokenize(t))) {
19092
+ const expr = parseExpression(s.tokens);
19093
+ const stmt = classifyStatement(s, expr);
19094
+ completedStmtMap.set(s.id, stmt);
19095
+ if (!firstId) firstId = s.id;
19096
+ }
19097
+ }
19098
+ function refreshCleaned() {
19099
+ const next = preprocess(buf);
19100
+ if (!next.startsWith(cleaned.slice(0, completedEnd))) {
19101
+ completedEnd = 0;
19102
+ completedStmtMap.clear();
19103
+ firstId = "";
19104
+ }
19105
+ cleaned = next;
19106
+ }
19107
+ function scanNewCompleted() {
19108
+ let depth = 0, ternaryDepth = 0, inStr = false, esc = false;
19109
+ let stmtStart = completedEnd;
19110
+ for (let i = completedEnd; i < cleaned.length; i++) {
19111
+ const c = cleaned[i];
19112
+ if (esc) {
19113
+ esc = false;
19114
+ continue;
19115
+ }
19116
+ if (c === "\\" && inStr) {
19117
+ esc = true;
19118
+ continue;
19119
+ }
19120
+ if (inStr) {
19121
+ if (c === inStr) inStr = false;
19122
+ continue;
19123
+ }
19124
+ if (c === '"' || c === "'") {
19125
+ inStr = c;
19126
+ continue;
19127
+ }
19128
+ if (c === "(" || c === "[" || c === "{") depth++;
19129
+ else if (c === ")" || c === "]" || c === "}") depth = Math.max(0, depth - 1);
19130
+ else if (c === "?" && depth === 0) ternaryDepth++;
19131
+ else if (c === ":" && depth === 0 && ternaryDepth > 0) ternaryDepth--;
19132
+ else if (c === "\n" && depth <= 0 && ternaryDepth <= 0) {
19133
+ let peek = i + 1;
19134
+ while (peek < cleaned.length && (cleaned[peek] === " " || cleaned[peek] === " " || cleaned[peek] === "\r" || cleaned[peek] === "\n"))
19135
+ peek++;
19136
+ if (peek < cleaned.length && (cleaned[peek] === "?" || cleaned[peek] === ":" && ternaryDepth > 0)) {
19137
+ continue;
19138
+ }
19139
+ const t = cleaned.slice(stmtStart, i).trim();
19140
+ if (t) addStmt(t);
19141
+ stmtStart = i + 1;
19142
+ completedEnd = i + 1;
19143
+ }
19667
19144
  }
19668
- if (c === "\\" && inStr) {
19669
- esc = true;
19670
- continue;
19145
+ return stmtStart;
19146
+ }
19147
+ function currentResult() {
19148
+ refreshCleaned();
19149
+ const pendingStart = scanNewCompleted();
19150
+ const pendingText = cleaned.slice(pendingStart).trim();
19151
+ if (!pendingText) {
19152
+ if (completedStmtMap.size === 0) return emptyResult();
19153
+ return buildResult(
19154
+ completedStmtMap,
19155
+ [...completedStmtMap.values()],
19156
+ firstId,
19157
+ false,
19158
+ completedStmtMap.size,
19159
+ cat,
19160
+ rootName
19161
+ );
19671
19162
  }
19672
- if (inStr) {
19673
- if (c === inStr) inStr = false;
19674
- continue;
19163
+ const { text: closed, wasIncomplete } = autoClose(pendingText);
19164
+ const stmts = split(tokenize(closed));
19165
+ if (!stmts.length) {
19166
+ if (completedStmtMap.size === 0) return emptyResult(wasIncomplete);
19167
+ return buildResult(
19168
+ completedStmtMap,
19169
+ [...completedStmtMap.values()],
19170
+ firstId,
19171
+ wasIncomplete,
19172
+ completedStmtMap.size,
19173
+ cat,
19174
+ rootName
19175
+ );
19675
19176
  }
19676
- if (c === '"' || c === "'") {
19677
- inStr = c;
19678
- continue;
19177
+ const allStmtMap = new Map(completedStmtMap);
19178
+ for (const s of stmts) {
19179
+ const expr = parseExpression(s.tokens);
19180
+ const stmt = classifyStatement(s, expr);
19181
+ allStmtMap.set(s.id, stmt);
19679
19182
  }
19680
- if (c === "(" || c === "[" || c === "{") stack.push(c);
19681
- else if (c === ")" && stack[stack.length - 1] === "(") stack.pop();
19682
- else if (c === "]" && stack[stack.length - 1] === "[") stack.pop();
19683
- else if (c === "}" && stack[stack.length - 1] === "{") stack.pop();
19183
+ const allTypedStmts = [...allStmtMap.values()];
19184
+ const fid = firstId || stmts[0].id;
19185
+ return buildResult(
19186
+ allStmtMap,
19187
+ allTypedStmts,
19188
+ fid,
19189
+ wasIncomplete,
19190
+ allStmtMap.size,
19191
+ cat,
19192
+ rootName
19193
+ );
19684
19194
  }
19685
- const wasIncomplete = !!inStr || stack.length > 0;
19686
- if (!wasIncomplete) return { text: input, wasIncomplete: false };
19687
- let out = input;
19688
- if (inStr) {
19689
- if (esc) out += "\\";
19690
- out += inStr;
19195
+ function reset() {
19196
+ buf = "";
19197
+ cleaned = "";
19198
+ completedEnd = 0;
19199
+ completedStmtMap.clear();
19200
+ firstId = "";
19691
19201
  }
19692
- for (let j = stack.length - 1; j >= 0; j--)
19693
- out += stack[j] === "(" ? ")" : stack[j] === "[" ? "]" : "}";
19694
- return { text: out, wasIncomplete: true };
19695
- }
19696
- function split(tokens) {
19697
- const stmts = [];
19698
- let pos = 0;
19699
- while (pos < tokens.length) {
19700
- while (pos < tokens.length && tokens[pos].t === 0 /* Newline */) pos++;
19701
- if (pos >= tokens.length || tokens[pos].t === 13 /* EOF */) break;
19702
- const tok = tokens[pos];
19703
- if (tok.t !== 16 /* Ident */ && tok.t !== 17 /* Type */ && tok.t !== 18 /* StateVar */) {
19704
- while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
19705
- continue;
19706
- }
19707
- const id = tok.v;
19708
- const idTokenType = tok.t;
19709
- pos++;
19710
- if (pos >= tokens.length || tokens[pos].t !== 9 /* Equals */) {
19711
- while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
19712
- continue;
19713
- }
19714
- pos++;
19715
- const expr = [];
19716
- let depth = 0;
19717
- let ternaryDepth = 0;
19718
- while (pos < tokens.length && tokens[pos].t !== 13 /* EOF */) {
19719
- const tt = tokens[pos].t;
19720
- if (tt === 0 /* Newline */ && depth <= 0 && ternaryDepth <= 0) {
19721
- let peek = pos + 1;
19722
- while (peek < tokens.length && tokens[peek].t === 0 /* Newline */) peek++;
19723
- const nextT = peek < tokens.length ? tokens[peek].t : 13 /* EOF */;
19724
- if (nextT === 34 /* Question */ || nextT === 8 /* Colon */ && ternaryDepth > 0) {
19725
- pos++;
19726
- continue;
19727
- }
19728
- break;
19729
- }
19730
- if (tt === 0 /* Newline */) {
19731
- pos++;
19732
- continue;
19202
+ return {
19203
+ push(chunk) {
19204
+ buf += chunk;
19205
+ return currentResult();
19206
+ },
19207
+ set(fullText) {
19208
+ if (fullText.length < buf.length || !fullText.startsWith(buf)) {
19209
+ reset();
19733
19210
  }
19734
- if (tt === 1 /* LParen */ || tt === 3 /* LBrack */ || tt === 5 /* LBrace */) depth++;
19735
- else if ((tt === 2 /* RParen */ || tt === 4 /* RBrack */ || tt === 6 /* RBrace */) && depth > 0) depth--;
19736
- else if (tt === 34 /* Question */ && depth === 0) ternaryDepth++;
19737
- else if (tt === 8 /* Colon */ && depth === 0 && ternaryDepth > 0) ternaryDepth--;
19738
- expr.push(tokens[pos++]);
19739
- }
19740
- if (expr.length) stmts.push({ id, idTokenType, tokens: expr });
19741
- }
19742
- return stmts;
19211
+ const delta = fullText.slice(buf.length);
19212
+ if (delta) buf += delta;
19213
+ return currentResult();
19214
+ },
19215
+ getResult: currentResult
19216
+ };
19743
19217
  }
19744
19218
 
19745
- // src/genui/lang/parser/parser.ts
19746
- function emptyResult(incomplete = true) {
19747
- return {
19748
- root: null,
19749
- meta: {
19750
- incomplete,
19751
- unresolved: [],
19752
- orphaned: [],
19753
- statementCount: 0,
19754
- errors: []
19219
+ // src/genui/registry.ts
19220
+ var CIMPLIFY_GENUI_REGISTRY = {
19221
+ version: "3",
19222
+ components: [
19223
+ {
19224
+ name: "Stack",
19225
+ description: 'Layout container. direction: "column" (default) or "row"; gap in points (default 12)',
19226
+ params: [{ name: "children", required: true }, { name: "direction" }, { name: "gap" }]
19227
+ },
19228
+ {
19229
+ name: "Card",
19230
+ description: "Elevated surface grouping related content, with an optional title",
19231
+ params: [{ name: "children", required: true }, { name: "title" }]
19232
+ },
19233
+ {
19234
+ name: "TextContent",
19235
+ description: 'Text block. size: "display", "heading", "title", "body" (default), "label", "caption"',
19236
+ params: [{ name: "text", required: true }, { name: "size" }]
19237
+ },
19238
+ {
19239
+ name: "Stat",
19240
+ description: "Compact stat: small label over a bold value, optional sub line and tone",
19241
+ params: [
19242
+ { name: "label", required: true },
19243
+ { name: "value", required: true },
19244
+ { name: "sub" },
19245
+ { name: "tone" }
19246
+ ]
19247
+ },
19248
+ {
19249
+ name: "Metric",
19250
+ description: "KPI card with label, value, optional percent change and SF Symbol icon",
19251
+ params: [
19252
+ { name: "label", required: true },
19253
+ { name: "value", required: true },
19254
+ { name: "change" },
19255
+ { name: "icon" }
19256
+ ]
19257
+ },
19258
+ {
19259
+ name: "KeyValue",
19260
+ description: "Label/value row; set mono true for numeric values",
19261
+ params: [
19262
+ { name: "label", required: true },
19263
+ { name: "value", required: true },
19264
+ { name: "mono" }
19265
+ ]
19266
+ },
19267
+ {
19268
+ name: "Badge",
19269
+ description: "Small status chip. tone: neutral, positive, info, indigo, violet, warning, danger",
19270
+ params: [{ name: "label", required: true }, { name: "tone" }]
19271
+ },
19272
+ {
19273
+ name: "Callout",
19274
+ description: "Inline alert with title, optional message. tone: info (default), success, warning, danger, accent, neutral",
19275
+ params: [{ name: "title", required: true }, { name: "message" }, { name: "tone" }]
19276
+ },
19277
+ {
19278
+ name: "Button",
19279
+ description: 'Action button. action: "action:name" string or Action([...]) steps. variant: primary, cta, secondary (default), ghost, destructive',
19280
+ params: [
19281
+ { name: "label", required: true },
19282
+ { name: "action", required: true },
19283
+ { name: "variant" }
19284
+ ]
19285
+ },
19286
+ {
19287
+ name: "Buttons",
19288
+ description: 'Row (default) or column of Buttons; direction: "row" or "column"',
19289
+ params: [{ name: "children", required: true }, { name: "direction" }]
19290
+ },
19291
+ {
19292
+ name: "Divider",
19293
+ description: "Hairline separator",
19294
+ params: []
19295
+ },
19296
+ {
19297
+ name: "Table",
19298
+ description: "Data table: headers is an array of strings, rows an array of cell arrays",
19299
+ params: [
19300
+ { name: "headers", required: true },
19301
+ { name: "rows", required: true }
19302
+ ]
19303
+ },
19304
+ {
19305
+ name: "BarChart",
19306
+ description: "Bar chart: labels (x axis strings) and values (numbers), optional title",
19307
+ params: [
19308
+ { name: "labels", required: true },
19309
+ { name: "values", required: true },
19310
+ { name: "title" }
19311
+ ]
19312
+ },
19313
+ {
19314
+ name: "LineChart",
19315
+ description: "Line chart with area fill: labels and values, optional title",
19316
+ params: [
19317
+ { name: "labels", required: true },
19318
+ { name: "values", required: true },
19319
+ { name: "title" }
19320
+ ]
19321
+ },
19322
+ {
19323
+ name: "Money",
19324
+ description: "Formatted money amount, never abbreviated. currency: ISO code (default USD)",
19325
+ params: [{ name: "amount", required: true }, { name: "currency" }]
19755
19326
  },
19756
- stateDeclarations: {},
19757
- queryStatements: [],
19758
- mutationStatements: []
19759
- };
19760
- }
19761
- function collectQueryDeps(node) {
19762
- if (!isASTNode(node)) return [];
19763
- const refs = /* @__PURE__ */ new Set();
19764
- walkAST(node, (current) => {
19765
- if (current.k === "StateRef") refs.add(current.n);
19766
- });
19767
- return [...refs];
19768
- }
19769
- function classifyStatement(raw, expr) {
19770
- if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Query) {
19771
- const deps = collectQueryDeps(expr.args[1]);
19772
- return {
19773
- kind: "query",
19774
- id: raw.id,
19775
- call: { callee: RESERVED_CALLS.Query, args: expr.args },
19776
- expr,
19777
- deps: deps.length > 0 ? deps : void 0
19778
- };
19779
- }
19780
- if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Mutation) {
19781
- return {
19782
- kind: "mutation",
19783
- id: raw.id,
19784
- call: { callee: RESERVED_CALLS.Mutation, args: expr.args },
19785
- expr
19786
- };
19787
- }
19788
- if (raw.idTokenType === 18 /* StateVar */) {
19789
- return { kind: "state", id: raw.id, init: expr };
19790
- }
19791
- return { kind: "value", id: raw.id, expr };
19792
- }
19793
- function extractStatements(stmts, ctx) {
19794
- const stateDeclarations = {};
19795
- const queryStatements = [];
19796
- const mutationStatements = [];
19797
- for (const stmt of stmts) {
19798
- switch (stmt.kind) {
19799
- case "state":
19800
- stateDeclarations[stmt.id] = materializeValue(stmt.init, ctx);
19801
- break;
19802
- case "query":
19803
- queryStatements.push({
19804
- statementId: stmt.id,
19805
- toolAST: stmt.call.args[0] ?? null,
19806
- argsAST: stmt.call.args[1] ?? null,
19807
- defaultsAST: stmt.call.args[2] ?? null,
19808
- refreshAST: stmt.call.args[3] ?? null,
19809
- deps: stmt.deps,
19810
- complete: true
19811
- });
19812
- break;
19813
- case "mutation":
19814
- mutationStatements.push({
19815
- statementId: stmt.id,
19816
- toolAST: stmt.call.args[0] ?? null,
19817
- argsAST: stmt.call.args[1] ?? null
19818
- });
19819
- break;
19327
+ {
19328
+ name: "Image",
19329
+ description: "Remote image by URL with optional caption",
19330
+ params: [{ name: "url", required: true }, { name: "caption" }]
19331
+ },
19332
+ {
19333
+ name: "ListRow",
19334
+ description: "Entity row for orders/customers/products/invoices: initials avatar (or SF Symbol tile via symbol), title + subtitle, trailing value and badge with tone",
19335
+ params: [
19336
+ { name: "title", required: true },
19337
+ { name: "subtitle" },
19338
+ { name: "value" },
19339
+ { name: "badge" },
19340
+ { name: "tone" },
19341
+ { name: "symbol" }
19342
+ ]
19343
+ },
19344
+ {
19345
+ name: "Timeline",
19346
+ description: "Event history (order journey, payment trail): events is an array of {title, detail?, time?, tone?} objects, newest first",
19347
+ params: [{ name: "events", required: true }]
19348
+ },
19349
+ {
19350
+ name: "Progress",
19351
+ description: "Amount toward a bound (goal, stock cover, capacity): numeric value and target (default 100), optional detail caption and tone",
19352
+ params: [
19353
+ { name: "label", required: true },
19354
+ { name: "value", required: true },
19355
+ { name: "target" },
19356
+ { name: "detail" },
19357
+ { name: "tone" }
19358
+ ]
19359
+ },
19360
+ {
19361
+ name: "DonutChart",
19362
+ description: "Share of a whole (sales by channel, revenue by category): labels + values, optional title and center display string (e.g. the formatted total)",
19363
+ params: [
19364
+ { name: "labels", required: true },
19365
+ { name: "values", required: true },
19366
+ { name: "title" },
19367
+ { name: "center" }
19368
+ ]
19369
+ },
19370
+ {
19371
+ name: "StatusTrack",
19372
+ description: "Stage tracker (order fulfilment, invoice lifecycle): steps are stage names, current is the active step name or index",
19373
+ params: [
19374
+ { name: "steps", required: true },
19375
+ { name: "current", required: true }
19376
+ ]
19377
+ },
19378
+ {
19379
+ name: "LineItems",
19380
+ description: "Order/quote/invoice money block: items is an array of {qty?, name, amount} with display-ready amount strings, adjustments an array of {label, amount, tone?}, total the anchored display string",
19381
+ params: [
19382
+ { name: "items", required: true },
19383
+ { name: "total", required: true },
19384
+ { name: "adjustments" }
19385
+ ]
19386
+ },
19387
+ {
19388
+ name: "Grid",
19389
+ description: "Tile layout for Stat/Metric children, columns default 2",
19390
+ params: [{ name: "children", required: true }, { name: "columns" }]
19391
+ },
19392
+ {
19393
+ name: "Banner",
19394
+ description: "Hero-stat moment on a tonal wash: big value, label, optional percent delta. tone: positive (default), warning, danger, info, neutral",
19395
+ params: [
19396
+ { name: "value", required: true },
19397
+ { name: "label", required: true },
19398
+ { name: "delta" },
19399
+ { name: "tone" }
19400
+ ]
19401
+ },
19402
+ {
19403
+ name: "EmptyState",
19404
+ description: "Nothing-to-show state with an SF Symbol and optional message",
19405
+ params: [{ name: "title", required: true }, { name: "message" }, { name: "symbol" }]
19820
19406
  }
19821
- }
19822
- for (const stmt of stmts) {
19823
- const nodes = stmt.kind === "state" ? [stmt.init] : stmt.kind === "value" ? [stmt.expr] : stmt.kind === "query" || stmt.kind === "mutation" ? stmt.call.args : [];
19824
- for (const node of nodes) {
19825
- for (const dep of collectQueryDeps(node)) {
19826
- if (!(dep in stateDeclarations)) {
19827
- stateDeclarations[dep] = null;
19828
- }
19407
+ ]
19408
+ };
19409
+
19410
+ // src/react/genui/catalog.ts
19411
+ function genUICatalog(registry) {
19412
+ return new Map(
19413
+ registry.components.map((component) => [
19414
+ component.name,
19415
+ {
19416
+ params: component.params.map((param) => ({
19417
+ name: param.name,
19418
+ required: param.required ?? false
19419
+ }))
19829
19420
  }
19830
- }
19831
- }
19832
- return { stateDeclarations, queryStatements, mutationStatements };
19833
- }
19834
- var DEFAULT_ROOT_STATEMENT_ID = "root";
19835
- function isComponentStatement(stmt) {
19836
- return stmt.kind === "value" && stmt.expr.k === "Comp" && !isBuiltin(stmt.expr.name) && stmt.expr.name !== RESERVED_CALLS.Query && stmt.expr.name !== RESERVED_CALLS.Mutation;
19421
+ ])
19422
+ );
19837
19423
  }
19838
- function pickEntryId(stmtMap, typedStmts, firstId, rootName) {
19839
- if (stmtMap.has(DEFAULT_ROOT_STATEMENT_ID)) return DEFAULT_ROOT_STATEMENT_ID;
19840
- if (rootName && stmtMap.has(rootName)) return rootName;
19841
- const preferredComponent = rootName ? typedStmts.find((stmt) => isComponentStatement(stmt) && stmt.expr.name === rootName) : void 0;
19842
- if (preferredComponent) return preferredComponent.id;
19843
- const firstComponent = typedStmts.find(isComponentStatement);
19844
- return firstComponent?.id ?? firstId;
19424
+ var noop = () => {
19425
+ };
19426
+ function childKey(value, index) {
19427
+ if (isElementNode(value) && value.statementId) return `s:${value.statementId}`;
19428
+ return `i:${index}`;
19845
19429
  }
19846
- function buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtCount, cat, rootName) {
19847
- const entryId = pickEntryId(stmtMap, typedStmts, firstId, rootName);
19848
- if (!stmtMap.has(entryId)) return emptyResult(wasIncomplete);
19849
- const syms = /* @__PURE__ */ new Map();
19850
- for (const [id, stmt] of stmtMap) {
19851
- syms.set(id, stmt.kind === "state" ? stmt.init : stmt.expr);
19852
- }
19853
- const unres = [];
19854
- const errors = [];
19855
- const unreached = /* @__PURE__ */ new Set();
19856
- for (const [id, stmt] of stmtMap) {
19857
- if (id === entryId) continue;
19858
- if (stmt.kind === "state" || stmt.kind === "query" || stmt.kind === "mutation") continue;
19859
- unreached.add(id);
19430
+ var RenderDepthContext = createContext(0);
19431
+ var MAX_DEPTH = 64;
19432
+ var GenUINode = memo(function GenUINode2({
19433
+ node,
19434
+ components,
19435
+ onAction,
19436
+ isStreaming,
19437
+ onTelemetry
19438
+ }) {
19439
+ const depth = useContext(RenderDepthContext);
19440
+ if (depth > MAX_DEPTH) {
19441
+ onTelemetry?.({ kind: "dropped-node", typeName: node.typeName });
19442
+ return null;
19860
19443
  }
19861
- const ctx = {
19862
- syms,
19863
- cat,
19864
- errors,
19865
- unres,
19866
- visited: /* @__PURE__ */ new Set(),
19867
- partial: wasIncomplete,
19868
- currentStatementId: entryId,
19869
- unreached
19870
- };
19871
- const materialized = materializeValue(syms.get(entryId), ctx);
19872
- const root = isElementNode(materialized) ? materialized : null;
19873
- if (root) root.statementId = entryId;
19874
- const { stateDeclarations, queryStatements, mutationStatements } = extractStatements(
19875
- typedStmts,
19876
- ctx
19877
- );
19878
- const orphaned = [...unreached];
19879
- return {
19880
- root,
19881
- meta: {
19882
- incomplete: wasIncomplete,
19883
- unresolved: unres,
19884
- orphaned,
19885
- statementCount: stmtCount,
19886
- errors
19887
- },
19888
- stateDeclarations,
19889
- queryStatements,
19890
- mutationStatements
19891
- };
19892
- }
19893
- function skipString(input, start) {
19894
- if (input[start] !== '"') return start;
19895
- let i = start + 1;
19896
- while (i < input.length) {
19897
- const c = input[i];
19898
- if (c === "\\") {
19899
- i += 2;
19900
- } else if (c === '"') {
19901
- return i + 1;
19902
- } else {
19903
- i++;
19904
- }
19444
+ const renderer = components[node.typeName];
19445
+ if (!renderer) {
19446
+ onTelemetry?.({ kind: "unknown-component", typeName: node.typeName });
19447
+ return null;
19905
19448
  }
19906
- return i;
19907
- }
19908
- function stripFences(input) {
19909
- const blocks = [];
19910
- let i = 0;
19911
- while (i < input.length) {
19912
- let fenceStart = -1;
19913
- while (i < input.length) {
19914
- const nextI = skipString(input, i);
19915
- if (nextI > i) {
19916
- i = nextI;
19917
- continue;
19918
- }
19919
- const c = input[i];
19920
- if (c === "`" && i + 1 < input.length && input[i + 1] === "`" && i + 2 < input.length && input[i + 2] === "`") {
19921
- fenceStart = i;
19922
- break;
19923
- }
19924
- i++;
19925
- }
19926
- if (fenceStart === -1) break;
19927
- let j = fenceStart + 3;
19928
- while (j < input.length && input[j] !== "\n") j++;
19929
- if (j >= input.length) {
19930
- blocks.push(input.slice(fenceStart + 3).replace(/^[^\n]*\n?/, ""));
19931
- i = input.length;
19932
- break;
19449
+ const render = (value) => {
19450
+ if (value == null) return null;
19451
+ if (isElementNode(value)) {
19452
+ return /* @__PURE__ */ jsx(RenderDepthContext.Provider, { value: depth + 1, children: /* @__PURE__ */ jsx(
19453
+ GenUINode2,
19454
+ {
19455
+ node: value,
19456
+ components,
19457
+ onAction,
19458
+ isStreaming,
19459
+ onTelemetry
19460
+ }
19461
+ ) });
19933
19462
  }
19934
- j++;
19935
- let closePos = -1;
19936
- let k = j;
19937
- while (k < input.length) {
19938
- const nextK = skipString(input, k);
19939
- if (nextK > k) {
19940
- k = nextK;
19941
- continue;
19942
- }
19943
- const c = input[k];
19944
- if (c === "`" && k + 1 < input.length && input[k + 1] === "`" && k + 2 < input.length && input[k + 2] === "`") {
19945
- closePos = k;
19946
- break;
19947
- }
19948
- k++;
19463
+ if (Array.isArray(value)) {
19464
+ return value.map((item, index) => /* @__PURE__ */ jsx(React14.Fragment, { children: render(item) }, childKey(item, index)));
19949
19465
  }
19950
- if (closePos !== -1) {
19951
- blocks.push(input.slice(j, closePos));
19952
- i = closePos + 3;
19953
- } else {
19954
- blocks.push(input.slice(j));
19955
- i = input.length;
19466
+ if (typeof value === "object" && isRuntimeExpr(value)) return null;
19467
+ if (typeof value === "string" || typeof value === "number") return String(value);
19468
+ return null;
19469
+ };
19470
+ return renderer({ node, ctx: { render, onAction, isStreaming } });
19471
+ });
19472
+ function GenUIView({
19473
+ root,
19474
+ components,
19475
+ onAction = noop,
19476
+ isStreaming = false,
19477
+ onTelemetry,
19478
+ placeholder = null
19479
+ }) {
19480
+ if (!root) return placeholder;
19481
+ return /* @__PURE__ */ jsx(
19482
+ GenUINode,
19483
+ {
19484
+ node: root,
19485
+ components,
19486
+ onAction,
19487
+ isStreaming,
19488
+ onTelemetry
19956
19489
  }
19490
+ );
19491
+ }
19492
+ var CHAT_ACTION_VIEW_PRODUCT = "view_product";
19493
+ var CHAT_ACTION_VIEW_ORDER = "view_order";
19494
+ function chatAction(verb, id) {
19495
+ return `chat:${verb}:${id}`;
19496
+ }
19497
+ function parseChatAction(action) {
19498
+ const [, verb, id] = action.split(":");
19499
+ return verb && id ? { verb, id } : null;
19500
+ }
19501
+ function text(value) {
19502
+ return typeof value === "string" ? value : typeof value === "number" ? String(value) : void 0;
19503
+ }
19504
+ function num(value) {
19505
+ if (typeof value === "number" && Number.isFinite(value)) return value;
19506
+ if (typeof value === "string" && Number.isFinite(Number(value))) return Number(value);
19507
+ return void 0;
19508
+ }
19509
+ function rowsOf(value) {
19510
+ if (!Array.isArray(value)) return [];
19511
+ return value.filter(
19512
+ (item) => typeof item === "object" && item !== null && !Array.isArray(item)
19513
+ );
19514
+ }
19515
+ function tone(value) {
19516
+ switch (value) {
19517
+ case "positive":
19518
+ case "success":
19519
+ return "positive";
19520
+ case "warning":
19521
+ return "warning";
19522
+ case "danger":
19523
+ case "negative":
19524
+ return "danger";
19525
+ default:
19526
+ return "neutral";
19957
19527
  }
19958
- if (blocks.length > 0) return blocks.join("\n");
19959
- if (input.startsWith("```")) {
19960
- let j = 3;
19961
- while (j < input.length && input[j] !== "\n") j++;
19962
- const start = j < input.length ? j + 1 : 3;
19963
- const body = input.slice(start);
19964
- const trailingFence = body.lastIndexOf("```");
19965
- if (trailingFence !== -1) {
19966
- return body.slice(0, trailingFence);
19967
- }
19968
- return body;
19528
+ }
19529
+ var WARNING_CHIP_STYLE = {
19530
+ color: "var(--color-pending, #B45309)",
19531
+ background: "var(--color-pending-wash, #FEF3C7)"
19532
+ };
19533
+ function toneChip(value) {
19534
+ switch (tone(value)) {
19535
+ case "positive":
19536
+ return { className: "bg-primary/10 text-primary" };
19537
+ case "warning":
19538
+ return { className: "", style: WARNING_CHIP_STYLE };
19539
+ case "danger":
19540
+ return { className: "bg-destructive/10 text-destructive" };
19541
+ default:
19542
+ return { className: "bg-muted text-muted-foreground" };
19969
19543
  }
19970
- return input;
19971
19544
  }
19972
- function stripComments(input) {
19973
- let inStr = false;
19974
- return input.split("\n").map((line) => {
19975
- for (let i = 0; i < line.length; i++) {
19976
- const c = line[i];
19977
- if (inStr) {
19978
- if (c === "\\" && i + 1 < line.length) {
19979
- i++;
19980
- continue;
19981
- }
19982
- if (c === inStr) inStr = false;
19983
- continue;
19984
- }
19985
- if (c === '"' || c === "'") {
19986
- inStr = c;
19987
- continue;
19545
+ function money(amount, currency) {
19546
+ return formatPrice(parsePrice(num(amount) ?? 0), text(currency));
19547
+ }
19548
+ var CHAT_GENUI_COMPONENTS = {
19549
+ Stack: ({ node, ctx }) => /* @__PURE__ */ jsx(
19550
+ "div",
19551
+ {
19552
+ className: cn("flex w-full", node.props.direction === "row" ? "flex-row" : "flex-col"),
19553
+ style: { gap: num(node.props.gap) ?? 10 },
19554
+ children: ctx.render(node.props.children)
19555
+ }
19556
+ ),
19557
+ Card: ({ node, ctx }) => /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-2 rounded-2xl border border-border bg-background p-3.5", children: [
19558
+ text(node.props.title) && /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: text(node.props.title) }),
19559
+ ctx.render(node.props.children)
19560
+ ] }),
19561
+ TextContent: ({ node }) => {
19562
+ const size = text(node.props.size);
19563
+ return /* @__PURE__ */ jsx(
19564
+ "p",
19565
+ {
19566
+ className: cn(
19567
+ "break-words text-foreground",
19568
+ size === "display" || size === "heading" ? "text-[17px] font-bold leading-6" : size === "title" ? "text-[15px] font-semibold leading-5" : size === "label" || size === "caption" ? "text-xs text-muted-foreground" : "text-sm leading-5"
19569
+ ),
19570
+ children: text(node.props.text) ?? ""
19988
19571
  }
19989
- if (c === "/" && line[i + 1] === "/") {
19990
- return line.substring(0, i).trimEnd();
19572
+ );
19573
+ },
19574
+ Stat: ({ node }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
19575
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: text(node.props.label) }),
19576
+ /* @__PURE__ */ jsx("span", { className: "text-[17px] font-bold text-foreground tabular-nums", children: text(node.props.value) }),
19577
+ text(node.props.sub) && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: text(node.props.sub) })
19578
+ ] }),
19579
+ KeyValue: ({ node }) => /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [
19580
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: text(node.props.label) }),
19581
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground tabular-nums", children: text(node.props.value) })
19582
+ ] }),
19583
+ Badge: ({ node }) => {
19584
+ const chip = toneChip(node.props.tone);
19585
+ return /* @__PURE__ */ jsx(
19586
+ "span",
19587
+ {
19588
+ style: chip.style,
19589
+ className: cn("w-fit rounded-full px-2 py-0.5 text-[11px] font-semibold", chip.className),
19590
+ children: text(node.props.label)
19991
19591
  }
19992
- if (c === "#") {
19993
- return line.substring(0, i).trimEnd();
19592
+ );
19593
+ },
19594
+ Callout: ({ node }) => {
19595
+ const chip = toneChip(node.props.tone);
19596
+ return /* @__PURE__ */ jsxs(
19597
+ "div",
19598
+ {
19599
+ style: chip.style,
19600
+ className: cn("flex flex-col gap-0.5 rounded-xl px-3 py-2.5", chip.className),
19601
+ children: [
19602
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] font-semibold", children: text(node.props.title) }),
19603
+ text(node.props.message) && /* @__PURE__ */ jsx("span", { className: "text-xs leading-4 opacity-90", children: text(node.props.message) })
19604
+ ]
19994
19605
  }
19606
+ );
19607
+ },
19608
+ Button: ({ node, ctx }) => /* @__PURE__ */ jsx(
19609
+ "button",
19610
+ {
19611
+ type: "button",
19612
+ disabled: ctx.isStreaming,
19613
+ onClick: () => {
19614
+ const action = node.props.action;
19615
+ if (typeof action === "string") {
19616
+ ctx.onAction({ action, label: text(node.props.label) ?? "" });
19617
+ }
19618
+ },
19619
+ className: "w-fit rounded-full bg-foreground px-4 py-2 text-[13px] font-semibold text-background active:opacity-80",
19620
+ children: text(node.props.label)
19995
19621
  }
19996
- return line;
19997
- }).join("\n");
19998
- }
19999
- function preprocess(input) {
20000
- return stripComments(stripFences(input.trim())).trim();
20001
- }
20002
- function parse(input, cat, rootName) {
20003
- const trimmed = preprocess(input);
20004
- if (!trimmed) return emptyResult();
20005
- const { text, wasIncomplete } = autoClose(trimmed);
20006
- const stmts = split(tokenize(text));
20007
- if (!stmts.length) return emptyResult(wasIncomplete);
20008
- const stmtMap = /* @__PURE__ */ new Map();
20009
- let firstId = "";
20010
- for (const s of stmts) {
20011
- const expr = parseExpression(s.tokens);
20012
- const stmt = classifyStatement(s, expr);
20013
- stmtMap.set(s.id, stmt);
20014
- if (!firstId) firstId = s.id;
20015
- }
20016
- const typedStmts = [...stmtMap.values()];
20017
- return buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtMap.size, cat, rootName);
20018
- }
20019
- function createStreamParser(cat, rootName) {
20020
- let buf = "";
20021
- let cleaned = "";
20022
- let completedEnd = 0;
20023
- const completedStmtMap = /* @__PURE__ */ new Map();
20024
- let firstId = "";
20025
- function addStmt(text) {
20026
- const t = text.trim();
20027
- if (!t) return;
20028
- for (const s of split(tokenize(t))) {
20029
- const expr = parseExpression(s.tokens);
20030
- const stmt = classifyStatement(s, expr);
20031
- completedStmtMap.set(s.id, stmt);
20032
- if (!firstId) firstId = s.id;
19622
+ ),
19623
+ Buttons: ({ node, ctx }) => /* @__PURE__ */ jsx(
19624
+ "div",
19625
+ {
19626
+ className: cn(
19627
+ "flex gap-2",
19628
+ node.props.direction === "column" ? "flex-col items-start" : "flex-row flex-wrap"
19629
+ ),
19630
+ children: ctx.render(node.props.children)
20033
19631
  }
19632
+ ),
19633
+ Divider: () => /* @__PURE__ */ jsx("div", { className: "h-px w-full bg-border" }),
19634
+ Table: ({ node }) => {
19635
+ const headers = Array.isArray(node.props.headers) ? node.props.headers.map(String) : [];
19636
+ const rows = Array.isArray(node.props.rows) ? node.props.rows : [];
19637
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col", children: [
19638
+ headers.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex gap-3 border-b border-border pb-1", children: headers.map((header, index) => /* @__PURE__ */ jsx(
19639
+ "span",
19640
+ {
19641
+ className: "min-w-0 flex-1 text-[11px] font-semibold tracking-wide text-muted-foreground uppercase",
19642
+ children: header
19643
+ },
19644
+ index
19645
+ )) }),
19646
+ rows.map((row, rowIndex) => /* @__PURE__ */ jsx("div", { className: "flex gap-3 border-b border-border py-1.5 last:border-b-0", children: (Array.isArray(row) ? row : [row]).map((cell, cellIndex) => /* @__PURE__ */ jsx(
19647
+ "span",
19648
+ {
19649
+ className: "min-w-0 flex-1 text-[13px] text-foreground tabular-nums",
19650
+ children: text(cell) ?? ""
19651
+ },
19652
+ cellIndex
19653
+ )) }, rowIndex))
19654
+ ] });
19655
+ },
19656
+ Money: ({ node }) => /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground tabular-nums", children: money(node.props.amount, node.props.currency) }),
19657
+ Image: ({ node }) => text(node.props.url) ? /* @__PURE__ */ jsx(
19658
+ "img",
19659
+ {
19660
+ src: text(node.props.url),
19661
+ alt: text(node.props.caption) ?? "",
19662
+ loading: "lazy",
19663
+ className: "max-h-40 w-full rounded-xl object-cover"
19664
+ }
19665
+ ) : null,
19666
+ ListRow: ({ node, ctx }) => {
19667
+ const action = text(node.props.action);
19668
+ const title = text(node.props.title) ?? "";
19669
+ const chip = toneChip(node.props.tone);
19670
+ return /* @__PURE__ */ jsxs(
19671
+ "div",
19672
+ {
19673
+ role: action ? "button" : void 0,
19674
+ onClick: action ? () => ctx.onAction({ action, label: title }) : void 0,
19675
+ className: cn("flex items-center gap-2.5 py-1", action && "cursor-pointer active:opacity-70"),
19676
+ children: [
19677
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 grow flex-col", children: [
19678
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: title }),
19679
+ text(node.props.subtitle) && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: text(node.props.subtitle) })
19680
+ ] }),
19681
+ text(node.props.value) && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold text-foreground tabular-nums", children: text(node.props.value) }),
19682
+ text(node.props.badge) && /* @__PURE__ */ jsx(
19683
+ "span",
19684
+ {
19685
+ style: chip.style,
19686
+ className: cn(
19687
+ "shrink-0 rounded-full px-2 py-0.5 text-[10.5px] font-semibold",
19688
+ chip.className
19689
+ ),
19690
+ children: text(node.props.badge)
19691
+ }
19692
+ )
19693
+ ]
19694
+ }
19695
+ );
19696
+ },
19697
+ Timeline: ({ node }) => /* @__PURE__ */ jsx("div", { className: "flex flex-col", children: rowsOf(node.props.events).map((event, index, events) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5", children: [
19698
+ /* @__PURE__ */ jsxs("div", { className: "flex w-2.5 flex-col items-center", children: [
19699
+ /* @__PURE__ */ jsx("span", { className: "mt-1.5 size-1.5 rounded-full bg-primary" }),
19700
+ index < events.length - 1 && /* @__PURE__ */ jsx("span", { className: "w-px grow bg-border" })
19701
+ ] }),
19702
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col pb-2.5", children: [
19703
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium text-foreground", children: text(event.title) }),
19704
+ text(event.detail) && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: text(event.detail) }),
19705
+ text(event.time) && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: text(event.time) })
19706
+ ] })
19707
+ ] }, index)) }),
19708
+ Progress: ({ node }) => {
19709
+ const value = num(node.props.value) ?? 0;
19710
+ const target = num(node.props.target) || 100;
19711
+ const ratio = Math.min(1, Math.max(0, target === 0 ? 0 : value / target));
19712
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1", children: [
19713
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
19714
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: text(node.props.label) }),
19715
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-foreground tabular-nums", children: text(node.props.value) })
19716
+ ] }),
19717
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx("div", { className: "h-full rounded-full bg-primary", style: { width: `${ratio * 100}%` } }) })
19718
+ ] });
19719
+ },
19720
+ StatusTrack: ({ node }) => {
19721
+ const steps = Array.isArray(node.props.steps) ? node.props.steps.map(String) : [];
19722
+ const current = node.props.current;
19723
+ const currentIndex = typeof current === "number" ? Math.min(Math.max(0, Math.trunc(current)), Math.max(0, steps.length - 1)) : Math.max(0, steps.indexOf(String(current ?? "")));
19724
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1", children: [
19725
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: steps.map((_, index) => /* @__PURE__ */ jsx(
19726
+ "span",
19727
+ {
19728
+ className: cn(
19729
+ "h-1 grow rounded-full",
19730
+ index <= currentIndex ? "bg-primary" : "bg-muted"
19731
+ )
19732
+ },
19733
+ index
19734
+ )) }),
19735
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground", children: steps[currentIndex] })
19736
+ ] });
19737
+ },
19738
+ LineItems: ({ node }) => /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col", children: [
19739
+ rowsOf(node.props.items).map((item, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 py-0.5", children: [
19740
+ text(item.qty) && /* @__PURE__ */ jsxs("span", { className: "w-5 shrink-0 text-[13px] text-muted-foreground tabular-nums", children: [
19741
+ text(item.qty),
19742
+ "\xD7"
19743
+ ] }),
19744
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 grow truncate text-sm text-foreground", children: text(item.name) }),
19745
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold text-foreground tabular-nums", children: text(item.amount) })
19746
+ ] }, index)),
19747
+ rowsOf(node.props.adjustments).map((adjustment, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between py-0.5", children: [
19748
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: text(adjustment.label) }),
19749
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground tabular-nums", children: text(adjustment.amount) })
19750
+ ] }, `adj-${index}`)),
19751
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-baseline justify-between border-t border-border pt-1.5", children: [
19752
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: "Total" }),
19753
+ /* @__PURE__ */ jsx("span", { className: "text-[15px] font-bold text-foreground tabular-nums", children: text(node.props.total) })
19754
+ ] })
19755
+ ] }),
19756
+ Grid: ({ node, ctx }) => /* @__PURE__ */ jsx("div", { className: "grid w-full grid-cols-2 gap-2", children: ctx.render(node.props.children) }),
19757
+ EmptyState: ({ node }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5 py-4 text-center", children: [
19758
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: text(node.props.title) }),
19759
+ text(node.props.message) && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: text(node.props.message) })
19760
+ ] })
19761
+ };
19762
+ var CATALOG = genUICatalog(CIMPLIFY_GENUI_REGISTRY);
19763
+ function ChatKitProgram({ source, onAction }) {
19764
+ const parsed = useMemo(() => parse(source, CATALOG, "root"), [source]);
19765
+ if (!parsed.root) return null;
19766
+ return /* @__PURE__ */ jsx("div", { className: "w-full max-w-[290px]", children: /* @__PURE__ */ jsx(GenUIView, { root: parsed.root, components: CHAT_GENUI_COMPONENTS, onAction }) });
19767
+ }
19768
+ function ChatReplyCards({ message, currency, onAction }) {
19769
+ const cards = chatMessageCards(message);
19770
+ if (cards.length === 0) return null;
19771
+ return /* @__PURE__ */ jsx("div", { className: "scrollbar-hide flex max-w-full gap-2 overflow-x-auto pl-[30px]", children: cards.map((card, index) => /* @__PURE__ */ jsx(ReplyCardView, { card, currency, onAction }, index)) });
19772
+ }
19773
+ function ReplyCardView({
19774
+ card,
19775
+ currency,
19776
+ onAction
19777
+ }) {
19778
+ if (card.kind === "product") {
19779
+ const fire2 = onAction ? () => onAction({ action: chatAction(CHAT_ACTION_VIEW_PRODUCT, card.id), label: card.name }) : void 0;
19780
+ return /* @__PURE__ */ jsxs(
19781
+ "button",
19782
+ {
19783
+ type: "button",
19784
+ onClick: fire2,
19785
+ disabled: !fire2,
19786
+ className: cn(
19787
+ "flex w-[230px] shrink-0 items-center gap-3 rounded-2xl border border-border bg-background p-2.5 text-left",
19788
+ fire2 && "active:opacity-80"
19789
+ ),
19790
+ children: [
19791
+ card.image_url && /* @__PURE__ */ jsx("img", { src: card.image_url, alt: "", className: "size-12 shrink-0 rounded-xl object-cover" }),
19792
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
19793
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: card.name }),
19794
+ card.price !== void 0 && /* @__PURE__ */ jsx("span", { className: "text-[13px] font-semibold text-foreground tabular-nums", children: formatPrice(parsePrice(card.price), currency ?? void 0) }),
19795
+ fire2 && /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-primary", children: "View" })
19796
+ ] })
19797
+ ]
19798
+ }
19799
+ );
20034
19800
  }
20035
- function refreshCleaned() {
20036
- const next = preprocess(buf);
20037
- if (!next.startsWith(cleaned.slice(0, completedEnd))) {
20038
- completedEnd = 0;
20039
- completedStmtMap.clear();
20040
- firstId = "";
19801
+ const fire = onAction ? () => onAction({
19802
+ action: chatAction(CHAT_ACTION_VIEW_ORDER, card.order_id),
19803
+ label: card.order_number ?? card.order_id
19804
+ }) : void 0;
19805
+ return /* @__PURE__ */ jsxs(
19806
+ "button",
19807
+ {
19808
+ type: "button",
19809
+ onClick: fire,
19810
+ disabled: !fire,
19811
+ className: cn(
19812
+ "flex w-[230px] shrink-0 flex-col gap-1 rounded-2xl border border-border bg-background p-3 text-left",
19813
+ fire && "active:opacity-80"
19814
+ ),
19815
+ children: [
19816
+ /* @__PURE__ */ jsxs("span", { className: "text-sm font-semibold text-foreground", children: [
19817
+ "Order ",
19818
+ card.order_number ? `#${card.order_number}` : ""
19819
+ ] }),
19820
+ card.status && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: card.status }),
19821
+ fire && /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium text-primary", children: "View order" })
19822
+ ]
20041
19823
  }
20042
- cleaned = next;
20043
- }
20044
- function scanNewCompleted() {
20045
- let depth = 0, ternaryDepth = 0, inStr = false, esc = false;
20046
- let stmtStart = completedEnd;
20047
- for (let i = completedEnd; i < cleaned.length; i++) {
20048
- const c = cleaned[i];
20049
- if (esc) {
20050
- esc = false;
20051
- continue;
20052
- }
20053
- if (c === "\\" && inStr) {
20054
- esc = true;
20055
- continue;
20056
- }
20057
- if (inStr) {
20058
- if (c === inStr) inStr = false;
20059
- continue;
19824
+ );
19825
+ }
19826
+ var MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
19827
+ var KEEP_DISMISSED_KEY = "cimplify_keep_chat_dismissed";
19828
+ var AT_BOTTOM_SLACK_PX = 48;
19829
+ function ChatWidget({
19830
+ client: clientProp,
19831
+ businessName = "Support",
19832
+ greeting = "Hi there!",
19833
+ subtitle = "Ask us anything \u2014 we typically reply in a few seconds.",
19834
+ starters,
19835
+ placeholder = "Type a message\u2026",
19836
+ position = "bottom-right",
19837
+ defaultOpen = false,
19838
+ pollInterval,
19839
+ currency,
19840
+ onGenUIAction,
19841
+ className,
19842
+ classNames
19843
+ }) {
19844
+ const context = useOptionalCimplifyClient();
19845
+ const client = clientProp ?? context?.client;
19846
+ const [open, setOpen] = useState(defaultOpen);
19847
+ const [readAt, setReadAt] = useState(null);
19848
+ const [attachError, setAttachError] = useState(null);
19849
+ const [replyTo, setReplyTo] = useState(null);
19850
+ const {
19851
+ messages,
19852
+ isLoading,
19853
+ isSending,
19854
+ awaitingReply,
19855
+ seenAt,
19856
+ error,
19857
+ isActive,
19858
+ humanRequested,
19859
+ claimedAs,
19860
+ signOut,
19861
+ send,
19862
+ react,
19863
+ requestHuman,
19864
+ startConversation,
19865
+ loadOlder,
19866
+ mayHaveOlder,
19867
+ openIfExisting
19868
+ } = useChat({ client, pollInterval });
19869
+ const showTyping = isSending || awaitingReply;
19870
+ const attachPhoto = useCallback(
19871
+ async (file) => {
19872
+ if (!client) return;
19873
+ setAttachError(null);
19874
+ if (!file.type.startsWith("image/")) {
19875
+ setAttachError(new CimplifyError("invalid_attachment", "Only photos can be attached."));
19876
+ return;
20060
19877
  }
20061
- if (c === '"' || c === "'") {
20062
- inStr = c;
20063
- continue;
19878
+ if (file.size > MAX_ATTACHMENT_BYTES) {
19879
+ setAttachError(new CimplifyError("attachment_too_large", "Photos must be under 5MB."));
19880
+ return;
20064
19881
  }
20065
- if (c === "(" || c === "[" || c === "{") depth++;
20066
- else if (c === ")" || c === "]" || c === "}") depth = Math.max(0, depth - 1);
20067
- else if (c === "?" && depth === 0) ternaryDepth++;
20068
- else if (c === ":" && depth === 0 && ternaryDepth > 0) ternaryDepth--;
20069
- else if (c === "\n" && depth <= 0 && ternaryDepth <= 0) {
20070
- let peek = i + 1;
20071
- while (peek < cleaned.length && (cleaned[peek] === " " || cleaned[peek] === " " || cleaned[peek] === "\r" || cleaned[peek] === "\n"))
20072
- peek++;
20073
- if (peek < cleaned.length && (cleaned[peek] === "?" || cleaned[peek] === ":" && ternaryDepth > 0)) {
20074
- continue;
20075
- }
20076
- const t = cleaned.slice(stmtStart, i).trim();
20077
- if (t) addStmt(t);
20078
- stmtStart = i + 1;
20079
- completedEnd = i + 1;
19882
+ const uploaded = await client.uploads.upload(file);
19883
+ if (!uploaded.ok) {
19884
+ setAttachError(uploaded.error);
19885
+ return;
20080
19886
  }
19887
+ const attachment = {
19888
+ upload_id: uploaded.value.id,
19889
+ url: uploaded.value.url,
19890
+ mime_type: uploaded.value.content_type,
19891
+ filename: uploaded.value.filename,
19892
+ size: uploaded.value.size_bytes
19893
+ };
19894
+ await send("", { attachments: [attachment] });
19895
+ },
19896
+ [client, send]
19897
+ );
19898
+ const handleSend = useCallback(
19899
+ (content) => {
19900
+ setAttachError(null);
19901
+ const target = replyTo;
19902
+ setReplyTo(null);
19903
+ return send(content, target ? { replyTo: target } : void 0);
19904
+ },
19905
+ [send, replyTo]
19906
+ );
19907
+ const unread = open ? 0 : messages.filter(
19908
+ (m) => m.sender_type !== "customer" && (!readAt || m.created_at > readAt)
19909
+ ).length;
19910
+ useState(() => {
19911
+ if (defaultOpen) void openIfExisting();
19912
+ });
19913
+ const handleOpen = useCallback(() => {
19914
+ setOpen(true);
19915
+ void openIfExisting();
19916
+ }, [openIfExisting]);
19917
+ const handleClose = useCallback(() => {
19918
+ const newest = messages[messages.length - 1];
19919
+ setReadAt(newest?.created_at ?? null);
19920
+ setOpen(false);
19921
+ }, [messages]);
19922
+ const isLeft = position === "bottom-left";
19923
+ return /* @__PURE__ */ jsxs(
19924
+ "div",
19925
+ {
19926
+ className: cn(
19927
+ "fixed bottom-6 z-[9999]",
19928
+ isLeft ? "left-6" : "right-6",
19929
+ className,
19930
+ classNames?.root
19931
+ ),
19932
+ children: [
19933
+ !open && /* @__PURE__ */ jsx(
19934
+ Bubble,
19935
+ {
19936
+ unread,
19937
+ onClick: handleOpen,
19938
+ className: classNames?.bubble
19939
+ }
19940
+ ),
19941
+ open && /* @__PURE__ */ jsx(
19942
+ Panel,
19943
+ {
19944
+ client,
19945
+ businessName,
19946
+ greeting,
19947
+ subtitle,
19948
+ starters,
19949
+ placeholder,
19950
+ messages,
19951
+ isLoading,
19952
+ isSending,
19953
+ isTyping: showTyping,
19954
+ seenAt,
19955
+ readAt,
19956
+ error: error ?? attachError,
19957
+ isActive,
19958
+ isLeft,
19959
+ humanRequested,
19960
+ mayHaveOlder,
19961
+ replyTo,
19962
+ claimedAs,
19963
+ onSignOut: () => void signOut(),
19964
+ onClose: handleClose,
19965
+ onSend: handleSend,
19966
+ onReact: react,
19967
+ onReply: setReplyTo,
19968
+ onCancelReply: () => setReplyTo(null),
19969
+ onRequestHuman: () => void requestHuman(),
19970
+ onLoadOlder: () => void loadOlder(),
19971
+ onAttach: attachPhoto,
19972
+ onStartConversation: startConversation,
19973
+ currency,
19974
+ onGenUIAction,
19975
+ classNames
19976
+ }
19977
+ )
19978
+ ]
20081
19979
  }
20082
- return stmtStart;
20083
- }
20084
- function currentResult() {
20085
- refreshCleaned();
20086
- const pendingStart = scanNewCompleted();
20087
- const pendingText = cleaned.slice(pendingStart).trim();
20088
- if (!pendingText) {
20089
- if (completedStmtMap.size === 0) return emptyResult();
20090
- return buildResult(
20091
- completedStmtMap,
20092
- [...completedStmtMap.values()],
20093
- firstId,
20094
- false,
20095
- completedStmtMap.size,
20096
- cat,
20097
- rootName
20098
- );
20099
- }
20100
- const { text: closed, wasIncomplete } = autoClose(pendingText);
20101
- const stmts = split(tokenize(closed));
20102
- if (!stmts.length) {
20103
- if (completedStmtMap.size === 0) return emptyResult(wasIncomplete);
20104
- return buildResult(
20105
- completedStmtMap,
20106
- [...completedStmtMap.values()],
20107
- firstId,
20108
- wasIncomplete,
20109
- completedStmtMap.size,
20110
- cat,
20111
- rootName
20112
- );
19980
+ );
19981
+ }
19982
+ function Bubble({
19983
+ unread,
19984
+ onClick,
19985
+ className
19986
+ }) {
19987
+ return /* @__PURE__ */ jsxs(
19988
+ "button",
19989
+ {
19990
+ type: "button",
19991
+ onClick,
19992
+ "aria-label": "Open chat",
19993
+ className: cn(
19994
+ "relative flex h-14 w-14 items-center justify-center rounded-full",
19995
+ "bg-foreground text-background shadow-lg",
19996
+ "transition-transform duration-300 ease-out",
19997
+ "hover:scale-[1.08] active:scale-95",
19998
+ className
19999
+ ),
20000
+ children: [
20001
+ /* @__PURE__ */ jsx(ChatIcon, { className: "h-6 w-6" }),
20002
+ unread > 0 && /* @__PURE__ */ jsx(
20003
+ "span",
20004
+ {
20005
+ className: cn(
20006
+ "absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center",
20007
+ "rounded-full bg-destructive text-[10px] font-bold text-white",
20008
+ "ring-2 ring-background"
20009
+ ),
20010
+ children: unread > 9 ? "9+" : unread
20011
+ }
20012
+ )
20013
+ ]
20113
20014
  }
20114
- const allStmtMap = new Map(completedStmtMap);
20115
- for (const s of stmts) {
20116
- const expr = parseExpression(s.tokens);
20117
- const stmt = classifyStatement(s, expr);
20118
- allStmtMap.set(s.id, stmt);
20015
+ );
20016
+ }
20017
+ function Panel({
20018
+ client,
20019
+ businessName,
20020
+ greeting,
20021
+ subtitle,
20022
+ starters,
20023
+ placeholder,
20024
+ messages,
20025
+ isLoading,
20026
+ isSending,
20027
+ isTyping,
20028
+ seenAt,
20029
+ readAt,
20030
+ error,
20031
+ isActive,
20032
+ isLeft,
20033
+ humanRequested,
20034
+ mayHaveOlder,
20035
+ replyTo,
20036
+ claimedAs,
20037
+ onSignOut,
20038
+ onClose,
20039
+ onSend,
20040
+ onReact,
20041
+ onReply,
20042
+ onCancelReply,
20043
+ onRequestHuman,
20044
+ onLoadOlder,
20045
+ onAttach,
20046
+ onStartConversation,
20047
+ currency,
20048
+ onGenUIAction,
20049
+ classNames
20050
+ }) {
20051
+ return /* @__PURE__ */ jsxs(
20052
+ "div",
20053
+ {
20054
+ className: cn(
20055
+ "flex flex-col overflow-hidden rounded-2xl bg-background shadow-2xl",
20056
+ "w-[400px] max-w-[calc(100vw-3rem)]",
20057
+ "h-[min(600px,calc(100vh-6rem))]",
20058
+ "animate-in fade-in slide-in-from-bottom-4 duration-300",
20059
+ isLeft ? "origin-bottom-left" : "origin-bottom-right",
20060
+ classNames?.panel
20061
+ ),
20062
+ children: [
20063
+ /* @__PURE__ */ jsx(
20064
+ Header,
20065
+ {
20066
+ businessName,
20067
+ claimedAs,
20068
+ onSignOut,
20069
+ onClose,
20070
+ className: classNames?.header
20071
+ }
20072
+ ),
20073
+ !isActive && !isLoading ? /* @__PURE__ */ jsx(
20074
+ Welcome,
20075
+ {
20076
+ greeting,
20077
+ subtitle,
20078
+ starters,
20079
+ onStarter: onStartConversation,
20080
+ className: classNames?.welcome
20081
+ }
20082
+ ) : /* @__PURE__ */ jsx(
20083
+ MessageList,
20084
+ {
20085
+ messages,
20086
+ businessName,
20087
+ isLoading,
20088
+ isSending: isTyping,
20089
+ seenAt,
20090
+ readAt,
20091
+ humanRequested,
20092
+ mayHaveOlder,
20093
+ onReact,
20094
+ onReply,
20095
+ onRequestHuman,
20096
+ onLoadOlder,
20097
+ currency,
20098
+ onGenUIAction,
20099
+ className: classNames?.messages
20100
+ }
20101
+ ),
20102
+ isActive && !claimedAs && client && /* @__PURE__ */ jsx(KeepConversation, { client }),
20103
+ humanRequested && isActive && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t border-border bg-muted/50 px-4 py-2 text-[11px] text-muted-foreground", children: [
20104
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 flex-shrink-0 animate-pulse rounded-full bg-amber-500" }),
20105
+ "Waiting for ",
20106
+ businessName,
20107
+ " \u2014 usually replies in minutes"
20108
+ ] }),
20109
+ error != null && /* @__PURE__ */ jsx("div", { className: "border-t border-destructive/20 bg-destructive/10 px-4 py-2 text-xs text-destructive", children: error.userMessage || "Something went wrong. Please try again." }),
20110
+ replyTo && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 border-t border-border bg-muted/50 px-4 py-2", children: [
20111
+ /* @__PURE__ */ jsx("span", { className: "w-[3px] shrink-0 self-stretch rounded-full bg-foreground/40" }),
20112
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
20113
+ /* @__PURE__ */ jsxs("div", { className: "text-[11px] font-semibold text-foreground", children: [
20114
+ "Replying to ",
20115
+ replyTo.sender_type === "customer" ? "yourself" : businessName
20116
+ ] }),
20117
+ /* @__PURE__ */ jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: proseOf(replyTo.content) || "Photo" })
20118
+ ] }),
20119
+ /* @__PURE__ */ jsx(
20120
+ "button",
20121
+ {
20122
+ type: "button",
20123
+ "aria-label": "Cancel reply",
20124
+ onClick: onCancelReply,
20125
+ className: "flex-shrink-0 p-0.5 text-muted-foreground hover:text-foreground",
20126
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3.5 w-3.5" })
20127
+ }
20128
+ )
20129
+ ] }),
20130
+ /* @__PURE__ */ jsx(
20131
+ ChatInput,
20132
+ {
20133
+ placeholder,
20134
+ isSending,
20135
+ onSend: isActive ? onSend : async (text2) => {
20136
+ await onStartConversation(text2);
20137
+ return true;
20138
+ },
20139
+ onAttach,
20140
+ className: classNames?.input
20141
+ }
20142
+ ),
20143
+ /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-border bg-background px-4 py-1.5 text-center text-[10px] text-muted-foreground", children: [
20144
+ "Powered by",
20145
+ " ",
20146
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Cimplify" })
20147
+ ] })
20148
+ ]
20119
20149
  }
20120
- const allTypedStmts = [...allStmtMap.values()];
20121
- const fid = firstId || stmts[0].id;
20122
- return buildResult(
20123
- allStmtMap,
20124
- allTypedStmts,
20125
- fid,
20126
- wasIncomplete,
20127
- allStmtMap.size,
20128
- cat,
20129
- rootName
20130
- );
20131
- }
20132
- function reset() {
20133
- buf = "";
20134
- cleaned = "";
20135
- completedEnd = 0;
20136
- completedStmtMap.clear();
20137
- firstId = "";
20138
- }
20139
- return {
20140
- push(chunk) {
20141
- buf += chunk;
20142
- return currentResult();
20143
- },
20144
- set(fullText) {
20145
- if (fullText.length < buf.length || !fullText.startsWith(buf)) {
20146
- reset();
20147
- }
20148
- const delta = fullText.slice(buf.length);
20149
- if (delta) buf += delta;
20150
- return currentResult();
20151
- },
20152
- getResult: currentResult
20153
- };
20150
+ );
20154
20151
  }
20155
- function useGenUIProgram(text, catalog) {
20156
- const parserRef = useRef(
20157
- null
20152
+ function Header({
20153
+ businessName,
20154
+ claimedAs,
20155
+ onSignOut,
20156
+ onClose,
20157
+ className
20158
+ }) {
20159
+ return /* @__PURE__ */ jsxs(
20160
+ "div",
20161
+ {
20162
+ className: cn(
20163
+ "flex flex-shrink-0 items-center gap-3 bg-foreground px-5 py-4 text-background",
20164
+ className
20165
+ ),
20166
+ children: [
20167
+ /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-background/15", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-5 w-5" }) }),
20168
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
20169
+ /* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
20170
+ claimedAs ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-background/60", children: [
20171
+ /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
20172
+ "Chatting as ",
20173
+ claimedAs
20174
+ ] }),
20175
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
20176
+ /* @__PURE__ */ jsx(
20177
+ "button",
20178
+ {
20179
+ type: "button",
20180
+ onClick: onSignOut,
20181
+ className: "flex-shrink-0 underline underline-offset-2 hover:text-background",
20182
+ children: "Not you?"
20183
+ }
20184
+ )
20185
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-background/60", children: [
20186
+ /* @__PURE__ */ jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
20187
+ "Online"
20188
+ ] })
20189
+ ] }),
20190
+ /* @__PURE__ */ jsx(
20191
+ "button",
20192
+ {
20193
+ type: "button",
20194
+ onClick: onClose,
20195
+ "aria-label": "Close chat",
20196
+ className: cn(
20197
+ "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg",
20198
+ "bg-background/10 text-background/70 transition-colors hover:bg-background/20 hover:text-background"
20199
+ ),
20200
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-[18px] w-[18px]" })
20201
+ }
20202
+ )
20203
+ ]
20204
+ }
20158
20205
  );
20159
- if (!parserRef.current || parserRef.current.catalog !== catalog) {
20160
- parserRef.current = { catalog, parser: createStreamParser(catalog, "root") };
20161
- }
20162
- const lastGoodRef = useRef(null);
20163
- return useMemo(() => {
20164
- const result = parserRef.current.parser.set(text);
20165
- if (result.root) {
20166
- lastGoodRef.current = result.root;
20206
+ }
20207
+ function Welcome({
20208
+ greeting,
20209
+ subtitle,
20210
+ starters,
20211
+ onStarter,
20212
+ className
20213
+ }) {
20214
+ const defaultStarters = [
20215
+ { icon: "\u{1F50D}", text: "Help me find a product" },
20216
+ { icon: "\u{1F6D2}", text: "Check my cart" },
20217
+ { icon: "\u{1F4E6}", text: "Track my order" }
20218
+ ];
20219
+ const items = starters ?? defaultStarters;
20220
+ return /* @__PURE__ */ jsxs(
20221
+ "div",
20222
+ {
20223
+ className: cn(
20224
+ "flex flex-1 flex-col items-center justify-center overflow-y-auto bg-muted/50 px-8 py-10 text-center",
20225
+ className
20226
+ ),
20227
+ children: [
20228
+ /* @__PURE__ */ jsx("div", { className: "mb-5 flex h-16 w-16 items-center justify-center rounded-2xl bg-muted", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-7 w-7 text-muted-foreground" }) }),
20229
+ /* @__PURE__ */ jsx("h3", { className: "mb-2 text-xl font-bold text-foreground", children: greeting }),
20230
+ /* @__PURE__ */ jsx("p", { className: "mb-6 max-w-[280px] text-sm leading-relaxed text-muted-foreground", children: subtitle }),
20231
+ /* @__PURE__ */ jsx("div", { className: "flex w-full max-w-[300px] flex-col gap-2", children: items.map((s) => /* @__PURE__ */ jsxs(
20232
+ "button",
20233
+ {
20234
+ type: "button",
20235
+ onClick: () => onStarter(s.text),
20236
+ className: cn(
20237
+ "flex items-center gap-3 rounded-xl border border-border bg-background px-4 py-3",
20238
+ "text-left text-[13px] font-medium text-foreground",
20239
+ "transition-all hover:-translate-y-px hover:border-foreground hover:shadow-sm"
20240
+ ),
20241
+ children: [
20242
+ s.icon && /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-muted text-[15px]", children: s.icon }),
20243
+ /* @__PURE__ */ jsx("span", { children: s.text })
20244
+ ]
20245
+ },
20246
+ s.text
20247
+ )) })
20248
+ ]
20167
20249
  }
20168
- return { result, root: result.root ?? lastGoodRef.current };
20169
- }, [text, catalog]);
20250
+ );
20170
20251
  }
20171
- var noop = () => {
20172
- };
20173
- function childKey(value, index) {
20174
- if (isElementNode(value) && value.statementId) return `s:${value.statementId}`;
20175
- return `i:${index}`;
20252
+ var QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F"];
20253
+ function LetterAvatar({ name }) {
20254
+ return /* @__PURE__ */ jsx("span", { className: "flex size-6 items-center justify-center rounded-full bg-foreground text-[10px] font-bold text-background", children: name.charAt(0).toUpperCase() });
20176
20255
  }
20177
- var RenderDepthContext = createContext(0);
20178
- var MAX_DEPTH = 64;
20179
- var GenUINode = memo(function GenUINode2({
20180
- node,
20181
- components,
20182
- onAction,
20183
- isStreaming,
20184
- onTelemetry
20256
+ function MessageList({
20257
+ messages,
20258
+ businessName,
20259
+ isLoading,
20260
+ isSending,
20261
+ seenAt,
20262
+ readAt,
20263
+ humanRequested,
20264
+ mayHaveOlder,
20265
+ onReact,
20266
+ onReply,
20267
+ onRequestHuman,
20268
+ onLoadOlder,
20269
+ currency,
20270
+ onGenUIAction,
20271
+ className
20185
20272
  }) {
20186
- const depth = useContext(RenderDepthContext);
20187
- if (depth > MAX_DEPTH) {
20188
- onTelemetry?.({ kind: "dropped-node", typeName: node.typeName });
20189
- return null;
20273
+ const scrolledUpRef = useRef(false);
20274
+ const timeline = chatTimeline(messages, { readAt, rendersPrograms: true });
20275
+ const { bubbles, reactionsByTarget, firstUnreadId, unreadOnOpen, firstAgentMessageId } = timeline;
20276
+ const lastBubble = bubbles[bubbles.length - 1];
20277
+ const offerHuman = !humanRequested && lastBubble?.sender_type === "bot" && !isSending;
20278
+ const handleScroll = (event) => {
20279
+ const node = event.currentTarget;
20280
+ scrolledUpRef.current = node.scrollHeight - node.scrollTop - node.clientHeight >= AT_BOTTOM_SLACK_PX;
20281
+ };
20282
+ return /* @__PURE__ */ jsxs(
20283
+ "div",
20284
+ {
20285
+ onScroll: handleScroll,
20286
+ style: { background: "var(--color-chat-canvas, var(--color-muted, #f3f5f3))" },
20287
+ className: cn("flex flex-1 flex-col gap-0.5 overflow-y-auto px-3.5 py-3.5", className),
20288
+ children: [
20289
+ isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
20290
+ mayHaveOlder && /* @__PURE__ */ jsx("div", { className: "flex justify-center pb-1", children: /* @__PURE__ */ jsx(
20291
+ "button",
20292
+ {
20293
+ type: "button",
20294
+ onClick: onLoadOlder,
20295
+ className: "rounded-full border border-foreground/[0.06] bg-background px-3.5 py-[7px] text-xs font-semibold text-muted-foreground shadow-[0_1px_2px_rgba(15,23,42,0.04)]",
20296
+ children: "Show earlier messages"
20297
+ }
20298
+ ) }),
20299
+ bubbles.map((msg, i) => {
20300
+ const prev = bubbles[i - 1];
20301
+ const newDay = !prev || !sameDay(prev.created_at, msg.created_at);
20302
+ const group = groupPosition(timeline, i);
20303
+ const startsRun = group === "single" || group === "first";
20304
+ const delivery = deliveryState(msg, timeline, seenAt, isOptimistic);
20305
+ return /* @__PURE__ */ jsxs(
20306
+ "div",
20307
+ {
20308
+ className: cn("flex flex-col gap-1.5", startsRun && i > 0 && "mt-2.5"),
20309
+ children: [
20310
+ newDay && /* @__PURE__ */ jsx(Seam, { variant: "chip", label: dayLabel(msg.created_at) }),
20311
+ msg.id === firstUnreadId && /* @__PURE__ */ jsx(
20312
+ Seam,
20313
+ {
20314
+ variant: "unread",
20315
+ label: `${unreadOnOpen} new message${unreadOnOpen === 1 ? "" : "s"}`
20316
+ }
20317
+ ),
20318
+ msg.id === firstAgentMessageId && /* @__PURE__ */ jsx(Seam, { label: "A person joined" }),
20319
+ msg.sender_type === "system" ? /* @__PURE__ */ jsx(Seam, { label: "Passing you to the team" }) : /* @__PURE__ */ jsxs("div", { className: "group relative", children: [
20320
+ msg.sender_type !== "customer" && !isOptimistic(msg) && /* @__PURE__ */ jsx(HoverRail, { message: msg, onReact, onReply }),
20321
+ /* @__PURE__ */ jsx(
20322
+ MessageBubble,
20323
+ {
20324
+ message: msg,
20325
+ reactions: reactionsByTarget.get(msg.id),
20326
+ businessName,
20327
+ group,
20328
+ delivery,
20329
+ onReply,
20330
+ avatar: /* @__PURE__ */ jsx(LetterAvatar, { name: businessName }),
20331
+ renderProgram: (source) => /* @__PURE__ */ jsx(ChatKitProgram, { source, onAction: onGenUIAction }),
20332
+ renderCards: (target) => /* @__PURE__ */ jsx(ChatReplyCards, { message: target, currency, onAction: onGenUIAction }),
20333
+ onQuoteMiss: async () => {
20334
+ onLoadOlder();
20335
+ return true;
20336
+ }
20337
+ }
20338
+ )
20339
+ ] })
20340
+ ]
20341
+ },
20342
+ msg.id
20343
+ );
20344
+ }),
20345
+ isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
20346
+ offerHuman && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
20347
+ "button",
20348
+ {
20349
+ type: "button",
20350
+ onClick: onRequestHuman,
20351
+ className: "rounded-full border border-border bg-background px-3 py-1.5 text-xs font-semibold text-foreground hover:border-foreground",
20352
+ children: "Talk to a person"
20353
+ }
20354
+ ) }),
20355
+ /* @__PURE__ */ jsx(
20356
+ "div",
20357
+ {
20358
+ ref: (node) => {
20359
+ if (node && !scrolledUpRef.current) node.scrollIntoView({ behavior: "smooth" });
20360
+ }
20361
+ },
20362
+ `${messages.length}:${isSending}`
20363
+ )
20364
+ ]
20365
+ }
20366
+ );
20367
+ }
20368
+ function HoverRail({
20369
+ message,
20370
+ onReact,
20371
+ onReply
20372
+ }) {
20373
+ return /* @__PURE__ */ jsxs("div", { className: "absolute -top-3 right-0 z-10 hidden gap-0.5 rounded-full border border-border bg-background px-1 py-0.5 shadow-sm group-hover:flex", children: [
20374
+ QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsx(
20375
+ "button",
20376
+ {
20377
+ type: "button",
20378
+ "aria-label": `React ${emoji}`,
20379
+ onClick: () => void onReact(message, emoji),
20380
+ className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
20381
+ children: emoji
20382
+ },
20383
+ emoji
20384
+ )),
20385
+ /* @__PURE__ */ jsx(
20386
+ "button",
20387
+ {
20388
+ type: "button",
20389
+ "aria-label": "Reply",
20390
+ onClick: () => onReply(message),
20391
+ className: "flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground",
20392
+ children: /* @__PURE__ */ jsx(ReplyIcon, { className: "h-3.5 w-3.5" })
20393
+ }
20394
+ )
20395
+ ] });
20396
+ }
20397
+ function readKeepDismissed() {
20398
+ try {
20399
+ return typeof window !== "undefined" && window.localStorage.getItem(KEEP_DISMISSED_KEY) === "1";
20400
+ } catch {
20401
+ return false;
20190
20402
  }
20191
- const renderer = components[node.typeName];
20192
- if (!renderer) {
20193
- onTelemetry?.({ kind: "unknown-component", typeName: node.typeName });
20194
- return null;
20403
+ }
20404
+ function writeKeepDismissed() {
20405
+ try {
20406
+ window.localStorage.setItem(KEEP_DISMISSED_KEY, "1");
20407
+ } catch {
20195
20408
  }
20196
- const render = (value) => {
20197
- if (value == null) return null;
20198
- if (isElementNode(value)) {
20199
- return /* @__PURE__ */ jsx(RenderDepthContext.Provider, { value: depth + 1, children: /* @__PURE__ */ jsx(
20200
- GenUINode2,
20201
- {
20202
- node: value,
20203
- components,
20204
- onAction,
20205
- isStreaming,
20206
- onTelemetry
20207
- }
20208
- ) });
20409
+ }
20410
+ function KeepConversation({ client }) {
20411
+ const [step, setStep] = useState("row");
20412
+ const [dismissed, setDismissed] = useState(readKeepDismissed);
20413
+ const [contact, setContact] = useState("");
20414
+ const [code, setCode] = useState("");
20415
+ const [busy, setBusy] = useState(false);
20416
+ const [verifyError, setVerifyError] = useState(null);
20417
+ if (dismissed) return null;
20418
+ const contactType = contact.includes("@") ? "email" : "phone";
20419
+ const requestCode = async () => {
20420
+ const trimmed = contact.trim();
20421
+ if (!trimmed || busy) return;
20422
+ setBusy(true);
20423
+ setVerifyError(null);
20424
+ const result = await client.link.requestOtp({ contact: trimmed, contact_type: contactType });
20425
+ setBusy(false);
20426
+ if (result.ok) {
20427
+ setCode("");
20428
+ setStep("code");
20429
+ } else {
20430
+ setVerifyError(result.error.userMessage || "Couldn't send a code \u2014 try again.");
20209
20431
  }
20210
- if (Array.isArray(value)) {
20211
- return value.map((item, index) => /* @__PURE__ */ jsx(React14.Fragment, { children: render(item) }, childKey(item, index)));
20432
+ };
20433
+ const verify = async (value) => {
20434
+ if (value.length !== 6 || busy) return;
20435
+ setBusy(true);
20436
+ setVerifyError(null);
20437
+ const result = await client.link.verifyOtp({
20438
+ contact: contact.trim(),
20439
+ contact_type: contactType,
20440
+ otp_code: value
20441
+ });
20442
+ setBusy(false);
20443
+ if (!result.ok) {
20444
+ setCode("");
20445
+ setVerifyError(result.error.userMessage || "Invalid or expired code.");
20212
20446
  }
20213
- if (typeof value === "object" && isRuntimeExpr(value)) return null;
20214
- if (typeof value === "string" || typeof value === "number") return String(value);
20215
- return null;
20216
20447
  };
20217
- return renderer({ node, ctx: { render, onAction, isStreaming } });
20218
- });
20219
- function GenUIView({
20220
- root,
20221
- components,
20222
- onAction = noop,
20223
- isStreaming = false,
20224
- onTelemetry,
20225
- placeholder = null
20226
- }) {
20227
- if (!root) return placeholder;
20228
- return /* @__PURE__ */ jsx(
20229
- GenUINode,
20230
- {
20231
- node: root,
20232
- components,
20233
- onAction,
20234
- isStreaming,
20235
- onTelemetry
20236
- }
20237
- );
20448
+ return /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-border bg-muted/50 px-4 py-2.5", children: [
20449
+ step === "row" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
20450
+ /* @__PURE__ */ jsxs(
20451
+ "button",
20452
+ {
20453
+ type: "button",
20454
+ onClick: () => setStep("contact"),
20455
+ className: "flex min-w-0 flex-1 flex-col text-left",
20456
+ children: [
20457
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-foreground", children: "Keep this conversation" }),
20458
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: "Verify your number and it follows you \u2014 any device" })
20459
+ ]
20460
+ }
20461
+ ),
20462
+ /* @__PURE__ */ jsx(
20463
+ "button",
20464
+ {
20465
+ type: "button",
20466
+ "aria-label": "Dismiss",
20467
+ onClick: () => {
20468
+ writeKeepDismissed();
20469
+ setDismissed(true);
20470
+ },
20471
+ className: "flex-shrink-0 p-1 text-muted-foreground hover:text-foreground",
20472
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3.5 w-3.5" })
20473
+ }
20474
+ )
20475
+ ] }),
20476
+ step === "contact" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
20477
+ /* @__PURE__ */ jsx(
20478
+ "input",
20479
+ {
20480
+ type: "text",
20481
+ inputMode: "email",
20482
+ value: contact,
20483
+ onChange: (e) => setContact(e.target.value),
20484
+ onKeyDown: (e) => e.key === "Enter" && void requestCode(),
20485
+ placeholder: "Phone or email",
20486
+ autoFocus: true,
20487
+ className: "h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-3 text-xs text-foreground outline-none placeholder:text-muted-foreground focus:border-foreground"
20488
+ }
20489
+ ),
20490
+ /* @__PURE__ */ jsx(
20491
+ "button",
20492
+ {
20493
+ type: "button",
20494
+ onClick: () => void requestCode(),
20495
+ disabled: !contact.trim() || busy,
20496
+ className: "h-9 flex-shrink-0 rounded-lg bg-foreground px-3 text-xs font-semibold text-background disabled:opacity-50",
20497
+ children: busy ? "Sending\u2026" : "Send code"
20498
+ }
20499
+ )
20500
+ ] }),
20501
+ step === "code" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
20502
+ /* @__PURE__ */ jsx(
20503
+ "input",
20504
+ {
20505
+ type: "text",
20506
+ inputMode: "numeric",
20507
+ autoComplete: "one-time-code",
20508
+ maxLength: 6,
20509
+ value: code,
20510
+ onChange: (e) => {
20511
+ const digits = e.target.value.replace(/\D/g, "").slice(0, 6);
20512
+ setCode(digits);
20513
+ if (digits.length === 6) void verify(digits);
20514
+ },
20515
+ placeholder: "6-digit code",
20516
+ autoFocus: true,
20517
+ className: "h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-3 text-xs tracking-[0.2em] text-foreground outline-none placeholder:tracking-normal placeholder:text-muted-foreground focus:border-foreground"
20518
+ }
20519
+ ),
20520
+ /* @__PURE__ */ jsx(
20521
+ "button",
20522
+ {
20523
+ type: "button",
20524
+ onClick: () => void requestCode(),
20525
+ disabled: busy,
20526
+ className: "h-9 flex-shrink-0 rounded-lg px-2 text-[11px] font-medium text-muted-foreground hover:text-foreground disabled:opacity-50",
20527
+ children: "Resend"
20528
+ }
20529
+ )
20530
+ ] }),
20531
+ verifyError && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-[11px] text-destructive", children: verifyError })
20532
+ ] });
20238
20533
  }
20239
-
20240
- // src/genui/registry.ts
20241
- var CIMPLIFY_GENUI_REGISTRY = {
20242
- version: "3",
20243
- components: [
20244
- {
20245
- name: "Stack",
20246
- description: 'Layout container. direction: "column" (default) or "row"; gap in points (default 12)',
20247
- params: [{ name: "children", required: true }, { name: "direction" }, { name: "gap" }]
20248
- },
20249
- {
20250
- name: "Card",
20251
- description: "Elevated surface grouping related content, with an optional title",
20252
- params: [{ name: "children", required: true }, { name: "title" }]
20253
- },
20254
- {
20255
- name: "TextContent",
20256
- description: 'Text block. size: "display", "heading", "title", "body" (default), "label", "caption"',
20257
- params: [{ name: "text", required: true }, { name: "size" }]
20258
- },
20259
- {
20260
- name: "Stat",
20261
- description: "Compact stat: small label over a bold value, optional sub line and tone",
20262
- params: [
20263
- { name: "label", required: true },
20264
- { name: "value", required: true },
20265
- { name: "sub" },
20266
- { name: "tone" }
20267
- ]
20268
- },
20269
- {
20270
- name: "Metric",
20271
- description: "KPI card with label, value, optional percent change and SF Symbol icon",
20272
- params: [
20273
- { name: "label", required: true },
20274
- { name: "value", required: true },
20275
- { name: "change" },
20276
- { name: "icon" }
20277
- ]
20278
- },
20279
- {
20280
- name: "KeyValue",
20281
- description: "Label/value row; set mono true for numeric values",
20282
- params: [
20283
- { name: "label", required: true },
20284
- { name: "value", required: true },
20285
- { name: "mono" }
20286
- ]
20287
- },
20288
- {
20289
- name: "Badge",
20290
- description: "Small status chip. tone: neutral, positive, info, indigo, violet, warning, danger",
20291
- params: [{ name: "label", required: true }, { name: "tone" }]
20292
- },
20293
- {
20294
- name: "Callout",
20295
- description: "Inline alert with title, optional message. tone: info (default), success, warning, danger, accent, neutral",
20296
- params: [{ name: "title", required: true }, { name: "message" }, { name: "tone" }]
20297
- },
20298
- {
20299
- name: "Button",
20300
- description: 'Action button. action: "action:name" string or Action([...]) steps. variant: primary, cta, secondary (default), ghost, destructive',
20301
- params: [
20302
- { name: "label", required: true },
20303
- { name: "action", required: true },
20304
- { name: "variant" }
20305
- ]
20306
- },
20307
- {
20308
- name: "Buttons",
20309
- description: 'Row (default) or column of Buttons; direction: "row" or "column"',
20310
- params: [{ name: "children", required: true }, { name: "direction" }]
20311
- },
20312
- {
20313
- name: "Divider",
20314
- description: "Hairline separator",
20315
- params: []
20316
- },
20317
- {
20318
- name: "Table",
20319
- description: "Data table: headers is an array of strings, rows an array of cell arrays",
20320
- params: [
20321
- { name: "headers", required: true },
20322
- { name: "rows", required: true }
20323
- ]
20324
- },
20325
- {
20326
- name: "BarChart",
20327
- description: "Bar chart: labels (x axis strings) and values (numbers), optional title",
20328
- params: [
20329
- { name: "labels", required: true },
20330
- { name: "values", required: true },
20331
- { name: "title" }
20332
- ]
20333
- },
20334
- {
20335
- name: "LineChart",
20336
- description: "Line chart with area fill: labels and values, optional title",
20337
- params: [
20338
- { name: "labels", required: true },
20339
- { name: "values", required: true },
20340
- { name: "title" }
20341
- ]
20342
- },
20343
- {
20344
- name: "Money",
20345
- description: "Formatted money amount, never abbreviated. currency: ISO code (default USD)",
20346
- params: [{ name: "amount", required: true }, { name: "currency" }]
20534
+ function ChatInput({
20535
+ placeholder,
20536
+ isSending,
20537
+ onSend,
20538
+ onAttach,
20539
+ className
20540
+ }) {
20541
+ const [value, setValue] = useState("");
20542
+ const [uploading, setUploading] = useState(false);
20543
+ const textareaRef = useRef(null);
20544
+ const fileRef = useRef(null);
20545
+ const handleAttach = useCallback(
20546
+ async (file) => {
20547
+ setUploading(true);
20548
+ try {
20549
+ await onAttach(file);
20550
+ } finally {
20551
+ setUploading(false);
20552
+ }
20347
20553
  },
20348
- {
20349
- name: "Image",
20350
- description: "Remote image by URL with optional caption",
20351
- params: [{ name: "url", required: true }, { name: "caption" }]
20554
+ [onAttach]
20555
+ );
20556
+ const handleSend = useCallback(async () => {
20557
+ const trimmed = value.trim();
20558
+ if (!trimmed || isSending) return;
20559
+ setValue("");
20560
+ if (textareaRef.current) {
20561
+ textareaRef.current.style.height = "auto";
20562
+ }
20563
+ const sent = await onSend(trimmed);
20564
+ if (!sent) setValue((current) => current || trimmed);
20565
+ }, [value, isSending, onSend]);
20566
+ const handleKeyDown = useCallback(
20567
+ (e) => {
20568
+ if (e.key === "Enter" && !e.shiftKey) {
20569
+ e.preventDefault();
20570
+ handleSend();
20571
+ }
20352
20572
  },
20573
+ [handleSend]
20574
+ );
20575
+ const handleInput = useCallback(() => {
20576
+ const el = textareaRef.current;
20577
+ if (!el) return;
20578
+ el.style.height = "auto";
20579
+ el.style.height = `${Math.min(el.scrollHeight, 100)}px`;
20580
+ }, []);
20581
+ const hasContent = value.trim().length > 0;
20582
+ return /* @__PURE__ */ jsxs(
20583
+ "div",
20353
20584
  {
20354
- name: "ListRow",
20355
- description: "Entity row for orders/customers/products/invoices: initials avatar (or SF Symbol tile via symbol), title + subtitle, trailing value and badge with tone",
20356
- params: [
20357
- { name: "title", required: true },
20358
- { name: "subtitle" },
20359
- { name: "value" },
20360
- { name: "badge" },
20361
- { name: "tone" },
20362
- { name: "symbol" }
20585
+ className: cn(
20586
+ "flex flex-shrink-0 items-end gap-2 border-t border-border bg-background px-4 py-3",
20587
+ className
20588
+ ),
20589
+ children: [
20590
+ /* @__PURE__ */ jsx(
20591
+ "input",
20592
+ {
20593
+ ref: fileRef,
20594
+ type: "file",
20595
+ accept: "image/*",
20596
+ className: "hidden",
20597
+ "aria-hidden": "true",
20598
+ tabIndex: -1,
20599
+ onChange: (e) => {
20600
+ const file = e.target.files?.[0];
20601
+ e.target.value = "";
20602
+ if (file) void handleAttach(file);
20603
+ }
20604
+ }
20605
+ ),
20606
+ /* @__PURE__ */ jsx(
20607
+ "button",
20608
+ {
20609
+ type: "button",
20610
+ "aria-label": "Attach a photo",
20611
+ disabled: uploading,
20612
+ onClick: () => fileRef.current?.click(),
20613
+ className: cn(
20614
+ "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
20615
+ "bg-muted text-muted-foreground transition-colors hover:text-foreground",
20616
+ uploading && "animate-pulse"
20617
+ ),
20618
+ children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-[18px] w-[18px]" })
20619
+ }
20620
+ ),
20621
+ /* @__PURE__ */ jsx(
20622
+ "div",
20623
+ {
20624
+ className: cn(
20625
+ "flex flex-1 items-end rounded-xl border-[1.5px] border-transparent bg-muted px-1",
20626
+ "transition-colors focus-within:border-foreground focus-within:bg-background"
20627
+ ),
20628
+ children: /* @__PURE__ */ jsx(
20629
+ "textarea",
20630
+ {
20631
+ ref: textareaRef,
20632
+ value,
20633
+ onChange: (e) => setValue(e.target.value),
20634
+ onKeyDown: handleKeyDown,
20635
+ onInput: handleInput,
20636
+ placeholder,
20637
+ rows: 1,
20638
+ className: cn(
20639
+ "max-h-[100px] min-h-[20px] flex-1 resize-none bg-transparent px-2.5 py-2",
20640
+ "text-sm text-foreground outline-none placeholder:text-muted-foreground"
20641
+ )
20642
+ }
20643
+ )
20644
+ }
20645
+ ),
20646
+ /* @__PURE__ */ jsx(
20647
+ "button",
20648
+ {
20649
+ type: "button",
20650
+ onClick: handleSend,
20651
+ disabled: !hasContent || isSending,
20652
+ "aria-label": "Send message",
20653
+ className: cn(
20654
+ "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
20655
+ "transition-all duration-200",
20656
+ hasContent && !isSending ? "bg-foreground text-background hover:scale-105 active:scale-95" : "bg-muted text-muted-foreground"
20657
+ ),
20658
+ children: /* @__PURE__ */ jsx(SendIcon, { className: "h-[18px] w-[18px]" })
20659
+ }
20660
+ )
20363
20661
  ]
20364
- },
20662
+ }
20663
+ );
20664
+ }
20665
+ function TypingDots() {
20666
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 px-4 py-3", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
20667
+ "span",
20365
20668
  {
20366
- name: "Timeline",
20367
- description: "Event history (order journey, payment trail): events is an array of {title, detail?, time?, tone?} objects, newest first",
20368
- params: [{ name: "events", required: true }]
20669
+ className: "h-[7px] w-[7px] animate-bounce rounded-full bg-muted-foreground/40",
20670
+ style: { animationDelay: `${i * 160}ms` }
20369
20671
  },
20672
+ i
20673
+ )) });
20674
+ }
20675
+ function ChatIcon({ className }) {
20676
+ return /* @__PURE__ */ jsx(
20677
+ "svg",
20370
20678
  {
20371
- name: "Progress",
20372
- description: "Amount toward a bound (goal, stock cover, capacity): numeric value and target (default 100), optional detail caption and tone",
20373
- params: [
20374
- { name: "label", required: true },
20375
- { name: "value", required: true },
20376
- { name: "target" },
20377
- { name: "detail" },
20378
- { name: "tone" }
20379
- ]
20380
- },
20679
+ className,
20680
+ viewBox: "0 0 24 24",
20681
+ fill: "none",
20682
+ stroke: "currentColor",
20683
+ strokeWidth: 2,
20684
+ strokeLinecap: "round",
20685
+ strokeLinejoin: "round",
20686
+ children: /* @__PURE__ */ jsx("path", { d: "M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" })
20687
+ }
20688
+ );
20689
+ }
20690
+ function CloseIcon({ className }) {
20691
+ return /* @__PURE__ */ jsx(
20692
+ "svg",
20381
20693
  {
20382
- name: "DonutChart",
20383
- description: "Share of a whole (sales by channel, revenue by category): labels + values, optional title and center display string (e.g. the formatted total)",
20384
- params: [
20385
- { name: "labels", required: true },
20386
- { name: "values", required: true },
20387
- { name: "title" },
20388
- { name: "center" }
20389
- ]
20390
- },
20694
+ className,
20695
+ viewBox: "0 0 24 24",
20696
+ fill: "none",
20697
+ stroke: "currentColor",
20698
+ strokeWidth: 2,
20699
+ strokeLinecap: "round",
20700
+ strokeLinejoin: "round",
20701
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
20702
+ }
20703
+ );
20704
+ }
20705
+ function SendIcon({ className }) {
20706
+ return /* @__PURE__ */ jsxs(
20707
+ "svg",
20391
20708
  {
20392
- name: "StatusTrack",
20393
- description: "Stage tracker (order fulfilment, invoice lifecycle): steps are stage names, current is the active step name or index",
20394
- params: [
20395
- { name: "steps", required: true },
20396
- { name: "current", required: true }
20709
+ className,
20710
+ viewBox: "0 0 24 24",
20711
+ fill: "none",
20712
+ stroke: "currentColor",
20713
+ strokeWidth: 2,
20714
+ strokeLinecap: "round",
20715
+ strokeLinejoin: "round",
20716
+ children: [
20717
+ /* @__PURE__ */ jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
20718
+ /* @__PURE__ */ jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
20397
20719
  ]
20398
- },
20720
+ }
20721
+ );
20722
+ }
20723
+ function ImageIcon({ className }) {
20724
+ return /* @__PURE__ */ jsxs(
20725
+ "svg",
20399
20726
  {
20400
- name: "LineItems",
20401
- description: "Order/quote/invoice money block: items is an array of {qty?, name, amount} with display-ready amount strings, adjustments an array of {label, amount, tone?}, total the anchored display string",
20402
- params: [
20403
- { name: "items", required: true },
20404
- { name: "total", required: true },
20405
- { name: "adjustments" }
20727
+ className,
20728
+ viewBox: "0 0 24 24",
20729
+ fill: "none",
20730
+ stroke: "currentColor",
20731
+ strokeWidth: 2,
20732
+ strokeLinecap: "round",
20733
+ strokeLinejoin: "round",
20734
+ children: [
20735
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
20736
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
20737
+ /* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
20406
20738
  ]
20407
- },
20408
- {
20409
- name: "Grid",
20410
- description: "Tile layout for Stat/Metric children, columns default 2",
20411
- params: [{ name: "children", required: true }, { name: "columns" }]
20412
- },
20739
+ }
20740
+ );
20741
+ }
20742
+ function ReplyIcon({ className }) {
20743
+ return /* @__PURE__ */ jsxs(
20744
+ "svg",
20413
20745
  {
20414
- name: "Banner",
20415
- description: "Hero-stat moment on a tonal wash: big value, label, optional percent delta. tone: positive (default), warning, danger, info, neutral",
20416
- params: [
20417
- { name: "value", required: true },
20418
- { name: "label", required: true },
20419
- { name: "delta" },
20420
- { name: "tone" }
20746
+ className,
20747
+ viewBox: "0 0 24 24",
20748
+ fill: "none",
20749
+ stroke: "currentColor",
20750
+ strokeWidth: 2,
20751
+ strokeLinecap: "round",
20752
+ strokeLinejoin: "round",
20753
+ children: [
20754
+ /* @__PURE__ */ jsx("polyline", { points: "9 17 4 12 9 7" }),
20755
+ /* @__PURE__ */ jsx("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
20421
20756
  ]
20422
- },
20423
- {
20424
- name: "EmptyState",
20425
- description: "Nothing-to-show state with an SF Symbol and optional message",
20426
- params: [{ name: "title", required: true }, { name: "message" }, { name: "symbol" }]
20427
20757
  }
20428
- ]
20429
- };
20758
+ );
20759
+ }
20760
+ function useGenUIProgram(text2, catalog) {
20761
+ const parserRef = useRef(
20762
+ null
20763
+ );
20764
+ if (!parserRef.current || parserRef.current.catalog !== catalog) {
20765
+ parserRef.current = { catalog, parser: createStreamParser(catalog, "root") };
20766
+ }
20767
+ const lastGoodRef = useRef(null);
20768
+ return useMemo(() => {
20769
+ const result = parserRef.current.parser.set(text2);
20770
+ if (result.root) {
20771
+ lastGoodRef.current = result.root;
20772
+ }
20773
+ return { result, root: result.root ?? lastGoodRef.current };
20774
+ }, [text2, catalog]);
20775
+ }
20430
20776
 
20431
20777
  // src/genui/manifest.ts
20432
20778
  function genUIManifest(registry) {
@@ -20463,4 +20809,4 @@ async function genUIFingerprint(manifest) {
20463
20809
  return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 16);
20464
20810
  }
20465
20811
 
20466
- export { AccommodationCard, AccountContent, AccountDashboardPage, AccountHero, AccountIdentityStrip, AccountLineItem, AccountLineItems, AccountNav, AccountOrderDetailPage, AccountOrderView, AccountOrdersPage, AccountShell, AccountSidebar, AccountSignedOutPrompt, AccountSubscriptionDetailPage, AccountSubscriptionsPage, Ad, AdProvider, AddOnSelector, AddressAutocomplete, AddressElement, AuthElement, AvailabilityBadge, BackLink, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingSummary, BookingsPage, BoxIcon24, BubbleTail, BundleProductCard, BundleProductLayout, BundleSelector, Button, CIMPLIFY_GENUI_REGISTRY, CardImage, CardShell, CardVariant, CartDrawer, CartDrawerProvider, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, ChatMarkdown, ChatWidget, CheckIcon, CheckoutPage, CimplifyCheckout, CimplifyProvider, CimplifySignInButton, CimplifyUserButton, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeProductCard, CompositeProductLayout, CompositeSelector, ContextCard, ContinuityCard, CurrencySelector, CustomAttributesTable, CustomerInputFields, DashboardIcon, DashboardIcon20, DatePicker, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DeliveryTracker, DeliveryTruckIcon, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, EmptyState, ErrorState, FeaturedCollectionLayout, FilterTabs, FoodProductCard, FoodProductLayout, GenUIView, HappeningCard, IdentityIcon, IdentityOfframp, InventoryBadge, ItemsCard, LeaseServiceCard, LineItemDetails, LocationPicker, MediaGallery, MessageBubble, MetadataStringList, MobileAccountShell, MobileBackButton, MobileMoreButton, MobileTabBar, NextDeliveryCard, OrderDetailHeader, OrderDetailMetaPipe, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderList, OrderRow, OrderSummary, OrderTimeline, OrderTotals, OrdersIcon, OrdersIcon20, OvenIcon, PackageIcon, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductModel3D, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, QuoteBlock, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, RichText, SWIPE_COMMIT_PX, SaleBadge, SalePrice, SaleTeaser, ScheduleServiceCard, Seam, SearchInput, SearchPage, SearchTemplate, Section, SectionMore, ServiceProductLayout, SessionMessageBanner, SignOutIcon, Skeleton, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, Status, StoreNav, StoreVideo, SubscriptionCard, SubscriptionList, SubscriptionRow, SubscriptionsIcon, SubscriptionsIcon20, TagPills, TimePicker, Toast, TwoColumnGrid, VariantSelector, VolumePricing, WelcomeCard, WholesaleProductCard, WholesaleProductLayout, WishlistButton, chatTimeline, clearOrdersCache, cn, createPlacesSessionToken, dayLabel, defaultBoundaryKind, deliveryState, genUICatalog, genUIFingerprint, genUIManifest, genUIManifestJson, getBoundaryLabels, groupPosition, isImageAttachment, isOptimistic, isTerminalOrderStatus, optimisticId, parse as parseGenUIProgram, partitionMessages, prefersReducedMotion, prefetchProduct, prefetchProducts, proseOf, resolveBoundaryKind, roomToResource, safeAttachmentUrl, sameDay, sanitizeRichTextHtml, scrollBehavior, stripHtmlToText, swipeCommitted, swipeProgress, swipeTravel, toAccountLineItem, toAccountLineItems, toAccountOrderView, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCartDrawer, useCategories, useChat, useCheckout, useCimplify, useCimplifyClient, useCimplifySession, useCollection, useCollections, useDeals, useDeliveryFee, useDeliveryOptions, useElements, useElementsReady, useFxRate, useGenUIProgram, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useStoreCredit, useSubscription, useSubscriptions, useSwipeReply, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };
20812
+ export { AccommodationCard, AccountContent, AccountDashboardPage, AccountHero, AccountIdentityStrip, AccountLineItem, AccountLineItems, AccountNav, AccountOrderDetailPage, AccountOrderView, AccountOrdersPage, AccountShell, AccountSidebar, AccountSignedOutPrompt, AccountSubscriptionDetailPage, AccountSubscriptionsPage, Ad, AdProvider, AddOnSelector, AddressAutocomplete, AddressElement, AuthElement, AvailabilityBadge, BackLink, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingSummary, BookingsPage, BoxIcon24, BubbleTail, BundleProductCard, BundleProductLayout, BundleSelector, Button, CHAT_ACTION_VIEW_ORDER, CHAT_ACTION_VIEW_PRODUCT, CHAT_GENUI_COMPONENTS, CIMPLIFY_GENUI_REGISTRY, CardImage, CardShell, CardVariant, CartDrawer, CartDrawerProvider, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, ChatKitProgram, ChatMarkdown, ChatReplyCards, ChatWidget, CheckIcon, CheckoutPage, CimplifyCheckout, CimplifyProvider, CimplifySignInButton, CimplifyUserButton, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeProductCard, CompositeProductLayout, CompositeSelector, ContextCard, ContinuityCard, CurrencySelector, CustomAttributesTable, CustomerInputFields, DashboardIcon, DashboardIcon20, DatePicker, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DeliveryTracker, DeliveryTruckIcon, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, EmptyState, ErrorState, FeaturedCollectionLayout, FilterTabs, FoodProductCard, FoodProductLayout, GenUIView, HappeningCard, IdentityIcon, IdentityOfframp, InventoryBadge, ItemsCard, LeaseServiceCard, LineItemDetails, LocationPicker, MediaGallery, MessageBubble, MetadataStringList, MobileAccountShell, MobileBackButton, MobileMoreButton, MobileTabBar, NextDeliveryCard, OrderDetailHeader, OrderDetailMetaPipe, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderList, OrderRow, OrderSummary, OrderTimeline, OrderTotals, OrdersIcon, OrdersIcon20, OvenIcon, PackageIcon, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductModel3D, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, QuoteBlock, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, RichText, SWIPE_COMMIT_PX, SaleBadge, SalePrice, SaleTeaser, ScheduleServiceCard, Seam, SearchInput, SearchPage, SearchTemplate, Section, SectionMore, ServiceProductLayout, SessionMessageBanner, SignOutIcon, Skeleton, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, Status, StoreNav, StoreVideo, SubscriptionCard, SubscriptionList, SubscriptionRow, SubscriptionsIcon, SubscriptionsIcon20, TagPills, TimePicker, Toast, TwoColumnGrid, VariantSelector, VolumePricing, WelcomeCard, WholesaleProductCard, WholesaleProductLayout, WishlistButton, chatTimeline, clearOrdersCache, cn, createPlacesSessionToken, dayLabel, defaultBoundaryKind, deliveryState, genUICatalog, genUIFingerprint, genUIManifest, genUIManifestJson, getBoundaryLabels, groupPosition, isImageAttachment, isOptimistic, isTerminalOrderStatus, optimisticId, parseChatAction, parse as parseGenUIProgram, partitionMessages, prefersReducedMotion, prefetchProduct, prefetchProducts, proseOf, resolveBoundaryKind, roomToResource, safeAttachmentUrl, sameDay, sanitizeRichTextHtml, scrollBehavior, stripHtmlToText, swipeCommitted, swipeProgress, swipeTravel, toAccountLineItem, toAccountLineItems, toAccountOrderView, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCartDrawer, useCategories, useChat, useCheckout, useCimplify, useCimplifyClient, useCimplifySession, useCollection, useCollections, useDeals, useDeliveryFee, useDeliveryOptions, useElements, useElementsReady, useFxRate, useGenUIProgram, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useStoreCredit, useSubscription, useSubscriptions, useSwipeReply, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };