@cimplify/sdk 0.89.0 → 0.90.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/advanced.d.mts +3 -3
- package/dist/advanced.d.ts +3 -3
- package/dist/{chunk-DENWOZEA.mjs → chunk-2UDEWILL.mjs} +46 -8
- package/dist/{chunk-T2CJ67QV.js → chunk-BN4V4ONE.js} +46 -8
- package/dist/{chunk-JHJTXAPS.js → chunk-JVWFIFQ7.js} +4 -4
- package/dist/{chunk-ROBTTS3Q.js → chunk-KJP4U6L3.js} +28 -1
- package/dist/{chunk-M4ALCELT.mjs → chunk-NGE37REW.mjs} +2 -2
- package/dist/{chunk-APBLFZ2I.mjs → chunk-NNGKC3MM.mjs} +28 -1
- package/dist/chunk-U4MW6PYU.mjs +23 -0
- package/dist/chunk-XREIZ56K.js +26 -0
- package/dist/{client-CqZgdUtw.d.ts → client-CR1f94wY.d.ts} +81 -6
- package/dist/{client-CUbwb6Om.d.ts → client-CqpOcbin.d.ts} +2 -2
- package/dist/{client-CAY2NbRG.d.mts → client-DMttx1xk.d.mts} +2 -2
- package/dist/{client-D8ZFs-sq.d.mts → client-YiSelM7x.d.mts} +81 -6
- package/dist/{index-Bc5qUxAt.d.ts → index-CxTVu0HZ.d.ts} +3 -3
- package/dist/{index-CmWOz9bi.d.mts → index-DD9z396M.d.mts} +3 -3
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +17 -10
- package/dist/index.mjs +8 -2
- package/dist/mock/cli.mjs +49 -1
- package/dist/mock/library.js +49 -1
- package/dist/mock/library.mjs +49 -1
- package/dist/mock/msw.js +49 -1
- package/dist/mock/msw.mjs +49 -1
- package/dist/{payment-Dcgb9n1n.d.mts → payment-5d8FTA1q.d.mts} +1 -1
- package/dist/{payment-Xq0WDZXh.d.ts → payment-BX7ccIKY.d.ts} +1 -1
- package/dist/{price-DAAjKgAp.d.ts → price-DtfP_Xfq.d.ts} +1 -1
- package/dist/{price--Ma_WO5_.d.mts → price-arl_4Rzn.d.mts} +1 -1
- package/dist/{product-BakHcsP2.d.mts → product-DZX4QuKS.d.mts} +57 -2
- package/dist/{product-BakHcsP2.d.ts → product-DZX4QuKS.d.ts} +57 -2
- package/dist/react.d.mts +405 -7
- package/dist/react.d.ts +405 -7
- package/dist/react.js +2325 -69
- package/dist/react.mjs +2303 -67
- package/dist/{server-Bk_hlefX.d.mts → server-BceZMxcK.d.mts} +8 -1
- package/dist/{server-Bei5lBrc.d.ts → server-DoJGSC7L.d.ts} +8 -1
- package/dist/server.d.mts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/server.mjs +1 -1
- package/dist/styles.css +1 -1
- package/dist/testing/msw.d.mts +2 -2
- package/dist/testing/msw.d.ts +2 -2
- package/dist/testing/msw.js +3 -2
- package/dist/testing/msw.mjs +2 -1
- package/dist/testing/suite.d.mts +6 -6
- package/dist/testing/suite.d.ts +6 -6
- package/dist/testing/suite.js +23 -22
- package/dist/testing/suite.mjs +4 -3
- package/dist/testing.d.mts +5 -5
- package/dist/testing.d.ts +5 -5
- package/dist/testing.js +79 -78
- package/dist/testing.mjs +5 -4
- package/dist/utils.d.mts +4 -4
- package/dist/utils.d.ts +4 -4
- package/package.json +4 -1
- package/registry/chat-widget.json +1 -1
- package/registry/product-customizer.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, PRODUCT_TYPE, BOUNDARY_KIND, resolveGallery, RENDER_HINT, DEFAULT_CDN_BASE_URL } from './chunk-OZDDHSIB.mjs';
|
|
3
3
|
export { getVariantDisplayName } from './chunk-OZDDHSIB.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { messagePreview } from './chunk-U4MW6PYU.mjs';
|
|
5
|
+
export { messagePreview, splitGenUIContent } from './chunk-U4MW6PYU.mjs';
|
|
6
|
+
import { createCimplifyClient } from './chunk-2UDEWILL.mjs';
|
|
5
7
|
import { ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-ZU4ZRYUR.mjs';
|
|
6
8
|
import { parsePrice, formatMoney, formatPrice, getProductCurrency, getUnitPriceAtQuantity, isOnSale, getDiscountPercentage, getBasePrice, getPriceRange, formatPriceRange } from './chunk-KFLGQGMC.mjs';
|
|
7
9
|
import { isSupportedCurrency } from './chunk-K5EN2W2Q.mjs';
|
|
8
10
|
import './chunk-3G6RQLXK.mjs';
|
|
9
|
-
import React14, { createContext, useRef, useState, useCallback, useEffect,
|
|
11
|
+
import React14, { createContext, lazy, memo, Suspense, useContext, useMemo, useRef, useState, useCallback, useEffect, useId, useSyncExternalStore } from 'react';
|
|
10
12
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
11
13
|
import * as ReactDOM from 'react-dom';
|
|
12
14
|
import { clsx } from 'clsx';
|
|
@@ -24,6 +26,8 @@ import { FilterXSS, safeAttrValue as safeAttrValue$1 } from 'xss';
|
|
|
24
26
|
import { Dialog } from '@base-ui/react/dialog';
|
|
25
27
|
import { Field } from '@base-ui/react/field';
|
|
26
28
|
import { Input } from '@base-ui/react/input';
|
|
29
|
+
import DOMPurify from 'dompurify';
|
|
30
|
+
import { marked } from 'marked';
|
|
27
31
|
|
|
28
32
|
// src/ads/identity.ts
|
|
29
33
|
var COOKIE_NAME = "_cimplify_uid";
|
|
@@ -766,15 +770,15 @@ function deriveView(base, ops) {
|
|
|
766
770
|
};
|
|
767
771
|
}
|
|
768
772
|
async function syncFromServer(entry) {
|
|
769
|
-
|
|
770
|
-
entry.syncing = true;
|
|
771
|
-
try {
|
|
773
|
+
const run = async () => {
|
|
772
774
|
const result = await entry.client.cart.get();
|
|
773
775
|
if (!result.ok) throw result.error;
|
|
774
776
|
entry.collection.setBase(mapServerCart(result.value, entry.currency));
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
|
|
777
|
+
};
|
|
778
|
+
const next = entry.syncTail.then(run, run);
|
|
779
|
+
entry.syncTail = next.catch(() => {
|
|
780
|
+
});
|
|
781
|
+
return next;
|
|
778
782
|
}
|
|
779
783
|
function emitMutationError(entry, operation, opId, idempotencyKey, error) {
|
|
780
784
|
entry.client.events.emit("mutation:error", {
|
|
@@ -814,7 +818,7 @@ function getOrCreateStore(params) {
|
|
|
814
818
|
optimisticEnabled,
|
|
815
819
|
initialized: false,
|
|
816
820
|
initializePromise: null,
|
|
817
|
-
|
|
821
|
+
syncTail: Promise.resolve(),
|
|
818
822
|
failedOpRemovers: /* @__PURE__ */ new Set()
|
|
819
823
|
};
|
|
820
824
|
cartStores.set(storeKey, entry);
|
|
@@ -4279,7 +4283,7 @@ function Price({
|
|
|
4279
4283
|
] });
|
|
4280
4284
|
}
|
|
4281
4285
|
function createCachedQuery(config) {
|
|
4282
|
-
const
|
|
4286
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
4283
4287
|
const inflight = /* @__PURE__ */ new Map();
|
|
4284
4288
|
const gcMaxAge = config.gcMaxAge ?? 3e5;
|
|
4285
4289
|
let lastSweep = 0;
|
|
@@ -4288,8 +4292,8 @@ function createCachedQuery(config) {
|
|
|
4288
4292
|
if (now - lastSweep < 6e4) return;
|
|
4289
4293
|
lastSweep = now;
|
|
4290
4294
|
const cutoff = now - gcMaxAge;
|
|
4291
|
-
for (const [key, entry] of
|
|
4292
|
-
if (entry.ts < cutoff)
|
|
4295
|
+
for (const [key, entry] of cache2) {
|
|
4296
|
+
if (entry.ts < cutoff) cache2.delete(key);
|
|
4293
4297
|
}
|
|
4294
4298
|
}
|
|
4295
4299
|
function useCachedQuery(options = {}) {
|
|
@@ -4307,13 +4311,13 @@ function createCachedQuery(config) {
|
|
|
4307
4311
|
const optionsRef = useRef(options);
|
|
4308
4312
|
optionsRef.current = options;
|
|
4309
4313
|
if (config.locationSensitive && previousLocationRef.current !== locationId) {
|
|
4310
|
-
|
|
4314
|
+
cache2.clear();
|
|
4311
4315
|
inflight.clear();
|
|
4312
4316
|
previousLocationRef.current = locationId;
|
|
4313
4317
|
}
|
|
4314
4318
|
sweep();
|
|
4315
4319
|
const cacheKey2 = config.key(client, options);
|
|
4316
|
-
const cached =
|
|
4320
|
+
const cached = cache2.get(cacheKey2);
|
|
4317
4321
|
const [data, setData] = useState(cached?.data ?? null);
|
|
4318
4322
|
const [isLoading, setIsLoading] = useState(enabled && !cached);
|
|
4319
4323
|
const [error, setError] = useState(null);
|
|
@@ -4326,7 +4330,7 @@ function createCachedQuery(config) {
|
|
|
4326
4330
|
const nextRequestId = ++requestIdRef.current;
|
|
4327
4331
|
setError(null);
|
|
4328
4332
|
if (!force) {
|
|
4329
|
-
const hit =
|
|
4333
|
+
const hit = cache2.get(cacheKey2);
|
|
4330
4334
|
if (hit) {
|
|
4331
4335
|
setData(hit.data);
|
|
4332
4336
|
setIsLoading(false);
|
|
@@ -4343,7 +4347,7 @@ function createCachedQuery(config) {
|
|
|
4343
4347
|
});
|
|
4344
4348
|
}
|
|
4345
4349
|
const value = await promise;
|
|
4346
|
-
|
|
4350
|
+
cache2.set(cacheKey2, { data: value, ts: Date.now() });
|
|
4347
4351
|
if (nextRequestId === requestIdRef.current) {
|
|
4348
4352
|
setData(value);
|
|
4349
4353
|
setError(null);
|
|
@@ -4364,23 +4368,23 @@ function createCachedQuery(config) {
|
|
|
4364
4368
|
void load(false);
|
|
4365
4369
|
}, [load]);
|
|
4366
4370
|
const refetch = useCallback(async () => {
|
|
4367
|
-
|
|
4371
|
+
cache2.delete(cacheKey2);
|
|
4368
4372
|
await load(true);
|
|
4369
4373
|
}, [cacheKey2, load]);
|
|
4370
4374
|
return { data, isLoading, error, refetch };
|
|
4371
4375
|
}
|
|
4372
4376
|
useCachedQuery.prefetch = (client, options) => {
|
|
4373
4377
|
const key = config.key(client, options);
|
|
4374
|
-
if (
|
|
4378
|
+
if (cache2.has(key) || inflight.has(key)) return void 0;
|
|
4375
4379
|
const promise = config.fetch(client, options);
|
|
4376
4380
|
inflight.set(key, promise);
|
|
4377
4381
|
return promise.then((value) => {
|
|
4378
|
-
|
|
4382
|
+
cache2.set(key, { data: value, ts: Date.now() });
|
|
4379
4383
|
}).catch(() => {
|
|
4380
4384
|
}).finally(() => inflight.delete(key));
|
|
4381
4385
|
};
|
|
4382
4386
|
useCachedQuery.clear = () => {
|
|
4383
|
-
|
|
4387
|
+
cache2.clear();
|
|
4384
4388
|
inflight.clear();
|
|
4385
4389
|
};
|
|
4386
4390
|
return useCachedQuery;
|
|
@@ -4508,6 +4512,9 @@ function useCategories(options = {}) {
|
|
|
4508
4512
|
return { categories: data ?? [], isLoading, error, refetch };
|
|
4509
4513
|
}
|
|
4510
4514
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "refunded"]);
|
|
4515
|
+
function isTerminalOrderStatus(status) {
|
|
4516
|
+
return TERMINAL_STATUSES.has(status);
|
|
4517
|
+
}
|
|
4511
4518
|
var orderCache = /* @__PURE__ */ new Map();
|
|
4512
4519
|
var orderInflight = /* @__PURE__ */ new Map();
|
|
4513
4520
|
function buildOrderCacheKey(client, orderId) {
|
|
@@ -5349,6 +5356,9 @@ function useOrders(options = {}) {
|
|
|
5349
5356
|
const { data, isLoading, error, refetch } = ordersQuery(options);
|
|
5350
5357
|
return { orders: data ?? [], isLoading, error, refetch };
|
|
5351
5358
|
}
|
|
5359
|
+
function clearOrdersCache() {
|
|
5360
|
+
ordersQuery.clear();
|
|
5361
|
+
}
|
|
5352
5362
|
function useSubscriptions(options = {}) {
|
|
5353
5363
|
const { client } = useCimplifyClient();
|
|
5354
5364
|
const { status, enabled = true } = options;
|
|
@@ -5854,6 +5864,7 @@ var POLL_IDLE_INTERVAL_MS = 15e3;
|
|
|
5854
5864
|
var IDLE_TIMEOUT_MS = 6e4;
|
|
5855
5865
|
var WS_MAX_RECONNECT_ATTEMPTS = 10;
|
|
5856
5866
|
var WS_PATH = "/api/v1/support/conversation/ws";
|
|
5867
|
+
var AWAIT_REPLY_TIMEOUT_MS = 3e4;
|
|
5857
5868
|
function useChat(options = {}) {
|
|
5858
5869
|
const context = useOptionalCimplifyClient();
|
|
5859
5870
|
const client = options.client ?? context?.client;
|
|
@@ -5862,6 +5873,10 @@ function useChat(options = {}) {
|
|
|
5862
5873
|
const [isSending, setIsSending] = useState(false);
|
|
5863
5874
|
const [error, setError] = useState(null);
|
|
5864
5875
|
const [conversationOpened, setConversationOpened] = useState(false);
|
|
5876
|
+
const [humanRequested, setHumanRequested] = useState(false);
|
|
5877
|
+
const [awaitingReply, setAwaitingReply] = useState(false);
|
|
5878
|
+
const [seenAt, setSeenAt] = useState(null);
|
|
5879
|
+
const [mayHaveOlder, setMayHaveOlder] = useState(false);
|
|
5865
5880
|
const pollRef = useRef(null);
|
|
5866
5881
|
const lastActivityRef = useRef(Date.now());
|
|
5867
5882
|
const openedRef = useRef(false);
|
|
@@ -5870,17 +5885,42 @@ function useChat(options = {}) {
|
|
|
5870
5885
|
const wsRef = useRef(null);
|
|
5871
5886
|
const wsReconnectAttemptRef = useRef(0);
|
|
5872
5887
|
const wsReconnectTimerRef = useRef(null);
|
|
5888
|
+
const aiEnabledRef = useRef(false);
|
|
5889
|
+
const humanRequestedRef = useRef(false);
|
|
5890
|
+
const awaitReplyTimerRef = useRef(null);
|
|
5873
5891
|
messagesRef.current = messages;
|
|
5874
5892
|
clientRef.current = client;
|
|
5893
|
+
humanRequestedRef.current = humanRequested;
|
|
5875
5894
|
const pollInterval = options.pollInterval ?? POLL_INTERVAL_MS;
|
|
5876
5895
|
const pollIdleInterval = options.pollIdleInterval ?? POLL_IDLE_INTERVAL_MS;
|
|
5877
|
-
const
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5896
|
+
const stopAwaitingReply = useCallback(() => {
|
|
5897
|
+
if (awaitReplyTimerRef.current) {
|
|
5898
|
+
clearTimeout(awaitReplyTimerRef.current);
|
|
5899
|
+
awaitReplyTimerRef.current = null;
|
|
5900
|
+
}
|
|
5901
|
+
setAwaitingReply(false);
|
|
5902
|
+
}, []);
|
|
5903
|
+
const startAwaitingReply = useCallback(() => {
|
|
5904
|
+
setAwaitingReply(true);
|
|
5905
|
+
if (awaitReplyTimerRef.current) clearTimeout(awaitReplyTimerRef.current);
|
|
5906
|
+
awaitReplyTimerRef.current = setTimeout(() => {
|
|
5907
|
+
awaitReplyTimerRef.current = null;
|
|
5908
|
+
setAwaitingReply(false);
|
|
5909
|
+
}, AWAIT_REPLY_TIMEOUT_MS);
|
|
5883
5910
|
}, []);
|
|
5911
|
+
const appendMessages = useCallback(
|
|
5912
|
+
(incoming) => {
|
|
5913
|
+
if (incoming.some((m) => m.sender_type === "bot" || m.sender_type === "agent")) {
|
|
5914
|
+
stopAwaitingReply();
|
|
5915
|
+
}
|
|
5916
|
+
setMessages((prev) => {
|
|
5917
|
+
const ids = new Set(prev.map((m) => m.id));
|
|
5918
|
+
const fresh = incoming.filter((m) => !ids.has(m.id));
|
|
5919
|
+
return fresh.length > 0 ? [...prev, ...fresh] : prev;
|
|
5920
|
+
});
|
|
5921
|
+
},
|
|
5922
|
+
[stopAwaitingReply]
|
|
5923
|
+
);
|
|
5884
5924
|
const fetchNewMessages = useCallback(async () => {
|
|
5885
5925
|
const c = clientRef.current;
|
|
5886
5926
|
if (!c) return;
|
|
@@ -5929,6 +5969,13 @@ function useChat(options = {}) {
|
|
|
5929
5969
|
const envelope = JSON.parse(event.data);
|
|
5930
5970
|
if (envelope.type === "message.created" && envelope.data) {
|
|
5931
5971
|
appendMessages([envelope.data]);
|
|
5972
|
+
} else if (envelope.type === "thread.read") {
|
|
5973
|
+
const at = envelope.data?.last_read_at;
|
|
5974
|
+
if (at) setSeenAt((prev) => !prev || prev < at ? at : prev);
|
|
5975
|
+
} else if (envelope.type === "thread.responding") {
|
|
5976
|
+
const active = envelope.data?.active;
|
|
5977
|
+
if (active) startAwaitingReply();
|
|
5978
|
+
else stopAwaitingReply();
|
|
5932
5979
|
} else if (envelope.type === "resync") {
|
|
5933
5980
|
const latest = envelope.latest_message_at;
|
|
5934
5981
|
const last = messagesRef.current[messagesRef.current.length - 1];
|
|
@@ -5959,6 +6006,7 @@ function useChat(options = {}) {
|
|
|
5959
6006
|
return () => {
|
|
5960
6007
|
if (pollRef.current) clearInterval(pollRef.current);
|
|
5961
6008
|
if (wsReconnectTimerRef.current) clearTimeout(wsReconnectTimerRef.current);
|
|
6009
|
+
if (awaitReplyTimerRef.current) clearTimeout(awaitReplyTimerRef.current);
|
|
5962
6010
|
if (wsRef.current) {
|
|
5963
6011
|
wsRef.current.onclose = null;
|
|
5964
6012
|
wsRef.current.close();
|
|
@@ -5974,6 +6022,10 @@ function useChat(options = {}) {
|
|
|
5974
6022
|
if (result.ok) {
|
|
5975
6023
|
setMessages(result.value.messages);
|
|
5976
6024
|
setConversationOpened(true);
|
|
6025
|
+
aiEnabledRef.current = result.value.conversation.ai_enabled;
|
|
6026
|
+
setHumanRequested(result.value.conversation.needs_human_review ?? false);
|
|
6027
|
+
setSeenAt(result.value.conversation.team_read_at ?? null);
|
|
6028
|
+
setMayHaveOlder(result.value.messages.length >= 50);
|
|
5977
6029
|
connectWebSocket();
|
|
5978
6030
|
} else {
|
|
5979
6031
|
openedRef.current = false;
|
|
@@ -5982,27 +6034,39 @@ function useChat(options = {}) {
|
|
|
5982
6034
|
setIsLoading(false);
|
|
5983
6035
|
}, [client, connectWebSocket]);
|
|
5984
6036
|
const send = useCallback(
|
|
5985
|
-
async (content) => {
|
|
5986
|
-
|
|
6037
|
+
async (content, opts) => {
|
|
6038
|
+
const attachments = opts?.attachments ?? [];
|
|
6039
|
+
if (!client || !content.trim() && attachments.length === 0) return false;
|
|
5987
6040
|
setError(null);
|
|
5988
6041
|
lastActivityRef.current = Date.now();
|
|
5989
6042
|
if (!openedRef.current) {
|
|
5990
6043
|
await openConversation();
|
|
5991
|
-
if (!openedRef.current) return;
|
|
6044
|
+
if (!openedRef.current) return false;
|
|
5992
6045
|
}
|
|
6046
|
+
const replyTo = opts?.replyTo;
|
|
5993
6047
|
const optimisticId = `opt_${Date.now()}`;
|
|
5994
6048
|
const optimistic = {
|
|
5995
6049
|
id: optimisticId,
|
|
5996
6050
|
sender_type: "customer",
|
|
5997
6051
|
content: content.trim(),
|
|
5998
|
-
content_type: "text",
|
|
5999
|
-
attachments
|
|
6052
|
+
content_type: attachments.length > 0 ? "image" : "text",
|
|
6053
|
+
attachments,
|
|
6000
6054
|
metadata: {},
|
|
6055
|
+
// Same shape the server echoes back, so the bubble doesn't reflow
|
|
6056
|
+
// when the confirmed message replaces it.
|
|
6057
|
+
reply_to: replyTo ? {
|
|
6058
|
+
id: replyTo.id,
|
|
6059
|
+
sender_type: replyTo.sender_type,
|
|
6060
|
+
preview: messagePreview(replyTo.content)
|
|
6061
|
+
} : void 0,
|
|
6001
6062
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6002
6063
|
};
|
|
6003
6064
|
setMessages((prev) => [...prev, optimistic]);
|
|
6004
6065
|
setIsSending(true);
|
|
6005
|
-
const result = await client.support.sendMessage(content.trim()
|
|
6066
|
+
const result = await client.support.sendMessage(content.trim(), {
|
|
6067
|
+
replyToMessageId: replyTo?.id,
|
|
6068
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
6069
|
+
});
|
|
6006
6070
|
if (result.ok) {
|
|
6007
6071
|
const confirmed = result.value;
|
|
6008
6072
|
setMessages(
|
|
@@ -6013,13 +6077,17 @@ function useChat(options = {}) {
|
|
|
6013
6077
|
prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== optimisticId) : prev.map((m) => m.id === optimisticId ? confirmed : m)
|
|
6014
6078
|
)
|
|
6015
6079
|
);
|
|
6080
|
+
if (aiEnabledRef.current && !humanRequestedRef.current) {
|
|
6081
|
+
startAwaitingReply();
|
|
6082
|
+
}
|
|
6016
6083
|
} else {
|
|
6017
6084
|
setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
|
|
6018
6085
|
setError(result.error);
|
|
6019
6086
|
}
|
|
6020
6087
|
setIsSending(false);
|
|
6088
|
+
return result.ok;
|
|
6021
6089
|
},
|
|
6022
|
-
[client, openConversation]
|
|
6090
|
+
[client, openConversation, startAwaitingReply]
|
|
6023
6091
|
);
|
|
6024
6092
|
const startConversation = useCallback(
|
|
6025
6093
|
async (starterText) => {
|
|
@@ -6030,14 +6098,65 @@ function useChat(options = {}) {
|
|
|
6030
6098
|
},
|
|
6031
6099
|
[openConversation, send]
|
|
6032
6100
|
);
|
|
6101
|
+
const react = useCallback(
|
|
6102
|
+
async (message, emoji) => {
|
|
6103
|
+
if (!client || message.id.startsWith("opt_")) return false;
|
|
6104
|
+
const result = await client.support.reactToMessage(message.id, emoji);
|
|
6105
|
+
if (result.ok) appendMessages([result.value]);
|
|
6106
|
+
return result.ok;
|
|
6107
|
+
},
|
|
6108
|
+
[client, appendMessages]
|
|
6109
|
+
);
|
|
6110
|
+
const requestHuman = useCallback(async () => {
|
|
6111
|
+
if (!client) return false;
|
|
6112
|
+
setError(null);
|
|
6113
|
+
if (!openedRef.current) {
|
|
6114
|
+
await openConversation();
|
|
6115
|
+
if (!openedRef.current) return false;
|
|
6116
|
+
}
|
|
6117
|
+
const result = await client.support.requestHuman();
|
|
6118
|
+
if (result.ok) {
|
|
6119
|
+
setHumanRequested(true);
|
|
6120
|
+
return true;
|
|
6121
|
+
}
|
|
6122
|
+
setError(result.error);
|
|
6123
|
+
return false;
|
|
6124
|
+
}, [client, openConversation]);
|
|
6125
|
+
const loadOlder = useCallback(async () => {
|
|
6126
|
+
const c = clientRef.current;
|
|
6127
|
+
const earliest = messagesRef.current[0];
|
|
6128
|
+
if (!c || !earliest) return false;
|
|
6129
|
+
const result = await c.support.getMessages({ before: earliest.created_at, limit: 50 });
|
|
6130
|
+
if (!result.ok) {
|
|
6131
|
+
setError(result.error);
|
|
6132
|
+
return false;
|
|
6133
|
+
}
|
|
6134
|
+
const older = result.value;
|
|
6135
|
+
setMayHaveOlder(older.length >= 50);
|
|
6136
|
+
if (older.length === 0) return false;
|
|
6137
|
+
setMessages((prev) => {
|
|
6138
|
+
const ids = new Set(prev.map((m) => m.id));
|
|
6139
|
+
const fresh = older.filter((m) => !ids.has(m.id));
|
|
6140
|
+
return fresh.length > 0 ? [...fresh, ...prev] : prev;
|
|
6141
|
+
});
|
|
6142
|
+
return true;
|
|
6143
|
+
}, []);
|
|
6144
|
+
const agentArrived = messages.some((m) => m.sender_type === "agent");
|
|
6033
6145
|
return {
|
|
6034
6146
|
messages,
|
|
6035
6147
|
isLoading,
|
|
6036
6148
|
isSending,
|
|
6037
6149
|
error,
|
|
6038
6150
|
isActive: conversationOpened && messages.length > 0,
|
|
6151
|
+
humanRequested: humanRequested && !agentArrived,
|
|
6152
|
+
awaitingReply,
|
|
6153
|
+
seenAt,
|
|
6154
|
+
mayHaveOlder,
|
|
6039
6155
|
send,
|
|
6040
|
-
startConversation
|
|
6156
|
+
startConversation,
|
|
6157
|
+
react,
|
|
6158
|
+
requestHuman,
|
|
6159
|
+
loadOlder
|
|
6041
6160
|
};
|
|
6042
6161
|
}
|
|
6043
6162
|
var ElementsContext = createContext({
|
|
@@ -9335,6 +9454,7 @@ function ProductCustomizer({
|
|
|
9335
9454
|
onAddToCart,
|
|
9336
9455
|
onVariantChange,
|
|
9337
9456
|
showSpecialInstructions = true,
|
|
9457
|
+
submitLabel,
|
|
9338
9458
|
className,
|
|
9339
9459
|
classNames
|
|
9340
9460
|
}) {
|
|
@@ -9723,8 +9843,10 @@ function ProductCustomizer({
|
|
|
9723
9843
|
isAdded && classNames?.submitButtonAdded,
|
|
9724
9844
|
classNames?.submitButton
|
|
9725
9845
|
),
|
|
9726
|
-
children: isAdded ? "Added
|
|
9727
|
-
"Add to Cart
|
|
9846
|
+
children: isAdded ? "Added" : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9847
|
+
submitLabel ?? "Add to Cart",
|
|
9848
|
+
" \xB7",
|
|
9849
|
+
" ",
|
|
9728
9850
|
/* @__PURE__ */ jsx(Price, { amount: displayTotalPrice, currency })
|
|
9729
9851
|
] })
|
|
9730
9852
|
}
|
|
@@ -9884,13 +10006,13 @@ function StoreVideo({
|
|
|
9884
10006
|
autoplay = true,
|
|
9885
10007
|
loop = true,
|
|
9886
10008
|
muted = true,
|
|
9887
|
-
lazy = true,
|
|
10009
|
+
lazy: lazy2 = true,
|
|
9888
10010
|
className
|
|
9889
10011
|
}) {
|
|
9890
10012
|
const ref = useRef(null);
|
|
9891
|
-
const [inView, setInView] = useState(!
|
|
10013
|
+
const [inView, setInView] = useState(!lazy2);
|
|
9892
10014
|
useEffect(() => {
|
|
9893
|
-
if (!
|
|
10015
|
+
if (!lazy2 || inView) return;
|
|
9894
10016
|
const node = ref.current;
|
|
9895
10017
|
if (!node || typeof IntersectionObserver === "undefined") {
|
|
9896
10018
|
setInView(true);
|
|
@@ -9907,7 +10029,7 @@ function StoreVideo({
|
|
|
9907
10029
|
);
|
|
9908
10030
|
observer.observe(node);
|
|
9909
10031
|
return () => observer.disconnect();
|
|
9910
|
-
}, [
|
|
10032
|
+
}, [lazy2, inView]);
|
|
9911
10033
|
return /* @__PURE__ */ jsx(
|
|
9912
10034
|
"div",
|
|
9913
10035
|
{
|
|
@@ -9925,7 +10047,7 @@ function StoreVideo({
|
|
|
9925
10047
|
muted,
|
|
9926
10048
|
playsInline: true,
|
|
9927
10049
|
controls,
|
|
9928
|
-
preload:
|
|
10050
|
+
preload: lazy2 ? "metadata" : "auto",
|
|
9929
10051
|
"aria-label": alt,
|
|
9930
10052
|
style: { width: "100%", height: "100%", objectFit: "cover", display: "block" },
|
|
9931
10053
|
children: poster ? /* @__PURE__ */ jsx(
|
|
@@ -16727,6 +16849,112 @@ function CurrencySelector({
|
|
|
16727
16849
|
);
|
|
16728
16850
|
}) });
|
|
16729
16851
|
}
|
|
16852
|
+
var Streamdown = lazy(() => import('streamdown').then((m) => ({ default: m.Streamdown })));
|
|
16853
|
+
function detectLookbehindSupport() {
|
|
16854
|
+
try {
|
|
16855
|
+
new RegExp("(?<=a)b");
|
|
16856
|
+
return true;
|
|
16857
|
+
} catch {
|
|
16858
|
+
return false;
|
|
16859
|
+
}
|
|
16860
|
+
}
|
|
16861
|
+
var supportsRegExpLookbehind = detectLookbehindSupport();
|
|
16862
|
+
marked.setOptions({
|
|
16863
|
+
gfm: true,
|
|
16864
|
+
breaks: true
|
|
16865
|
+
});
|
|
16866
|
+
var ALLOWED_TAGS = [
|
|
16867
|
+
"a",
|
|
16868
|
+
"b",
|
|
16869
|
+
"blockquote",
|
|
16870
|
+
"br",
|
|
16871
|
+
"code",
|
|
16872
|
+
"del",
|
|
16873
|
+
"em",
|
|
16874
|
+
"h1",
|
|
16875
|
+
"h2",
|
|
16876
|
+
"h3",
|
|
16877
|
+
"h4",
|
|
16878
|
+
"hr",
|
|
16879
|
+
"i",
|
|
16880
|
+
"li",
|
|
16881
|
+
"ol",
|
|
16882
|
+
"p",
|
|
16883
|
+
"pre",
|
|
16884
|
+
"strong",
|
|
16885
|
+
"table",
|
|
16886
|
+
"tbody",
|
|
16887
|
+
"td",
|
|
16888
|
+
"th",
|
|
16889
|
+
"thead",
|
|
16890
|
+
"tr",
|
|
16891
|
+
"ul"
|
|
16892
|
+
];
|
|
16893
|
+
var ALLOWED_ATTR = ["href", "rel", "target", "title", "start"];
|
|
16894
|
+
var hooksInstalled = false;
|
|
16895
|
+
function installHooks() {
|
|
16896
|
+
if (hooksInstalled) return;
|
|
16897
|
+
hooksInstalled = true;
|
|
16898
|
+
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
|
|
16899
|
+
if (!(node instanceof HTMLAnchorElement)) return;
|
|
16900
|
+
if (!node.getAttribute("href")) return;
|
|
16901
|
+
node.setAttribute("rel", "noreferrer noopener");
|
|
16902
|
+
node.setAttribute("target", "_blank");
|
|
16903
|
+
});
|
|
16904
|
+
}
|
|
16905
|
+
var CACHE_LIMIT = 300;
|
|
16906
|
+
var cache = /* @__PURE__ */ new Map();
|
|
16907
|
+
function renderMarkdown(text) {
|
|
16908
|
+
const cached = cache.get(text);
|
|
16909
|
+
if (cached !== void 0) return cached;
|
|
16910
|
+
installHooks();
|
|
16911
|
+
const html = DOMPurify.sanitize(marked.parse(text), {
|
|
16912
|
+
ALLOWED_TAGS,
|
|
16913
|
+
ALLOWED_ATTR
|
|
16914
|
+
});
|
|
16915
|
+
if (cache.size >= CACHE_LIMIT) {
|
|
16916
|
+
const oldest = cache.keys().next().value;
|
|
16917
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
16918
|
+
}
|
|
16919
|
+
cache.set(text, html);
|
|
16920
|
+
return html;
|
|
16921
|
+
}
|
|
16922
|
+
function SettledMarkdown({ text, className }) {
|
|
16923
|
+
const html = useMemo(() => renderMarkdown(text), [text]);
|
|
16924
|
+
return /* @__PURE__ */ jsx("div", { className, dangerouslySetInnerHTML: { __html: html } });
|
|
16925
|
+
}
|
|
16926
|
+
var ChatMarkdown = memo(function ChatMarkdown2({
|
|
16927
|
+
text,
|
|
16928
|
+
className,
|
|
16929
|
+
streaming = false
|
|
16930
|
+
}) {
|
|
16931
|
+
if (streaming && supportsRegExpLookbehind) {
|
|
16932
|
+
return (
|
|
16933
|
+
// While the streamdown chunk loads, the settled renderer keeps the
|
|
16934
|
+
// text on screen — no blank frame at stream start.
|
|
16935
|
+
/* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(SettledMarkdown, { text, className }), children: /* @__PURE__ */ jsx(Streamdown, { className, children: text }) })
|
|
16936
|
+
);
|
|
16937
|
+
}
|
|
16938
|
+
return /* @__PURE__ */ jsx(SettledMarkdown, { text, className });
|
|
16939
|
+
});
|
|
16940
|
+
|
|
16941
|
+
// src/react/chat-model.ts
|
|
16942
|
+
function partitionMessages(messages) {
|
|
16943
|
+
const bubbles = [];
|
|
16944
|
+
const reactionsByTarget = /* @__PURE__ */ new Map();
|
|
16945
|
+
for (const message of messages) {
|
|
16946
|
+
if (message.content_type === "reaction") {
|
|
16947
|
+
const targetId = message.reply_to?.id;
|
|
16948
|
+
if (!targetId) continue;
|
|
16949
|
+
const list = reactionsByTarget.get(targetId);
|
|
16950
|
+
if (list) list.push(message);
|
|
16951
|
+
else reactionsByTarget.set(targetId, [message]);
|
|
16952
|
+
continue;
|
|
16953
|
+
}
|
|
16954
|
+
bubbles.push(message);
|
|
16955
|
+
}
|
|
16956
|
+
return { bubbles, reactionsByTarget };
|
|
16957
|
+
}
|
|
16730
16958
|
function ChatWidget({
|
|
16731
16959
|
client: clientProp,
|
|
16732
16960
|
businessName = "Support",
|
|
@@ -16744,7 +16972,35 @@ function ChatWidget({
|
|
|
16744
16972
|
const client = clientProp ?? context?.client;
|
|
16745
16973
|
const [open, setOpen] = useState(defaultOpen);
|
|
16746
16974
|
const [unread, setUnread] = useState(0);
|
|
16747
|
-
const {
|
|
16975
|
+
const {
|
|
16976
|
+
messages,
|
|
16977
|
+
isLoading,
|
|
16978
|
+
isSending,
|
|
16979
|
+
awaitingReply,
|
|
16980
|
+
seenAt,
|
|
16981
|
+
error,
|
|
16982
|
+
isActive,
|
|
16983
|
+
send,
|
|
16984
|
+
react,
|
|
16985
|
+
startConversation
|
|
16986
|
+
} = useChat({ client, pollInterval });
|
|
16987
|
+
const showTyping = isSending || awaitingReply;
|
|
16988
|
+
const attachPhoto = useCallback(
|
|
16989
|
+
async (file) => {
|
|
16990
|
+
if (!client || !file.type.startsWith("image/") || file.size > 5 * 1024 * 1024) return;
|
|
16991
|
+
const uploaded = await client.uploads.upload(file);
|
|
16992
|
+
if (!uploaded.ok) return;
|
|
16993
|
+
const attachment = {
|
|
16994
|
+
upload_id: uploaded.value.id,
|
|
16995
|
+
url: uploaded.value.url,
|
|
16996
|
+
mime_type: uploaded.value.content_type,
|
|
16997
|
+
filename: uploaded.value.filename,
|
|
16998
|
+
size: uploaded.value.size_bytes
|
|
16999
|
+
};
|
|
17000
|
+
await send("", { attachments: [attachment] });
|
|
17001
|
+
},
|
|
17002
|
+
[client, send]
|
|
17003
|
+
);
|
|
16748
17004
|
const prevCountRef = useRef(messages.length);
|
|
16749
17005
|
useEffect(() => {
|
|
16750
17006
|
if (!open && messages.length > prevCountRef.current) {
|
|
@@ -16791,11 +17047,15 @@ function ChatWidget({
|
|
|
16791
17047
|
messages,
|
|
16792
17048
|
isLoading,
|
|
16793
17049
|
isSending,
|
|
17050
|
+
isTyping: showTyping,
|
|
17051
|
+
seenAt,
|
|
16794
17052
|
error,
|
|
16795
17053
|
isActive,
|
|
16796
17054
|
isLeft,
|
|
16797
17055
|
onClose: handleClose,
|
|
16798
17056
|
onSend: send,
|
|
17057
|
+
onReact: react,
|
|
17058
|
+
onAttach: attachPhoto,
|
|
16799
17059
|
onStartConversation: startConversation,
|
|
16800
17060
|
classNames
|
|
16801
17061
|
}
|
|
@@ -16848,11 +17108,15 @@ function Panel({
|
|
|
16848
17108
|
messages,
|
|
16849
17109
|
isLoading,
|
|
16850
17110
|
isSending,
|
|
17111
|
+
isTyping,
|
|
17112
|
+
seenAt,
|
|
16851
17113
|
error,
|
|
16852
17114
|
isActive,
|
|
16853
17115
|
isLeft,
|
|
16854
17116
|
onClose,
|
|
16855
17117
|
onSend,
|
|
17118
|
+
onReact,
|
|
17119
|
+
onAttach,
|
|
16856
17120
|
onStartConversation,
|
|
16857
17121
|
classNames
|
|
16858
17122
|
}) {
|
|
@@ -16890,7 +17154,9 @@ function Panel({
|
|
|
16890
17154
|
{
|
|
16891
17155
|
messages,
|
|
16892
17156
|
isLoading,
|
|
16893
|
-
isSending,
|
|
17157
|
+
isSending: isTyping,
|
|
17158
|
+
seenAt,
|
|
17159
|
+
onReact,
|
|
16894
17160
|
className: classNames?.messages
|
|
16895
17161
|
}
|
|
16896
17162
|
),
|
|
@@ -16901,6 +17167,7 @@ function Panel({
|
|
|
16901
17167
|
placeholder,
|
|
16902
17168
|
isSending,
|
|
16903
17169
|
onSend,
|
|
17170
|
+
onAttach,
|
|
16904
17171
|
className: classNames?.input
|
|
16905
17172
|
}
|
|
16906
17173
|
),
|
|
@@ -16996,16 +17263,29 @@ function Welcome({
|
|
|
16996
17263
|
}
|
|
16997
17264
|
);
|
|
16998
17265
|
}
|
|
17266
|
+
var QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F"];
|
|
17267
|
+
function lastSeenCustomerMessageId(bubbles, seenAt) {
|
|
17268
|
+
if (!seenAt || !bubbles.some((m) => m.sender_type === "agent")) return null;
|
|
17269
|
+
for (let i = bubbles.length - 1; i >= 0; i--) {
|
|
17270
|
+
if (bubbles[i].sender_type !== "customer") continue;
|
|
17271
|
+
return bubbles[i].created_at <= seenAt ? bubbles[i].id : null;
|
|
17272
|
+
}
|
|
17273
|
+
return null;
|
|
17274
|
+
}
|
|
16999
17275
|
function MessageList({
|
|
17000
17276
|
messages,
|
|
17001
17277
|
isLoading,
|
|
17002
17278
|
isSending,
|
|
17279
|
+
seenAt,
|
|
17280
|
+
onReact,
|
|
17003
17281
|
className
|
|
17004
17282
|
}) {
|
|
17005
17283
|
const bottomRef = useRef(null);
|
|
17006
17284
|
useEffect(() => {
|
|
17007
17285
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
17008
17286
|
}, [messages.length, isSending]);
|
|
17287
|
+
const { bubbles, reactionsByTarget } = partitionMessages(messages);
|
|
17288
|
+
const seenMessageId = lastSeenCustomerMessageId(bubbles, seenAt);
|
|
17009
17289
|
return /* @__PURE__ */ jsxs(
|
|
17010
17290
|
"div",
|
|
17011
17291
|
{
|
|
@@ -17015,40 +17295,111 @@ function MessageList({
|
|
|
17015
17295
|
),
|
|
17016
17296
|
children: [
|
|
17017
17297
|
isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
|
|
17018
|
-
|
|
17298
|
+
bubbles.map((msg, i) => {
|
|
17019
17299
|
const isCustomer = msg.sender_type === "customer";
|
|
17020
|
-
const prev =
|
|
17300
|
+
const prev = bubbles[i - 1];
|
|
17021
17301
|
const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
|
|
17022
17302
|
const isOptimistic = msg.id.startsWith("opt_");
|
|
17303
|
+
const reactions = reactionsByTarget.get(msg.id);
|
|
17023
17304
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
17024
17305
|
!sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsx("div", { className: "h-3" }),
|
|
17025
|
-
/* @__PURE__ */
|
|
17306
|
+
/* @__PURE__ */ jsxs(
|
|
17026
17307
|
"div",
|
|
17027
17308
|
{
|
|
17028
17309
|
className: cn(
|
|
17029
|
-
"flex max-w-[85%]",
|
|
17310
|
+
"group relative flex max-w-[85%]",
|
|
17030
17311
|
isCustomer ? "ml-auto" : "mr-auto"
|
|
17031
17312
|
),
|
|
17032
|
-
children:
|
|
17033
|
-
"div",
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
|
|
17039
|
-
|
|
17040
|
-
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17313
|
+
children: [
|
|
17314
|
+
!isCustomer && !isOptimistic && /* @__PURE__ */ jsx("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: QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsx(
|
|
17315
|
+
"button",
|
|
17316
|
+
{
|
|
17317
|
+
type: "button",
|
|
17318
|
+
"aria-label": `React ${emoji}`,
|
|
17319
|
+
onClick: () => void onReact(msg, emoji),
|
|
17320
|
+
className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
|
|
17321
|
+
children: emoji
|
|
17322
|
+
},
|
|
17323
|
+
emoji
|
|
17324
|
+
)) }),
|
|
17325
|
+
/* @__PURE__ */ jsxs(
|
|
17326
|
+
"div",
|
|
17327
|
+
{
|
|
17328
|
+
className: cn(
|
|
17329
|
+
"rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
|
|
17330
|
+
isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
|
|
17331
|
+
isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
|
|
17332
|
+
isOptimistic && "opacity-70"
|
|
17333
|
+
),
|
|
17334
|
+
children: [
|
|
17335
|
+
!isCustomer && !sameSenderAsPrev && /* @__PURE__ */ jsxs("span", { className: "mb-1 inline-flex items-center gap-1 rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-600", children: [
|
|
17336
|
+
/* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
|
|
17337
|
+
"AI"
|
|
17338
|
+
] }),
|
|
17339
|
+
msg.reply_to && /* @__PURE__ */ jsxs(
|
|
17340
|
+
"div",
|
|
17341
|
+
{
|
|
17342
|
+
className: cn(
|
|
17343
|
+
"mb-1.5 flex gap-1.5 rounded-lg px-2 py-1 text-xs",
|
|
17344
|
+
isCustomer ? "bg-background/15" : "bg-muted"
|
|
17345
|
+
),
|
|
17346
|
+
children: [
|
|
17347
|
+
/* @__PURE__ */ jsx(
|
|
17348
|
+
"span",
|
|
17349
|
+
{
|
|
17350
|
+
className: cn(
|
|
17351
|
+
"w-[3px] shrink-0 self-stretch rounded-full",
|
|
17352
|
+
isCustomer ? "bg-background/40" : "bg-muted-foreground/40"
|
|
17353
|
+
)
|
|
17354
|
+
}
|
|
17355
|
+
),
|
|
17356
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate opacity-80", children: msg.reply_to.preview })
|
|
17357
|
+
]
|
|
17358
|
+
}
|
|
17359
|
+
),
|
|
17360
|
+
msg.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-1.5", msg.content && "mb-1.5"), children: msg.attachments.map(
|
|
17361
|
+
(attachment, index) => attachment.mime_type?.startsWith("image/") ? /* @__PURE__ */ jsx("a", { href: attachment.url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx(
|
|
17362
|
+
"img",
|
|
17363
|
+
{
|
|
17364
|
+
src: attachment.url,
|
|
17365
|
+
alt: attachment.filename ?? "Photo",
|
|
17366
|
+
loading: "lazy",
|
|
17367
|
+
className: "max-h-48 max-w-full rounded-lg object-cover"
|
|
17368
|
+
}
|
|
17369
|
+
) }, index) : /* @__PURE__ */ jsx(
|
|
17370
|
+
"a",
|
|
17371
|
+
{
|
|
17372
|
+
href: attachment.url,
|
|
17373
|
+
target: "_blank",
|
|
17374
|
+
rel: "noreferrer",
|
|
17375
|
+
className: "truncate text-xs underline",
|
|
17376
|
+
children: attachment.filename ?? "Attachment"
|
|
17377
|
+
},
|
|
17378
|
+
index
|
|
17379
|
+
)
|
|
17380
|
+
) }),
|
|
17381
|
+
isCustomer ? msg.content && /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: msg.content }) : /* @__PURE__ */ jsx(
|
|
17382
|
+
ChatMarkdown,
|
|
17383
|
+
{
|
|
17384
|
+
text: msg.content,
|
|
17385
|
+
className: "[&_p]:my-0 [&_p+p]:mt-1.5 [&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:my-1 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:my-0.5 [&_h1]:text-sm [&_h1]:font-bold [&_h2]:text-sm [&_h2]:font-bold [&_h3]:text-sm [&_h3]:font-semibold [&_hr]:my-2 [&_table]:my-1 [&_table]:w-full [&_th]:pb-1 [&_th]:text-left [&_th]:text-[10px] [&_th]:font-semibold [&_th]:uppercase [&_td]:py-1 [&_td:last-child]:text-right [&_td:last-child]:font-semibold [&_td:last-child]:tabular-nums [&_a]:underline"
|
|
17386
|
+
}
|
|
17387
|
+
)
|
|
17388
|
+
]
|
|
17389
|
+
}
|
|
17390
|
+
)
|
|
17391
|
+
]
|
|
17050
17392
|
}
|
|
17051
|
-
)
|
|
17393
|
+
),
|
|
17394
|
+
reactions && reactions.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("mt-0.5 flex gap-1", isCustomer ? "justify-end" : "justify-start"), children: reactions.map((reaction) => /* @__PURE__ */ jsx(
|
|
17395
|
+
"span",
|
|
17396
|
+
{
|
|
17397
|
+
className: "rounded-full border border-border bg-background px-1.5 py-0.5 text-xs shadow-sm",
|
|
17398
|
+
children: reaction.content
|
|
17399
|
+
},
|
|
17400
|
+
reaction.id
|
|
17401
|
+
)) }),
|
|
17402
|
+
msg.id === seenMessageId && /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-right text-[10px] font-medium text-muted-foreground", children: "Seen" })
|
|
17052
17403
|
] }, msg.id);
|
|
17053
17404
|
}),
|
|
17054
17405
|
isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
|
|
@@ -17061,10 +17412,24 @@ function ChatInput({
|
|
|
17061
17412
|
placeholder,
|
|
17062
17413
|
isSending,
|
|
17063
17414
|
onSend,
|
|
17415
|
+
onAttach,
|
|
17064
17416
|
className
|
|
17065
17417
|
}) {
|
|
17066
17418
|
const [value, setValue] = useState("");
|
|
17419
|
+
const [uploading, setUploading] = useState(false);
|
|
17067
17420
|
const textareaRef = useRef(null);
|
|
17421
|
+
const fileRef = useRef(null);
|
|
17422
|
+
const handleAttach = useCallback(
|
|
17423
|
+
async (file) => {
|
|
17424
|
+
setUploading(true);
|
|
17425
|
+
try {
|
|
17426
|
+
await onAttach(file);
|
|
17427
|
+
} finally {
|
|
17428
|
+
setUploading(false);
|
|
17429
|
+
}
|
|
17430
|
+
},
|
|
17431
|
+
[onAttach]
|
|
17432
|
+
);
|
|
17068
17433
|
const handleSend = useCallback(() => {
|
|
17069
17434
|
const trimmed = value.trim();
|
|
17070
17435
|
if (!trimmed || isSending) return;
|
|
@@ -17098,6 +17463,37 @@ function ChatInput({
|
|
|
17098
17463
|
className
|
|
17099
17464
|
),
|
|
17100
17465
|
children: [
|
|
17466
|
+
/* @__PURE__ */ jsx(
|
|
17467
|
+
"input",
|
|
17468
|
+
{
|
|
17469
|
+
ref: fileRef,
|
|
17470
|
+
type: "file",
|
|
17471
|
+
accept: "image/*",
|
|
17472
|
+
className: "hidden",
|
|
17473
|
+
"aria-hidden": "true",
|
|
17474
|
+
tabIndex: -1,
|
|
17475
|
+
onChange: (e) => {
|
|
17476
|
+
const file = e.target.files?.[0];
|
|
17477
|
+
e.target.value = "";
|
|
17478
|
+
if (file) void handleAttach(file);
|
|
17479
|
+
}
|
|
17480
|
+
}
|
|
17481
|
+
),
|
|
17482
|
+
/* @__PURE__ */ jsx(
|
|
17483
|
+
"button",
|
|
17484
|
+
{
|
|
17485
|
+
type: "button",
|
|
17486
|
+
"aria-label": "Attach a photo",
|
|
17487
|
+
disabled: uploading,
|
|
17488
|
+
onClick: () => fileRef.current?.click(),
|
|
17489
|
+
className: cn(
|
|
17490
|
+
"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
|
|
17491
|
+
"bg-muted text-muted-foreground transition-colors hover:text-foreground",
|
|
17492
|
+
uploading && "animate-pulse"
|
|
17493
|
+
),
|
|
17494
|
+
children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-[18px] w-[18px]" })
|
|
17495
|
+
}
|
|
17496
|
+
),
|
|
17101
17497
|
/* @__PURE__ */ jsx(
|
|
17102
17498
|
"div",
|
|
17103
17499
|
{
|
|
@@ -17200,6 +17596,25 @@ function SendIcon({ className }) {
|
|
|
17200
17596
|
}
|
|
17201
17597
|
);
|
|
17202
17598
|
}
|
|
17599
|
+
function ImageIcon({ className }) {
|
|
17600
|
+
return /* @__PURE__ */ jsxs(
|
|
17601
|
+
"svg",
|
|
17602
|
+
{
|
|
17603
|
+
className,
|
|
17604
|
+
viewBox: "0 0 24 24",
|
|
17605
|
+
fill: "none",
|
|
17606
|
+
stroke: "currentColor",
|
|
17607
|
+
strokeWidth: 2,
|
|
17608
|
+
strokeLinecap: "round",
|
|
17609
|
+
strokeLinejoin: "round",
|
|
17610
|
+
children: [
|
|
17611
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
17612
|
+
/* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
17613
|
+
/* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
|
|
17614
|
+
]
|
|
17615
|
+
}
|
|
17616
|
+
);
|
|
17617
|
+
}
|
|
17203
17618
|
function SparkleIcon({ className }) {
|
|
17204
17619
|
return /* @__PURE__ */ jsx(
|
|
17205
17620
|
"svg",
|
|
@@ -17214,4 +17629,1825 @@ function SparkleIcon({ className }) {
|
|
|
17214
17629
|
);
|
|
17215
17630
|
}
|
|
17216
17631
|
|
|
17217
|
-
|
|
17632
|
+
// src/react/genui/catalog.ts
|
|
17633
|
+
function genUICatalog(registry) {
|
|
17634
|
+
return new Map(
|
|
17635
|
+
registry.components.map((component) => [
|
|
17636
|
+
component.name,
|
|
17637
|
+
{
|
|
17638
|
+
params: component.params.map((param) => ({
|
|
17639
|
+
name: param.name,
|
|
17640
|
+
required: param.required ?? false
|
|
17641
|
+
}))
|
|
17642
|
+
}
|
|
17643
|
+
])
|
|
17644
|
+
);
|
|
17645
|
+
}
|
|
17646
|
+
|
|
17647
|
+
// src/genui/lang/parser/ast.ts
|
|
17648
|
+
function isRuntimeExpr(node) {
|
|
17649
|
+
switch (node.k) {
|
|
17650
|
+
case "StateRef":
|
|
17651
|
+
case "RuntimeRef":
|
|
17652
|
+
case "BinOp":
|
|
17653
|
+
case "UnaryOp":
|
|
17654
|
+
case "Ternary":
|
|
17655
|
+
case "Member":
|
|
17656
|
+
case "Index":
|
|
17657
|
+
case "Assign":
|
|
17658
|
+
return true;
|
|
17659
|
+
default:
|
|
17660
|
+
return false;
|
|
17661
|
+
}
|
|
17662
|
+
}
|
|
17663
|
+
var AST_KINDS = /* @__PURE__ */ new Set([
|
|
17664
|
+
"Comp",
|
|
17665
|
+
"Ref",
|
|
17666
|
+
"StateRef",
|
|
17667
|
+
"RuntimeRef",
|
|
17668
|
+
"BinOp",
|
|
17669
|
+
"UnaryOp",
|
|
17670
|
+
"Ternary",
|
|
17671
|
+
"Member",
|
|
17672
|
+
"Index",
|
|
17673
|
+
"Assign",
|
|
17674
|
+
"Str",
|
|
17675
|
+
"Num",
|
|
17676
|
+
"Bool",
|
|
17677
|
+
"Null",
|
|
17678
|
+
"Arr",
|
|
17679
|
+
"Obj",
|
|
17680
|
+
"Ph"
|
|
17681
|
+
]);
|
|
17682
|
+
function isASTNode(value) {
|
|
17683
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
17684
|
+
return AST_KINDS.has(value.k);
|
|
17685
|
+
}
|
|
17686
|
+
function walkAST(node, visit) {
|
|
17687
|
+
const walk = (current) => {
|
|
17688
|
+
visit(current);
|
|
17689
|
+
switch (current.k) {
|
|
17690
|
+
case "Comp":
|
|
17691
|
+
current.args.forEach(walk);
|
|
17692
|
+
Object.values(current.mappedProps ?? {}).forEach(walk);
|
|
17693
|
+
break;
|
|
17694
|
+
case "Arr":
|
|
17695
|
+
current.els.forEach(walk);
|
|
17696
|
+
break;
|
|
17697
|
+
case "Obj":
|
|
17698
|
+
current.entries.forEach(([, value]) => walk(value));
|
|
17699
|
+
break;
|
|
17700
|
+
case "BinOp":
|
|
17701
|
+
walk(current.left);
|
|
17702
|
+
walk(current.right);
|
|
17703
|
+
break;
|
|
17704
|
+
case "UnaryOp":
|
|
17705
|
+
walk(current.operand);
|
|
17706
|
+
break;
|
|
17707
|
+
case "Ternary":
|
|
17708
|
+
walk(current.cond);
|
|
17709
|
+
walk(current.then);
|
|
17710
|
+
walk(current.else);
|
|
17711
|
+
break;
|
|
17712
|
+
case "Member":
|
|
17713
|
+
walk(current.obj);
|
|
17714
|
+
break;
|
|
17715
|
+
case "Index":
|
|
17716
|
+
walk(current.obj);
|
|
17717
|
+
walk(current.index);
|
|
17718
|
+
break;
|
|
17719
|
+
case "Assign":
|
|
17720
|
+
walk(current.value);
|
|
17721
|
+
break;
|
|
17722
|
+
}
|
|
17723
|
+
};
|
|
17724
|
+
walk(node);
|
|
17725
|
+
}
|
|
17726
|
+
|
|
17727
|
+
// src/genui/lang/parser/builtins.ts
|
|
17728
|
+
function resolveField(obj, path) {
|
|
17729
|
+
if (!path || obj == null) return void 0;
|
|
17730
|
+
if (!path.includes(".")) return obj[path];
|
|
17731
|
+
let cur = obj;
|
|
17732
|
+
for (const p of path.split(".")) {
|
|
17733
|
+
if (cur == null) return void 0;
|
|
17734
|
+
cur = cur[p];
|
|
17735
|
+
}
|
|
17736
|
+
return cur;
|
|
17737
|
+
}
|
|
17738
|
+
function toNumber2(val) {
|
|
17739
|
+
if (typeof val === "number") return val;
|
|
17740
|
+
if (typeof val === "string") {
|
|
17741
|
+
const n = Number(val);
|
|
17742
|
+
return isNaN(n) ? 0 : n;
|
|
17743
|
+
}
|
|
17744
|
+
if (typeof val === "boolean") return val ? 1 : 0;
|
|
17745
|
+
return 0;
|
|
17746
|
+
}
|
|
17747
|
+
var BUILTINS = {
|
|
17748
|
+
Count: {
|
|
17749
|
+
name: "Count",
|
|
17750
|
+
signature: "Count(array) \u2192 number",
|
|
17751
|
+
description: "Returns array length",
|
|
17752
|
+
fn: (arr) => Array.isArray(arr) ? arr.length : 0
|
|
17753
|
+
},
|
|
17754
|
+
First: {
|
|
17755
|
+
name: "First",
|
|
17756
|
+
signature: "First(array) \u2192 element",
|
|
17757
|
+
description: "Returns first element of array",
|
|
17758
|
+
fn: (arr) => Array.isArray(arr) ? arr[0] ?? null : null
|
|
17759
|
+
},
|
|
17760
|
+
Last: {
|
|
17761
|
+
name: "Last",
|
|
17762
|
+
signature: "Last(array) \u2192 element",
|
|
17763
|
+
description: "Returns last element of array",
|
|
17764
|
+
fn: (arr) => Array.isArray(arr) ? arr[arr.length - 1] ?? null : null
|
|
17765
|
+
},
|
|
17766
|
+
Sum: {
|
|
17767
|
+
name: "Sum",
|
|
17768
|
+
signature: "Sum(numbers[]) \u2192 number",
|
|
17769
|
+
description: "Sum of numeric array",
|
|
17770
|
+
fn: (arr) => Array.isArray(arr) ? arr.reduce((a, b) => a + toNumber2(b), 0) : 0
|
|
17771
|
+
},
|
|
17772
|
+
Avg: {
|
|
17773
|
+
name: "Avg",
|
|
17774
|
+
signature: "Avg(numbers[]) \u2192 number",
|
|
17775
|
+
description: "Average of numeric array",
|
|
17776
|
+
fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((a, b) => a + toNumber2(b), 0) / arr.length : 0
|
|
17777
|
+
},
|
|
17778
|
+
Min: {
|
|
17779
|
+
name: "Min",
|
|
17780
|
+
signature: "Min(numbers[]) \u2192 number",
|
|
17781
|
+
description: "Minimum value in array",
|
|
17782
|
+
fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.min(acc, toNumber2(b)), toNumber2(arr[0])) : 0
|
|
17783
|
+
},
|
|
17784
|
+
Max: {
|
|
17785
|
+
name: "Max",
|
|
17786
|
+
signature: "Max(numbers[]) \u2192 number",
|
|
17787
|
+
description: "Maximum value in array",
|
|
17788
|
+
fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.max(acc, toNumber2(b)), toNumber2(arr[0])) : 0
|
|
17789
|
+
},
|
|
17790
|
+
Sort: {
|
|
17791
|
+
name: "Sort",
|
|
17792
|
+
signature: "Sort(array, field, direction?) \u2192 sorted array",
|
|
17793
|
+
description: 'Sort array by field. Direction: "asc" (default) or "desc"',
|
|
17794
|
+
fn: (arr, field, dir) => {
|
|
17795
|
+
if (!Array.isArray(arr)) return arr;
|
|
17796
|
+
const f = String(field ?? "");
|
|
17797
|
+
const desc = String(dir ?? "asc") === "desc";
|
|
17798
|
+
return [...arr].sort((a, b) => {
|
|
17799
|
+
const av = f ? resolveField(a, f) : a;
|
|
17800
|
+
const bv = f ? resolveField(b, f) : b;
|
|
17801
|
+
const aIsNumeric = typeof av === "number" || typeof av === "string" && !isNaN(Number(av)) && av !== "";
|
|
17802
|
+
const bIsNumeric = typeof bv === "number" || typeof bv === "string" && !isNaN(Number(bv)) && bv !== "";
|
|
17803
|
+
if (aIsNumeric && bIsNumeric) {
|
|
17804
|
+
const diff = toNumber2(av) - toNumber2(bv);
|
|
17805
|
+
return desc ? -diff : diff;
|
|
17806
|
+
}
|
|
17807
|
+
const cmp = String(av ?? "").localeCompare(String(bv ?? ""));
|
|
17808
|
+
return desc ? -cmp : cmp;
|
|
17809
|
+
});
|
|
17810
|
+
}
|
|
17811
|
+
},
|
|
17812
|
+
Filter: {
|
|
17813
|
+
name: "Filter",
|
|
17814
|
+
signature: 'Filter(array, field, operator: "==" | "!=" | ">" | "<" | ">=" | "<=" | "contains", value) \u2192 filtered array',
|
|
17815
|
+
description: "Filter array by field value",
|
|
17816
|
+
fn: (arr, field, op, value) => {
|
|
17817
|
+
if (!Array.isArray(arr)) return [];
|
|
17818
|
+
const f = String(field ?? "");
|
|
17819
|
+
const o = String(op ?? "==");
|
|
17820
|
+
return arr.filter((item) => {
|
|
17821
|
+
const v = f ? resolveField(item, f) : item;
|
|
17822
|
+
switch (o) {
|
|
17823
|
+
case "==":
|
|
17824
|
+
return v == value;
|
|
17825
|
+
case "!=":
|
|
17826
|
+
return v != value;
|
|
17827
|
+
case ">":
|
|
17828
|
+
return toNumber2(v) > toNumber2(value);
|
|
17829
|
+
case "<":
|
|
17830
|
+
return toNumber2(v) < toNumber2(value);
|
|
17831
|
+
case ">=":
|
|
17832
|
+
return toNumber2(v) >= toNumber2(value);
|
|
17833
|
+
case "<=":
|
|
17834
|
+
return toNumber2(v) <= toNumber2(value);
|
|
17835
|
+
case "contains":
|
|
17836
|
+
return String(v ?? "").includes(String(value ?? ""));
|
|
17837
|
+
default:
|
|
17838
|
+
return false;
|
|
17839
|
+
}
|
|
17840
|
+
});
|
|
17841
|
+
}
|
|
17842
|
+
},
|
|
17843
|
+
Round: {
|
|
17844
|
+
name: "Round",
|
|
17845
|
+
signature: "Round(number, decimals?) \u2192 number",
|
|
17846
|
+
description: "Round to N decimal places (default 0)",
|
|
17847
|
+
fn: (n, decimals) => {
|
|
17848
|
+
const num = toNumber2(n);
|
|
17849
|
+
const d = decimals != null ? toNumber2(decimals) : 0;
|
|
17850
|
+
const factor = Math.pow(10, d);
|
|
17851
|
+
return Math.round(num * factor) / factor;
|
|
17852
|
+
}
|
|
17853
|
+
},
|
|
17854
|
+
Abs: {
|
|
17855
|
+
name: "Abs",
|
|
17856
|
+
signature: "Abs(number) \u2192 number",
|
|
17857
|
+
description: "Absolute value",
|
|
17858
|
+
fn: (n) => Math.abs(toNumber2(n))
|
|
17859
|
+
},
|
|
17860
|
+
Floor: {
|
|
17861
|
+
name: "Floor",
|
|
17862
|
+
signature: "Floor(number) \u2192 number",
|
|
17863
|
+
description: "Round down to nearest integer",
|
|
17864
|
+
fn: (n) => Math.floor(toNumber2(n))
|
|
17865
|
+
},
|
|
17866
|
+
Ceil: {
|
|
17867
|
+
name: "Ceil",
|
|
17868
|
+
signature: "Ceil(number) \u2192 number",
|
|
17869
|
+
description: "Round up to nearest integer",
|
|
17870
|
+
fn: (n) => Math.ceil(toNumber2(n))
|
|
17871
|
+
}
|
|
17872
|
+
};
|
|
17873
|
+
var LAZY_BUILTINS = /* @__PURE__ */ new Set(["Each"]);
|
|
17874
|
+
var ACTION_STEPS = {
|
|
17875
|
+
Run: "run",
|
|
17876
|
+
ToAssistant: "continue_conversation",
|
|
17877
|
+
OpenUrl: "open_url",
|
|
17878
|
+
Set: "set",
|
|
17879
|
+
Reset: "reset"
|
|
17880
|
+
};
|
|
17881
|
+
var ACTION_NAMES = /* @__PURE__ */ new Set(["Action", ...Object.keys(ACTION_STEPS)]);
|
|
17882
|
+
var BUILTIN_NAMES = /* @__PURE__ */ new Set([
|
|
17883
|
+
...Object.keys(BUILTINS),
|
|
17884
|
+
...LAZY_BUILTINS,
|
|
17885
|
+
...ACTION_NAMES
|
|
17886
|
+
]);
|
|
17887
|
+
function isBuiltin(name) {
|
|
17888
|
+
return BUILTIN_NAMES.has(name);
|
|
17889
|
+
}
|
|
17890
|
+
var RESERVED_CALLS = { Query: "Query", Mutation: "Mutation" };
|
|
17891
|
+
function isReservedCall(name) {
|
|
17892
|
+
return name in RESERVED_CALLS;
|
|
17893
|
+
}
|
|
17894
|
+
|
|
17895
|
+
// src/genui/lang/parser/expressions.ts
|
|
17896
|
+
var PREC_TERNARY = 1;
|
|
17897
|
+
var PREC_OR = 2;
|
|
17898
|
+
var PREC_AND = 3;
|
|
17899
|
+
var PREC_EQ = 4;
|
|
17900
|
+
var PREC_CMP = 5;
|
|
17901
|
+
var PREC_ADD = 6;
|
|
17902
|
+
var PREC_MUL = 7;
|
|
17903
|
+
var PREC_UNARY = 8;
|
|
17904
|
+
var PREC_MEMBER = 9;
|
|
17905
|
+
function parseExpression(tokens) {
|
|
17906
|
+
let pos = 0;
|
|
17907
|
+
const cur = () => tokens[pos] ?? { t: 13 /* EOF */ };
|
|
17908
|
+
const adv = () => {
|
|
17909
|
+
const tok = cur();
|
|
17910
|
+
pos++;
|
|
17911
|
+
return tok;
|
|
17912
|
+
};
|
|
17913
|
+
const eat = (kind) => {
|
|
17914
|
+
if (cur().t === kind) pos++;
|
|
17915
|
+
};
|
|
17916
|
+
function getInfixPrec(tok) {
|
|
17917
|
+
switch (tok.t) {
|
|
17918
|
+
case 34 /* Question */:
|
|
17919
|
+
return PREC_TERNARY;
|
|
17920
|
+
case 32 /* Or */:
|
|
17921
|
+
return PREC_OR;
|
|
17922
|
+
case 31 /* And */:
|
|
17923
|
+
return PREC_AND;
|
|
17924
|
+
case 25 /* EqEq */:
|
|
17925
|
+
case 26 /* NotEq */:
|
|
17926
|
+
return PREC_EQ;
|
|
17927
|
+
case 27 /* Greater */:
|
|
17928
|
+
case 28 /* Less */:
|
|
17929
|
+
case 29 /* GreaterEq */:
|
|
17930
|
+
case 30 /* LessEq */:
|
|
17931
|
+
return PREC_CMP;
|
|
17932
|
+
case 20 /* Plus */:
|
|
17933
|
+
case 21 /* Minus */:
|
|
17934
|
+
return PREC_ADD;
|
|
17935
|
+
case 22 /* Star */:
|
|
17936
|
+
case 23 /* Slash */:
|
|
17937
|
+
case 24 /* Percent */:
|
|
17938
|
+
return PREC_MUL;
|
|
17939
|
+
case 19 /* Dot */:
|
|
17940
|
+
case 3 /* LBrack */:
|
|
17941
|
+
return PREC_MEMBER;
|
|
17942
|
+
default:
|
|
17943
|
+
return 0;
|
|
17944
|
+
}
|
|
17945
|
+
}
|
|
17946
|
+
function parseExpr(minPrec = 0) {
|
|
17947
|
+
let left = parsePrefix();
|
|
17948
|
+
while (getInfixPrec(cur()) > minPrec) {
|
|
17949
|
+
left = parseInfix(left);
|
|
17950
|
+
}
|
|
17951
|
+
return left;
|
|
17952
|
+
}
|
|
17953
|
+
function parsePrefix() {
|
|
17954
|
+
const tok = cur();
|
|
17955
|
+
if (tok.t === 14 /* Str */) {
|
|
17956
|
+
adv();
|
|
17957
|
+
return { k: "Str", v: tok.v };
|
|
17958
|
+
}
|
|
17959
|
+
if (tok.t === 15 /* Num */) {
|
|
17960
|
+
adv();
|
|
17961
|
+
return { k: "Num", v: tok.v };
|
|
17962
|
+
}
|
|
17963
|
+
if (tok.t === 10 /* True */) {
|
|
17964
|
+
adv();
|
|
17965
|
+
return { k: "Bool", v: true };
|
|
17966
|
+
}
|
|
17967
|
+
if (tok.t === 11 /* False */) {
|
|
17968
|
+
adv();
|
|
17969
|
+
return { k: "Bool", v: false };
|
|
17970
|
+
}
|
|
17971
|
+
if (tok.t === 12 /* Null */) {
|
|
17972
|
+
adv();
|
|
17973
|
+
return { k: "Null" };
|
|
17974
|
+
}
|
|
17975
|
+
if (tok.t === 3 /* LBrack */) return parseArr();
|
|
17976
|
+
if (tok.t === 5 /* LBrace */) return parseObj();
|
|
17977
|
+
if (tok.t === 18 /* StateVar */) {
|
|
17978
|
+
const name = tok.v;
|
|
17979
|
+
adv();
|
|
17980
|
+
if (cur().t === 9 /* Equals */) {
|
|
17981
|
+
adv();
|
|
17982
|
+
const value = parseExpr(0);
|
|
17983
|
+
return { k: "Assign", target: name, value };
|
|
17984
|
+
}
|
|
17985
|
+
return { k: "StateRef", n: name };
|
|
17986
|
+
}
|
|
17987
|
+
if (tok.t === 17 /* Type */) {
|
|
17988
|
+
const name = tok.v;
|
|
17989
|
+
if (tokens[pos + 1]?.t === 1 /* LParen */ && (!isBuiltin(name) || name === "Action"))
|
|
17990
|
+
return parseComp();
|
|
17991
|
+
adv();
|
|
17992
|
+
return { k: "Ref", n: name };
|
|
17993
|
+
}
|
|
17994
|
+
if (tok.t === 35 /* BuiltinCall */) {
|
|
17995
|
+
if (tokens[pos + 1]?.t === 1 /* LParen */) return parseComp();
|
|
17996
|
+
adv();
|
|
17997
|
+
return { k: "Ref", n: tok.v };
|
|
17998
|
+
}
|
|
17999
|
+
if (tok.t === 16 /* Ident */) {
|
|
18000
|
+
adv();
|
|
18001
|
+
return { k: "Ref", n: tok.v };
|
|
18002
|
+
}
|
|
18003
|
+
if (tok.t === 33 /* Not */) {
|
|
18004
|
+
adv();
|
|
18005
|
+
return { k: "UnaryOp", op: "!", operand: parseExpr(PREC_UNARY) };
|
|
18006
|
+
}
|
|
18007
|
+
if (tok.t === 21 /* Minus */) {
|
|
18008
|
+
adv();
|
|
18009
|
+
return { k: "UnaryOp", op: "-", operand: parseExpr(PREC_UNARY) };
|
|
18010
|
+
}
|
|
18011
|
+
if (tok.t === 1 /* LParen */) {
|
|
18012
|
+
adv();
|
|
18013
|
+
const inner = parseExpr(0);
|
|
18014
|
+
eat(2 /* RParen */);
|
|
18015
|
+
return inner;
|
|
18016
|
+
}
|
|
18017
|
+
adv();
|
|
18018
|
+
return { k: "Null" };
|
|
18019
|
+
}
|
|
18020
|
+
function parseInfix(left) {
|
|
18021
|
+
const tok = cur();
|
|
18022
|
+
if (tok.t === 20 /* Plus */) {
|
|
18023
|
+
adv();
|
|
18024
|
+
return { k: "BinOp", op: "+", left, right: parseExpr(PREC_ADD) };
|
|
18025
|
+
}
|
|
18026
|
+
if (tok.t === 21 /* Minus */) {
|
|
18027
|
+
adv();
|
|
18028
|
+
return { k: "BinOp", op: "-", left, right: parseExpr(PREC_ADD) };
|
|
18029
|
+
}
|
|
18030
|
+
if (tok.t === 22 /* Star */) {
|
|
18031
|
+
adv();
|
|
18032
|
+
return { k: "BinOp", op: "*", left, right: parseExpr(PREC_MUL) };
|
|
18033
|
+
}
|
|
18034
|
+
if (tok.t === 23 /* Slash */) {
|
|
18035
|
+
adv();
|
|
18036
|
+
return { k: "BinOp", op: "/", left, right: parseExpr(PREC_MUL) };
|
|
18037
|
+
}
|
|
18038
|
+
if (tok.t === 24 /* Percent */) {
|
|
18039
|
+
adv();
|
|
18040
|
+
return { k: "BinOp", op: "%", left, right: parseExpr(PREC_MUL) };
|
|
18041
|
+
}
|
|
18042
|
+
if (tok.t === 25 /* EqEq */) {
|
|
18043
|
+
adv();
|
|
18044
|
+
return { k: "BinOp", op: "==", left, right: parseExpr(PREC_EQ) };
|
|
18045
|
+
}
|
|
18046
|
+
if (tok.t === 26 /* NotEq */) {
|
|
18047
|
+
adv();
|
|
18048
|
+
return { k: "BinOp", op: "!=", left, right: parseExpr(PREC_EQ) };
|
|
18049
|
+
}
|
|
18050
|
+
if (tok.t === 27 /* Greater */) {
|
|
18051
|
+
adv();
|
|
18052
|
+
return { k: "BinOp", op: ">", left, right: parseExpr(PREC_CMP) };
|
|
18053
|
+
}
|
|
18054
|
+
if (tok.t === 28 /* Less */) {
|
|
18055
|
+
adv();
|
|
18056
|
+
return { k: "BinOp", op: "<", left, right: parseExpr(PREC_CMP) };
|
|
18057
|
+
}
|
|
18058
|
+
if (tok.t === 29 /* GreaterEq */) {
|
|
18059
|
+
adv();
|
|
18060
|
+
return { k: "BinOp", op: ">=", left, right: parseExpr(PREC_CMP) };
|
|
18061
|
+
}
|
|
18062
|
+
if (tok.t === 30 /* LessEq */) {
|
|
18063
|
+
adv();
|
|
18064
|
+
return { k: "BinOp", op: "<=", left, right: parseExpr(PREC_CMP) };
|
|
18065
|
+
}
|
|
18066
|
+
if (tok.t === 31 /* And */) {
|
|
18067
|
+
adv();
|
|
18068
|
+
return { k: "BinOp", op: "&&", left, right: parseExpr(PREC_AND) };
|
|
18069
|
+
}
|
|
18070
|
+
if (tok.t === 32 /* Or */) {
|
|
18071
|
+
adv();
|
|
18072
|
+
return { k: "BinOp", op: "||", left, right: parseExpr(PREC_OR) };
|
|
18073
|
+
}
|
|
18074
|
+
if (tok.t === 34 /* Question */) {
|
|
18075
|
+
adv();
|
|
18076
|
+
const then = parseExpr(0);
|
|
18077
|
+
eat(8 /* Colon */);
|
|
18078
|
+
const els = parseExpr(0);
|
|
18079
|
+
return { k: "Ternary", cond: left, then, else: els };
|
|
18080
|
+
}
|
|
18081
|
+
if (tok.t === 19 /* Dot */) {
|
|
18082
|
+
adv();
|
|
18083
|
+
const fieldTok = cur();
|
|
18084
|
+
const field = fieldTok.t === 16 /* Ident */ || fieldTok.t === 17 /* Type */ || fieldTok.t === 14 /* Str */ || fieldTok.t === 15 /* Num */ ? (adv(), String(fieldTok.v)) : fieldTok.t === 18 /* StateVar */ ? (adv(), fieldTok.v.replace(/^\$/, "")) : (adv(), "?");
|
|
18085
|
+
return { k: "Member", obj: left, field };
|
|
18086
|
+
}
|
|
18087
|
+
if (tok.t === 3 /* LBrack */) {
|
|
18088
|
+
adv();
|
|
18089
|
+
const index = parseExpr(0);
|
|
18090
|
+
eat(4 /* RBrack */);
|
|
18091
|
+
return { k: "Index", obj: left, index };
|
|
18092
|
+
}
|
|
18093
|
+
return left;
|
|
18094
|
+
}
|
|
18095
|
+
function parseComp() {
|
|
18096
|
+
const name = cur().v;
|
|
18097
|
+
adv();
|
|
18098
|
+
eat(1 /* LParen */);
|
|
18099
|
+
const args = [];
|
|
18100
|
+
while (cur().t !== 2 /* RParen */ && cur().t !== 13 /* EOF */) {
|
|
18101
|
+
args.push(parseExpr(0));
|
|
18102
|
+
if (cur().t === 7 /* Comma */) adv();
|
|
18103
|
+
}
|
|
18104
|
+
eat(2 /* RParen */);
|
|
18105
|
+
return { k: "Comp", name, args };
|
|
18106
|
+
}
|
|
18107
|
+
function parseArr() {
|
|
18108
|
+
adv();
|
|
18109
|
+
const els = [];
|
|
18110
|
+
while (cur().t !== 4 /* RBrack */ && cur().t !== 13 /* EOF */) {
|
|
18111
|
+
els.push(parseExpr(0));
|
|
18112
|
+
if (cur().t === 7 /* Comma */) adv();
|
|
18113
|
+
}
|
|
18114
|
+
eat(4 /* RBrack */);
|
|
18115
|
+
return { k: "Arr", els };
|
|
18116
|
+
}
|
|
18117
|
+
function parseObj() {
|
|
18118
|
+
adv();
|
|
18119
|
+
const entries = [];
|
|
18120
|
+
while (cur().t !== 6 /* RBrace */ && cur().t !== 13 /* EOF */) {
|
|
18121
|
+
const kt = cur();
|
|
18122
|
+
const key = kt.t === 16 /* Ident */ || kt.t === 14 /* Str */ || kt.t === 17 /* Type */ || kt.t === 15 /* Num */ ? (adv(), String(kt.v)) : kt.t === 18 /* StateVar */ ? (adv(), kt.v.replace(/^\$/, "")) : (adv(), "?");
|
|
18123
|
+
eat(8 /* Colon */);
|
|
18124
|
+
entries.push([key, parseExpr(0)]);
|
|
18125
|
+
if (cur().t === 7 /* Comma */) adv();
|
|
18126
|
+
}
|
|
18127
|
+
eat(6 /* RBrace */);
|
|
18128
|
+
return { k: "Obj", entries };
|
|
18129
|
+
}
|
|
18130
|
+
return parseExpr(0);
|
|
18131
|
+
}
|
|
18132
|
+
|
|
18133
|
+
// src/genui/lang/parser/lexer.ts
|
|
18134
|
+
function tokenize(src) {
|
|
18135
|
+
const tokens = [];
|
|
18136
|
+
let i = 0;
|
|
18137
|
+
const n = src.length;
|
|
18138
|
+
while (i < n) {
|
|
18139
|
+
while (i < n && (src[i] === " " || src[i] === " " || src[i] === "\r")) i++;
|
|
18140
|
+
if (i >= n) break;
|
|
18141
|
+
const c = src[i];
|
|
18142
|
+
if (c === "\n") {
|
|
18143
|
+
tokens.push({ t: 0 /* Newline */ });
|
|
18144
|
+
i++;
|
|
18145
|
+
continue;
|
|
18146
|
+
}
|
|
18147
|
+
if (c === "(") {
|
|
18148
|
+
tokens.push({ t: 1 /* LParen */ });
|
|
18149
|
+
i++;
|
|
18150
|
+
continue;
|
|
18151
|
+
}
|
|
18152
|
+
if (c === ")") {
|
|
18153
|
+
tokens.push({ t: 2 /* RParen */ });
|
|
18154
|
+
i++;
|
|
18155
|
+
continue;
|
|
18156
|
+
}
|
|
18157
|
+
if (c === "[") {
|
|
18158
|
+
tokens.push({ t: 3 /* LBrack */ });
|
|
18159
|
+
i++;
|
|
18160
|
+
continue;
|
|
18161
|
+
}
|
|
18162
|
+
if (c === "]") {
|
|
18163
|
+
tokens.push({ t: 4 /* RBrack */ });
|
|
18164
|
+
i++;
|
|
18165
|
+
continue;
|
|
18166
|
+
}
|
|
18167
|
+
if (c === "{") {
|
|
18168
|
+
tokens.push({ t: 5 /* LBrace */ });
|
|
18169
|
+
i++;
|
|
18170
|
+
continue;
|
|
18171
|
+
}
|
|
18172
|
+
if (c === "}") {
|
|
18173
|
+
tokens.push({ t: 6 /* RBrace */ });
|
|
18174
|
+
i++;
|
|
18175
|
+
continue;
|
|
18176
|
+
}
|
|
18177
|
+
if (c === ",") {
|
|
18178
|
+
tokens.push({ t: 7 /* Comma */ });
|
|
18179
|
+
i++;
|
|
18180
|
+
continue;
|
|
18181
|
+
}
|
|
18182
|
+
if (c === ":") {
|
|
18183
|
+
tokens.push({ t: 8 /* Colon */ });
|
|
18184
|
+
i++;
|
|
18185
|
+
continue;
|
|
18186
|
+
}
|
|
18187
|
+
if (c === "=") {
|
|
18188
|
+
if (i + 1 < n && src[i + 1] === "=") {
|
|
18189
|
+
tokens.push({ t: 25 /* EqEq */ });
|
|
18190
|
+
i += 2;
|
|
18191
|
+
} else {
|
|
18192
|
+
tokens.push({ t: 9 /* Equals */ });
|
|
18193
|
+
i++;
|
|
18194
|
+
}
|
|
18195
|
+
continue;
|
|
18196
|
+
}
|
|
18197
|
+
if (c === "!") {
|
|
18198
|
+
if (i + 1 < n && src[i + 1] === "=") {
|
|
18199
|
+
tokens.push({ t: 26 /* NotEq */ });
|
|
18200
|
+
i += 2;
|
|
18201
|
+
} else {
|
|
18202
|
+
tokens.push({ t: 33 /* Not */ });
|
|
18203
|
+
i++;
|
|
18204
|
+
}
|
|
18205
|
+
continue;
|
|
18206
|
+
}
|
|
18207
|
+
if (c === ">") {
|
|
18208
|
+
if (i + 1 < n && src[i + 1] === "=") {
|
|
18209
|
+
tokens.push({ t: 29 /* GreaterEq */ });
|
|
18210
|
+
i += 2;
|
|
18211
|
+
} else {
|
|
18212
|
+
tokens.push({ t: 27 /* Greater */ });
|
|
18213
|
+
i++;
|
|
18214
|
+
}
|
|
18215
|
+
continue;
|
|
18216
|
+
}
|
|
18217
|
+
if (c === "<") {
|
|
18218
|
+
if (i + 1 < n && src[i + 1] === "=") {
|
|
18219
|
+
tokens.push({ t: 30 /* LessEq */ });
|
|
18220
|
+
i += 2;
|
|
18221
|
+
} else {
|
|
18222
|
+
tokens.push({ t: 28 /* Less */ });
|
|
18223
|
+
i++;
|
|
18224
|
+
}
|
|
18225
|
+
continue;
|
|
18226
|
+
}
|
|
18227
|
+
if (c === "&") {
|
|
18228
|
+
if (i + 1 < n && src[i + 1] === "&") {
|
|
18229
|
+
tokens.push({ t: 31 /* And */ });
|
|
18230
|
+
i += 2;
|
|
18231
|
+
} else {
|
|
18232
|
+
tokens.push({ t: 31 /* And */ });
|
|
18233
|
+
i++;
|
|
18234
|
+
}
|
|
18235
|
+
continue;
|
|
18236
|
+
}
|
|
18237
|
+
if (c === "|") {
|
|
18238
|
+
if (i + 1 < n && src[i + 1] === "|") {
|
|
18239
|
+
tokens.push({ t: 32 /* Or */ });
|
|
18240
|
+
i += 2;
|
|
18241
|
+
} else {
|
|
18242
|
+
tokens.push({ t: 32 /* Or */ });
|
|
18243
|
+
i++;
|
|
18244
|
+
}
|
|
18245
|
+
continue;
|
|
18246
|
+
}
|
|
18247
|
+
if (c === ".") {
|
|
18248
|
+
tokens.push({ t: 19 /* Dot */ });
|
|
18249
|
+
i++;
|
|
18250
|
+
continue;
|
|
18251
|
+
}
|
|
18252
|
+
if (c === "?") {
|
|
18253
|
+
tokens.push({ t: 34 /* Question */ });
|
|
18254
|
+
i++;
|
|
18255
|
+
continue;
|
|
18256
|
+
}
|
|
18257
|
+
if (c === "+") {
|
|
18258
|
+
tokens.push({ t: 20 /* Plus */ });
|
|
18259
|
+
i++;
|
|
18260
|
+
continue;
|
|
18261
|
+
}
|
|
18262
|
+
if (c === "*") {
|
|
18263
|
+
tokens.push({ t: 22 /* Star */ });
|
|
18264
|
+
i++;
|
|
18265
|
+
continue;
|
|
18266
|
+
}
|
|
18267
|
+
if (c === "/") {
|
|
18268
|
+
tokens.push({ t: 23 /* Slash */ });
|
|
18269
|
+
i++;
|
|
18270
|
+
continue;
|
|
18271
|
+
}
|
|
18272
|
+
if (c === "%") {
|
|
18273
|
+
tokens.push({ t: 24 /* Percent */ });
|
|
18274
|
+
i++;
|
|
18275
|
+
continue;
|
|
18276
|
+
}
|
|
18277
|
+
if (c === '"') {
|
|
18278
|
+
const start = i;
|
|
18279
|
+
i++;
|
|
18280
|
+
let isClosed = false;
|
|
18281
|
+
while (i < n) {
|
|
18282
|
+
if (src[i] === "\\") {
|
|
18283
|
+
i += 2;
|
|
18284
|
+
} else if (src[i] === '"') {
|
|
18285
|
+
i++;
|
|
18286
|
+
isClosed = true;
|
|
18287
|
+
break;
|
|
18288
|
+
} else {
|
|
18289
|
+
i++;
|
|
18290
|
+
}
|
|
18291
|
+
}
|
|
18292
|
+
const rawString = src.slice(start, i);
|
|
18293
|
+
try {
|
|
18294
|
+
const validJsonString = isClosed ? rawString : rawString + '"';
|
|
18295
|
+
tokens.push({ t: 14 /* Str */, v: JSON.parse(validJsonString) });
|
|
18296
|
+
} catch {
|
|
18297
|
+
const stripped = rawString.replace(/^"|"$/g, "");
|
|
18298
|
+
tokens.push({ t: 14 /* Str */, v: stripped });
|
|
18299
|
+
}
|
|
18300
|
+
continue;
|
|
18301
|
+
}
|
|
18302
|
+
if (c === "'") {
|
|
18303
|
+
i++;
|
|
18304
|
+
let result = "";
|
|
18305
|
+
while (i < n) {
|
|
18306
|
+
if (src[i] === "\\") {
|
|
18307
|
+
i++;
|
|
18308
|
+
if (i < n) {
|
|
18309
|
+
const esc = src[i];
|
|
18310
|
+
if (esc === "'") result += "'";
|
|
18311
|
+
else if (esc === "\\") result += "\\";
|
|
18312
|
+
else if (esc === "n") result += "\n";
|
|
18313
|
+
else if (esc === "t") result += " ";
|
|
18314
|
+
else result += esc;
|
|
18315
|
+
i++;
|
|
18316
|
+
}
|
|
18317
|
+
} else if (src[i] === "'") {
|
|
18318
|
+
i++;
|
|
18319
|
+
break;
|
|
18320
|
+
} else {
|
|
18321
|
+
result += src[i];
|
|
18322
|
+
i++;
|
|
18323
|
+
}
|
|
18324
|
+
}
|
|
18325
|
+
tokens.push({ t: 14 /* Str */, v: result });
|
|
18326
|
+
continue;
|
|
18327
|
+
}
|
|
18328
|
+
if (c === "-") {
|
|
18329
|
+
const prev = tokens.length > 0 ? tokens[tokens.length - 1] : null;
|
|
18330
|
+
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 */);
|
|
18331
|
+
if (!afterValue && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") ; else {
|
|
18332
|
+
tokens.push({ t: 21 /* Minus */ });
|
|
18333
|
+
i++;
|
|
18334
|
+
continue;
|
|
18335
|
+
}
|
|
18336
|
+
}
|
|
18337
|
+
const isDigit = c >= "0" && c <= "9";
|
|
18338
|
+
const isNegDigit = c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9";
|
|
18339
|
+
if (isDigit || isNegDigit) {
|
|
18340
|
+
const start = i;
|
|
18341
|
+
if (src[i] === "-") i++;
|
|
18342
|
+
while (i < n && src[i] >= "0" && src[i] <= "9") i++;
|
|
18343
|
+
if (i < n && src[i] === "." && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") {
|
|
18344
|
+
i++;
|
|
18345
|
+
while (i < n && src[i] >= "0" && src[i] <= "9") i++;
|
|
18346
|
+
}
|
|
18347
|
+
if (i < n && (src[i] === "e" || src[i] === "E")) {
|
|
18348
|
+
i++;
|
|
18349
|
+
if (i < n && (src[i] === "+" || src[i] === "-")) i++;
|
|
18350
|
+
while (i < n && src[i] >= "0" && src[i] <= "9") i++;
|
|
18351
|
+
}
|
|
18352
|
+
tokens.push({ t: 15 /* Num */, v: +src.slice(start, i) });
|
|
18353
|
+
continue;
|
|
18354
|
+
}
|
|
18355
|
+
if (c === "$" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
|
|
18356
|
+
const start = i;
|
|
18357
|
+
i++;
|
|
18358
|
+
while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
|
|
18359
|
+
i++;
|
|
18360
|
+
tokens.push({ t: 18 /* StateVar */, v: src.slice(start, i) });
|
|
18361
|
+
continue;
|
|
18362
|
+
}
|
|
18363
|
+
const isAlpha = c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
|
|
18364
|
+
if (isAlpha) {
|
|
18365
|
+
const start = i;
|
|
18366
|
+
while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
|
|
18367
|
+
i++;
|
|
18368
|
+
const word = src.slice(start, i);
|
|
18369
|
+
if (word === "true") {
|
|
18370
|
+
tokens.push({ t: 10 /* True */ });
|
|
18371
|
+
continue;
|
|
18372
|
+
}
|
|
18373
|
+
if (word === "false") {
|
|
18374
|
+
tokens.push({ t: 11 /* False */ });
|
|
18375
|
+
continue;
|
|
18376
|
+
}
|
|
18377
|
+
if (word === "null") {
|
|
18378
|
+
tokens.push({ t: 12 /* Null */ });
|
|
18379
|
+
continue;
|
|
18380
|
+
}
|
|
18381
|
+
const kind = c >= "A" && c <= "Z" ? 17 /* Type */ : 16 /* Ident */;
|
|
18382
|
+
tokens.push({ t: kind, v: word });
|
|
18383
|
+
continue;
|
|
18384
|
+
}
|
|
18385
|
+
if (c === "@" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
|
|
18386
|
+
i++;
|
|
18387
|
+
const start = i;
|
|
18388
|
+
while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
|
|
18389
|
+
i++;
|
|
18390
|
+
tokens.push({ t: 35 /* BuiltinCall */, v: src.slice(start, i) });
|
|
18391
|
+
continue;
|
|
18392
|
+
}
|
|
18393
|
+
i++;
|
|
18394
|
+
}
|
|
18395
|
+
tokens.push({ t: 13 /* EOF */ });
|
|
18396
|
+
return tokens;
|
|
18397
|
+
}
|
|
18398
|
+
|
|
18399
|
+
// src/genui/lang/parser/types.ts
|
|
18400
|
+
function isElementNode(value) {
|
|
18401
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
18402
|
+
const node = value;
|
|
18403
|
+
return node.type === "element" && typeof node.typeName === "string" && typeof node.props === "object" && node.props !== null && typeof node.partial === "boolean";
|
|
18404
|
+
}
|
|
18405
|
+
|
|
18406
|
+
// src/genui/lang/parser/materialize.ts
|
|
18407
|
+
function containsDynamicValue(v) {
|
|
18408
|
+
if (v == null || typeof v !== "object") return false;
|
|
18409
|
+
if (isASTNode(v)) return true;
|
|
18410
|
+
if (Array.isArray(v)) return v.some(containsDynamicValue);
|
|
18411
|
+
if (isElementNode(v)) {
|
|
18412
|
+
return Object.values(v.props).some(containsDynamicValue);
|
|
18413
|
+
}
|
|
18414
|
+
const obj = v;
|
|
18415
|
+
return Object.values(obj).some(containsDynamicValue);
|
|
18416
|
+
}
|
|
18417
|
+
function resolveRef(name, ctx, mode) {
|
|
18418
|
+
if (ctx.visited.has(name)) {
|
|
18419
|
+
ctx.unres.push(name);
|
|
18420
|
+
return mode === "expr" ? { k: "Ph", n: name } : null;
|
|
18421
|
+
}
|
|
18422
|
+
if (!ctx.syms.has(name)) {
|
|
18423
|
+
ctx.unres.push(name);
|
|
18424
|
+
return mode === "expr" ? { k: "Ph", n: name } : null;
|
|
18425
|
+
}
|
|
18426
|
+
const target = ctx.syms.get(name);
|
|
18427
|
+
ctx.unreached?.delete(name);
|
|
18428
|
+
if (target.k === "Comp" && isReservedCall(target.name)) {
|
|
18429
|
+
const refType = target.name === RESERVED_CALLS.Mutation ? "mutation" : "query";
|
|
18430
|
+
return { k: "RuntimeRef", n: name, refType };
|
|
18431
|
+
}
|
|
18432
|
+
ctx.visited.add(name);
|
|
18433
|
+
const prevStatementId = ctx.currentStatementId;
|
|
18434
|
+
ctx.currentStatementId = name;
|
|
18435
|
+
try {
|
|
18436
|
+
const result = mode === "value" ? materializeValue(target, ctx) : materializeExpr(target, ctx);
|
|
18437
|
+
if (mode === "value" && isElementNode(result)) {
|
|
18438
|
+
result.statementId = name;
|
|
18439
|
+
}
|
|
18440
|
+
return result;
|
|
18441
|
+
} finally {
|
|
18442
|
+
ctx.currentStatementId = prevStatementId;
|
|
18443
|
+
ctx.visited.delete(name);
|
|
18444
|
+
}
|
|
18445
|
+
}
|
|
18446
|
+
function materializeLazyBuiltin(node, ctx, scopedRefs) {
|
|
18447
|
+
if (!LAZY_BUILTINS.has(node.name) || node.args.length < 3) return null;
|
|
18448
|
+
const varArg = node.args[1];
|
|
18449
|
+
const varName = varArg.k === "Ref" ? varArg.n : varArg.k === "Str" ? varArg.v : null;
|
|
18450
|
+
if (!varName) return null;
|
|
18451
|
+
const nextScopedRefs = new Set(scopedRefs);
|
|
18452
|
+
nextScopedRefs.add(varName);
|
|
18453
|
+
const recursedArgs = node.args.map(
|
|
18454
|
+
(a, i) => i === 1 ? a : materializeExprInternal(a, ctx, nextScopedRefs)
|
|
18455
|
+
);
|
|
18456
|
+
return { ...node, args: recursedArgs };
|
|
18457
|
+
}
|
|
18458
|
+
function materializeExprInternal(node, ctx, scopedRefs) {
|
|
18459
|
+
switch (node.k) {
|
|
18460
|
+
case "Ref":
|
|
18461
|
+
return scopedRefs.has(node.n) ? node : resolveRef(node.n, ctx, "expr");
|
|
18462
|
+
case "Ph":
|
|
18463
|
+
return node;
|
|
18464
|
+
case "Comp": {
|
|
18465
|
+
const lazy2 = materializeLazyBuiltin(node, ctx, scopedRefs);
|
|
18466
|
+
if (lazy2) return lazy2;
|
|
18467
|
+
const recursedArgs = node.args.map((a) => materializeExprInternal(a, ctx, scopedRefs));
|
|
18468
|
+
if (isBuiltin(node.name) || isReservedCall(node.name)) {
|
|
18469
|
+
return { ...node, args: recursedArgs };
|
|
18470
|
+
}
|
|
18471
|
+
const def = ctx.cat?.get(node.name);
|
|
18472
|
+
if (def) {
|
|
18473
|
+
const mappedProps = {};
|
|
18474
|
+
for (let i = 0; i < def.params.length && i < recursedArgs.length; i++) {
|
|
18475
|
+
mappedProps[def.params[i].name] = recursedArgs[i];
|
|
18476
|
+
}
|
|
18477
|
+
return { ...node, args: recursedArgs, mappedProps };
|
|
18478
|
+
}
|
|
18479
|
+
ctx.errors.push({
|
|
18480
|
+
code: "unknown-component",
|
|
18481
|
+
component: node.name,
|
|
18482
|
+
path: "",
|
|
18483
|
+
message: `Unknown component "${node.name}" \u2014 not found in catalog or builtins`,
|
|
18484
|
+
statementId: ctx.currentStatementId
|
|
18485
|
+
});
|
|
18486
|
+
return { ...node, args: recursedArgs };
|
|
18487
|
+
}
|
|
18488
|
+
case "Arr":
|
|
18489
|
+
return { ...node, els: node.els.map((e) => materializeExprInternal(e, ctx, scopedRefs)) };
|
|
18490
|
+
case "Obj":
|
|
18491
|
+
return {
|
|
18492
|
+
...node,
|
|
18493
|
+
entries: node.entries.map(
|
|
18494
|
+
([k, v]) => [k, materializeExprInternal(v, ctx, scopedRefs)]
|
|
18495
|
+
)
|
|
18496
|
+
};
|
|
18497
|
+
case "BinOp":
|
|
18498
|
+
return {
|
|
18499
|
+
...node,
|
|
18500
|
+
left: materializeExprInternal(node.left, ctx, scopedRefs),
|
|
18501
|
+
right: materializeExprInternal(node.right, ctx, scopedRefs)
|
|
18502
|
+
};
|
|
18503
|
+
case "UnaryOp":
|
|
18504
|
+
return { ...node, operand: materializeExprInternal(node.operand, ctx, scopedRefs) };
|
|
18505
|
+
case "Ternary":
|
|
18506
|
+
return {
|
|
18507
|
+
...node,
|
|
18508
|
+
cond: materializeExprInternal(node.cond, ctx, scopedRefs),
|
|
18509
|
+
then: materializeExprInternal(node.then, ctx, scopedRefs),
|
|
18510
|
+
else: materializeExprInternal(node.else, ctx, scopedRefs)
|
|
18511
|
+
};
|
|
18512
|
+
case "Member":
|
|
18513
|
+
return { ...node, obj: materializeExprInternal(node.obj, ctx, scopedRefs) };
|
|
18514
|
+
case "Index":
|
|
18515
|
+
return {
|
|
18516
|
+
...node,
|
|
18517
|
+
obj: materializeExprInternal(node.obj, ctx, scopedRefs),
|
|
18518
|
+
index: materializeExprInternal(node.index, ctx, scopedRefs)
|
|
18519
|
+
};
|
|
18520
|
+
case "Assign":
|
|
18521
|
+
return { ...node, value: materializeExprInternal(node.value, ctx, scopedRefs) };
|
|
18522
|
+
// Literals, StateRef, RuntimeRef — pass through unchanged
|
|
18523
|
+
default:
|
|
18524
|
+
return node;
|
|
18525
|
+
}
|
|
18526
|
+
}
|
|
18527
|
+
function materializeExpr(node, ctx) {
|
|
18528
|
+
return materializeExprInternal(node, ctx, /* @__PURE__ */ new Set());
|
|
18529
|
+
}
|
|
18530
|
+
function materializeValue(node, ctx) {
|
|
18531
|
+
switch (node.k) {
|
|
18532
|
+
// ── Ref resolution ───────────────────────────────────────────────────
|
|
18533
|
+
case "Ref":
|
|
18534
|
+
return resolveRef(node.n, ctx, "value");
|
|
18535
|
+
// ── Literals → plain values ──────────────────────────────────────────
|
|
18536
|
+
case "Str":
|
|
18537
|
+
return node.v;
|
|
18538
|
+
case "Num":
|
|
18539
|
+
return node.v;
|
|
18540
|
+
case "Bool":
|
|
18541
|
+
return node.v;
|
|
18542
|
+
case "Null":
|
|
18543
|
+
return null;
|
|
18544
|
+
case "Ph":
|
|
18545
|
+
return null;
|
|
18546
|
+
// ── Collections ──────────────────────────────────────────────────────
|
|
18547
|
+
case "Arr": {
|
|
18548
|
+
const items = [];
|
|
18549
|
+
for (const e of node.els) {
|
|
18550
|
+
if (e.k === "Ph") continue;
|
|
18551
|
+
const value = materializeValue(e, ctx);
|
|
18552
|
+
if (value === null && (e.k === "Comp" || e.k === "Ref")) continue;
|
|
18553
|
+
items.push(value);
|
|
18554
|
+
}
|
|
18555
|
+
return items;
|
|
18556
|
+
}
|
|
18557
|
+
case "Obj": {
|
|
18558
|
+
const o = {};
|
|
18559
|
+
for (const [k, v] of node.entries) o[k] = materializeValue(v, ctx);
|
|
18560
|
+
return o;
|
|
18561
|
+
}
|
|
18562
|
+
// ── Component nodes ──────────────────────────────────────────────────
|
|
18563
|
+
case "Comp": {
|
|
18564
|
+
const { name, args } = node;
|
|
18565
|
+
if (isBuiltin(name)) {
|
|
18566
|
+
const lazy2 = materializeLazyBuiltin(node, ctx, /* @__PURE__ */ new Set());
|
|
18567
|
+
if (lazy2) return lazy2;
|
|
18568
|
+
return { ...node, args: args.map((a) => materializeExpr(a, ctx)) };
|
|
18569
|
+
}
|
|
18570
|
+
if (isReservedCall(name)) {
|
|
18571
|
+
ctx.errors.push({
|
|
18572
|
+
code: "inline-reserved",
|
|
18573
|
+
component: name,
|
|
18574
|
+
path: "",
|
|
18575
|
+
message: `${name}() must be declared as a top-level statement, not used inline as a value`,
|
|
18576
|
+
statementId: ctx.currentStatementId
|
|
18577
|
+
});
|
|
18578
|
+
return null;
|
|
18579
|
+
}
|
|
18580
|
+
const def = ctx.cat?.get(name);
|
|
18581
|
+
const props = {};
|
|
18582
|
+
if (def) {
|
|
18583
|
+
for (let i = 0; i < def.params.length && i < args.length; i++) {
|
|
18584
|
+
props[def.params[i].name] = materializeValue(args[i], ctx);
|
|
18585
|
+
}
|
|
18586
|
+
if (args.length > def.params.length) {
|
|
18587
|
+
const excessCount = args.length - def.params.length;
|
|
18588
|
+
ctx.errors.push({
|
|
18589
|
+
code: "excess-args",
|
|
18590
|
+
component: name,
|
|
18591
|
+
path: "",
|
|
18592
|
+
message: `${name} takes ${def.params.length} arg(s), got ${args.length} (${excessCount} excess dropped)`,
|
|
18593
|
+
statementId: ctx.currentStatementId
|
|
18594
|
+
});
|
|
18595
|
+
}
|
|
18596
|
+
const missingRequired = def.params.filter(
|
|
18597
|
+
(p) => p.required && (!(p.name in props) || props[p.name] === null)
|
|
18598
|
+
);
|
|
18599
|
+
if (missingRequired.length) {
|
|
18600
|
+
const stillInvalid = missingRequired.filter((p) => {
|
|
18601
|
+
if (p.defaultValue !== void 0) {
|
|
18602
|
+
props[p.name] = p.defaultValue;
|
|
18603
|
+
return false;
|
|
18604
|
+
}
|
|
18605
|
+
return true;
|
|
18606
|
+
});
|
|
18607
|
+
if (stillInvalid.length) {
|
|
18608
|
+
for (const p of stillInvalid) {
|
|
18609
|
+
const isNull = p.name in props;
|
|
18610
|
+
ctx.errors.push({
|
|
18611
|
+
code: isNull ? "null-required" : "missing-required",
|
|
18612
|
+
component: name,
|
|
18613
|
+
path: `/${p.name}`,
|
|
18614
|
+
message: isNull ? `required field "${p.name}" cannot be null` : `missing required field "${p.name}"`,
|
|
18615
|
+
statementId: ctx.currentStatementId
|
|
18616
|
+
});
|
|
18617
|
+
}
|
|
18618
|
+
return null;
|
|
18619
|
+
}
|
|
18620
|
+
}
|
|
18621
|
+
} else if (!isBuiltin(name) && !isReservedCall(name)) {
|
|
18622
|
+
ctx.errors.push({
|
|
18623
|
+
code: "unknown-component",
|
|
18624
|
+
component: name,
|
|
18625
|
+
path: "",
|
|
18626
|
+
message: `Unknown component "${name}" \u2014 not found in catalog or builtins`,
|
|
18627
|
+
statementId: ctx.currentStatementId
|
|
18628
|
+
});
|
|
18629
|
+
return null;
|
|
18630
|
+
}
|
|
18631
|
+
const hasDynamicProps = Object.values(props).some((v) => containsDynamicValue(v));
|
|
18632
|
+
return { type: "element", typeName: name, props, partial: ctx.partial, hasDynamicProps };
|
|
18633
|
+
}
|
|
18634
|
+
// ── Runtime expression nodes → preserve as ASTNode, normalize children ─
|
|
18635
|
+
default: {
|
|
18636
|
+
if (isRuntimeExpr(node)) {
|
|
18637
|
+
return materializeExpr(node, ctx);
|
|
18638
|
+
}
|
|
18639
|
+
return node;
|
|
18640
|
+
}
|
|
18641
|
+
}
|
|
18642
|
+
}
|
|
18643
|
+
|
|
18644
|
+
// src/genui/lang/parser/statements.ts
|
|
18645
|
+
function autoClose(input) {
|
|
18646
|
+
const stack = [];
|
|
18647
|
+
let inStr = false;
|
|
18648
|
+
let esc = false;
|
|
18649
|
+
for (let i = 0; i < input.length; i++) {
|
|
18650
|
+
const c = input[i];
|
|
18651
|
+
if (esc) {
|
|
18652
|
+
esc = false;
|
|
18653
|
+
continue;
|
|
18654
|
+
}
|
|
18655
|
+
if (c === "\\" && inStr) {
|
|
18656
|
+
esc = true;
|
|
18657
|
+
continue;
|
|
18658
|
+
}
|
|
18659
|
+
if (inStr) {
|
|
18660
|
+
if (c === inStr) inStr = false;
|
|
18661
|
+
continue;
|
|
18662
|
+
}
|
|
18663
|
+
if (c === '"' || c === "'") {
|
|
18664
|
+
inStr = c;
|
|
18665
|
+
continue;
|
|
18666
|
+
}
|
|
18667
|
+
if (c === "(" || c === "[" || c === "{") stack.push(c);
|
|
18668
|
+
else if (c === ")" && stack[stack.length - 1] === "(") stack.pop();
|
|
18669
|
+
else if (c === "]" && stack[stack.length - 1] === "[") stack.pop();
|
|
18670
|
+
else if (c === "}" && stack[stack.length - 1] === "{") stack.pop();
|
|
18671
|
+
}
|
|
18672
|
+
const wasIncomplete = !!inStr || stack.length > 0;
|
|
18673
|
+
if (!wasIncomplete) return { text: input, wasIncomplete: false };
|
|
18674
|
+
let out = input;
|
|
18675
|
+
if (inStr) {
|
|
18676
|
+
if (esc) out += "\\";
|
|
18677
|
+
out += inStr;
|
|
18678
|
+
}
|
|
18679
|
+
for (let j = stack.length - 1; j >= 0; j--)
|
|
18680
|
+
out += stack[j] === "(" ? ")" : stack[j] === "[" ? "]" : "}";
|
|
18681
|
+
return { text: out, wasIncomplete: true };
|
|
18682
|
+
}
|
|
18683
|
+
function split(tokens) {
|
|
18684
|
+
const stmts = [];
|
|
18685
|
+
let pos = 0;
|
|
18686
|
+
while (pos < tokens.length) {
|
|
18687
|
+
while (pos < tokens.length && tokens[pos].t === 0 /* Newline */) pos++;
|
|
18688
|
+
if (pos >= tokens.length || tokens[pos].t === 13 /* EOF */) break;
|
|
18689
|
+
const tok = tokens[pos];
|
|
18690
|
+
if (tok.t !== 16 /* Ident */ && tok.t !== 17 /* Type */ && tok.t !== 18 /* StateVar */) {
|
|
18691
|
+
while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
|
|
18692
|
+
continue;
|
|
18693
|
+
}
|
|
18694
|
+
const id = tok.v;
|
|
18695
|
+
const idTokenType = tok.t;
|
|
18696
|
+
pos++;
|
|
18697
|
+
if (pos >= tokens.length || tokens[pos].t !== 9 /* Equals */) {
|
|
18698
|
+
while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
|
|
18699
|
+
continue;
|
|
18700
|
+
}
|
|
18701
|
+
pos++;
|
|
18702
|
+
const expr = [];
|
|
18703
|
+
let depth = 0;
|
|
18704
|
+
let ternaryDepth = 0;
|
|
18705
|
+
while (pos < tokens.length && tokens[pos].t !== 13 /* EOF */) {
|
|
18706
|
+
const tt = tokens[pos].t;
|
|
18707
|
+
if (tt === 0 /* Newline */ && depth <= 0 && ternaryDepth <= 0) {
|
|
18708
|
+
let peek = pos + 1;
|
|
18709
|
+
while (peek < tokens.length && tokens[peek].t === 0 /* Newline */) peek++;
|
|
18710
|
+
const nextT = peek < tokens.length ? tokens[peek].t : 13 /* EOF */;
|
|
18711
|
+
if (nextT === 34 /* Question */ || nextT === 8 /* Colon */ && ternaryDepth > 0) {
|
|
18712
|
+
pos++;
|
|
18713
|
+
continue;
|
|
18714
|
+
}
|
|
18715
|
+
break;
|
|
18716
|
+
}
|
|
18717
|
+
if (tt === 0 /* Newline */) {
|
|
18718
|
+
pos++;
|
|
18719
|
+
continue;
|
|
18720
|
+
}
|
|
18721
|
+
if (tt === 1 /* LParen */ || tt === 3 /* LBrack */ || tt === 5 /* LBrace */) depth++;
|
|
18722
|
+
else if ((tt === 2 /* RParen */ || tt === 4 /* RBrack */ || tt === 6 /* RBrace */) && depth > 0) depth--;
|
|
18723
|
+
else if (tt === 34 /* Question */ && depth === 0) ternaryDepth++;
|
|
18724
|
+
else if (tt === 8 /* Colon */ && depth === 0 && ternaryDepth > 0) ternaryDepth--;
|
|
18725
|
+
expr.push(tokens[pos++]);
|
|
18726
|
+
}
|
|
18727
|
+
if (expr.length) stmts.push({ id, idTokenType, tokens: expr });
|
|
18728
|
+
}
|
|
18729
|
+
return stmts;
|
|
18730
|
+
}
|
|
18731
|
+
|
|
18732
|
+
// src/genui/lang/parser/parser.ts
|
|
18733
|
+
function emptyResult(incomplete = true) {
|
|
18734
|
+
return {
|
|
18735
|
+
root: null,
|
|
18736
|
+
meta: {
|
|
18737
|
+
incomplete,
|
|
18738
|
+
unresolved: [],
|
|
18739
|
+
orphaned: [],
|
|
18740
|
+
statementCount: 0,
|
|
18741
|
+
errors: []
|
|
18742
|
+
},
|
|
18743
|
+
stateDeclarations: {},
|
|
18744
|
+
queryStatements: [],
|
|
18745
|
+
mutationStatements: []
|
|
18746
|
+
};
|
|
18747
|
+
}
|
|
18748
|
+
function collectQueryDeps(node) {
|
|
18749
|
+
if (!isASTNode(node)) return [];
|
|
18750
|
+
const refs = /* @__PURE__ */ new Set();
|
|
18751
|
+
walkAST(node, (current) => {
|
|
18752
|
+
if (current.k === "StateRef") refs.add(current.n);
|
|
18753
|
+
});
|
|
18754
|
+
return [...refs];
|
|
18755
|
+
}
|
|
18756
|
+
function classifyStatement(raw, expr) {
|
|
18757
|
+
if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Query) {
|
|
18758
|
+
const deps = collectQueryDeps(expr.args[1]);
|
|
18759
|
+
return {
|
|
18760
|
+
kind: "query",
|
|
18761
|
+
id: raw.id,
|
|
18762
|
+
call: { callee: RESERVED_CALLS.Query, args: expr.args },
|
|
18763
|
+
expr,
|
|
18764
|
+
deps: deps.length > 0 ? deps : void 0
|
|
18765
|
+
};
|
|
18766
|
+
}
|
|
18767
|
+
if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Mutation) {
|
|
18768
|
+
return {
|
|
18769
|
+
kind: "mutation",
|
|
18770
|
+
id: raw.id,
|
|
18771
|
+
call: { callee: RESERVED_CALLS.Mutation, args: expr.args },
|
|
18772
|
+
expr
|
|
18773
|
+
};
|
|
18774
|
+
}
|
|
18775
|
+
if (raw.idTokenType === 18 /* StateVar */) {
|
|
18776
|
+
return { kind: "state", id: raw.id, init: expr };
|
|
18777
|
+
}
|
|
18778
|
+
return { kind: "value", id: raw.id, expr };
|
|
18779
|
+
}
|
|
18780
|
+
function extractStatements(stmts, ctx) {
|
|
18781
|
+
const stateDeclarations = {};
|
|
18782
|
+
const queryStatements = [];
|
|
18783
|
+
const mutationStatements = [];
|
|
18784
|
+
for (const stmt of stmts) {
|
|
18785
|
+
switch (stmt.kind) {
|
|
18786
|
+
case "state":
|
|
18787
|
+
stateDeclarations[stmt.id] = materializeValue(stmt.init, ctx);
|
|
18788
|
+
break;
|
|
18789
|
+
case "query":
|
|
18790
|
+
queryStatements.push({
|
|
18791
|
+
statementId: stmt.id,
|
|
18792
|
+
toolAST: stmt.call.args[0] ?? null,
|
|
18793
|
+
argsAST: stmt.call.args[1] ?? null,
|
|
18794
|
+
defaultsAST: stmt.call.args[2] ?? null,
|
|
18795
|
+
refreshAST: stmt.call.args[3] ?? null,
|
|
18796
|
+
deps: stmt.deps,
|
|
18797
|
+
complete: true
|
|
18798
|
+
});
|
|
18799
|
+
break;
|
|
18800
|
+
case "mutation":
|
|
18801
|
+
mutationStatements.push({
|
|
18802
|
+
statementId: stmt.id,
|
|
18803
|
+
toolAST: stmt.call.args[0] ?? null,
|
|
18804
|
+
argsAST: stmt.call.args[1] ?? null
|
|
18805
|
+
});
|
|
18806
|
+
break;
|
|
18807
|
+
}
|
|
18808
|
+
}
|
|
18809
|
+
for (const stmt of stmts) {
|
|
18810
|
+
const nodes = stmt.kind === "state" ? [stmt.init] : stmt.kind === "value" ? [stmt.expr] : stmt.kind === "query" || stmt.kind === "mutation" ? stmt.call.args : [];
|
|
18811
|
+
for (const node of nodes) {
|
|
18812
|
+
for (const dep of collectQueryDeps(node)) {
|
|
18813
|
+
if (!(dep in stateDeclarations)) {
|
|
18814
|
+
stateDeclarations[dep] = null;
|
|
18815
|
+
}
|
|
18816
|
+
}
|
|
18817
|
+
}
|
|
18818
|
+
}
|
|
18819
|
+
return { stateDeclarations, queryStatements, mutationStatements };
|
|
18820
|
+
}
|
|
18821
|
+
var DEFAULT_ROOT_STATEMENT_ID = "root";
|
|
18822
|
+
function isComponentStatement(stmt) {
|
|
18823
|
+
return stmt.kind === "value" && stmt.expr.k === "Comp" && !isBuiltin(stmt.expr.name) && stmt.expr.name !== RESERVED_CALLS.Query && stmt.expr.name !== RESERVED_CALLS.Mutation;
|
|
18824
|
+
}
|
|
18825
|
+
function pickEntryId(stmtMap, typedStmts, firstId, rootName) {
|
|
18826
|
+
if (stmtMap.has(DEFAULT_ROOT_STATEMENT_ID)) return DEFAULT_ROOT_STATEMENT_ID;
|
|
18827
|
+
if (rootName && stmtMap.has(rootName)) return rootName;
|
|
18828
|
+
const preferredComponent = rootName ? typedStmts.find((stmt) => isComponentStatement(stmt) && stmt.expr.name === rootName) : void 0;
|
|
18829
|
+
if (preferredComponent) return preferredComponent.id;
|
|
18830
|
+
const firstComponent = typedStmts.find(isComponentStatement);
|
|
18831
|
+
return firstComponent?.id ?? firstId;
|
|
18832
|
+
}
|
|
18833
|
+
function buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtCount, cat, rootName) {
|
|
18834
|
+
const entryId = pickEntryId(stmtMap, typedStmts, firstId, rootName);
|
|
18835
|
+
if (!stmtMap.has(entryId)) return emptyResult(wasIncomplete);
|
|
18836
|
+
const syms = /* @__PURE__ */ new Map();
|
|
18837
|
+
for (const [id, stmt] of stmtMap) {
|
|
18838
|
+
syms.set(id, stmt.kind === "state" ? stmt.init : stmt.expr);
|
|
18839
|
+
}
|
|
18840
|
+
const unres = [];
|
|
18841
|
+
const errors = [];
|
|
18842
|
+
const unreached = /* @__PURE__ */ new Set();
|
|
18843
|
+
for (const [id, stmt] of stmtMap) {
|
|
18844
|
+
if (id === entryId) continue;
|
|
18845
|
+
if (stmt.kind === "state" || stmt.kind === "query" || stmt.kind === "mutation") continue;
|
|
18846
|
+
unreached.add(id);
|
|
18847
|
+
}
|
|
18848
|
+
const ctx = {
|
|
18849
|
+
syms,
|
|
18850
|
+
cat,
|
|
18851
|
+
errors,
|
|
18852
|
+
unres,
|
|
18853
|
+
visited: /* @__PURE__ */ new Set(),
|
|
18854
|
+
partial: wasIncomplete,
|
|
18855
|
+
currentStatementId: entryId,
|
|
18856
|
+
unreached
|
|
18857
|
+
};
|
|
18858
|
+
const materialized = materializeValue(syms.get(entryId), ctx);
|
|
18859
|
+
const root = isElementNode(materialized) ? materialized : null;
|
|
18860
|
+
if (root) root.statementId = entryId;
|
|
18861
|
+
const { stateDeclarations, queryStatements, mutationStatements } = extractStatements(
|
|
18862
|
+
typedStmts,
|
|
18863
|
+
ctx
|
|
18864
|
+
);
|
|
18865
|
+
const orphaned = [...unreached];
|
|
18866
|
+
return {
|
|
18867
|
+
root,
|
|
18868
|
+
meta: {
|
|
18869
|
+
incomplete: wasIncomplete,
|
|
18870
|
+
unresolved: unres,
|
|
18871
|
+
orphaned,
|
|
18872
|
+
statementCount: stmtCount,
|
|
18873
|
+
errors
|
|
18874
|
+
},
|
|
18875
|
+
stateDeclarations,
|
|
18876
|
+
queryStatements,
|
|
18877
|
+
mutationStatements
|
|
18878
|
+
};
|
|
18879
|
+
}
|
|
18880
|
+
function skipString(input, start) {
|
|
18881
|
+
if (input[start] !== '"') return start;
|
|
18882
|
+
let i = start + 1;
|
|
18883
|
+
while (i < input.length) {
|
|
18884
|
+
const c = input[i];
|
|
18885
|
+
if (c === "\\") {
|
|
18886
|
+
i += 2;
|
|
18887
|
+
} else if (c === '"') {
|
|
18888
|
+
return i + 1;
|
|
18889
|
+
} else {
|
|
18890
|
+
i++;
|
|
18891
|
+
}
|
|
18892
|
+
}
|
|
18893
|
+
return i;
|
|
18894
|
+
}
|
|
18895
|
+
function stripFences(input) {
|
|
18896
|
+
const blocks = [];
|
|
18897
|
+
let i = 0;
|
|
18898
|
+
while (i < input.length) {
|
|
18899
|
+
let fenceStart = -1;
|
|
18900
|
+
while (i < input.length) {
|
|
18901
|
+
const nextI = skipString(input, i);
|
|
18902
|
+
if (nextI > i) {
|
|
18903
|
+
i = nextI;
|
|
18904
|
+
continue;
|
|
18905
|
+
}
|
|
18906
|
+
const c = input[i];
|
|
18907
|
+
if (c === "`" && i + 1 < input.length && input[i + 1] === "`" && i + 2 < input.length && input[i + 2] === "`") {
|
|
18908
|
+
fenceStart = i;
|
|
18909
|
+
break;
|
|
18910
|
+
}
|
|
18911
|
+
i++;
|
|
18912
|
+
}
|
|
18913
|
+
if (fenceStart === -1) break;
|
|
18914
|
+
let j = fenceStart + 3;
|
|
18915
|
+
while (j < input.length && input[j] !== "\n") j++;
|
|
18916
|
+
if (j >= input.length) {
|
|
18917
|
+
blocks.push(input.slice(fenceStart + 3).replace(/^[^\n]*\n?/, ""));
|
|
18918
|
+
i = input.length;
|
|
18919
|
+
break;
|
|
18920
|
+
}
|
|
18921
|
+
j++;
|
|
18922
|
+
let closePos = -1;
|
|
18923
|
+
let k = j;
|
|
18924
|
+
while (k < input.length) {
|
|
18925
|
+
const nextK = skipString(input, k);
|
|
18926
|
+
if (nextK > k) {
|
|
18927
|
+
k = nextK;
|
|
18928
|
+
continue;
|
|
18929
|
+
}
|
|
18930
|
+
const c = input[k];
|
|
18931
|
+
if (c === "`" && k + 1 < input.length && input[k + 1] === "`" && k + 2 < input.length && input[k + 2] === "`") {
|
|
18932
|
+
closePos = k;
|
|
18933
|
+
break;
|
|
18934
|
+
}
|
|
18935
|
+
k++;
|
|
18936
|
+
}
|
|
18937
|
+
if (closePos !== -1) {
|
|
18938
|
+
blocks.push(input.slice(j, closePos));
|
|
18939
|
+
i = closePos + 3;
|
|
18940
|
+
} else {
|
|
18941
|
+
blocks.push(input.slice(j));
|
|
18942
|
+
i = input.length;
|
|
18943
|
+
}
|
|
18944
|
+
}
|
|
18945
|
+
if (blocks.length > 0) return blocks.join("\n");
|
|
18946
|
+
if (input.startsWith("```")) {
|
|
18947
|
+
let j = 3;
|
|
18948
|
+
while (j < input.length && input[j] !== "\n") j++;
|
|
18949
|
+
const start = j < input.length ? j + 1 : 3;
|
|
18950
|
+
const body = input.slice(start);
|
|
18951
|
+
const trailingFence = body.lastIndexOf("```");
|
|
18952
|
+
if (trailingFence !== -1) {
|
|
18953
|
+
return body.slice(0, trailingFence);
|
|
18954
|
+
}
|
|
18955
|
+
return body;
|
|
18956
|
+
}
|
|
18957
|
+
return input;
|
|
18958
|
+
}
|
|
18959
|
+
function stripComments(input) {
|
|
18960
|
+
let inStr = false;
|
|
18961
|
+
return input.split("\n").map((line) => {
|
|
18962
|
+
for (let i = 0; i < line.length; i++) {
|
|
18963
|
+
const c = line[i];
|
|
18964
|
+
if (inStr) {
|
|
18965
|
+
if (c === "\\" && i + 1 < line.length) {
|
|
18966
|
+
i++;
|
|
18967
|
+
continue;
|
|
18968
|
+
}
|
|
18969
|
+
if (c === inStr) inStr = false;
|
|
18970
|
+
continue;
|
|
18971
|
+
}
|
|
18972
|
+
if (c === '"' || c === "'") {
|
|
18973
|
+
inStr = c;
|
|
18974
|
+
continue;
|
|
18975
|
+
}
|
|
18976
|
+
if (c === "/" && line[i + 1] === "/") {
|
|
18977
|
+
return line.substring(0, i).trimEnd();
|
|
18978
|
+
}
|
|
18979
|
+
if (c === "#") {
|
|
18980
|
+
return line.substring(0, i).trimEnd();
|
|
18981
|
+
}
|
|
18982
|
+
}
|
|
18983
|
+
return line;
|
|
18984
|
+
}).join("\n");
|
|
18985
|
+
}
|
|
18986
|
+
function preprocess(input) {
|
|
18987
|
+
return stripComments(stripFences(input.trim())).trim();
|
|
18988
|
+
}
|
|
18989
|
+
function parse(input, cat, rootName) {
|
|
18990
|
+
const trimmed = preprocess(input);
|
|
18991
|
+
if (!trimmed) return emptyResult();
|
|
18992
|
+
const { text, wasIncomplete } = autoClose(trimmed);
|
|
18993
|
+
const stmts = split(tokenize(text));
|
|
18994
|
+
if (!stmts.length) return emptyResult(wasIncomplete);
|
|
18995
|
+
const stmtMap = /* @__PURE__ */ new Map();
|
|
18996
|
+
let firstId = "";
|
|
18997
|
+
for (const s of stmts) {
|
|
18998
|
+
const expr = parseExpression(s.tokens);
|
|
18999
|
+
const stmt = classifyStatement(s, expr);
|
|
19000
|
+
stmtMap.set(s.id, stmt);
|
|
19001
|
+
if (!firstId) firstId = s.id;
|
|
19002
|
+
}
|
|
19003
|
+
const typedStmts = [...stmtMap.values()];
|
|
19004
|
+
return buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtMap.size, cat, rootName);
|
|
19005
|
+
}
|
|
19006
|
+
function createStreamParser(cat, rootName) {
|
|
19007
|
+
let buf = "";
|
|
19008
|
+
let cleaned = "";
|
|
19009
|
+
let completedEnd = 0;
|
|
19010
|
+
const completedStmtMap = /* @__PURE__ */ new Map();
|
|
19011
|
+
let firstId = "";
|
|
19012
|
+
function addStmt(text) {
|
|
19013
|
+
const t = text.trim();
|
|
19014
|
+
if (!t) return;
|
|
19015
|
+
for (const s of split(tokenize(t))) {
|
|
19016
|
+
const expr = parseExpression(s.tokens);
|
|
19017
|
+
const stmt = classifyStatement(s, expr);
|
|
19018
|
+
completedStmtMap.set(s.id, stmt);
|
|
19019
|
+
if (!firstId) firstId = s.id;
|
|
19020
|
+
}
|
|
19021
|
+
}
|
|
19022
|
+
function refreshCleaned() {
|
|
19023
|
+
const next = preprocess(buf);
|
|
19024
|
+
if (!next.startsWith(cleaned.slice(0, completedEnd))) {
|
|
19025
|
+
completedEnd = 0;
|
|
19026
|
+
completedStmtMap.clear();
|
|
19027
|
+
firstId = "";
|
|
19028
|
+
}
|
|
19029
|
+
cleaned = next;
|
|
19030
|
+
}
|
|
19031
|
+
function scanNewCompleted() {
|
|
19032
|
+
let depth = 0, ternaryDepth = 0, inStr = false, esc = false;
|
|
19033
|
+
let stmtStart = completedEnd;
|
|
19034
|
+
for (let i = completedEnd; i < cleaned.length; i++) {
|
|
19035
|
+
const c = cleaned[i];
|
|
19036
|
+
if (esc) {
|
|
19037
|
+
esc = false;
|
|
19038
|
+
continue;
|
|
19039
|
+
}
|
|
19040
|
+
if (c === "\\" && inStr) {
|
|
19041
|
+
esc = true;
|
|
19042
|
+
continue;
|
|
19043
|
+
}
|
|
19044
|
+
if (inStr) {
|
|
19045
|
+
if (c === inStr) inStr = false;
|
|
19046
|
+
continue;
|
|
19047
|
+
}
|
|
19048
|
+
if (c === '"' || c === "'") {
|
|
19049
|
+
inStr = c;
|
|
19050
|
+
continue;
|
|
19051
|
+
}
|
|
19052
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
19053
|
+
else if (c === ")" || c === "]" || c === "}") depth = Math.max(0, depth - 1);
|
|
19054
|
+
else if (c === "?" && depth === 0) ternaryDepth++;
|
|
19055
|
+
else if (c === ":" && depth === 0 && ternaryDepth > 0) ternaryDepth--;
|
|
19056
|
+
else if (c === "\n" && depth <= 0 && ternaryDepth <= 0) {
|
|
19057
|
+
let peek = i + 1;
|
|
19058
|
+
while (peek < cleaned.length && (cleaned[peek] === " " || cleaned[peek] === " " || cleaned[peek] === "\r" || cleaned[peek] === "\n"))
|
|
19059
|
+
peek++;
|
|
19060
|
+
if (peek < cleaned.length && (cleaned[peek] === "?" || cleaned[peek] === ":" && ternaryDepth > 0)) {
|
|
19061
|
+
continue;
|
|
19062
|
+
}
|
|
19063
|
+
const t = cleaned.slice(stmtStart, i).trim();
|
|
19064
|
+
if (t) addStmt(t);
|
|
19065
|
+
stmtStart = i + 1;
|
|
19066
|
+
completedEnd = i + 1;
|
|
19067
|
+
}
|
|
19068
|
+
}
|
|
19069
|
+
return stmtStart;
|
|
19070
|
+
}
|
|
19071
|
+
function currentResult() {
|
|
19072
|
+
refreshCleaned();
|
|
19073
|
+
const pendingStart = scanNewCompleted();
|
|
19074
|
+
const pendingText = cleaned.slice(pendingStart).trim();
|
|
19075
|
+
if (!pendingText) {
|
|
19076
|
+
if (completedStmtMap.size === 0) return emptyResult();
|
|
19077
|
+
return buildResult(
|
|
19078
|
+
completedStmtMap,
|
|
19079
|
+
[...completedStmtMap.values()],
|
|
19080
|
+
firstId,
|
|
19081
|
+
false,
|
|
19082
|
+
completedStmtMap.size,
|
|
19083
|
+
cat,
|
|
19084
|
+
rootName
|
|
19085
|
+
);
|
|
19086
|
+
}
|
|
19087
|
+
const { text: closed, wasIncomplete } = autoClose(pendingText);
|
|
19088
|
+
const stmts = split(tokenize(closed));
|
|
19089
|
+
if (!stmts.length) {
|
|
19090
|
+
if (completedStmtMap.size === 0) return emptyResult(wasIncomplete);
|
|
19091
|
+
return buildResult(
|
|
19092
|
+
completedStmtMap,
|
|
19093
|
+
[...completedStmtMap.values()],
|
|
19094
|
+
firstId,
|
|
19095
|
+
wasIncomplete,
|
|
19096
|
+
completedStmtMap.size,
|
|
19097
|
+
cat,
|
|
19098
|
+
rootName
|
|
19099
|
+
);
|
|
19100
|
+
}
|
|
19101
|
+
const allStmtMap = new Map(completedStmtMap);
|
|
19102
|
+
for (const s of stmts) {
|
|
19103
|
+
const expr = parseExpression(s.tokens);
|
|
19104
|
+
const stmt = classifyStatement(s, expr);
|
|
19105
|
+
allStmtMap.set(s.id, stmt);
|
|
19106
|
+
}
|
|
19107
|
+
const allTypedStmts = [...allStmtMap.values()];
|
|
19108
|
+
const fid = firstId || stmts[0].id;
|
|
19109
|
+
return buildResult(
|
|
19110
|
+
allStmtMap,
|
|
19111
|
+
allTypedStmts,
|
|
19112
|
+
fid,
|
|
19113
|
+
wasIncomplete,
|
|
19114
|
+
allStmtMap.size,
|
|
19115
|
+
cat,
|
|
19116
|
+
rootName
|
|
19117
|
+
);
|
|
19118
|
+
}
|
|
19119
|
+
function reset() {
|
|
19120
|
+
buf = "";
|
|
19121
|
+
cleaned = "";
|
|
19122
|
+
completedEnd = 0;
|
|
19123
|
+
completedStmtMap.clear();
|
|
19124
|
+
firstId = "";
|
|
19125
|
+
}
|
|
19126
|
+
return {
|
|
19127
|
+
push(chunk) {
|
|
19128
|
+
buf += chunk;
|
|
19129
|
+
return currentResult();
|
|
19130
|
+
},
|
|
19131
|
+
set(fullText) {
|
|
19132
|
+
if (fullText.length < buf.length || !fullText.startsWith(buf)) {
|
|
19133
|
+
reset();
|
|
19134
|
+
}
|
|
19135
|
+
const delta = fullText.slice(buf.length);
|
|
19136
|
+
if (delta) buf += delta;
|
|
19137
|
+
return currentResult();
|
|
19138
|
+
},
|
|
19139
|
+
getResult: currentResult
|
|
19140
|
+
};
|
|
19141
|
+
}
|
|
19142
|
+
function useGenUIProgram(text, catalog) {
|
|
19143
|
+
const parserRef = useRef(
|
|
19144
|
+
null
|
|
19145
|
+
);
|
|
19146
|
+
if (!parserRef.current || parserRef.current.catalog !== catalog) {
|
|
19147
|
+
parserRef.current = { catalog, parser: createStreamParser(catalog, "root") };
|
|
19148
|
+
}
|
|
19149
|
+
const lastGoodRef = useRef(null);
|
|
19150
|
+
return useMemo(() => {
|
|
19151
|
+
const result = parserRef.current.parser.set(text);
|
|
19152
|
+
if (result.root) {
|
|
19153
|
+
lastGoodRef.current = result.root;
|
|
19154
|
+
}
|
|
19155
|
+
return { result, root: result.root ?? lastGoodRef.current };
|
|
19156
|
+
}, [text, catalog]);
|
|
19157
|
+
}
|
|
19158
|
+
var noop = () => {
|
|
19159
|
+
};
|
|
19160
|
+
function childKey(value, index) {
|
|
19161
|
+
if (isElementNode(value) && value.statementId) return `s:${value.statementId}`;
|
|
19162
|
+
return `i:${index}`;
|
|
19163
|
+
}
|
|
19164
|
+
var RenderDepthContext = createContext(0);
|
|
19165
|
+
var MAX_DEPTH = 64;
|
|
19166
|
+
var GenUINode = memo(function GenUINode2({
|
|
19167
|
+
node,
|
|
19168
|
+
components,
|
|
19169
|
+
onAction,
|
|
19170
|
+
isStreaming,
|
|
19171
|
+
onTelemetry
|
|
19172
|
+
}) {
|
|
19173
|
+
const depth = useContext(RenderDepthContext);
|
|
19174
|
+
if (depth > MAX_DEPTH) {
|
|
19175
|
+
onTelemetry?.({ kind: "dropped-node", typeName: node.typeName });
|
|
19176
|
+
return null;
|
|
19177
|
+
}
|
|
19178
|
+
const renderer = components[node.typeName];
|
|
19179
|
+
if (!renderer) {
|
|
19180
|
+
onTelemetry?.({ kind: "unknown-component", typeName: node.typeName });
|
|
19181
|
+
return null;
|
|
19182
|
+
}
|
|
19183
|
+
const render = (value) => {
|
|
19184
|
+
if (value == null) return null;
|
|
19185
|
+
if (isElementNode(value)) {
|
|
19186
|
+
return /* @__PURE__ */ jsx(RenderDepthContext.Provider, { value: depth + 1, children: /* @__PURE__ */ jsx(
|
|
19187
|
+
GenUINode2,
|
|
19188
|
+
{
|
|
19189
|
+
node: value,
|
|
19190
|
+
components,
|
|
19191
|
+
onAction,
|
|
19192
|
+
isStreaming,
|
|
19193
|
+
onTelemetry
|
|
19194
|
+
}
|
|
19195
|
+
) });
|
|
19196
|
+
}
|
|
19197
|
+
if (Array.isArray(value)) {
|
|
19198
|
+
return value.map((item, index) => /* @__PURE__ */ jsx(React14.Fragment, { children: render(item) }, childKey(item, index)));
|
|
19199
|
+
}
|
|
19200
|
+
if (typeof value === "object" && isRuntimeExpr(value)) return null;
|
|
19201
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
19202
|
+
return null;
|
|
19203
|
+
};
|
|
19204
|
+
return renderer({ node, ctx: { render, onAction, isStreaming } });
|
|
19205
|
+
});
|
|
19206
|
+
function GenUIView({
|
|
19207
|
+
root,
|
|
19208
|
+
components,
|
|
19209
|
+
onAction = noop,
|
|
19210
|
+
isStreaming = false,
|
|
19211
|
+
onTelemetry,
|
|
19212
|
+
placeholder = null
|
|
19213
|
+
}) {
|
|
19214
|
+
if (!root) return placeholder;
|
|
19215
|
+
return /* @__PURE__ */ jsx(
|
|
19216
|
+
GenUINode,
|
|
19217
|
+
{
|
|
19218
|
+
node: root,
|
|
19219
|
+
components,
|
|
19220
|
+
onAction,
|
|
19221
|
+
isStreaming,
|
|
19222
|
+
onTelemetry
|
|
19223
|
+
}
|
|
19224
|
+
);
|
|
19225
|
+
}
|
|
19226
|
+
|
|
19227
|
+
// src/genui/registry.ts
|
|
19228
|
+
var CIMPLIFY_GENUI_REGISTRY = {
|
|
19229
|
+
version: "3",
|
|
19230
|
+
components: [
|
|
19231
|
+
{
|
|
19232
|
+
name: "Stack",
|
|
19233
|
+
description: 'Layout container. direction: "column" (default) or "row"; gap in points (default 12)',
|
|
19234
|
+
params: [{ name: "children", required: true }, { name: "direction" }, { name: "gap" }]
|
|
19235
|
+
},
|
|
19236
|
+
{
|
|
19237
|
+
name: "Card",
|
|
19238
|
+
description: "Elevated surface grouping related content, with an optional title",
|
|
19239
|
+
params: [{ name: "children", required: true }, { name: "title" }]
|
|
19240
|
+
},
|
|
19241
|
+
{
|
|
19242
|
+
name: "TextContent",
|
|
19243
|
+
description: 'Text block. size: "display", "heading", "title", "body" (default), "label", "caption"',
|
|
19244
|
+
params: [{ name: "text", required: true }, { name: "size" }]
|
|
19245
|
+
},
|
|
19246
|
+
{
|
|
19247
|
+
name: "Stat",
|
|
19248
|
+
description: "Compact stat: small label over a bold value, optional sub line and tone",
|
|
19249
|
+
params: [
|
|
19250
|
+
{ name: "label", required: true },
|
|
19251
|
+
{ name: "value", required: true },
|
|
19252
|
+
{ name: "sub" },
|
|
19253
|
+
{ name: "tone" }
|
|
19254
|
+
]
|
|
19255
|
+
},
|
|
19256
|
+
{
|
|
19257
|
+
name: "Metric",
|
|
19258
|
+
description: "KPI card with label, value, optional percent change and SF Symbol icon",
|
|
19259
|
+
params: [
|
|
19260
|
+
{ name: "label", required: true },
|
|
19261
|
+
{ name: "value", required: true },
|
|
19262
|
+
{ name: "change" },
|
|
19263
|
+
{ name: "icon" }
|
|
19264
|
+
]
|
|
19265
|
+
},
|
|
19266
|
+
{
|
|
19267
|
+
name: "KeyValue",
|
|
19268
|
+
description: "Label/value row; set mono true for numeric values",
|
|
19269
|
+
params: [
|
|
19270
|
+
{ name: "label", required: true },
|
|
19271
|
+
{ name: "value", required: true },
|
|
19272
|
+
{ name: "mono" }
|
|
19273
|
+
]
|
|
19274
|
+
},
|
|
19275
|
+
{
|
|
19276
|
+
name: "Badge",
|
|
19277
|
+
description: "Small status chip. tone: neutral, positive, info, indigo, violet, warning, danger",
|
|
19278
|
+
params: [{ name: "label", required: true }, { name: "tone" }]
|
|
19279
|
+
},
|
|
19280
|
+
{
|
|
19281
|
+
name: "Callout",
|
|
19282
|
+
description: "Inline alert with title, optional message. tone: info (default), success, warning, danger, accent, neutral",
|
|
19283
|
+
params: [{ name: "title", required: true }, { name: "message" }, { name: "tone" }]
|
|
19284
|
+
},
|
|
19285
|
+
{
|
|
19286
|
+
name: "Button",
|
|
19287
|
+
description: 'Action button. action: "action:name" string or Action([...]) steps. variant: primary, cta, secondary (default), ghost, destructive',
|
|
19288
|
+
params: [
|
|
19289
|
+
{ name: "label", required: true },
|
|
19290
|
+
{ name: "action", required: true },
|
|
19291
|
+
{ name: "variant" }
|
|
19292
|
+
]
|
|
19293
|
+
},
|
|
19294
|
+
{
|
|
19295
|
+
name: "Buttons",
|
|
19296
|
+
description: 'Row (default) or column of Buttons; direction: "row" or "column"',
|
|
19297
|
+
params: [{ name: "children", required: true }, { name: "direction" }]
|
|
19298
|
+
},
|
|
19299
|
+
{
|
|
19300
|
+
name: "Divider",
|
|
19301
|
+
description: "Hairline separator",
|
|
19302
|
+
params: []
|
|
19303
|
+
},
|
|
19304
|
+
{
|
|
19305
|
+
name: "Table",
|
|
19306
|
+
description: "Data table: headers is an array of strings, rows an array of cell arrays",
|
|
19307
|
+
params: [
|
|
19308
|
+
{ name: "headers", required: true },
|
|
19309
|
+
{ name: "rows", required: true }
|
|
19310
|
+
]
|
|
19311
|
+
},
|
|
19312
|
+
{
|
|
19313
|
+
name: "BarChart",
|
|
19314
|
+
description: "Bar chart: labels (x axis strings) and values (numbers), optional title",
|
|
19315
|
+
params: [
|
|
19316
|
+
{ name: "labels", required: true },
|
|
19317
|
+
{ name: "values", required: true },
|
|
19318
|
+
{ name: "title" }
|
|
19319
|
+
]
|
|
19320
|
+
},
|
|
19321
|
+
{
|
|
19322
|
+
name: "LineChart",
|
|
19323
|
+
description: "Line chart with area fill: labels and values, optional title",
|
|
19324
|
+
params: [
|
|
19325
|
+
{ name: "labels", required: true },
|
|
19326
|
+
{ name: "values", required: true },
|
|
19327
|
+
{ name: "title" }
|
|
19328
|
+
]
|
|
19329
|
+
},
|
|
19330
|
+
{
|
|
19331
|
+
name: "Money",
|
|
19332
|
+
description: "Formatted money amount, never abbreviated. currency: ISO code (default USD)",
|
|
19333
|
+
params: [{ name: "amount", required: true }, { name: "currency" }]
|
|
19334
|
+
},
|
|
19335
|
+
{
|
|
19336
|
+
name: "Image",
|
|
19337
|
+
description: "Remote image by URL with optional caption",
|
|
19338
|
+
params: [{ name: "url", required: true }, { name: "caption" }]
|
|
19339
|
+
},
|
|
19340
|
+
{
|
|
19341
|
+
name: "ListRow",
|
|
19342
|
+
description: "Entity row for orders/customers/products/invoices: initials avatar (or SF Symbol tile via symbol), title + subtitle, trailing value and badge with tone",
|
|
19343
|
+
params: [
|
|
19344
|
+
{ name: "title", required: true },
|
|
19345
|
+
{ name: "subtitle" },
|
|
19346
|
+
{ name: "value" },
|
|
19347
|
+
{ name: "badge" },
|
|
19348
|
+
{ name: "tone" },
|
|
19349
|
+
{ name: "symbol" }
|
|
19350
|
+
]
|
|
19351
|
+
},
|
|
19352
|
+
{
|
|
19353
|
+
name: "Timeline",
|
|
19354
|
+
description: "Event history (order journey, payment trail): events is an array of {title, detail?, time?, tone?} objects, newest first",
|
|
19355
|
+
params: [{ name: "events", required: true }]
|
|
19356
|
+
},
|
|
19357
|
+
{
|
|
19358
|
+
name: "Progress",
|
|
19359
|
+
description: "Amount toward a bound (goal, stock cover, capacity): numeric value and target (default 100), optional detail caption and tone",
|
|
19360
|
+
params: [
|
|
19361
|
+
{ name: "label", required: true },
|
|
19362
|
+
{ name: "value", required: true },
|
|
19363
|
+
{ name: "target" },
|
|
19364
|
+
{ name: "detail" },
|
|
19365
|
+
{ name: "tone" }
|
|
19366
|
+
]
|
|
19367
|
+
},
|
|
19368
|
+
{
|
|
19369
|
+
name: "DonutChart",
|
|
19370
|
+
description: "Share of a whole (sales by channel, revenue by category): labels + values, optional title and center display string (e.g. the formatted total)",
|
|
19371
|
+
params: [
|
|
19372
|
+
{ name: "labels", required: true },
|
|
19373
|
+
{ name: "values", required: true },
|
|
19374
|
+
{ name: "title" },
|
|
19375
|
+
{ name: "center" }
|
|
19376
|
+
]
|
|
19377
|
+
},
|
|
19378
|
+
{
|
|
19379
|
+
name: "StatusTrack",
|
|
19380
|
+
description: "Stage tracker (order fulfilment, invoice lifecycle): steps are stage names, current is the active step name or index",
|
|
19381
|
+
params: [
|
|
19382
|
+
{ name: "steps", required: true },
|
|
19383
|
+
{ name: "current", required: true }
|
|
19384
|
+
]
|
|
19385
|
+
},
|
|
19386
|
+
{
|
|
19387
|
+
name: "LineItems",
|
|
19388
|
+
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",
|
|
19389
|
+
params: [
|
|
19390
|
+
{ name: "items", required: true },
|
|
19391
|
+
{ name: "total", required: true },
|
|
19392
|
+
{ name: "adjustments" }
|
|
19393
|
+
]
|
|
19394
|
+
},
|
|
19395
|
+
{
|
|
19396
|
+
name: "Grid",
|
|
19397
|
+
description: "Tile layout for Stat/Metric children, columns default 2",
|
|
19398
|
+
params: [{ name: "children", required: true }, { name: "columns" }]
|
|
19399
|
+
},
|
|
19400
|
+
{
|
|
19401
|
+
name: "Banner",
|
|
19402
|
+
description: "Hero-stat moment on a tonal wash: big value, label, optional percent delta. tone: positive (default), warning, danger, info, neutral",
|
|
19403
|
+
params: [
|
|
19404
|
+
{ name: "value", required: true },
|
|
19405
|
+
{ name: "label", required: true },
|
|
19406
|
+
{ name: "delta" },
|
|
19407
|
+
{ name: "tone" }
|
|
19408
|
+
]
|
|
19409
|
+
},
|
|
19410
|
+
{
|
|
19411
|
+
name: "EmptyState",
|
|
19412
|
+
description: "Nothing-to-show state with an SF Symbol and optional message",
|
|
19413
|
+
params: [{ name: "title", required: true }, { name: "message" }, { name: "symbol" }]
|
|
19414
|
+
}
|
|
19415
|
+
]
|
|
19416
|
+
};
|
|
19417
|
+
|
|
19418
|
+
// src/genui/manifest.ts
|
|
19419
|
+
function genUIManifest(registry) {
|
|
19420
|
+
return {
|
|
19421
|
+
version: registry.version,
|
|
19422
|
+
components: registry.components.map((component) => ({
|
|
19423
|
+
name: component.name,
|
|
19424
|
+
description: component.description,
|
|
19425
|
+
params: component.params.map((param) => ({
|
|
19426
|
+
name: param.name,
|
|
19427
|
+
required: param.required ?? false
|
|
19428
|
+
}))
|
|
19429
|
+
}))
|
|
19430
|
+
};
|
|
19431
|
+
}
|
|
19432
|
+
function encodeString(value) {
|
|
19433
|
+
return JSON.stringify(value).replace(/\//g, "\\/");
|
|
19434
|
+
}
|
|
19435
|
+
function canonicalize(value) {
|
|
19436
|
+
if (value === null) return "null";
|
|
19437
|
+
if (typeof value === "string") return encodeString(value);
|
|
19438
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
19439
|
+
if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
|
|
19440
|
+
const keys = Object.keys(value).sort();
|
|
19441
|
+
const entries = keys.map((key) => `${encodeString(key)}:${canonicalize(value[key])}`);
|
|
19442
|
+
return `{${entries.join(",")}}`;
|
|
19443
|
+
}
|
|
19444
|
+
function genUIManifestJson(manifest) {
|
|
19445
|
+
return canonicalize(manifest);
|
|
19446
|
+
}
|
|
19447
|
+
async function genUIFingerprint(manifest) {
|
|
19448
|
+
const bytes = new TextEncoder().encode(genUIManifestJson(manifest));
|
|
19449
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
19450
|
+
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 16);
|
|
19451
|
+
}
|
|
19452
|
+
|
|
19453
|
+
export { AccommodationCard, AccountContent, AccountDashboardPage, AccountHero, AccountIdentityStrip, AccountLineItem, AccountLineItems, AccountNav, AccountOrderDetailPage, AccountOrderView, AccountOrdersPage, AccountShell, AccountSidebar, AccountSignedOutPrompt, AccountSubscriptionDetailPage, AccountSubscriptionsPage, Ad, AdProvider, AddOnSelector, AddressElement, AuthElement, AvailabilityBadge, BackLink, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingSummary, BookingsPage, BoxIcon24, 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, 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, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, RichText, SaleBadge, SalePrice, SaleTeaser, ScheduleServiceCard, 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, clearOrdersCache, cn, defaultBoundaryKind, genUICatalog, genUIFingerprint, genUIManifest, genUIManifestJson, getBoundaryLabels, isTerminalOrderStatus, parse as parseGenUIProgram, partitionMessages, prefetchProduct, prefetchProducts, resolveBoundaryKind, roomToResource, sanitizeRichTextHtml, stripHtmlToText, 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, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };
|