@cimplify/sdk 0.88.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-A7NODD4Q.mjs → chunk-2UDEWILL.mjs} +83 -17
- package/dist/{chunk-W5BVGAO6.js → chunk-BN4V4ONE.js} +83 -17
- package/dist/{chunk-6JA4HBBD.js → chunk-JVWFIFQ7.js} +4 -4
- package/dist/{chunk-ROBTTS3Q.js → chunk-KJP4U6L3.js} +28 -1
- package/dist/{chunk-VU5LFNEX.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-D0PrO1-Q.d.ts → client-CR1f94wY.d.ts} +97 -7
- package/dist/{client-B3FBDfTg.d.ts → client-CqpOcbin.d.ts} +2 -2
- package/dist/{client-B-8ceVyt.d.mts → client-DMttx1xk.d.mts} +2 -2
- package/dist/{client-r5VSGFRU.d.mts → client-YiSelM7x.d.mts} +97 -7
- package/dist/{index-C-Pxrzna.d.ts → index-CxTVu0HZ.d.ts} +3 -3
- package/dist/{index-DLulxmF1.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-TnJUoHqL.d.mts → payment-5d8FTA1q.d.mts} +1 -1
- package/dist/{payment-Cfoo9MT7.d.ts → payment-BX7ccIKY.d.ts} +1 -1
- package/dist/{price-CCDSrIsc.d.ts → price-DtfP_Xfq.d.ts} +1 -1
- package/dist/{price-CM0zVSyM.d.mts → price-arl_4Rzn.d.mts} +1 -1
- package/dist/{product-DoA36Eqh.d.mts → product-DZX4QuKS.d.mts} +68 -4
- package/dist/{product-DoA36Eqh.d.ts → product-DZX4QuKS.d.ts} +68 -4
- package/dist/react.d.mts +405 -7
- package/dist/react.d.ts +405 -7
- package/dist/react.js +2345 -74
- package/dist/react.mjs +2323 -72
- package/dist/{server-CI1CUFVu.d.mts → server-BceZMxcK.d.mts} +8 -1
- package/dist/{server-CHfYeTAa.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,21 +5885,45 @@ 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);
|
|
5883
5902
|
}, []);
|
|
5884
|
-
const
|
|
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);
|
|
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
|
+
);
|
|
5924
|
+
const fetchNewMessages = useCallback(async () => {
|
|
5885
5925
|
const c = clientRef.current;
|
|
5886
5926
|
if (!c) return;
|
|
5887
|
-
if (Date.now() - lastActivityRef.current > IDLE_TIMEOUT_MS) return;
|
|
5888
5927
|
const last = messagesRef.current[messagesRef.current.length - 1];
|
|
5889
5928
|
const result = await c.support.getMessages({
|
|
5890
5929
|
after: last?.created_at,
|
|
@@ -5894,6 +5933,10 @@ function useChat(options = {}) {
|
|
|
5894
5933
|
appendMessages(result.value);
|
|
5895
5934
|
}
|
|
5896
5935
|
}, [appendMessages]);
|
|
5936
|
+
const pollMessages = useCallback(async () => {
|
|
5937
|
+
if (Date.now() - lastActivityRef.current > IDLE_TIMEOUT_MS) return;
|
|
5938
|
+
await fetchNewMessages();
|
|
5939
|
+
}, [fetchNewMessages]);
|
|
5897
5940
|
const startPolling = useCallback(() => {
|
|
5898
5941
|
if (pollRef.current) clearInterval(pollRef.current);
|
|
5899
5942
|
const interval = Date.now() - lastActivityRef.current > IDLE_TIMEOUT_MS ? pollIdleInterval : pollInterval;
|
|
@@ -5926,6 +5969,19 @@ function useChat(options = {}) {
|
|
|
5926
5969
|
const envelope = JSON.parse(event.data);
|
|
5927
5970
|
if (envelope.type === "message.created" && envelope.data) {
|
|
5928
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();
|
|
5979
|
+
} else if (envelope.type === "resync") {
|
|
5980
|
+
const latest = envelope.latest_message_at;
|
|
5981
|
+
const last = messagesRef.current[messagesRef.current.length - 1];
|
|
5982
|
+
if (latest && (!last || new Date(last.created_at).getTime() < new Date(latest).getTime())) {
|
|
5983
|
+
fetchNewMessages();
|
|
5984
|
+
}
|
|
5929
5985
|
}
|
|
5930
5986
|
} catch {
|
|
5931
5987
|
}
|
|
@@ -5933,7 +5989,7 @@ function useChat(options = {}) {
|
|
|
5933
5989
|
ws.onclose = () => {
|
|
5934
5990
|
wsRef.current = null;
|
|
5935
5991
|
if (!openedRef.current) return;
|
|
5936
|
-
|
|
5992
|
+
fetchNewMessages();
|
|
5937
5993
|
if (wsReconnectAttemptRef.current < WS_MAX_RECONNECT_ATTEMPTS) {
|
|
5938
5994
|
const attempt = wsReconnectAttemptRef.current++;
|
|
5939
5995
|
const delay = Math.min(1e3 * Math.pow(2, attempt) + Math.random() * 500, 3e4);
|
|
@@ -5945,11 +6001,12 @@ function useChat(options = {}) {
|
|
|
5945
6001
|
ws.onerror = () => {
|
|
5946
6002
|
};
|
|
5947
6003
|
wsRef.current = ws;
|
|
5948
|
-
}, [appendMessages, startPolling, stopPolling,
|
|
6004
|
+
}, [appendMessages, startPolling, stopPolling, fetchNewMessages]);
|
|
5949
6005
|
useEffect(() => {
|
|
5950
6006
|
return () => {
|
|
5951
6007
|
if (pollRef.current) clearInterval(pollRef.current);
|
|
5952
6008
|
if (wsReconnectTimerRef.current) clearTimeout(wsReconnectTimerRef.current);
|
|
6009
|
+
if (awaitReplyTimerRef.current) clearTimeout(awaitReplyTimerRef.current);
|
|
5953
6010
|
if (wsRef.current) {
|
|
5954
6011
|
wsRef.current.onclose = null;
|
|
5955
6012
|
wsRef.current.close();
|
|
@@ -5965,6 +6022,10 @@ function useChat(options = {}) {
|
|
|
5965
6022
|
if (result.ok) {
|
|
5966
6023
|
setMessages(result.value.messages);
|
|
5967
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);
|
|
5968
6029
|
connectWebSocket();
|
|
5969
6030
|
} else {
|
|
5970
6031
|
openedRef.current = false;
|
|
@@ -5973,38 +6034,60 @@ function useChat(options = {}) {
|
|
|
5973
6034
|
setIsLoading(false);
|
|
5974
6035
|
}, [client, connectWebSocket]);
|
|
5975
6036
|
const send = useCallback(
|
|
5976
|
-
async (content) => {
|
|
5977
|
-
|
|
6037
|
+
async (content, opts) => {
|
|
6038
|
+
const attachments = opts?.attachments ?? [];
|
|
6039
|
+
if (!client || !content.trim() && attachments.length === 0) return false;
|
|
5978
6040
|
setError(null);
|
|
5979
6041
|
lastActivityRef.current = Date.now();
|
|
5980
6042
|
if (!openedRef.current) {
|
|
5981
6043
|
await openConversation();
|
|
5982
|
-
if (!openedRef.current) return;
|
|
6044
|
+
if (!openedRef.current) return false;
|
|
5983
6045
|
}
|
|
6046
|
+
const replyTo = opts?.replyTo;
|
|
5984
6047
|
const optimisticId = `opt_${Date.now()}`;
|
|
5985
6048
|
const optimistic = {
|
|
5986
6049
|
id: optimisticId,
|
|
5987
6050
|
sender_type: "customer",
|
|
5988
6051
|
content: content.trim(),
|
|
5989
|
-
content_type: "text",
|
|
5990
|
-
attachments
|
|
6052
|
+
content_type: attachments.length > 0 ? "image" : "text",
|
|
6053
|
+
attachments,
|
|
5991
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,
|
|
5992
6062
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5993
6063
|
};
|
|
5994
6064
|
setMessages((prev) => [...prev, optimistic]);
|
|
5995
6065
|
setIsSending(true);
|
|
5996
|
-
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
|
+
});
|
|
5997
6070
|
if (result.ok) {
|
|
6071
|
+
const confirmed = result.value;
|
|
5998
6072
|
setMessages(
|
|
5999
|
-
(prev) =>
|
|
6073
|
+
(prev) => (
|
|
6074
|
+
// The websocket may have already streamed the confirmed message
|
|
6075
|
+
// in while the POST was in flight; drop the optimistic bubble
|
|
6076
|
+
// instead of inserting a duplicate.
|
|
6077
|
+
prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== optimisticId) : prev.map((m) => m.id === optimisticId ? confirmed : m)
|
|
6078
|
+
)
|
|
6000
6079
|
);
|
|
6080
|
+
if (aiEnabledRef.current && !humanRequestedRef.current) {
|
|
6081
|
+
startAwaitingReply();
|
|
6082
|
+
}
|
|
6001
6083
|
} else {
|
|
6002
6084
|
setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
|
|
6003
6085
|
setError(result.error);
|
|
6004
6086
|
}
|
|
6005
6087
|
setIsSending(false);
|
|
6088
|
+
return result.ok;
|
|
6006
6089
|
},
|
|
6007
|
-
[client, openConversation]
|
|
6090
|
+
[client, openConversation, startAwaitingReply]
|
|
6008
6091
|
);
|
|
6009
6092
|
const startConversation = useCallback(
|
|
6010
6093
|
async (starterText) => {
|
|
@@ -6015,14 +6098,65 @@ function useChat(options = {}) {
|
|
|
6015
6098
|
},
|
|
6016
6099
|
[openConversation, send]
|
|
6017
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");
|
|
6018
6145
|
return {
|
|
6019
6146
|
messages,
|
|
6020
6147
|
isLoading,
|
|
6021
6148
|
isSending,
|
|
6022
6149
|
error,
|
|
6023
6150
|
isActive: conversationOpened && messages.length > 0,
|
|
6151
|
+
humanRequested: humanRequested && !agentArrived,
|
|
6152
|
+
awaitingReply,
|
|
6153
|
+
seenAt,
|
|
6154
|
+
mayHaveOlder,
|
|
6024
6155
|
send,
|
|
6025
|
-
startConversation
|
|
6156
|
+
startConversation,
|
|
6157
|
+
react,
|
|
6158
|
+
requestHuman,
|
|
6159
|
+
loadOlder
|
|
6026
6160
|
};
|
|
6027
6161
|
}
|
|
6028
6162
|
var ElementsContext = createContext({
|
|
@@ -9320,6 +9454,7 @@ function ProductCustomizer({
|
|
|
9320
9454
|
onAddToCart,
|
|
9321
9455
|
onVariantChange,
|
|
9322
9456
|
showSpecialInstructions = true,
|
|
9457
|
+
submitLabel,
|
|
9323
9458
|
className,
|
|
9324
9459
|
classNames
|
|
9325
9460
|
}) {
|
|
@@ -9708,8 +9843,10 @@ function ProductCustomizer({
|
|
|
9708
9843
|
isAdded && classNames?.submitButtonAdded,
|
|
9709
9844
|
classNames?.submitButton
|
|
9710
9845
|
),
|
|
9711
|
-
children: isAdded ? "Added
|
|
9712
|
-
"Add to Cart
|
|
9846
|
+
children: isAdded ? "Added" : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9847
|
+
submitLabel ?? "Add to Cart",
|
|
9848
|
+
" \xB7",
|
|
9849
|
+
" ",
|
|
9713
9850
|
/* @__PURE__ */ jsx(Price, { amount: displayTotalPrice, currency })
|
|
9714
9851
|
] })
|
|
9715
9852
|
}
|
|
@@ -9869,13 +10006,13 @@ function StoreVideo({
|
|
|
9869
10006
|
autoplay = true,
|
|
9870
10007
|
loop = true,
|
|
9871
10008
|
muted = true,
|
|
9872
|
-
lazy = true,
|
|
10009
|
+
lazy: lazy2 = true,
|
|
9873
10010
|
className
|
|
9874
10011
|
}) {
|
|
9875
10012
|
const ref = useRef(null);
|
|
9876
|
-
const [inView, setInView] = useState(!
|
|
10013
|
+
const [inView, setInView] = useState(!lazy2);
|
|
9877
10014
|
useEffect(() => {
|
|
9878
|
-
if (!
|
|
10015
|
+
if (!lazy2 || inView) return;
|
|
9879
10016
|
const node = ref.current;
|
|
9880
10017
|
if (!node || typeof IntersectionObserver === "undefined") {
|
|
9881
10018
|
setInView(true);
|
|
@@ -9892,7 +10029,7 @@ function StoreVideo({
|
|
|
9892
10029
|
);
|
|
9893
10030
|
observer.observe(node);
|
|
9894
10031
|
return () => observer.disconnect();
|
|
9895
|
-
}, [
|
|
10032
|
+
}, [lazy2, inView]);
|
|
9896
10033
|
return /* @__PURE__ */ jsx(
|
|
9897
10034
|
"div",
|
|
9898
10035
|
{
|
|
@@ -9910,7 +10047,7 @@ function StoreVideo({
|
|
|
9910
10047
|
muted,
|
|
9911
10048
|
playsInline: true,
|
|
9912
10049
|
controls,
|
|
9913
|
-
preload:
|
|
10050
|
+
preload: lazy2 ? "metadata" : "auto",
|
|
9914
10051
|
"aria-label": alt,
|
|
9915
10052
|
style: { width: "100%", height: "100%", objectFit: "cover", display: "block" },
|
|
9916
10053
|
children: poster ? /* @__PURE__ */ jsx(
|
|
@@ -16712,6 +16849,112 @@ function CurrencySelector({
|
|
|
16712
16849
|
);
|
|
16713
16850
|
}) });
|
|
16714
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
|
+
}
|
|
16715
16958
|
function ChatWidget({
|
|
16716
16959
|
client: clientProp,
|
|
16717
16960
|
businessName = "Support",
|
|
@@ -16729,7 +16972,35 @@ function ChatWidget({
|
|
|
16729
16972
|
const client = clientProp ?? context?.client;
|
|
16730
16973
|
const [open, setOpen] = useState(defaultOpen);
|
|
16731
16974
|
const [unread, setUnread] = useState(0);
|
|
16732
|
-
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
|
+
);
|
|
16733
17004
|
const prevCountRef = useRef(messages.length);
|
|
16734
17005
|
useEffect(() => {
|
|
16735
17006
|
if (!open && messages.length > prevCountRef.current) {
|
|
@@ -16776,11 +17047,15 @@ function ChatWidget({
|
|
|
16776
17047
|
messages,
|
|
16777
17048
|
isLoading,
|
|
16778
17049
|
isSending,
|
|
17050
|
+
isTyping: showTyping,
|
|
17051
|
+
seenAt,
|
|
16779
17052
|
error,
|
|
16780
17053
|
isActive,
|
|
16781
17054
|
isLeft,
|
|
16782
17055
|
onClose: handleClose,
|
|
16783
17056
|
onSend: send,
|
|
17057
|
+
onReact: react,
|
|
17058
|
+
onAttach: attachPhoto,
|
|
16784
17059
|
onStartConversation: startConversation,
|
|
16785
17060
|
classNames
|
|
16786
17061
|
}
|
|
@@ -16833,11 +17108,15 @@ function Panel({
|
|
|
16833
17108
|
messages,
|
|
16834
17109
|
isLoading,
|
|
16835
17110
|
isSending,
|
|
17111
|
+
isTyping,
|
|
17112
|
+
seenAt,
|
|
16836
17113
|
error,
|
|
16837
17114
|
isActive,
|
|
16838
17115
|
isLeft,
|
|
16839
17116
|
onClose,
|
|
16840
17117
|
onSend,
|
|
17118
|
+
onReact,
|
|
17119
|
+
onAttach,
|
|
16841
17120
|
onStartConversation,
|
|
16842
17121
|
classNames
|
|
16843
17122
|
}) {
|
|
@@ -16875,7 +17154,9 @@ function Panel({
|
|
|
16875
17154
|
{
|
|
16876
17155
|
messages,
|
|
16877
17156
|
isLoading,
|
|
16878
|
-
isSending,
|
|
17157
|
+
isSending: isTyping,
|
|
17158
|
+
seenAt,
|
|
17159
|
+
onReact,
|
|
16879
17160
|
className: classNames?.messages
|
|
16880
17161
|
}
|
|
16881
17162
|
),
|
|
@@ -16886,6 +17167,7 @@ function Panel({
|
|
|
16886
17167
|
placeholder,
|
|
16887
17168
|
isSending,
|
|
16888
17169
|
onSend,
|
|
17170
|
+
onAttach,
|
|
16889
17171
|
className: classNames?.input
|
|
16890
17172
|
}
|
|
16891
17173
|
),
|
|
@@ -16981,16 +17263,29 @@ function Welcome({
|
|
|
16981
17263
|
}
|
|
16982
17264
|
);
|
|
16983
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
|
+
}
|
|
16984
17275
|
function MessageList({
|
|
16985
17276
|
messages,
|
|
16986
17277
|
isLoading,
|
|
16987
17278
|
isSending,
|
|
17279
|
+
seenAt,
|
|
17280
|
+
onReact,
|
|
16988
17281
|
className
|
|
16989
17282
|
}) {
|
|
16990
17283
|
const bottomRef = useRef(null);
|
|
16991
17284
|
useEffect(() => {
|
|
16992
17285
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
16993
17286
|
}, [messages.length, isSending]);
|
|
17287
|
+
const { bubbles, reactionsByTarget } = partitionMessages(messages);
|
|
17288
|
+
const seenMessageId = lastSeenCustomerMessageId(bubbles, seenAt);
|
|
16994
17289
|
return /* @__PURE__ */ jsxs(
|
|
16995
17290
|
"div",
|
|
16996
17291
|
{
|
|
@@ -17000,40 +17295,111 @@ function MessageList({
|
|
|
17000
17295
|
),
|
|
17001
17296
|
children: [
|
|
17002
17297
|
isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
|
|
17003
|
-
|
|
17298
|
+
bubbles.map((msg, i) => {
|
|
17004
17299
|
const isCustomer = msg.sender_type === "customer";
|
|
17005
|
-
const prev =
|
|
17300
|
+
const prev = bubbles[i - 1];
|
|
17006
17301
|
const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
|
|
17007
17302
|
const isOptimistic = msg.id.startsWith("opt_");
|
|
17303
|
+
const reactions = reactionsByTarget.get(msg.id);
|
|
17008
17304
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
17009
17305
|
!sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsx("div", { className: "h-3" }),
|
|
17010
|
-
/* @__PURE__ */
|
|
17306
|
+
/* @__PURE__ */ jsxs(
|
|
17011
17307
|
"div",
|
|
17012
17308
|
{
|
|
17013
17309
|
className: cn(
|
|
17014
|
-
"flex max-w-[85%]",
|
|
17310
|
+
"group relative flex max-w-[85%]",
|
|
17015
17311
|
isCustomer ? "ml-auto" : "mr-auto"
|
|
17016
17312
|
),
|
|
17017
|
-
children:
|
|
17018
|
-
"div",
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
|
|
17034
|
-
|
|
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
|
+
]
|
|
17035
17392
|
}
|
|
17036
|
-
)
|
|
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" })
|
|
17037
17403
|
] }, msg.id);
|
|
17038
17404
|
}),
|
|
17039
17405
|
isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
|
|
@@ -17046,10 +17412,24 @@ function ChatInput({
|
|
|
17046
17412
|
placeholder,
|
|
17047
17413
|
isSending,
|
|
17048
17414
|
onSend,
|
|
17415
|
+
onAttach,
|
|
17049
17416
|
className
|
|
17050
17417
|
}) {
|
|
17051
17418
|
const [value, setValue] = useState("");
|
|
17419
|
+
const [uploading, setUploading] = useState(false);
|
|
17052
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
|
+
);
|
|
17053
17433
|
const handleSend = useCallback(() => {
|
|
17054
17434
|
const trimmed = value.trim();
|
|
17055
17435
|
if (!trimmed || isSending) return;
|
|
@@ -17083,6 +17463,37 @@ function ChatInput({
|
|
|
17083
17463
|
className
|
|
17084
17464
|
),
|
|
17085
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
|
+
),
|
|
17086
17497
|
/* @__PURE__ */ jsx(
|
|
17087
17498
|
"div",
|
|
17088
17499
|
{
|
|
@@ -17185,6 +17596,25 @@ function SendIcon({ className }) {
|
|
|
17185
17596
|
}
|
|
17186
17597
|
);
|
|
17187
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
|
+
}
|
|
17188
17618
|
function SparkleIcon({ className }) {
|
|
17189
17619
|
return /* @__PURE__ */ jsx(
|
|
17190
17620
|
"svg",
|
|
@@ -17199,4 +17629,1825 @@ function SparkleIcon({ className }) {
|
|
|
17199
17629
|
);
|
|
17200
17630
|
}
|
|
17201
17631
|
|
|
17202
|
-
|
|
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 };
|