@cimplify/sdk 0.89.0 → 0.92.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/advanced.js +20 -20
- package/dist/advanced.mjs +1 -1
- package/dist/{chunk-JHJTXAPS.js → chunk-3MBMIEWT.js} +4 -4
- package/dist/{chunk-DENWOZEA.mjs → chunk-3XD2FVP4.mjs} +163 -43
- package/dist/{chunk-M4ALCELT.mjs → chunk-54H2YYIW.mjs} +2 -2
- package/dist/{chunk-T2CJ67QV.js → chunk-DCRN67NO.js} +201 -78
- package/dist/{chunk-T7Z25EY4.js → chunk-HKN3YG76.js} +84 -0
- package/dist/{chunk-GHFDYM5K.js → chunk-IVJEUGWF.js} +10 -1
- package/dist/{chunk-ROBTTS3Q.js → chunk-OAHSF542.js} +40 -1
- package/dist/{chunk-APBLFZ2I.mjs → chunk-PYBFTMHI.mjs} +40 -1
- package/dist/{chunk-OZDDHSIB.mjs → chunk-RRBY35TJ.mjs} +80 -1
- package/dist/{chunk-ZU4ZRYUR.mjs → chunk-T7LDBZZZ.mjs} +10 -1
- package/dist/chunk-U4MW6PYU.mjs +23 -0
- package/dist/chunk-XREIZ56K.js +26 -0
- package/dist/{client-CqZgdUtw.d.ts → client-BQbSKlHS.d.mts} +165 -11
- package/dist/{client-CAY2NbRG.d.mts → client-Bw4HtLP1.d.mts} +2 -2
- package/dist/{client-D8ZFs-sq.d.mts → client-CeW3acve.d.ts} +165 -11
- package/dist/{client-CUbwb6Om.d.ts → client-Otzv_rgP.d.ts} +2 -2
- package/dist/{index-CmWOz9bi.d.mts → index-CmIboQjl.d.mts} +3 -3
- package/dist/{index-Bc5qUxAt.d.ts → index-DgoRwz8l.d.ts} +3 -3
- package/dist/index.d.mts +49 -7
- package/dist/index.d.ts +49 -7
- package/dist/index.js +109 -82
- package/dist/index.mjs +11 -5
- package/dist/mock/cli.mjs +61 -1
- package/dist/mock/library.js +61 -1
- package/dist/mock/library.mjs +61 -1
- package/dist/mock/msw.js +61 -1
- package/dist/mock/msw.mjs +61 -1
- package/dist/{payment-Dcgb9n1n.d.mts → payment-BFtLqiG2.d.mts} +1 -1
- package/dist/{payment-Xq0WDZXh.d.ts → payment-Cq8FT1B6.d.ts} +1 -1
- package/dist/{price-DAAjKgAp.d.ts → price-DaFXT-Lr.d.ts} +1 -1
- package/dist/{price--Ma_WO5_.d.mts → price-DleDWk_s.d.mts} +1 -1
- package/dist/{product-BakHcsP2.d.ts → product-bZ8G5Z8D.d.mts} +95 -2
- package/dist/{product-BakHcsP2.d.mts → product-bZ8G5Z8D.d.ts} +95 -2
- package/dist/react.d.mts +471 -8
- package/dist/react.d.ts +471 -8
- package/dist/react.js +2787 -402
- package/dist/react.mjs +2699 -337
- package/dist/{server-Bk_hlefX.d.mts → server-D-iK-bNt.d.mts} +8 -1
- package/dist/{server-Bei5lBrc.d.ts → server-DwsY9hlX.d.ts} +8 -1
- package/dist/server.d.mts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +7 -7
- package/dist/server.mjs +2 -2
- 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 +24 -23
- package/dist/testing/suite.mjs +5 -4
- package/dist/testing.d.mts +5 -5
- package/dist/testing.d.ts +5 -5
- package/dist/testing.js +80 -79
- package/dist/testing.mjs +6 -5
- package/dist/utils.d.mts +4 -4
- package/dist/utils.d.ts +4 -4
- package/package.json +4 -1
- package/registry/booking-page.json +1 -1
- package/registry/chat-widget.json +1 -1
- package/registry/customer-input-fields.json +1 -1
- package/registry/date-slot-picker.json +1 -1
- package/registry/product-customizer.json +1 -1
- package/registry/rental-service-card.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, PRODUCT_TYPE, BOUNDARY_KIND, resolveGallery, RENDER_HINT, DEFAULT_CDN_BASE_URL } from './chunk-
|
|
3
|
-
export { getVariantDisplayName } from './chunk-
|
|
4
|
-
import {
|
|
5
|
-
|
|
2
|
+
import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, isMultiDayService, serviceBillingMultiplier, PRODUCT_TYPE, BOUNDARY_KIND, resolveGallery, RENDER_HINT, DEFAULT_CDN_BASE_URL } from './chunk-RRBY35TJ.mjs';
|
|
3
|
+
export { getVariantDisplayName } from './chunk-RRBY35TJ.mjs';
|
|
4
|
+
import { messagePreview } from './chunk-U4MW6PYU.mjs';
|
|
5
|
+
export { messagePreview, splitGenUIContent } from './chunk-U4MW6PYU.mjs';
|
|
6
|
+
import { SESSION_CLAIMED, SESSION_CLEARED, createCimplifyClient, MUTATION_ERROR } from './chunk-3XD2FVP4.mjs';
|
|
7
|
+
import { ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-T7LDBZZZ.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";
|
|
@@ -719,6 +723,7 @@ function mapServerCart(serverCart, fallbackCurrency) {
|
|
|
719
723
|
scheduledStart: item.scheduled_start || void 0,
|
|
720
724
|
scheduledEnd: item.scheduled_end || void 0,
|
|
721
725
|
staffId: item.staff_id || void 0,
|
|
726
|
+
units: item.units || void 0,
|
|
722
727
|
lineType: item.line_type,
|
|
723
728
|
confirmationCode: item.confirmation_code || void 0,
|
|
724
729
|
serviceStatus: item.service_status || void 0,
|
|
@@ -766,18 +771,18 @@ function deriveView(base, ops) {
|
|
|
766
771
|
};
|
|
767
772
|
}
|
|
768
773
|
async function syncFromServer(entry) {
|
|
769
|
-
|
|
770
|
-
entry.syncing = true;
|
|
771
|
-
try {
|
|
774
|
+
const run = async () => {
|
|
772
775
|
const result = await entry.client.cart.get();
|
|
773
776
|
if (!result.ok) throw result.error;
|
|
774
777
|
entry.collection.setBase(mapServerCart(result.value, entry.currency));
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
+
};
|
|
779
|
+
const next = entry.syncTail.then(run, run);
|
|
780
|
+
entry.syncTail = next.catch(() => {
|
|
781
|
+
});
|
|
782
|
+
return next;
|
|
778
783
|
}
|
|
779
784
|
function emitMutationError(entry, operation, opId, idempotencyKey, error) {
|
|
780
|
-
entry.client.events.emit(
|
|
785
|
+
entry.client.events.emit(MUTATION_ERROR, {
|
|
781
786
|
service: "cart",
|
|
782
787
|
operation,
|
|
783
788
|
opId,
|
|
@@ -814,9 +819,15 @@ function getOrCreateStore(params) {
|
|
|
814
819
|
optimisticEnabled,
|
|
815
820
|
initialized: false,
|
|
816
821
|
initializePromise: null,
|
|
817
|
-
|
|
822
|
+
syncTail: Promise.resolve(),
|
|
818
823
|
failedOpRemovers: /* @__PURE__ */ new Set()
|
|
819
824
|
};
|
|
825
|
+
client.events.on(SESSION_CLAIMED, () => {
|
|
826
|
+
if (entry.initialized) void syncFromServer(entry);
|
|
827
|
+
});
|
|
828
|
+
client.events.on(SESSION_CLEARED, () => {
|
|
829
|
+
if (entry.initialized) void syncFromServer(entry);
|
|
830
|
+
});
|
|
820
831
|
cartStores.set(storeKey, entry);
|
|
821
832
|
return entry;
|
|
822
833
|
}
|
|
@@ -875,7 +886,8 @@ async function performAddItem(entry, product, quantity, options) {
|
|
|
875
886
|
specialInstructions: normalizedOptions.specialInstructions,
|
|
876
887
|
scheduledStart: normalizedOptions.scheduledStart,
|
|
877
888
|
scheduledEnd: normalizedOptions.scheduledEnd,
|
|
878
|
-
staffId: normalizedOptions.staffId
|
|
889
|
+
staffId: normalizedOptions.staffId,
|
|
890
|
+
units: normalizedOptions.units
|
|
879
891
|
});
|
|
880
892
|
const idempotencyKey = `idem_${typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`}`;
|
|
881
893
|
const patch = { kind: "add_item", tempItem, lineKey };
|
|
@@ -897,6 +909,7 @@ async function performAddItem(entry, product, quantity, options) {
|
|
|
897
909
|
scheduled_end: normalizedOptions.scheduledEnd,
|
|
898
910
|
staff_id: normalizedOptions.staffId,
|
|
899
911
|
resource_id: normalizedOptions.resourceId,
|
|
912
|
+
units: normalizedOptions.units,
|
|
900
913
|
customer_inputs: normalizedOptions.customerInputs
|
|
901
914
|
},
|
|
902
915
|
{ idempotencyKey }
|
|
@@ -4279,7 +4292,7 @@ function Price({
|
|
|
4279
4292
|
] });
|
|
4280
4293
|
}
|
|
4281
4294
|
function createCachedQuery(config) {
|
|
4282
|
-
const
|
|
4295
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
4283
4296
|
const inflight = /* @__PURE__ */ new Map();
|
|
4284
4297
|
const gcMaxAge = config.gcMaxAge ?? 3e5;
|
|
4285
4298
|
let lastSweep = 0;
|
|
@@ -4288,8 +4301,8 @@ function createCachedQuery(config) {
|
|
|
4288
4301
|
if (now - lastSweep < 6e4) return;
|
|
4289
4302
|
lastSweep = now;
|
|
4290
4303
|
const cutoff = now - gcMaxAge;
|
|
4291
|
-
for (const [key, entry] of
|
|
4292
|
-
if (entry.ts < cutoff)
|
|
4304
|
+
for (const [key, entry] of cache2) {
|
|
4305
|
+
if (entry.ts < cutoff) cache2.delete(key);
|
|
4293
4306
|
}
|
|
4294
4307
|
}
|
|
4295
4308
|
function useCachedQuery(options = {}) {
|
|
@@ -4307,13 +4320,13 @@ function createCachedQuery(config) {
|
|
|
4307
4320
|
const optionsRef = useRef(options);
|
|
4308
4321
|
optionsRef.current = options;
|
|
4309
4322
|
if (config.locationSensitive && previousLocationRef.current !== locationId) {
|
|
4310
|
-
|
|
4323
|
+
cache2.clear();
|
|
4311
4324
|
inflight.clear();
|
|
4312
4325
|
previousLocationRef.current = locationId;
|
|
4313
4326
|
}
|
|
4314
4327
|
sweep();
|
|
4315
4328
|
const cacheKey2 = config.key(client, options);
|
|
4316
|
-
const cached =
|
|
4329
|
+
const cached = cache2.get(cacheKey2);
|
|
4317
4330
|
const [data, setData] = useState(cached?.data ?? null);
|
|
4318
4331
|
const [isLoading, setIsLoading] = useState(enabled && !cached);
|
|
4319
4332
|
const [error, setError] = useState(null);
|
|
@@ -4326,7 +4339,7 @@ function createCachedQuery(config) {
|
|
|
4326
4339
|
const nextRequestId = ++requestIdRef.current;
|
|
4327
4340
|
setError(null);
|
|
4328
4341
|
if (!force) {
|
|
4329
|
-
const hit =
|
|
4342
|
+
const hit = cache2.get(cacheKey2);
|
|
4330
4343
|
if (hit) {
|
|
4331
4344
|
setData(hit.data);
|
|
4332
4345
|
setIsLoading(false);
|
|
@@ -4343,7 +4356,7 @@ function createCachedQuery(config) {
|
|
|
4343
4356
|
});
|
|
4344
4357
|
}
|
|
4345
4358
|
const value = await promise;
|
|
4346
|
-
|
|
4359
|
+
cache2.set(cacheKey2, { data: value, ts: Date.now() });
|
|
4347
4360
|
if (nextRequestId === requestIdRef.current) {
|
|
4348
4361
|
setData(value);
|
|
4349
4362
|
setError(null);
|
|
@@ -4364,23 +4377,23 @@ function createCachedQuery(config) {
|
|
|
4364
4377
|
void load(false);
|
|
4365
4378
|
}, [load]);
|
|
4366
4379
|
const refetch = useCallback(async () => {
|
|
4367
|
-
|
|
4380
|
+
cache2.delete(cacheKey2);
|
|
4368
4381
|
await load(true);
|
|
4369
4382
|
}, [cacheKey2, load]);
|
|
4370
4383
|
return { data, isLoading, error, refetch };
|
|
4371
4384
|
}
|
|
4372
4385
|
useCachedQuery.prefetch = (client, options) => {
|
|
4373
4386
|
const key = config.key(client, options);
|
|
4374
|
-
if (
|
|
4387
|
+
if (cache2.has(key) || inflight.has(key)) return void 0;
|
|
4375
4388
|
const promise = config.fetch(client, options);
|
|
4376
4389
|
inflight.set(key, promise);
|
|
4377
4390
|
return promise.then((value) => {
|
|
4378
|
-
|
|
4391
|
+
cache2.set(key, { data: value, ts: Date.now() });
|
|
4379
4392
|
}).catch(() => {
|
|
4380
4393
|
}).finally(() => inflight.delete(key));
|
|
4381
4394
|
};
|
|
4382
4395
|
useCachedQuery.clear = () => {
|
|
4383
|
-
|
|
4396
|
+
cache2.clear();
|
|
4384
4397
|
inflight.clear();
|
|
4385
4398
|
};
|
|
4386
4399
|
return useCachedQuery;
|
|
@@ -4508,6 +4521,9 @@ function useCategories(options = {}) {
|
|
|
4508
4521
|
return { categories: data ?? [], isLoading, error, refetch };
|
|
4509
4522
|
}
|
|
4510
4523
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "refunded"]);
|
|
4524
|
+
function isTerminalOrderStatus(status) {
|
|
4525
|
+
return TERMINAL_STATUSES.has(status);
|
|
4526
|
+
}
|
|
4511
4527
|
var orderCache = /* @__PURE__ */ new Map();
|
|
4512
4528
|
var orderInflight = /* @__PURE__ */ new Map();
|
|
4513
4529
|
function buildOrderCacheKey(client, orderId) {
|
|
@@ -5349,6 +5365,9 @@ function useOrders(options = {}) {
|
|
|
5349
5365
|
const { data, isLoading, error, refetch } = ordersQuery(options);
|
|
5350
5366
|
return { orders: data ?? [], isLoading, error, refetch };
|
|
5351
5367
|
}
|
|
5368
|
+
function clearOrdersCache() {
|
|
5369
|
+
ordersQuery.clear();
|
|
5370
|
+
}
|
|
5352
5371
|
function useSubscriptions(options = {}) {
|
|
5353
5372
|
const { client } = useCimplifyClient();
|
|
5354
5373
|
const { status, enabled = true } = options;
|
|
@@ -5444,14 +5463,24 @@ function useServices(options = {}) {
|
|
|
5444
5463
|
}
|
|
5445
5464
|
|
|
5446
5465
|
// src/react/hooks/use-available-slots.ts
|
|
5466
|
+
function narrowingUnits(units) {
|
|
5467
|
+
return units != null && units > 1 ? units : void 0;
|
|
5468
|
+
}
|
|
5447
5469
|
var slotsQuery = createCachedQuery({
|
|
5448
5470
|
name: "useAvailableSlots",
|
|
5449
|
-
key: (client, options) => cacheKey(
|
|
5471
|
+
key: (client, options) => cacheKey(
|
|
5472
|
+
client.getPublicKey(),
|
|
5473
|
+
options.serviceId,
|
|
5474
|
+
options.date,
|
|
5475
|
+
options.participantCount,
|
|
5476
|
+
narrowingUnits(options.units)
|
|
5477
|
+
),
|
|
5450
5478
|
fetch: async (client, options) => {
|
|
5451
5479
|
const result = await client.scheduling.getAvailableSlots({
|
|
5452
5480
|
service_id: options.serviceId,
|
|
5453
5481
|
date: options.date,
|
|
5454
|
-
participant_count: options.participantCount
|
|
5482
|
+
participant_count: options.participantCount,
|
|
5483
|
+
units: narrowingUnits(options.units)
|
|
5455
5484
|
});
|
|
5456
5485
|
if (!result.ok) throw result.error;
|
|
5457
5486
|
return result.value;
|
|
@@ -5468,6 +5497,9 @@ function useAvailableSlots(serviceId, date, options = {}) {
|
|
|
5468
5497
|
}
|
|
5469
5498
|
|
|
5470
5499
|
// src/react/hooks/use-service-availability.ts
|
|
5500
|
+
function narrowingUnits2(units) {
|
|
5501
|
+
return units != null && units > 1 ? units : void 0;
|
|
5502
|
+
}
|
|
5471
5503
|
var serviceAvailabilityQuery = createCachedQuery({
|
|
5472
5504
|
name: "useServiceAvailability",
|
|
5473
5505
|
key: (client, options) => cacheKey(
|
|
@@ -5475,7 +5507,8 @@ var serviceAvailabilityQuery = createCachedQuery({
|
|
|
5475
5507
|
options.serviceId,
|
|
5476
5508
|
options.startDate,
|
|
5477
5509
|
options.endDate,
|
|
5478
|
-
options.participantCount
|
|
5510
|
+
options.participantCount,
|
|
5511
|
+
narrowingUnits2(options.units)
|
|
5479
5512
|
),
|
|
5480
5513
|
fetch: async (client, options) => {
|
|
5481
5514
|
const result = await client.scheduling.getServiceAvailability({
|
|
@@ -5483,7 +5516,8 @@ var serviceAvailabilityQuery = createCachedQuery({
|
|
|
5483
5516
|
start_date: options.startDate,
|
|
5484
5517
|
end_date: options.endDate,
|
|
5485
5518
|
location_id: options.locationId,
|
|
5486
|
-
participant_count: options.participantCount
|
|
5519
|
+
participant_count: options.participantCount,
|
|
5520
|
+
units: narrowingUnits2(options.units)
|
|
5487
5521
|
});
|
|
5488
5522
|
if (!result.ok) throw result.error;
|
|
5489
5523
|
return result.value.availability;
|
|
@@ -5854,6 +5888,7 @@ var POLL_IDLE_INTERVAL_MS = 15e3;
|
|
|
5854
5888
|
var IDLE_TIMEOUT_MS = 6e4;
|
|
5855
5889
|
var WS_MAX_RECONNECT_ATTEMPTS = 10;
|
|
5856
5890
|
var WS_PATH = "/api/v1/support/conversation/ws";
|
|
5891
|
+
var AWAIT_REPLY_TIMEOUT_MS = 3e4;
|
|
5857
5892
|
function useChat(options = {}) {
|
|
5858
5893
|
const context = useOptionalCimplifyClient();
|
|
5859
5894
|
const client = options.client ?? context?.client;
|
|
@@ -5862,6 +5897,13 @@ function useChat(options = {}) {
|
|
|
5862
5897
|
const [isSending, setIsSending] = useState(false);
|
|
5863
5898
|
const [error, setError] = useState(null);
|
|
5864
5899
|
const [conversationOpened, setConversationOpened] = useState(false);
|
|
5900
|
+
const [humanRequested, setHumanRequested] = useState(false);
|
|
5901
|
+
const [awaitingReply, setAwaitingReply] = useState(false);
|
|
5902
|
+
const [seenAt, setSeenAt] = useState(null);
|
|
5903
|
+
const [mayHaveOlder, setMayHaveOlder] = useState(false);
|
|
5904
|
+
const [claimedAs, setClaimedAs] = useState(
|
|
5905
|
+
() => client?.support.claimedCustomerName ?? null
|
|
5906
|
+
);
|
|
5865
5907
|
const pollRef = useRef(null);
|
|
5866
5908
|
const lastActivityRef = useRef(Date.now());
|
|
5867
5909
|
const openedRef = useRef(false);
|
|
@@ -5870,17 +5912,43 @@ function useChat(options = {}) {
|
|
|
5870
5912
|
const wsRef = useRef(null);
|
|
5871
5913
|
const wsReconnectAttemptRef = useRef(0);
|
|
5872
5914
|
const wsReconnectTimerRef = useRef(null);
|
|
5915
|
+
const aiEnabledRef = useRef(false);
|
|
5916
|
+
const humanRequestedRef = useRef(false);
|
|
5917
|
+
const awaitReplyTimerRef = useRef(null);
|
|
5918
|
+
const claimUnsubRef = useRef(null);
|
|
5873
5919
|
messagesRef.current = messages;
|
|
5874
5920
|
clientRef.current = client;
|
|
5921
|
+
humanRequestedRef.current = humanRequested;
|
|
5875
5922
|
const pollInterval = options.pollInterval ?? POLL_INTERVAL_MS;
|
|
5876
5923
|
const pollIdleInterval = options.pollIdleInterval ?? POLL_IDLE_INTERVAL_MS;
|
|
5877
|
-
const
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5924
|
+
const stopAwaitingReply = useCallback(() => {
|
|
5925
|
+
if (awaitReplyTimerRef.current) {
|
|
5926
|
+
clearTimeout(awaitReplyTimerRef.current);
|
|
5927
|
+
awaitReplyTimerRef.current = null;
|
|
5928
|
+
}
|
|
5929
|
+
setAwaitingReply(false);
|
|
5883
5930
|
}, []);
|
|
5931
|
+
const startAwaitingReply = useCallback(() => {
|
|
5932
|
+
setAwaitingReply(true);
|
|
5933
|
+
if (awaitReplyTimerRef.current) clearTimeout(awaitReplyTimerRef.current);
|
|
5934
|
+
awaitReplyTimerRef.current = setTimeout(() => {
|
|
5935
|
+
awaitReplyTimerRef.current = null;
|
|
5936
|
+
setAwaitingReply(false);
|
|
5937
|
+
}, AWAIT_REPLY_TIMEOUT_MS);
|
|
5938
|
+
}, []);
|
|
5939
|
+
const appendMessages = useCallback(
|
|
5940
|
+
(incoming) => {
|
|
5941
|
+
if (incoming.some((m) => m.sender_type === "bot" || m.sender_type === "agent")) {
|
|
5942
|
+
stopAwaitingReply();
|
|
5943
|
+
}
|
|
5944
|
+
setMessages((prev) => {
|
|
5945
|
+
const ids = new Set(prev.map((m) => m.id));
|
|
5946
|
+
const fresh = incoming.filter((m) => !ids.has(m.id));
|
|
5947
|
+
return fresh.length > 0 ? [...prev, ...fresh] : prev;
|
|
5948
|
+
});
|
|
5949
|
+
},
|
|
5950
|
+
[stopAwaitingReply]
|
|
5951
|
+
);
|
|
5884
5952
|
const fetchNewMessages = useCallback(async () => {
|
|
5885
5953
|
const c = clientRef.current;
|
|
5886
5954
|
if (!c) return;
|
|
@@ -5908,6 +5976,17 @@ function useChat(options = {}) {
|
|
|
5908
5976
|
pollRef.current = null;
|
|
5909
5977
|
}
|
|
5910
5978
|
}, []);
|
|
5979
|
+
const resyncConversation = useCallback(async () => {
|
|
5980
|
+
const c = clientRef.current;
|
|
5981
|
+
if (!c || !openedRef.current) return;
|
|
5982
|
+
const result = await c.support.openConversation();
|
|
5983
|
+
if (!result.ok) return;
|
|
5984
|
+
setMessages(result.value.messages);
|
|
5985
|
+
aiEnabledRef.current = result.value.conversation.ai_enabled;
|
|
5986
|
+
setHumanRequested(result.value.conversation.needs_human_review ?? false);
|
|
5987
|
+
setSeenAt(result.value.conversation.team_read_at ?? null);
|
|
5988
|
+
setMayHaveOlder(result.value.messages.length >= 50);
|
|
5989
|
+
}, []);
|
|
5911
5990
|
const connectWebSocket = useCallback(() => {
|
|
5912
5991
|
const c = clientRef.current;
|
|
5913
5992
|
if (!c || typeof WebSocket === "undefined") {
|
|
@@ -5929,12 +6008,21 @@ function useChat(options = {}) {
|
|
|
5929
6008
|
const envelope = JSON.parse(event.data);
|
|
5930
6009
|
if (envelope.type === "message.created" && envelope.data) {
|
|
5931
6010
|
appendMessages([envelope.data]);
|
|
6011
|
+
} else if (envelope.type === "thread.read") {
|
|
6012
|
+
const at = envelope.data?.last_read_at;
|
|
6013
|
+
if (at) setSeenAt((prev) => !prev || prev < at ? at : prev);
|
|
6014
|
+
} else if (envelope.type === "thread.responding") {
|
|
6015
|
+
const active = envelope.data?.active;
|
|
6016
|
+
if (active) startAwaitingReply();
|
|
6017
|
+
else stopAwaitingReply();
|
|
5932
6018
|
} else if (envelope.type === "resync") {
|
|
5933
6019
|
const latest = envelope.latest_message_at;
|
|
5934
6020
|
const last = messagesRef.current[messagesRef.current.length - 1];
|
|
5935
6021
|
if (latest && (!last || new Date(last.created_at).getTime() < new Date(latest).getTime())) {
|
|
5936
6022
|
fetchNewMessages();
|
|
5937
6023
|
}
|
|
6024
|
+
} else if (envelope.type === "rekey") {
|
|
6025
|
+
void resyncConversation();
|
|
5938
6026
|
}
|
|
5939
6027
|
} catch {
|
|
5940
6028
|
}
|
|
@@ -5954,11 +6042,57 @@ function useChat(options = {}) {
|
|
|
5954
6042
|
ws.onerror = () => {
|
|
5955
6043
|
};
|
|
5956
6044
|
wsRef.current = ws;
|
|
5957
|
-
}, [appendMessages, startPolling, stopPolling, fetchNewMessages]);
|
|
6045
|
+
}, [appendMessages, startPolling, stopPolling, fetchNewMessages, resyncConversation]);
|
|
6046
|
+
const resetConversation = useCallback(() => {
|
|
6047
|
+
stopPolling();
|
|
6048
|
+
if (wsReconnectTimerRef.current) {
|
|
6049
|
+
clearTimeout(wsReconnectTimerRef.current);
|
|
6050
|
+
wsReconnectTimerRef.current = null;
|
|
6051
|
+
}
|
|
6052
|
+
if (wsRef.current) {
|
|
6053
|
+
wsRef.current.onclose = null;
|
|
6054
|
+
wsRef.current.close();
|
|
6055
|
+
wsRef.current = null;
|
|
6056
|
+
}
|
|
6057
|
+
wsReconnectAttemptRef.current = 0;
|
|
6058
|
+
openedRef.current = false;
|
|
6059
|
+
stopAwaitingReply();
|
|
6060
|
+
setMessages([]);
|
|
6061
|
+
setConversationOpened(false);
|
|
6062
|
+
setHumanRequested(false);
|
|
6063
|
+
setSeenAt(null);
|
|
6064
|
+
setMayHaveOlder(false);
|
|
6065
|
+
setError(null);
|
|
6066
|
+
setClaimedAs(null);
|
|
6067
|
+
}, [stopPolling, stopAwaitingReply]);
|
|
6068
|
+
const watchSessionClaims = useCallback(() => {
|
|
6069
|
+
const c = clientRef.current;
|
|
6070
|
+
if (!c || claimUnsubRef.current) return;
|
|
6071
|
+
const unsubClaimed = c.events.on(SESSION_CLAIMED, (event) => {
|
|
6072
|
+
setClaimedAs(event.customerName || null);
|
|
6073
|
+
if (!openedRef.current) return;
|
|
6074
|
+
connectWebSocket();
|
|
6075
|
+
void resyncConversation();
|
|
6076
|
+
});
|
|
6077
|
+
const unsubCleared = c.events.on(SESSION_CLEARED, resetConversation);
|
|
6078
|
+
claimUnsubRef.current = () => {
|
|
6079
|
+
unsubClaimed();
|
|
6080
|
+
unsubCleared();
|
|
6081
|
+
};
|
|
6082
|
+
}, [connectWebSocket, resyncConversation, resetConversation]);
|
|
6083
|
+
const signOut = useCallback(async () => {
|
|
6084
|
+
const c = clientRef.current;
|
|
6085
|
+
if (!c) return false;
|
|
6086
|
+
const result = await c.link.logout();
|
|
6087
|
+
return result.ok;
|
|
6088
|
+
}, []);
|
|
5958
6089
|
useEffect(() => {
|
|
5959
6090
|
return () => {
|
|
5960
6091
|
if (pollRef.current) clearInterval(pollRef.current);
|
|
5961
6092
|
if (wsReconnectTimerRef.current) clearTimeout(wsReconnectTimerRef.current);
|
|
6093
|
+
if (awaitReplyTimerRef.current) clearTimeout(awaitReplyTimerRef.current);
|
|
6094
|
+
claimUnsubRef.current?.();
|
|
6095
|
+
claimUnsubRef.current = null;
|
|
5962
6096
|
if (wsRef.current) {
|
|
5963
6097
|
wsRef.current.onclose = null;
|
|
5964
6098
|
wsRef.current.close();
|
|
@@ -5974,35 +6108,53 @@ function useChat(options = {}) {
|
|
|
5974
6108
|
if (result.ok) {
|
|
5975
6109
|
setMessages(result.value.messages);
|
|
5976
6110
|
setConversationOpened(true);
|
|
6111
|
+
aiEnabledRef.current = result.value.conversation.ai_enabled;
|
|
6112
|
+
setHumanRequested(result.value.conversation.needs_human_review ?? false);
|
|
6113
|
+
setSeenAt(result.value.conversation.team_read_at ?? null);
|
|
6114
|
+
setMayHaveOlder(result.value.messages.length >= 50);
|
|
6115
|
+
setClaimedAs(client.support.claimedCustomerName);
|
|
5977
6116
|
connectWebSocket();
|
|
6117
|
+
watchSessionClaims();
|
|
5978
6118
|
} else {
|
|
5979
6119
|
openedRef.current = false;
|
|
5980
6120
|
setError(result.error);
|
|
5981
6121
|
}
|
|
5982
6122
|
setIsLoading(false);
|
|
5983
|
-
}, [client, connectWebSocket]);
|
|
6123
|
+
}, [client, connectWebSocket, watchSessionClaims]);
|
|
5984
6124
|
const send = useCallback(
|
|
5985
|
-
async (content) => {
|
|
5986
|
-
|
|
6125
|
+
async (content, opts) => {
|
|
6126
|
+
const attachments = opts?.attachments ?? [];
|
|
6127
|
+
if (!client || !content.trim() && attachments.length === 0) return false;
|
|
5987
6128
|
setError(null);
|
|
5988
6129
|
lastActivityRef.current = Date.now();
|
|
5989
6130
|
if (!openedRef.current) {
|
|
5990
6131
|
await openConversation();
|
|
5991
|
-
if (!openedRef.current) return;
|
|
6132
|
+
if (!openedRef.current) return false;
|
|
5992
6133
|
}
|
|
6134
|
+
const replyTo = opts?.replyTo;
|
|
5993
6135
|
const optimisticId = `opt_${Date.now()}`;
|
|
5994
6136
|
const optimistic = {
|
|
5995
6137
|
id: optimisticId,
|
|
5996
6138
|
sender_type: "customer",
|
|
5997
6139
|
content: content.trim(),
|
|
5998
|
-
content_type: "text",
|
|
5999
|
-
attachments
|
|
6140
|
+
content_type: attachments.length > 0 ? "image" : "text",
|
|
6141
|
+
attachments,
|
|
6000
6142
|
metadata: {},
|
|
6143
|
+
// Same shape the server echoes back, so the bubble doesn't reflow
|
|
6144
|
+
// when the confirmed message replaces it.
|
|
6145
|
+
reply_to: replyTo ? {
|
|
6146
|
+
id: replyTo.id,
|
|
6147
|
+
sender_type: replyTo.sender_type,
|
|
6148
|
+
preview: messagePreview(replyTo.content)
|
|
6149
|
+
} : void 0,
|
|
6001
6150
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6002
6151
|
};
|
|
6003
6152
|
setMessages((prev) => [...prev, optimistic]);
|
|
6004
6153
|
setIsSending(true);
|
|
6005
|
-
const result = await client.support.sendMessage(content.trim()
|
|
6154
|
+
const result = await client.support.sendMessage(content.trim(), {
|
|
6155
|
+
replyToMessageId: replyTo?.id,
|
|
6156
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
6157
|
+
});
|
|
6006
6158
|
if (result.ok) {
|
|
6007
6159
|
const confirmed = result.value;
|
|
6008
6160
|
setMessages(
|
|
@@ -6013,13 +6165,17 @@ function useChat(options = {}) {
|
|
|
6013
6165
|
prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== optimisticId) : prev.map((m) => m.id === optimisticId ? confirmed : m)
|
|
6014
6166
|
)
|
|
6015
6167
|
);
|
|
6168
|
+
if (aiEnabledRef.current && !humanRequestedRef.current) {
|
|
6169
|
+
startAwaitingReply();
|
|
6170
|
+
}
|
|
6016
6171
|
} else {
|
|
6017
6172
|
setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
|
|
6018
6173
|
setError(result.error);
|
|
6019
6174
|
}
|
|
6020
6175
|
setIsSending(false);
|
|
6176
|
+
return result.ok;
|
|
6021
6177
|
},
|
|
6022
|
-
[client, openConversation]
|
|
6178
|
+
[client, openConversation, startAwaitingReply]
|
|
6023
6179
|
);
|
|
6024
6180
|
const startConversation = useCallback(
|
|
6025
6181
|
async (starterText) => {
|
|
@@ -6030,14 +6186,67 @@ function useChat(options = {}) {
|
|
|
6030
6186
|
},
|
|
6031
6187
|
[openConversation, send]
|
|
6032
6188
|
);
|
|
6189
|
+
const react = useCallback(
|
|
6190
|
+
async (message, emoji) => {
|
|
6191
|
+
if (!client || message.id.startsWith("opt_")) return false;
|
|
6192
|
+
const result = await client.support.reactToMessage(message.id, emoji);
|
|
6193
|
+
if (result.ok) appendMessages([result.value]);
|
|
6194
|
+
return result.ok;
|
|
6195
|
+
},
|
|
6196
|
+
[client, appendMessages]
|
|
6197
|
+
);
|
|
6198
|
+
const requestHuman = useCallback(async () => {
|
|
6199
|
+
if (!client) return false;
|
|
6200
|
+
setError(null);
|
|
6201
|
+
if (!openedRef.current) {
|
|
6202
|
+
await openConversation();
|
|
6203
|
+
if (!openedRef.current) return false;
|
|
6204
|
+
}
|
|
6205
|
+
const result = await client.support.requestHuman();
|
|
6206
|
+
if (result.ok) {
|
|
6207
|
+
setHumanRequested(true);
|
|
6208
|
+
return true;
|
|
6209
|
+
}
|
|
6210
|
+
setError(result.error);
|
|
6211
|
+
return false;
|
|
6212
|
+
}, [client, openConversation]);
|
|
6213
|
+
const loadOlder = useCallback(async () => {
|
|
6214
|
+
const c = clientRef.current;
|
|
6215
|
+
const earliest = messagesRef.current[0];
|
|
6216
|
+
if (!c || !earliest) return false;
|
|
6217
|
+
const result = await c.support.getMessages({ before: earliest.created_at, limit: 50 });
|
|
6218
|
+
if (!result.ok) {
|
|
6219
|
+
setError(result.error);
|
|
6220
|
+
return false;
|
|
6221
|
+
}
|
|
6222
|
+
const older = result.value;
|
|
6223
|
+
setMayHaveOlder(older.length >= 50);
|
|
6224
|
+
if (older.length === 0) return false;
|
|
6225
|
+
setMessages((prev) => {
|
|
6226
|
+
const ids = new Set(prev.map((m) => m.id));
|
|
6227
|
+
const fresh = older.filter((m) => !ids.has(m.id));
|
|
6228
|
+
return fresh.length > 0 ? [...fresh, ...prev] : prev;
|
|
6229
|
+
});
|
|
6230
|
+
return true;
|
|
6231
|
+
}, []);
|
|
6232
|
+
const agentArrived = messages.some((m) => m.sender_type === "agent");
|
|
6033
6233
|
return {
|
|
6034
6234
|
messages,
|
|
6035
6235
|
isLoading,
|
|
6036
6236
|
isSending,
|
|
6037
6237
|
error,
|
|
6038
6238
|
isActive: conversationOpened && messages.length > 0,
|
|
6239
|
+
humanRequested: humanRequested && !agentArrived,
|
|
6240
|
+
awaitingReply,
|
|
6241
|
+
seenAt,
|
|
6242
|
+
mayHaveOlder,
|
|
6243
|
+
claimedAs,
|
|
6244
|
+
signOut,
|
|
6039
6245
|
send,
|
|
6040
|
-
startConversation
|
|
6246
|
+
startConversation,
|
|
6247
|
+
react,
|
|
6248
|
+
requestHuman,
|
|
6249
|
+
loadOlder
|
|
6041
6250
|
};
|
|
6042
6251
|
}
|
|
6043
6252
|
var ElementsContext = createContext({
|
|
@@ -7767,6 +7976,177 @@ function VolumePricing({
|
|
|
7767
7976
|
}
|
|
7768
7977
|
);
|
|
7769
7978
|
}
|
|
7979
|
+
function createPlacesSessionToken() {
|
|
7980
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
7981
|
+
return crypto.randomUUID();
|
|
7982
|
+
}
|
|
7983
|
+
return `places_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
7984
|
+
}
|
|
7985
|
+
var MIN_QUERY_LENGTH = 3;
|
|
7986
|
+
var DEBOUNCE_MS = 300;
|
|
7987
|
+
var BLUR_CLOSE_MS = 120;
|
|
7988
|
+
function AddressAutocomplete({
|
|
7989
|
+
value,
|
|
7990
|
+
onSelect,
|
|
7991
|
+
onDirty,
|
|
7992
|
+
placeholder,
|
|
7993
|
+
required,
|
|
7994
|
+
autoFocus,
|
|
7995
|
+
id,
|
|
7996
|
+
className,
|
|
7997
|
+
classNames
|
|
7998
|
+
}) {
|
|
7999
|
+
const clientContext = useOptionalCimplifyClient();
|
|
8000
|
+
const [inputText, setInputText] = React14.useState(value?.formatted_address ?? "");
|
|
8001
|
+
const [suggestions, setSuggestions] = React14.useState([]);
|
|
8002
|
+
const [isOpen, setIsOpen] = React14.useState(false);
|
|
8003
|
+
const [highlightedIndex, setHighlightedIndex] = React14.useState(-1);
|
|
8004
|
+
const debounceRef = React14.useRef(null);
|
|
8005
|
+
const seqRef = React14.useRef(0);
|
|
8006
|
+
const sessionTokenRef = React14.useRef(createPlacesSessionToken());
|
|
8007
|
+
const blurTimeoutRef = React14.useRef(null);
|
|
8008
|
+
React14.useEffect(() => {
|
|
8009
|
+
return () => {
|
|
8010
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
8011
|
+
if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
|
|
8012
|
+
seqRef.current += 1;
|
|
8013
|
+
};
|
|
8014
|
+
}, []);
|
|
8015
|
+
const fetchSuggestions = React14.useCallback(
|
|
8016
|
+
async (query) => {
|
|
8017
|
+
const client = clientContext?.client;
|
|
8018
|
+
if (!client || query.length < MIN_QUERY_LENGTH) {
|
|
8019
|
+
setSuggestions([]);
|
|
8020
|
+
setIsOpen(false);
|
|
8021
|
+
return;
|
|
8022
|
+
}
|
|
8023
|
+
const seq = ++seqRef.current;
|
|
8024
|
+
const result = await client.places.autocomplete(query, sessionTokenRef.current);
|
|
8025
|
+
if (seqRef.current !== seq) return;
|
|
8026
|
+
if (result.ok && result.value.predictions.length > 0) {
|
|
8027
|
+
setSuggestions(result.value.predictions);
|
|
8028
|
+
setHighlightedIndex(0);
|
|
8029
|
+
setIsOpen(true);
|
|
8030
|
+
} else {
|
|
8031
|
+
setSuggestions([]);
|
|
8032
|
+
setIsOpen(false);
|
|
8033
|
+
}
|
|
8034
|
+
},
|
|
8035
|
+
[clientContext]
|
|
8036
|
+
);
|
|
8037
|
+
const handleInputChange = (nextValue) => {
|
|
8038
|
+
setInputText(nextValue);
|
|
8039
|
+
onDirty?.();
|
|
8040
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
8041
|
+
debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), DEBOUNCE_MS);
|
|
8042
|
+
};
|
|
8043
|
+
const selectSuggestion = React14.useCallback(
|
|
8044
|
+
async (suggestion) => {
|
|
8045
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
8046
|
+
if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
|
|
8047
|
+
const fallback = suggestion.description.split(",")[0]?.trim() || suggestion.description;
|
|
8048
|
+
setInputText(fallback);
|
|
8049
|
+
setSuggestions([]);
|
|
8050
|
+
setIsOpen(false);
|
|
8051
|
+
const client = clientContext?.client;
|
|
8052
|
+
if (!client) return;
|
|
8053
|
+
const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
|
|
8054
|
+
sessionTokenRef.current = createPlacesSessionToken();
|
|
8055
|
+
if (result.ok) {
|
|
8056
|
+
const d = result.value;
|
|
8057
|
+
const address = {
|
|
8058
|
+
formatted_address: d.formatted_address || fallback,
|
|
8059
|
+
street_address: d.street_address,
|
|
8060
|
+
apartment: d.apartment,
|
|
8061
|
+
city: d.city,
|
|
8062
|
+
region: d.region,
|
|
8063
|
+
postal_code: d.postal_code,
|
|
8064
|
+
country: d.country,
|
|
8065
|
+
latitude: d.latitude,
|
|
8066
|
+
longitude: d.longitude,
|
|
8067
|
+
place_id: d.place_id
|
|
8068
|
+
};
|
|
8069
|
+
setInputText(address.formatted_address);
|
|
8070
|
+
onSelect(address);
|
|
8071
|
+
}
|
|
8072
|
+
},
|
|
8073
|
+
[clientContext, onSelect]
|
|
8074
|
+
);
|
|
8075
|
+
return /* @__PURE__ */ jsxs("div", { "data-cimplify-address-autocomplete": true, className: cn("relative", className, classNames?.root), children: [
|
|
8076
|
+
/* @__PURE__ */ jsx(
|
|
8077
|
+
"input",
|
|
8078
|
+
{
|
|
8079
|
+
id,
|
|
8080
|
+
type: "text",
|
|
8081
|
+
value: inputText,
|
|
8082
|
+
onChange: (e) => handleInputChange(e.target.value),
|
|
8083
|
+
onFocus: () => {
|
|
8084
|
+
if (suggestions.length > 0) setIsOpen(true);
|
|
8085
|
+
},
|
|
8086
|
+
onBlur: () => {
|
|
8087
|
+
blurTimeoutRef.current = setTimeout(() => setIsOpen(false), BLUR_CLOSE_MS);
|
|
8088
|
+
},
|
|
8089
|
+
onKeyDown: (e) => {
|
|
8090
|
+
if (e.key === "Escape") {
|
|
8091
|
+
setIsOpen(false);
|
|
8092
|
+
return;
|
|
8093
|
+
}
|
|
8094
|
+
if (!isOpen || suggestions.length === 0) return;
|
|
8095
|
+
if (e.key === "ArrowDown") {
|
|
8096
|
+
e.preventDefault();
|
|
8097
|
+
setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
|
|
8098
|
+
} else if (e.key === "ArrowUp") {
|
|
8099
|
+
e.preventDefault();
|
|
8100
|
+
setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
|
|
8101
|
+
} else if (e.key === "Enter" && highlightedIndex >= 0) {
|
|
8102
|
+
e.preventDefault();
|
|
8103
|
+
void selectSuggestion(suggestions[highlightedIndex]);
|
|
8104
|
+
}
|
|
8105
|
+
},
|
|
8106
|
+
placeholder: placeholder ?? "Search for an address...",
|
|
8107
|
+
required,
|
|
8108
|
+
autoFocus,
|
|
8109
|
+
autoComplete: "off",
|
|
8110
|
+
"aria-autocomplete": "list",
|
|
8111
|
+
"aria-expanded": isOpen && suggestions.length > 0,
|
|
8112
|
+
"data-cimplify-address-input": true,
|
|
8113
|
+
className: classNames?.input
|
|
8114
|
+
}
|
|
8115
|
+
),
|
|
8116
|
+
isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx(
|
|
8117
|
+
"div",
|
|
8118
|
+
{
|
|
8119
|
+
role: "listbox",
|
|
8120
|
+
"data-cimplify-address-list": true,
|
|
8121
|
+
className: cn(
|
|
8122
|
+
"absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg",
|
|
8123
|
+
classNames?.list
|
|
8124
|
+
),
|
|
8125
|
+
children: suggestions.map((s, i) => /* @__PURE__ */ jsx(
|
|
8126
|
+
"button",
|
|
8127
|
+
{
|
|
8128
|
+
type: "button",
|
|
8129
|
+
role: "option",
|
|
8130
|
+
"aria-selected": i === highlightedIndex,
|
|
8131
|
+
onMouseDown: (e) => {
|
|
8132
|
+
e.preventDefault();
|
|
8133
|
+
void selectSuggestion(s);
|
|
8134
|
+
},
|
|
8135
|
+
"data-cimplify-address-option": true,
|
|
8136
|
+
className: cn(
|
|
8137
|
+
"block w-full px-3 py-2.5 text-left text-sm transition-colors",
|
|
8138
|
+
i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70",
|
|
8139
|
+
classNames?.option,
|
|
8140
|
+
i === highlightedIndex && classNames?.activeOption
|
|
8141
|
+
),
|
|
8142
|
+
children: s.description
|
|
8143
|
+
},
|
|
8144
|
+
s.place_id
|
|
8145
|
+
))
|
|
8146
|
+
}
|
|
8147
|
+
)
|
|
8148
|
+
] });
|
|
8149
|
+
}
|
|
7770
8150
|
function parseValue(value) {
|
|
7771
8151
|
if (!value) return void 0;
|
|
7772
8152
|
const date = parseISO(value);
|
|
@@ -8527,129 +8907,18 @@ function AddressInput({
|
|
|
8527
8907
|
classNames,
|
|
8528
8908
|
inputClass
|
|
8529
8909
|
}) {
|
|
8530
|
-
const clientContext = useOptionalCimplifyClient();
|
|
8531
8910
|
const addr = value && typeof value === "object" ? value : null;
|
|
8532
|
-
|
|
8533
|
-
const [suggestions, setSuggestions] = React14.useState([]);
|
|
8534
|
-
const [isOpen, setIsOpen] = React14.useState(false);
|
|
8535
|
-
const [highlightedIndex, setHighlightedIndex] = React14.useState(-1);
|
|
8536
|
-
const debounceRef = React14.useRef(null);
|
|
8537
|
-
const seqRef = React14.useRef(0);
|
|
8538
|
-
const sessionTokenRef = React14.useRef(createSessionToken());
|
|
8539
|
-
const blurTimeoutRef = React14.useRef(null);
|
|
8540
|
-
React14.useEffect(() => {
|
|
8541
|
-
return () => {
|
|
8542
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
8543
|
-
if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
|
|
8544
|
-
seqRef.current += 1;
|
|
8545
|
-
};
|
|
8546
|
-
}, []);
|
|
8547
|
-
const fetchSuggestions = React14.useCallback(async (query) => {
|
|
8548
|
-
const client = clientContext?.client;
|
|
8549
|
-
if (!client || query.length < 3) {
|
|
8550
|
-
setSuggestions([]);
|
|
8551
|
-
setIsOpen(false);
|
|
8552
|
-
return;
|
|
8553
|
-
}
|
|
8554
|
-
const seq = ++seqRef.current;
|
|
8555
|
-
const result = await client.places.autocomplete(query, sessionTokenRef.current);
|
|
8556
|
-
if (seqRef.current !== seq) return;
|
|
8557
|
-
if (result.ok && result.value.predictions.length > 0) {
|
|
8558
|
-
setSuggestions(result.value.predictions);
|
|
8559
|
-
setHighlightedIndex(0);
|
|
8560
|
-
setIsOpen(true);
|
|
8561
|
-
} else {
|
|
8562
|
-
setSuggestions([]);
|
|
8563
|
-
setIsOpen(false);
|
|
8564
|
-
}
|
|
8565
|
-
}, [clientContext]);
|
|
8566
|
-
const handleInputChange = (nextValue) => {
|
|
8567
|
-
setInputText(nextValue);
|
|
8568
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
8569
|
-
debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
|
|
8570
|
-
};
|
|
8571
|
-
const selectSuggestion = React14.useCallback(async (suggestion) => {
|
|
8572
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
8573
|
-
if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
|
|
8574
|
-
const fallback = suggestion.description.split(",")[0]?.trim() || suggestion.description;
|
|
8575
|
-
setInputText(fallback);
|
|
8576
|
-
setSuggestions([]);
|
|
8577
|
-
setIsOpen(false);
|
|
8578
|
-
const client = clientContext?.client;
|
|
8579
|
-
if (!client) return;
|
|
8580
|
-
const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
|
|
8581
|
-
sessionTokenRef.current = createSessionToken();
|
|
8582
|
-
if (result.ok) {
|
|
8583
|
-
const d = result.value;
|
|
8584
|
-
const addressValue = {
|
|
8585
|
-
formatted_address: d.formatted_address || fallback,
|
|
8586
|
-
street_address: d.street_address,
|
|
8587
|
-
apartment: d.apartment,
|
|
8588
|
-
city: d.city,
|
|
8589
|
-
region: d.region,
|
|
8590
|
-
postal_code: d.postal_code,
|
|
8591
|
-
country: d.country,
|
|
8592
|
-
latitude: d.latitude,
|
|
8593
|
-
longitude: d.longitude,
|
|
8594
|
-
place_id: d.place_id
|
|
8595
|
-
};
|
|
8596
|
-
setInputText(addressValue.formatted_address);
|
|
8597
|
-
onValueChange(addressValue);
|
|
8598
|
-
}
|
|
8599
|
-
}, [clientContext, onValueChange]);
|
|
8600
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2 relative", classNames?.addressInput), children: [
|
|
8911
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", classNames?.addressInput), children: [
|
|
8601
8912
|
/* @__PURE__ */ jsx(
|
|
8602
|
-
|
|
8913
|
+
AddressAutocomplete,
|
|
8603
8914
|
{
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
onChange: (e) => handleInputChange(e.target.value),
|
|
8607
|
-
onFocus: () => {
|
|
8608
|
-
if (suggestions.length > 0) setIsOpen(true);
|
|
8609
|
-
},
|
|
8610
|
-
onBlur: () => {
|
|
8611
|
-
blurTimeoutRef.current = setTimeout(() => setIsOpen(false), 120);
|
|
8612
|
-
},
|
|
8613
|
-
onKeyDown: (e) => {
|
|
8614
|
-
if (e.key === "Escape") {
|
|
8615
|
-
setIsOpen(false);
|
|
8616
|
-
return;
|
|
8617
|
-
}
|
|
8618
|
-
if (!isOpen || suggestions.length === 0) return;
|
|
8619
|
-
if (e.key === "ArrowDown") {
|
|
8620
|
-
e.preventDefault();
|
|
8621
|
-
setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
|
|
8622
|
-
} else if (e.key === "ArrowUp") {
|
|
8623
|
-
e.preventDefault();
|
|
8624
|
-
setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
|
|
8625
|
-
} else if (e.key === "Enter" && highlightedIndex >= 0) {
|
|
8626
|
-
e.preventDefault();
|
|
8627
|
-
void selectSuggestion(suggestions[highlightedIndex]);
|
|
8628
|
-
}
|
|
8629
|
-
},
|
|
8915
|
+
value: addr,
|
|
8916
|
+
onSelect: onValueChange,
|
|
8630
8917
|
placeholder: field.placeholder || "Search for an address...",
|
|
8631
8918
|
required: field.is_required,
|
|
8632
|
-
|
|
8633
|
-
"aria-autocomplete": "list",
|
|
8634
|
-
"aria-expanded": isOpen && suggestions.length > 0,
|
|
8635
|
-
className: inputClass
|
|
8919
|
+
classNames: { input: inputClass }
|
|
8636
8920
|
}
|
|
8637
8921
|
),
|
|
8638
|
-
isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx("div", { role: "listbox", className: "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg", children: suggestions.map((s, i) => /* @__PURE__ */ jsx(
|
|
8639
|
-
"button",
|
|
8640
|
-
{
|
|
8641
|
-
type: "button",
|
|
8642
|
-
role: "option",
|
|
8643
|
-
"aria-selected": i === highlightedIndex,
|
|
8644
|
-
onMouseDown: (e) => {
|
|
8645
|
-
e.preventDefault();
|
|
8646
|
-
void selectSuggestion(s);
|
|
8647
|
-
},
|
|
8648
|
-
className: cn("block w-full px-3 py-2.5 text-left text-sm transition-colors", i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70"),
|
|
8649
|
-
children: s.description
|
|
8650
|
-
},
|
|
8651
|
-
s.place_id
|
|
8652
|
-
)) }),
|
|
8653
8922
|
addr && /* @__PURE__ */ jsx(
|
|
8654
8923
|
"input",
|
|
8655
8924
|
{
|
|
@@ -8669,121 +8938,22 @@ function LocationInput({
|
|
|
8669
8938
|
classNames,
|
|
8670
8939
|
inputClass
|
|
8671
8940
|
}) {
|
|
8672
|
-
const clientContext = useOptionalCimplifyClient();
|
|
8673
8941
|
const loc = value && typeof value === "object" ? value : null;
|
|
8674
|
-
|
|
8675
|
-
const [suggestions, setSuggestions] = React14.useState([]);
|
|
8676
|
-
const [isOpen, setIsOpen] = React14.useState(false);
|
|
8677
|
-
const [highlightedIndex, setHighlightedIndex] = React14.useState(-1);
|
|
8678
|
-
const debounceRef = React14.useRef(null);
|
|
8679
|
-
const seqRef = React14.useRef(0);
|
|
8680
|
-
const sessionTokenRef = React14.useRef(createSessionToken());
|
|
8681
|
-
const blurTimeoutRef = React14.useRef(null);
|
|
8682
|
-
React14.useEffect(() => {
|
|
8683
|
-
return () => {
|
|
8684
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
8685
|
-
if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
|
|
8686
|
-
seqRef.current += 1;
|
|
8687
|
-
};
|
|
8688
|
-
}, []);
|
|
8689
|
-
const fetchSuggestions = React14.useCallback(async (query) => {
|
|
8690
|
-
const client = clientContext?.client;
|
|
8691
|
-
if (!client || query.length < 3) {
|
|
8692
|
-
setSuggestions([]);
|
|
8693
|
-
setIsOpen(false);
|
|
8694
|
-
return;
|
|
8695
|
-
}
|
|
8696
|
-
const seq = ++seqRef.current;
|
|
8697
|
-
const result = await client.places.autocomplete(query, sessionTokenRef.current);
|
|
8698
|
-
if (seqRef.current !== seq) return;
|
|
8699
|
-
if (result.ok && result.value.predictions.length > 0) {
|
|
8700
|
-
setSuggestions(result.value.predictions);
|
|
8701
|
-
setHighlightedIndex(0);
|
|
8702
|
-
setIsOpen(true);
|
|
8703
|
-
} else {
|
|
8704
|
-
setSuggestions([]);
|
|
8705
|
-
setIsOpen(false);
|
|
8706
|
-
}
|
|
8707
|
-
}, [clientContext]);
|
|
8708
|
-
const handleInputChange = (nextValue) => {
|
|
8709
|
-
setInputText(nextValue);
|
|
8710
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
8711
|
-
debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
|
|
8712
|
-
};
|
|
8713
|
-
const selectSuggestion = React14.useCallback(async (suggestion) => {
|
|
8714
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
8715
|
-
if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
|
|
8716
|
-
const label = suggestion.description.split(",")[0]?.trim() || suggestion.description;
|
|
8717
|
-
setInputText(label);
|
|
8718
|
-
setSuggestions([]);
|
|
8719
|
-
setIsOpen(false);
|
|
8720
|
-
const client = clientContext?.client;
|
|
8721
|
-
if (!client) return;
|
|
8722
|
-
const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
|
|
8723
|
-
sessionTokenRef.current = createSessionToken();
|
|
8724
|
-
if (result.ok) {
|
|
8725
|
-
const d = result.value;
|
|
8726
|
-
onValueChange({
|
|
8727
|
-
latitude: d.latitude,
|
|
8728
|
-
longitude: d.longitude,
|
|
8729
|
-
label: d.formatted_address || label
|
|
8730
|
-
});
|
|
8731
|
-
setInputText(d.formatted_address || label);
|
|
8732
|
-
}
|
|
8733
|
-
}, [clientContext, onValueChange]);
|
|
8734
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("relative", classNames?.locationInput), children: [
|
|
8942
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(classNames?.locationInput), children: [
|
|
8735
8943
|
/* @__PURE__ */ jsx(
|
|
8736
|
-
|
|
8944
|
+
AddressAutocomplete,
|
|
8737
8945
|
{
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
},
|
|
8744
|
-
onBlur: () => {
|
|
8745
|
-
blurTimeoutRef.current = setTimeout(() => setIsOpen(false), 120);
|
|
8746
|
-
},
|
|
8747
|
-
onKeyDown: (e) => {
|
|
8748
|
-
if (e.key === "Escape") {
|
|
8749
|
-
setIsOpen(false);
|
|
8750
|
-
return;
|
|
8751
|
-
}
|
|
8752
|
-
if (!isOpen || suggestions.length === 0) return;
|
|
8753
|
-
if (e.key === "ArrowDown") {
|
|
8754
|
-
e.preventDefault();
|
|
8755
|
-
setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
|
|
8756
|
-
} else if (e.key === "ArrowUp") {
|
|
8757
|
-
e.preventDefault();
|
|
8758
|
-
setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
|
|
8759
|
-
} else if (e.key === "Enter" && highlightedIndex >= 0) {
|
|
8760
|
-
e.preventDefault();
|
|
8761
|
-
void selectSuggestion(suggestions[highlightedIndex]);
|
|
8762
|
-
}
|
|
8763
|
-
},
|
|
8946
|
+
value: loc ? { formatted_address: loc.label ?? "", latitude: loc.latitude, longitude: loc.longitude } : null,
|
|
8947
|
+
onSelect: (address) => onValueChange({
|
|
8948
|
+
latitude: address.latitude,
|
|
8949
|
+
longitude: address.longitude,
|
|
8950
|
+
label: address.formatted_address
|
|
8951
|
+
}),
|
|
8764
8952
|
placeholder: field.placeholder || "Search for a location...",
|
|
8765
8953
|
required: field.is_required,
|
|
8766
|
-
|
|
8767
|
-
"aria-autocomplete": "list",
|
|
8768
|
-
"aria-expanded": isOpen && suggestions.length > 0,
|
|
8769
|
-
className: inputClass
|
|
8954
|
+
classNames: { input: inputClass }
|
|
8770
8955
|
}
|
|
8771
8956
|
),
|
|
8772
|
-
isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx("div", { role: "listbox", className: "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg", children: suggestions.map((s, i) => /* @__PURE__ */ jsx(
|
|
8773
|
-
"button",
|
|
8774
|
-
{
|
|
8775
|
-
type: "button",
|
|
8776
|
-
role: "option",
|
|
8777
|
-
"aria-selected": i === highlightedIndex,
|
|
8778
|
-
onMouseDown: (e) => {
|
|
8779
|
-
e.preventDefault();
|
|
8780
|
-
void selectSuggestion(s);
|
|
8781
|
-
},
|
|
8782
|
-
className: cn("block w-full px-3 py-2.5 text-left text-sm transition-colors", i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70"),
|
|
8783
|
-
children: s.description
|
|
8784
|
-
},
|
|
8785
|
-
s.place_id
|
|
8786
|
-
)) }),
|
|
8787
8957
|
loc && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: [
|
|
8788
8958
|
loc.latitude.toFixed(4),
|
|
8789
8959
|
", ",
|
|
@@ -8914,12 +9084,6 @@ function SignatureInput({
|
|
|
8914
9084
|
!sig?.data_url && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: field.placeholder || "Draw your signature above" })
|
|
8915
9085
|
] });
|
|
8916
9086
|
}
|
|
8917
|
-
function createSessionToken() {
|
|
8918
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
8919
|
-
return crypto.randomUUID();
|
|
8920
|
-
}
|
|
8921
|
-
return `places_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
8922
|
-
}
|
|
8923
9087
|
function getTimeOfDay(timeStr) {
|
|
8924
9088
|
const hour = parseInt(timeStr.split("T").pop()?.split(":")[0] ?? timeStr.split(":")[0], 10);
|
|
8925
9089
|
if (hour < 12) return "morning";
|
|
@@ -9165,6 +9329,7 @@ function DateSlotPicker({
|
|
|
9165
9329
|
serviceId,
|
|
9166
9330
|
daysToShow = 7,
|
|
9167
9331
|
participantCount,
|
|
9332
|
+
units,
|
|
9168
9333
|
selectedSlot,
|
|
9169
9334
|
onSlotSelect,
|
|
9170
9335
|
availability: availabilityProp,
|
|
@@ -9199,6 +9364,7 @@ function DateSlotPicker({
|
|
|
9199
9364
|
dateRange.endDate,
|
|
9200
9365
|
{
|
|
9201
9366
|
participantCount,
|
|
9367
|
+
units,
|
|
9202
9368
|
enabled: availabilityProp === void 0
|
|
9203
9369
|
}
|
|
9204
9370
|
);
|
|
@@ -9335,6 +9501,7 @@ function ProductCustomizer({
|
|
|
9335
9501
|
onAddToCart,
|
|
9336
9502
|
onVariantChange,
|
|
9337
9503
|
showSpecialInstructions = true,
|
|
9504
|
+
submitLabel,
|
|
9338
9505
|
className,
|
|
9339
9506
|
classNames
|
|
9340
9507
|
}) {
|
|
@@ -9355,6 +9522,7 @@ function ProductCustomizer({
|
|
|
9355
9522
|
const [specialInstructions, setSpecialInstructions] = useState("");
|
|
9356
9523
|
const [selectedSlot, setSelectedSlot] = useState(null);
|
|
9357
9524
|
const [selectedStaffId, setSelectedStaffId] = useState(null);
|
|
9525
|
+
const [unitsCount, setUnitsCount] = useState(1);
|
|
9358
9526
|
const cart = useCart();
|
|
9359
9527
|
const currency = getProductCurrency(product);
|
|
9360
9528
|
const productType = product.type || "product";
|
|
@@ -9362,6 +9530,7 @@ function ProductCustomizer({
|
|
|
9362
9530
|
const isBundle = productType === "bundle";
|
|
9363
9531
|
const isDigital = productType === "digital";
|
|
9364
9532
|
const isService = productType === "service";
|
|
9533
|
+
const offersUnits = isService && isMultiDayService(product);
|
|
9365
9534
|
const isStandard = !isComposite && !isBundle;
|
|
9366
9535
|
const hasVariants = isStandard && product.variants && product.variants.length > 0;
|
|
9367
9536
|
const hasAddOns = isStandard && product.add_ons && product.add_ons.length > 0;
|
|
@@ -9383,6 +9552,7 @@ function ProductCustomizer({
|
|
|
9383
9552
|
setSpecialInstructions("");
|
|
9384
9553
|
setSelectedSlot(null);
|
|
9385
9554
|
setSelectedStaffId(null);
|
|
9555
|
+
setUnitsCount(1);
|
|
9386
9556
|
}, [product.id, product.min_order_quantity]);
|
|
9387
9557
|
const slotStaff = useMemo(
|
|
9388
9558
|
() => (selectedSlot?.available_staff ?? []).map((member) => ({
|
|
@@ -9472,7 +9642,17 @@ function ProductCustomizer({
|
|
|
9472
9642
|
if (perUnit === void 0 || perUnit === null) return void 0;
|
|
9473
9643
|
return parsePrice(perUnit) * quantity;
|
|
9474
9644
|
}, [quote, quantity]);
|
|
9475
|
-
const displayTotalPrice =
|
|
9645
|
+
const displayTotalPrice = useMemo(() => {
|
|
9646
|
+
const total = quotedTotalPrice ?? localTotalPrice;
|
|
9647
|
+
const multiplier = serviceBillingMultiplier(product, {
|
|
9648
|
+
quantity,
|
|
9649
|
+
units: unitsCount,
|
|
9650
|
+
scheduledStart: selectedSlot?.start_time,
|
|
9651
|
+
scheduledEnd: selectedSlot?.end_time
|
|
9652
|
+
});
|
|
9653
|
+
if (multiplier === null) return total;
|
|
9654
|
+
return total / Math.max(1, quantity) * multiplier;
|
|
9655
|
+
}, [quotedTotalPrice, localTotalPrice, product, quantity, unitsCount, selectedSlot]);
|
|
9476
9656
|
const handleVariantChange = useCallback(
|
|
9477
9657
|
(variantId, variant) => {
|
|
9478
9658
|
setSelectedVariantId(variantId);
|
|
@@ -9504,7 +9684,8 @@ function ProductCustomizer({
|
|
|
9504
9684
|
scheduledStart: selectedSlot.start_time,
|
|
9505
9685
|
scheduledEnd: selectedSlot.end_time,
|
|
9506
9686
|
staffId: selectedStaffId ?? selectedSlot.available_staff?.[0]?.staff_id,
|
|
9507
|
-
resourceId: selectedSlot.available_resources?.[0]?.resource_id
|
|
9687
|
+
resourceId: selectedSlot.available_resources?.[0]?.resource_id,
|
|
9688
|
+
units: unitsCount > 1 ? unitsCount : void 0
|
|
9508
9689
|
} : {},
|
|
9509
9690
|
customerInputs: Object.keys(customerInputValues).length > 0 ? Object.entries(customerInputValues).filter(([, v]) => v !== void 0 && v !== "").map(([fieldId, value]) => ({ field_id: fieldId, value })) : void 0,
|
|
9510
9691
|
specialInstructions: specialInstructions.trim() || void 0
|
|
@@ -9525,7 +9706,7 @@ function ProductCustomizer({
|
|
|
9525
9706
|
} finally {
|
|
9526
9707
|
setIsSubmitting(false);
|
|
9527
9708
|
}
|
|
9528
|
-
}, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isService, selectedSlot, selectedStaffId, isSubmitting, onAddToCart, cart]);
|
|
9709
|
+
}, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isService, selectedSlot, selectedStaffId, unitsCount, isSubmitting, onAddToCart, cart]);
|
|
9529
9710
|
return /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer": true, className: cn("space-y-6", className, classNames?.root), children: [
|
|
9530
9711
|
isComposite && product.groups && product.composite_id && /* @__PURE__ */ jsx(
|
|
9531
9712
|
CompositeSelector,
|
|
@@ -9562,9 +9743,17 @@ function ProductCustomizer({
|
|
|
9562
9743
|
setSelectedStaffId(null);
|
|
9563
9744
|
},
|
|
9564
9745
|
participantCount: quantity,
|
|
9565
|
-
|
|
9746
|
+
units: offersUnits ? unitsCount : void 0,
|
|
9747
|
+
currency,
|
|
9748
|
+
schedulingMode: product.scheduling_mode ?? (offersUnits ? "multi_day" : "intraday"),
|
|
9749
|
+
durationUnit: product.duration_unit,
|
|
9750
|
+
durationValue: product.duration_value
|
|
9566
9751
|
}
|
|
9567
9752
|
),
|
|
9753
|
+
offersUnits && /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer-units": true, className: classNames?.units, children: [
|
|
9754
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-medium uppercase tracking-wider text-muted-foreground mb-3", children: "Units" }),
|
|
9755
|
+
/* @__PURE__ */ jsx(QuantitySelector, { value: unitsCount, onChange: setUnitsCount, min: 1 })
|
|
9756
|
+
] }),
|
|
9568
9757
|
isService && slotStaff.length > 0 && /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer-staff": true, children: [
|
|
9569
9758
|
/* @__PURE__ */ jsx("label", { className: "block text-xs font-medium uppercase tracking-wider text-muted-foreground mb-3", children: "Choose your specialist" }),
|
|
9570
9759
|
/* @__PURE__ */ jsx(
|
|
@@ -9723,8 +9912,10 @@ function ProductCustomizer({
|
|
|
9723
9912
|
isAdded && classNames?.submitButtonAdded,
|
|
9724
9913
|
classNames?.submitButton
|
|
9725
9914
|
),
|
|
9726
|
-
children: isAdded ? "Added
|
|
9727
|
-
"Add to Cart
|
|
9915
|
+
children: isAdded ? "Added" : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9916
|
+
submitLabel ?? "Add to Cart",
|
|
9917
|
+
" \xB7",
|
|
9918
|
+
" ",
|
|
9728
9919
|
/* @__PURE__ */ jsx(Price, { amount: displayTotalPrice, currency })
|
|
9729
9920
|
] })
|
|
9730
9921
|
}
|
|
@@ -9884,13 +10075,13 @@ function StoreVideo({
|
|
|
9884
10075
|
autoplay = true,
|
|
9885
10076
|
loop = true,
|
|
9886
10077
|
muted = true,
|
|
9887
|
-
lazy = true,
|
|
10078
|
+
lazy: lazy2 = true,
|
|
9888
10079
|
className
|
|
9889
10080
|
}) {
|
|
9890
10081
|
const ref = useRef(null);
|
|
9891
|
-
const [inView, setInView] = useState(!
|
|
10082
|
+
const [inView, setInView] = useState(!lazy2);
|
|
9892
10083
|
useEffect(() => {
|
|
9893
|
-
if (!
|
|
10084
|
+
if (!lazy2 || inView) return;
|
|
9894
10085
|
const node = ref.current;
|
|
9895
10086
|
if (!node || typeof IntersectionObserver === "undefined") {
|
|
9896
10087
|
setInView(true);
|
|
@@ -9907,7 +10098,7 @@ function StoreVideo({
|
|
|
9907
10098
|
);
|
|
9908
10099
|
observer.observe(node);
|
|
9909
10100
|
return () => observer.disconnect();
|
|
9910
|
-
}, [
|
|
10101
|
+
}, [lazy2, inView]);
|
|
9911
10102
|
return /* @__PURE__ */ jsx(
|
|
9912
10103
|
"div",
|
|
9913
10104
|
{
|
|
@@ -9925,7 +10116,7 @@ function StoreVideo({
|
|
|
9925
10116
|
muted,
|
|
9926
10117
|
playsInline: true,
|
|
9927
10118
|
controls,
|
|
9928
|
-
preload:
|
|
10119
|
+
preload: lazy2 ? "metadata" : "auto",
|
|
9929
10120
|
"aria-label": alt,
|
|
9930
10121
|
style: { width: "100%", height: "100%", objectFit: "cover", display: "block" },
|
|
9931
10122
|
children: poster ? /* @__PURE__ */ jsx(
|
|
@@ -11616,18 +11807,18 @@ function RentalServiceCard({
|
|
|
11616
11807
|
const currency = getProductCurrency(product);
|
|
11617
11808
|
const hasDeposit = product.deposit_type && product.deposit_type !== "none" && product.deposit_amount;
|
|
11618
11809
|
const unit = product.duration_unit ? UNIT_LABELS2[product.duration_unit] || product.duration_unit : "day";
|
|
11810
|
+
const priceUnit = product.price_basis === "per_person" ? "guest" : product.price_basis === "flat" ? "" : unit;
|
|
11619
11811
|
const status = product.inventory_status;
|
|
11620
11812
|
return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
|
|
11621
11813
|
/* @__PURE__ */ jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "16/9", renderImage, children: [
|
|
11622
11814
|
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none" }),
|
|
11623
|
-
/* @__PURE__ */ jsxs("span", { className: "absolute bottom-3 left-3 inline-flex items-center text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm", children: [
|
|
11815
|
+
priceUnit && /* @__PURE__ */ jsxs("span", { className: "absolute bottom-3 left-3 inline-flex items-center text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm", children: [
|
|
11624
11816
|
"Per ",
|
|
11625
|
-
|
|
11817
|
+
priceUnit
|
|
11626
11818
|
] }),
|
|
11627
11819
|
/* @__PURE__ */ jsxs("span", { className: "absolute bottom-3 right-3 text-white font-bold text-lg drop-shadow-sm", children: [
|
|
11628
11820
|
/* @__PURE__ */ jsx(Price, { amount: product.default_price, currency }),
|
|
11629
|
-
|
|
11630
|
-
unit
|
|
11821
|
+
priceUnit && `/${priceUnit}`
|
|
11631
11822
|
] }),
|
|
11632
11823
|
product.general_service_capacity != null && product.general_service_capacity > 1 && /* @__PURE__ */ jsxs("span", { className: "absolute top-3 left-3 text-[11px] font-semibold tracking-wide bg-blue-50/90 text-blue-700 border border-blue-200/50 backdrop-blur px-2 py-0.5 rounded-md", children: [
|
|
11633
11824
|
"Up to ",
|
|
@@ -12221,6 +12412,15 @@ function ProductGrid({
|
|
|
12221
12412
|
] });
|
|
12222
12413
|
}
|
|
12223
12414
|
|
|
12415
|
+
// src/react/utils/attachment.ts
|
|
12416
|
+
var IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".svg"];
|
|
12417
|
+
function isImageAttachment(attachment) {
|
|
12418
|
+
if (attachment.mime_type?.startsWith("image/")) return true;
|
|
12419
|
+
if (attachment.mime_type) return false;
|
|
12420
|
+
const path = attachment.url.split("?")[0].split("#")[0].toLowerCase();
|
|
12421
|
+
return IMAGE_EXTENSIONS.some((extension) => path.endsWith(extension));
|
|
12422
|
+
}
|
|
12423
|
+
|
|
12224
12424
|
// src/react/utils/boundary-labels.ts
|
|
12225
12425
|
var PRESET_LABELS = {
|
|
12226
12426
|
check_in_out: { start: "Check-in", end: "Check-out" },
|
|
@@ -13402,6 +13602,7 @@ function BookingPage({
|
|
|
13402
13602
|
const [selectedDate, setSelectedDate] = useState(null);
|
|
13403
13603
|
const [selectedStaffId, setSelectedStaffId] = useState(null);
|
|
13404
13604
|
const [selectedResourceId, setSelectedResourceId] = useState(null);
|
|
13605
|
+
const [bookingUnits, setBookingUnits] = useState(1);
|
|
13405
13606
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
13406
13607
|
const [error, setError] = useState(null);
|
|
13407
13608
|
const hasResourceStep = resources && resources.length > 0;
|
|
@@ -13495,7 +13696,8 @@ function BookingPage({
|
|
|
13495
13696
|
scheduledStart: selectedSlot.start_time,
|
|
13496
13697
|
scheduledEnd: selectedSlot.end_time,
|
|
13497
13698
|
staffId: selectedStaffId || void 0,
|
|
13498
|
-
resourceId: selectedResourceId || void 0
|
|
13699
|
+
resourceId: selectedResourceId || void 0,
|
|
13700
|
+
units: bookingUnits > 1 ? bookingUnits : void 0
|
|
13499
13701
|
});
|
|
13500
13702
|
onBooked?.(selectedSlot, selectedStaffId);
|
|
13501
13703
|
}
|
|
@@ -13591,6 +13793,31 @@ function BookingPage({
|
|
|
13591
13793
|
] }),
|
|
13592
13794
|
step === STEP.CONFIRM && selectedSlot && /* @__PURE__ */ jsxs("div", { "data-cimplify-booking-step": "confirm", className: classNames?.step, children: [
|
|
13593
13795
|
/* @__PURE__ */ jsx("h2", { "data-cimplify-booking-step-title": true, className: classNames?.stepTitle, children: "Confirm your booking" }),
|
|
13796
|
+
/* @__PURE__ */ jsxs("div", { "data-cimplify-booking-units": true, className: classNames?.summaryRow, children: [
|
|
13797
|
+
/* @__PURE__ */ jsx("span", { children: "Units" }),
|
|
13798
|
+
/* @__PURE__ */ jsxs("div", { "data-cimplify-booking-units-stepper": true, children: [
|
|
13799
|
+
/* @__PURE__ */ jsx(
|
|
13800
|
+
"button",
|
|
13801
|
+
{
|
|
13802
|
+
type: "button",
|
|
13803
|
+
"data-cimplify-booking-units-decrement": true,
|
|
13804
|
+
onClick: () => setBookingUnits((current) => Math.max(1, current - 1)),
|
|
13805
|
+
disabled: bookingUnits <= 1,
|
|
13806
|
+
children: "\u2212"
|
|
13807
|
+
}
|
|
13808
|
+
),
|
|
13809
|
+
/* @__PURE__ */ jsx("span", { "data-cimplify-booking-units-value": true, children: bookingUnits }),
|
|
13810
|
+
/* @__PURE__ */ jsx(
|
|
13811
|
+
"button",
|
|
13812
|
+
{
|
|
13813
|
+
type: "button",
|
|
13814
|
+
"data-cimplify-booking-units-increment": true,
|
|
13815
|
+
onClick: () => setBookingUnits((current) => current + 1),
|
|
13816
|
+
children: "+"
|
|
13817
|
+
}
|
|
13818
|
+
)
|
|
13819
|
+
] })
|
|
13820
|
+
] }),
|
|
13594
13821
|
/* @__PURE__ */ jsxs("div", { "data-cimplify-booking-summary": true, className: classNames?.summary, children: [
|
|
13595
13822
|
/* @__PURE__ */ jsxs("div", { "data-cimplify-booking-summary-row": true, className: classNames?.summaryRow, children: [
|
|
13596
13823
|
/* @__PURE__ */ jsx("span", { children: "Service" }),
|
|
@@ -16727,6 +16954,112 @@ function CurrencySelector({
|
|
|
16727
16954
|
);
|
|
16728
16955
|
}) });
|
|
16729
16956
|
}
|
|
16957
|
+
var Streamdown = lazy(() => import('streamdown').then((m) => ({ default: m.Streamdown })));
|
|
16958
|
+
function detectLookbehindSupport() {
|
|
16959
|
+
try {
|
|
16960
|
+
new RegExp("(?<=a)b");
|
|
16961
|
+
return true;
|
|
16962
|
+
} catch {
|
|
16963
|
+
return false;
|
|
16964
|
+
}
|
|
16965
|
+
}
|
|
16966
|
+
var supportsRegExpLookbehind = detectLookbehindSupport();
|
|
16967
|
+
marked.setOptions({
|
|
16968
|
+
gfm: true,
|
|
16969
|
+
breaks: true
|
|
16970
|
+
});
|
|
16971
|
+
var ALLOWED_TAGS = [
|
|
16972
|
+
"a",
|
|
16973
|
+
"b",
|
|
16974
|
+
"blockquote",
|
|
16975
|
+
"br",
|
|
16976
|
+
"code",
|
|
16977
|
+
"del",
|
|
16978
|
+
"em",
|
|
16979
|
+
"h1",
|
|
16980
|
+
"h2",
|
|
16981
|
+
"h3",
|
|
16982
|
+
"h4",
|
|
16983
|
+
"hr",
|
|
16984
|
+
"i",
|
|
16985
|
+
"li",
|
|
16986
|
+
"ol",
|
|
16987
|
+
"p",
|
|
16988
|
+
"pre",
|
|
16989
|
+
"strong",
|
|
16990
|
+
"table",
|
|
16991
|
+
"tbody",
|
|
16992
|
+
"td",
|
|
16993
|
+
"th",
|
|
16994
|
+
"thead",
|
|
16995
|
+
"tr",
|
|
16996
|
+
"ul"
|
|
16997
|
+
];
|
|
16998
|
+
var ALLOWED_ATTR = ["href", "rel", "target", "title", "start"];
|
|
16999
|
+
var hooksInstalled = false;
|
|
17000
|
+
function installHooks() {
|
|
17001
|
+
if (hooksInstalled) return;
|
|
17002
|
+
hooksInstalled = true;
|
|
17003
|
+
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
|
|
17004
|
+
if (!(node instanceof HTMLAnchorElement)) return;
|
|
17005
|
+
if (!node.getAttribute("href")) return;
|
|
17006
|
+
node.setAttribute("rel", "noreferrer noopener");
|
|
17007
|
+
node.setAttribute("target", "_blank");
|
|
17008
|
+
});
|
|
17009
|
+
}
|
|
17010
|
+
var CACHE_LIMIT = 300;
|
|
17011
|
+
var cache = /* @__PURE__ */ new Map();
|
|
17012
|
+
function renderMarkdown(text) {
|
|
17013
|
+
const cached = cache.get(text);
|
|
17014
|
+
if (cached !== void 0) return cached;
|
|
17015
|
+
installHooks();
|
|
17016
|
+
const html = DOMPurify.sanitize(marked.parse(text), {
|
|
17017
|
+
ALLOWED_TAGS,
|
|
17018
|
+
ALLOWED_ATTR
|
|
17019
|
+
});
|
|
17020
|
+
if (cache.size >= CACHE_LIMIT) {
|
|
17021
|
+
const oldest = cache.keys().next().value;
|
|
17022
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
17023
|
+
}
|
|
17024
|
+
cache.set(text, html);
|
|
17025
|
+
return html;
|
|
17026
|
+
}
|
|
17027
|
+
function SettledMarkdown({ text, className }) {
|
|
17028
|
+
const html = useMemo(() => renderMarkdown(text), [text]);
|
|
17029
|
+
return /* @__PURE__ */ jsx("div", { className, dangerouslySetInnerHTML: { __html: html } });
|
|
17030
|
+
}
|
|
17031
|
+
var ChatMarkdown = memo(function ChatMarkdown2({
|
|
17032
|
+
text,
|
|
17033
|
+
className,
|
|
17034
|
+
streaming = false
|
|
17035
|
+
}) {
|
|
17036
|
+
if (streaming && supportsRegExpLookbehind) {
|
|
17037
|
+
return (
|
|
17038
|
+
// While the streamdown chunk loads, the settled renderer keeps the
|
|
17039
|
+
// text on screen — no blank frame at stream start.
|
|
17040
|
+
/* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(SettledMarkdown, { text, className }), children: /* @__PURE__ */ jsx(Streamdown, { className, children: text }) })
|
|
17041
|
+
);
|
|
17042
|
+
}
|
|
17043
|
+
return /* @__PURE__ */ jsx(SettledMarkdown, { text, className });
|
|
17044
|
+
});
|
|
17045
|
+
|
|
17046
|
+
// src/react/chat-model.ts
|
|
17047
|
+
function partitionMessages(messages) {
|
|
17048
|
+
const bubbles = [];
|
|
17049
|
+
const reactionsByTarget = /* @__PURE__ */ new Map();
|
|
17050
|
+
for (const message of messages) {
|
|
17051
|
+
if (message.content_type === "reaction") {
|
|
17052
|
+
const targetId = message.reply_to?.id;
|
|
17053
|
+
if (!targetId) continue;
|
|
17054
|
+
const list = reactionsByTarget.get(targetId);
|
|
17055
|
+
if (list) list.push(message);
|
|
17056
|
+
else reactionsByTarget.set(targetId, [message]);
|
|
17057
|
+
continue;
|
|
17058
|
+
}
|
|
17059
|
+
bubbles.push(message);
|
|
17060
|
+
}
|
|
17061
|
+
return { bubbles, reactionsByTarget };
|
|
17062
|
+
}
|
|
16730
17063
|
function ChatWidget({
|
|
16731
17064
|
client: clientProp,
|
|
16732
17065
|
businessName = "Support",
|
|
@@ -16743,24 +17076,46 @@ function ChatWidget({
|
|
|
16743
17076
|
const context = useOptionalCimplifyClient();
|
|
16744
17077
|
const client = clientProp ?? context?.client;
|
|
16745
17078
|
const [open, setOpen] = useState(defaultOpen);
|
|
16746
|
-
const [
|
|
16747
|
-
const {
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
|
|
16752
|
-
|
|
16753
|
-
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
17079
|
+
const [readCount, setReadCount] = useState(0);
|
|
17080
|
+
const {
|
|
17081
|
+
messages,
|
|
17082
|
+
isLoading,
|
|
17083
|
+
isSending,
|
|
17084
|
+
awaitingReply,
|
|
17085
|
+
seenAt,
|
|
17086
|
+
error,
|
|
17087
|
+
isActive,
|
|
17088
|
+
claimedAs,
|
|
17089
|
+
signOut,
|
|
17090
|
+
send,
|
|
17091
|
+
react,
|
|
17092
|
+
startConversation
|
|
17093
|
+
} = useChat({ client, pollInterval });
|
|
17094
|
+
const showTyping = isSending || awaitingReply;
|
|
17095
|
+
const attachPhoto = useCallback(
|
|
17096
|
+
async (file) => {
|
|
17097
|
+
if (!client || !file.type.startsWith("image/") || file.size > 5 * 1024 * 1024) return;
|
|
17098
|
+
const uploaded = await client.uploads.upload(file);
|
|
17099
|
+
if (!uploaded.ok) return;
|
|
17100
|
+
const attachment = {
|
|
17101
|
+
upload_id: uploaded.value.id,
|
|
17102
|
+
url: uploaded.value.url,
|
|
17103
|
+
mime_type: uploaded.value.content_type,
|
|
17104
|
+
filename: uploaded.value.filename,
|
|
17105
|
+
size: uploaded.value.size_bytes
|
|
17106
|
+
};
|
|
17107
|
+
await send("", { attachments: [attachment] });
|
|
17108
|
+
},
|
|
17109
|
+
[client, send]
|
|
17110
|
+
);
|
|
17111
|
+
const unread = open ? 0 : messages.slice(readCount).filter((m) => m.sender_type !== "customer").length;
|
|
16759
17112
|
const handleOpen = useCallback(() => {
|
|
16760
17113
|
setOpen(true);
|
|
16761
|
-
setUnread(0);
|
|
16762
17114
|
}, []);
|
|
16763
|
-
const handleClose = useCallback(() =>
|
|
17115
|
+
const handleClose = useCallback(() => {
|
|
17116
|
+
setReadCount(messages.length);
|
|
17117
|
+
setOpen(false);
|
|
17118
|
+
}, [messages.length]);
|
|
16764
17119
|
const isLeft = position === "bottom-left";
|
|
16765
17120
|
return /* @__PURE__ */ jsxs(
|
|
16766
17121
|
"div",
|
|
@@ -16791,11 +17146,17 @@ function ChatWidget({
|
|
|
16791
17146
|
messages,
|
|
16792
17147
|
isLoading,
|
|
16793
17148
|
isSending,
|
|
17149
|
+
isTyping: showTyping,
|
|
17150
|
+
seenAt,
|
|
16794
17151
|
error,
|
|
16795
17152
|
isActive,
|
|
16796
17153
|
isLeft,
|
|
17154
|
+
claimedAs,
|
|
17155
|
+
onSignOut: () => void signOut(),
|
|
16797
17156
|
onClose: handleClose,
|
|
16798
17157
|
onSend: send,
|
|
17158
|
+
onReact: react,
|
|
17159
|
+
onAttach: attachPhoto,
|
|
16799
17160
|
onStartConversation: startConversation,
|
|
16800
17161
|
classNames
|
|
16801
17162
|
}
|
|
@@ -16848,11 +17209,17 @@ function Panel({
|
|
|
16848
17209
|
messages,
|
|
16849
17210
|
isLoading,
|
|
16850
17211
|
isSending,
|
|
17212
|
+
isTyping,
|
|
17213
|
+
seenAt,
|
|
16851
17214
|
error,
|
|
16852
17215
|
isActive,
|
|
16853
17216
|
isLeft,
|
|
17217
|
+
claimedAs,
|
|
17218
|
+
onSignOut,
|
|
16854
17219
|
onClose,
|
|
16855
17220
|
onSend,
|
|
17221
|
+
onReact,
|
|
17222
|
+
onAttach,
|
|
16856
17223
|
onStartConversation,
|
|
16857
17224
|
classNames
|
|
16858
17225
|
}) {
|
|
@@ -16872,6 +17239,8 @@ function Panel({
|
|
|
16872
17239
|
Header,
|
|
16873
17240
|
{
|
|
16874
17241
|
businessName,
|
|
17242
|
+
claimedAs,
|
|
17243
|
+
onSignOut,
|
|
16875
17244
|
onClose,
|
|
16876
17245
|
className: classNames?.header
|
|
16877
17246
|
}
|
|
@@ -16890,7 +17259,9 @@ function Panel({
|
|
|
16890
17259
|
{
|
|
16891
17260
|
messages,
|
|
16892
17261
|
isLoading,
|
|
16893
|
-
isSending,
|
|
17262
|
+
isSending: isTyping,
|
|
17263
|
+
seenAt,
|
|
17264
|
+
onReact,
|
|
16894
17265
|
className: classNames?.messages
|
|
16895
17266
|
}
|
|
16896
17267
|
),
|
|
@@ -16901,6 +17272,7 @@ function Panel({
|
|
|
16901
17272
|
placeholder,
|
|
16902
17273
|
isSending,
|
|
16903
17274
|
onSend,
|
|
17275
|
+
onAttach,
|
|
16904
17276
|
className: classNames?.input
|
|
16905
17277
|
}
|
|
16906
17278
|
),
|
|
@@ -16915,6 +17287,8 @@ function Panel({
|
|
|
16915
17287
|
}
|
|
16916
17288
|
function Header({
|
|
16917
17289
|
businessName,
|
|
17290
|
+
claimedAs,
|
|
17291
|
+
onSignOut,
|
|
16918
17292
|
onClose,
|
|
16919
17293
|
className
|
|
16920
17294
|
}) {
|
|
@@ -16929,7 +17303,22 @@ function Header({
|
|
|
16929
17303
|
/* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-white/15", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-5 w-5" }) }),
|
|
16930
17304
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
16931
17305
|
/* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
|
|
16932
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1
|
|
17306
|
+
claimedAs ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-white/60", children: [
|
|
17307
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
17308
|
+
"Chatting as ",
|
|
17309
|
+
claimedAs
|
|
17310
|
+
] }),
|
|
17311
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
17312
|
+
/* @__PURE__ */ jsx(
|
|
17313
|
+
"button",
|
|
17314
|
+
{
|
|
17315
|
+
type: "button",
|
|
17316
|
+
onClick: onSignOut,
|
|
17317
|
+
className: "flex-shrink-0 underline underline-offset-2 hover:text-white",
|
|
17318
|
+
children: "Not you?"
|
|
17319
|
+
}
|
|
17320
|
+
)
|
|
17321
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-white/60", children: [
|
|
16933
17322
|
/* @__PURE__ */ jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
|
|
16934
17323
|
"Online"
|
|
16935
17324
|
] })
|
|
@@ -16996,16 +17385,25 @@ function Welcome({
|
|
|
16996
17385
|
}
|
|
16997
17386
|
);
|
|
16998
17387
|
}
|
|
17388
|
+
var QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F"];
|
|
17389
|
+
function lastSeenCustomerMessageId(bubbles, seenAt) {
|
|
17390
|
+
if (!seenAt || !bubbles.some((m) => m.sender_type === "agent")) return null;
|
|
17391
|
+
for (let i = bubbles.length - 1; i >= 0; i--) {
|
|
17392
|
+
if (bubbles[i].sender_type !== "customer") continue;
|
|
17393
|
+
return bubbles[i].created_at <= seenAt ? bubbles[i].id : null;
|
|
17394
|
+
}
|
|
17395
|
+
return null;
|
|
17396
|
+
}
|
|
16999
17397
|
function MessageList({
|
|
17000
17398
|
messages,
|
|
17001
17399
|
isLoading,
|
|
17002
17400
|
isSending,
|
|
17401
|
+
seenAt,
|
|
17402
|
+
onReact,
|
|
17003
17403
|
className
|
|
17004
17404
|
}) {
|
|
17005
|
-
const
|
|
17006
|
-
|
|
17007
|
-
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
17008
|
-
}, [messages.length, isSending]);
|
|
17405
|
+
const { bubbles, reactionsByTarget } = partitionMessages(messages);
|
|
17406
|
+
const seenMessageId = lastSeenCustomerMessageId(bubbles, seenAt);
|
|
17009
17407
|
return /* @__PURE__ */ jsxs(
|
|
17010
17408
|
"div",
|
|
17011
17409
|
{
|
|
@@ -17015,44 +17413,123 @@ function MessageList({
|
|
|
17015
17413
|
),
|
|
17016
17414
|
children: [
|
|
17017
17415
|
isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
|
|
17018
|
-
|
|
17416
|
+
bubbles.map((msg, i) => {
|
|
17019
17417
|
const isCustomer = msg.sender_type === "customer";
|
|
17020
|
-
const prev =
|
|
17418
|
+
const prev = bubbles[i - 1];
|
|
17021
17419
|
const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
|
|
17022
17420
|
const isOptimistic = msg.id.startsWith("opt_");
|
|
17421
|
+
const reactions = reactionsByTarget.get(msg.id);
|
|
17023
17422
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
17024
17423
|
!sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsx("div", { className: "h-3" }),
|
|
17025
|
-
/* @__PURE__ */
|
|
17424
|
+
/* @__PURE__ */ jsxs(
|
|
17026
17425
|
"div",
|
|
17027
17426
|
{
|
|
17028
17427
|
className: cn(
|
|
17029
|
-
"flex max-w-[85%]",
|
|
17428
|
+
"group relative flex max-w-[85%]",
|
|
17030
17429
|
isCustomer ? "ml-auto" : "mr-auto"
|
|
17031
17430
|
),
|
|
17032
|
-
children:
|
|
17033
|
-
"div",
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
|
|
17039
|
-
|
|
17040
|
-
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17431
|
+
children: [
|
|
17432
|
+
!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(
|
|
17433
|
+
"button",
|
|
17434
|
+
{
|
|
17435
|
+
type: "button",
|
|
17436
|
+
"aria-label": `React ${emoji}`,
|
|
17437
|
+
onClick: () => void onReact(msg, emoji),
|
|
17438
|
+
className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
|
|
17439
|
+
children: emoji
|
|
17440
|
+
},
|
|
17441
|
+
emoji
|
|
17442
|
+
)) }),
|
|
17443
|
+
/* @__PURE__ */ jsxs(
|
|
17444
|
+
"div",
|
|
17445
|
+
{
|
|
17446
|
+
className: cn(
|
|
17447
|
+
"rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
|
|
17448
|
+
isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
|
|
17449
|
+
isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
|
|
17450
|
+
isOptimistic && "opacity-70"
|
|
17451
|
+
),
|
|
17452
|
+
children: [
|
|
17453
|
+
!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: [
|
|
17454
|
+
/* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
|
|
17455
|
+
"AI"
|
|
17456
|
+
] }),
|
|
17457
|
+
msg.reply_to && /* @__PURE__ */ jsxs(
|
|
17458
|
+
"div",
|
|
17459
|
+
{
|
|
17460
|
+
className: cn(
|
|
17461
|
+
"mb-1.5 flex gap-1.5 rounded-lg px-2 py-1 text-xs",
|
|
17462
|
+
isCustomer ? "bg-background/15" : "bg-muted"
|
|
17463
|
+
),
|
|
17464
|
+
children: [
|
|
17465
|
+
/* @__PURE__ */ jsx(
|
|
17466
|
+
"span",
|
|
17467
|
+
{
|
|
17468
|
+
className: cn(
|
|
17469
|
+
"w-[3px] shrink-0 self-stretch rounded-full",
|
|
17470
|
+
isCustomer ? "bg-background/40" : "bg-muted-foreground/40"
|
|
17471
|
+
)
|
|
17472
|
+
}
|
|
17473
|
+
),
|
|
17474
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate opacity-80", children: msg.reply_to.preview })
|
|
17475
|
+
]
|
|
17476
|
+
}
|
|
17477
|
+
),
|
|
17478
|
+
msg.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-1.5", msg.content && "mb-1.5"), children: msg.attachments.map(
|
|
17479
|
+
(attachment, index) => attachment.mime_type?.startsWith("image/") ? /* @__PURE__ */ jsx("a", { href: attachment.url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx(
|
|
17480
|
+
"img",
|
|
17481
|
+
{
|
|
17482
|
+
src: attachment.url,
|
|
17483
|
+
alt: attachment.filename ?? "Photo",
|
|
17484
|
+
loading: "lazy",
|
|
17485
|
+
className: "max-h-48 max-w-full rounded-lg object-cover"
|
|
17486
|
+
}
|
|
17487
|
+
) }, index) : /* @__PURE__ */ jsx(
|
|
17488
|
+
"a",
|
|
17489
|
+
{
|
|
17490
|
+
href: attachment.url,
|
|
17491
|
+
target: "_blank",
|
|
17492
|
+
rel: "noreferrer",
|
|
17493
|
+
className: "truncate text-xs underline",
|
|
17494
|
+
children: attachment.filename ?? "Attachment"
|
|
17495
|
+
},
|
|
17496
|
+
index
|
|
17497
|
+
)
|
|
17498
|
+
) }),
|
|
17499
|
+
isCustomer ? msg.content && /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: msg.content }) : /* @__PURE__ */ jsx(
|
|
17500
|
+
ChatMarkdown,
|
|
17501
|
+
{
|
|
17502
|
+
text: msg.content,
|
|
17503
|
+
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"
|
|
17504
|
+
}
|
|
17505
|
+
)
|
|
17506
|
+
]
|
|
17507
|
+
}
|
|
17508
|
+
)
|
|
17509
|
+
]
|
|
17050
17510
|
}
|
|
17051
|
-
)
|
|
17511
|
+
),
|
|
17512
|
+
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(
|
|
17513
|
+
"span",
|
|
17514
|
+
{
|
|
17515
|
+
className: "rounded-full border border-border bg-background px-1.5 py-0.5 text-xs shadow-sm",
|
|
17516
|
+
children: reaction.content
|
|
17517
|
+
},
|
|
17518
|
+
reaction.id
|
|
17519
|
+
)) }),
|
|
17520
|
+
msg.id === seenMessageId && /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-right text-[10px] font-medium text-muted-foreground", children: "Seen" })
|
|
17052
17521
|
] }, msg.id);
|
|
17053
17522
|
}),
|
|
17054
17523
|
isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
|
|
17055
|
-
/* @__PURE__ */ jsx(
|
|
17524
|
+
/* @__PURE__ */ jsx(
|
|
17525
|
+
"div",
|
|
17526
|
+
{
|
|
17527
|
+
ref: (node) => {
|
|
17528
|
+
node?.scrollIntoView({ behavior: "smooth" });
|
|
17529
|
+
}
|
|
17530
|
+
},
|
|
17531
|
+
`${messages.length}:${isSending}`
|
|
17532
|
+
)
|
|
17056
17533
|
]
|
|
17057
17534
|
}
|
|
17058
17535
|
);
|
|
@@ -17061,10 +17538,24 @@ function ChatInput({
|
|
|
17061
17538
|
placeholder,
|
|
17062
17539
|
isSending,
|
|
17063
17540
|
onSend,
|
|
17541
|
+
onAttach,
|
|
17064
17542
|
className
|
|
17065
17543
|
}) {
|
|
17066
17544
|
const [value, setValue] = useState("");
|
|
17545
|
+
const [uploading, setUploading] = useState(false);
|
|
17067
17546
|
const textareaRef = useRef(null);
|
|
17547
|
+
const fileRef = useRef(null);
|
|
17548
|
+
const handleAttach = useCallback(
|
|
17549
|
+
async (file) => {
|
|
17550
|
+
setUploading(true);
|
|
17551
|
+
try {
|
|
17552
|
+
await onAttach(file);
|
|
17553
|
+
} finally {
|
|
17554
|
+
setUploading(false);
|
|
17555
|
+
}
|
|
17556
|
+
},
|
|
17557
|
+
[onAttach]
|
|
17558
|
+
);
|
|
17068
17559
|
const handleSend = useCallback(() => {
|
|
17069
17560
|
const trimmed = value.trim();
|
|
17070
17561
|
if (!trimmed || isSending) return;
|
|
@@ -17098,6 +17589,37 @@ function ChatInput({
|
|
|
17098
17589
|
className
|
|
17099
17590
|
),
|
|
17100
17591
|
children: [
|
|
17592
|
+
/* @__PURE__ */ jsx(
|
|
17593
|
+
"input",
|
|
17594
|
+
{
|
|
17595
|
+
ref: fileRef,
|
|
17596
|
+
type: "file",
|
|
17597
|
+
accept: "image/*",
|
|
17598
|
+
className: "hidden",
|
|
17599
|
+
"aria-hidden": "true",
|
|
17600
|
+
tabIndex: -1,
|
|
17601
|
+
onChange: (e) => {
|
|
17602
|
+
const file = e.target.files?.[0];
|
|
17603
|
+
e.target.value = "";
|
|
17604
|
+
if (file) void handleAttach(file);
|
|
17605
|
+
}
|
|
17606
|
+
}
|
|
17607
|
+
),
|
|
17608
|
+
/* @__PURE__ */ jsx(
|
|
17609
|
+
"button",
|
|
17610
|
+
{
|
|
17611
|
+
type: "button",
|
|
17612
|
+
"aria-label": "Attach a photo",
|
|
17613
|
+
disabled: uploading,
|
|
17614
|
+
onClick: () => fileRef.current?.click(),
|
|
17615
|
+
className: cn(
|
|
17616
|
+
"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
|
|
17617
|
+
"bg-muted text-muted-foreground transition-colors hover:text-foreground",
|
|
17618
|
+
uploading && "animate-pulse"
|
|
17619
|
+
),
|
|
17620
|
+
children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-[18px] w-[18px]" })
|
|
17621
|
+
}
|
|
17622
|
+
),
|
|
17101
17623
|
/* @__PURE__ */ jsx(
|
|
17102
17624
|
"div",
|
|
17103
17625
|
{
|
|
@@ -17200,6 +17722,25 @@ function SendIcon({ className }) {
|
|
|
17200
17722
|
}
|
|
17201
17723
|
);
|
|
17202
17724
|
}
|
|
17725
|
+
function ImageIcon({ className }) {
|
|
17726
|
+
return /* @__PURE__ */ jsxs(
|
|
17727
|
+
"svg",
|
|
17728
|
+
{
|
|
17729
|
+
className,
|
|
17730
|
+
viewBox: "0 0 24 24",
|
|
17731
|
+
fill: "none",
|
|
17732
|
+
stroke: "currentColor",
|
|
17733
|
+
strokeWidth: 2,
|
|
17734
|
+
strokeLinecap: "round",
|
|
17735
|
+
strokeLinejoin: "round",
|
|
17736
|
+
children: [
|
|
17737
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
17738
|
+
/* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
17739
|
+
/* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
|
|
17740
|
+
]
|
|
17741
|
+
}
|
|
17742
|
+
);
|
|
17743
|
+
}
|
|
17203
17744
|
function SparkleIcon({ className }) {
|
|
17204
17745
|
return /* @__PURE__ */ jsx(
|
|
17205
17746
|
"svg",
|
|
@@ -17214,4 +17755,1825 @@ function SparkleIcon({ className }) {
|
|
|
17214
17755
|
);
|
|
17215
17756
|
}
|
|
17216
17757
|
|
|
17217
|
-
|
|
17758
|
+
// src/react/genui/catalog.ts
|
|
17759
|
+
function genUICatalog(registry) {
|
|
17760
|
+
return new Map(
|
|
17761
|
+
registry.components.map((component) => [
|
|
17762
|
+
component.name,
|
|
17763
|
+
{
|
|
17764
|
+
params: component.params.map((param) => ({
|
|
17765
|
+
name: param.name,
|
|
17766
|
+
required: param.required ?? false
|
|
17767
|
+
}))
|
|
17768
|
+
}
|
|
17769
|
+
])
|
|
17770
|
+
);
|
|
17771
|
+
}
|
|
17772
|
+
|
|
17773
|
+
// src/genui/lang/parser/ast.ts
|
|
17774
|
+
function isRuntimeExpr(node) {
|
|
17775
|
+
switch (node.k) {
|
|
17776
|
+
case "StateRef":
|
|
17777
|
+
case "RuntimeRef":
|
|
17778
|
+
case "BinOp":
|
|
17779
|
+
case "UnaryOp":
|
|
17780
|
+
case "Ternary":
|
|
17781
|
+
case "Member":
|
|
17782
|
+
case "Index":
|
|
17783
|
+
case "Assign":
|
|
17784
|
+
return true;
|
|
17785
|
+
default:
|
|
17786
|
+
return false;
|
|
17787
|
+
}
|
|
17788
|
+
}
|
|
17789
|
+
var AST_KINDS = /* @__PURE__ */ new Set([
|
|
17790
|
+
"Comp",
|
|
17791
|
+
"Ref",
|
|
17792
|
+
"StateRef",
|
|
17793
|
+
"RuntimeRef",
|
|
17794
|
+
"BinOp",
|
|
17795
|
+
"UnaryOp",
|
|
17796
|
+
"Ternary",
|
|
17797
|
+
"Member",
|
|
17798
|
+
"Index",
|
|
17799
|
+
"Assign",
|
|
17800
|
+
"Str",
|
|
17801
|
+
"Num",
|
|
17802
|
+
"Bool",
|
|
17803
|
+
"Null",
|
|
17804
|
+
"Arr",
|
|
17805
|
+
"Obj",
|
|
17806
|
+
"Ph"
|
|
17807
|
+
]);
|
|
17808
|
+
function isASTNode(value) {
|
|
17809
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
17810
|
+
return AST_KINDS.has(value.k);
|
|
17811
|
+
}
|
|
17812
|
+
function walkAST(node, visit) {
|
|
17813
|
+
const walk = (current) => {
|
|
17814
|
+
visit(current);
|
|
17815
|
+
switch (current.k) {
|
|
17816
|
+
case "Comp":
|
|
17817
|
+
current.args.forEach(walk);
|
|
17818
|
+
Object.values(current.mappedProps ?? {}).forEach(walk);
|
|
17819
|
+
break;
|
|
17820
|
+
case "Arr":
|
|
17821
|
+
current.els.forEach(walk);
|
|
17822
|
+
break;
|
|
17823
|
+
case "Obj":
|
|
17824
|
+
current.entries.forEach(([, value]) => walk(value));
|
|
17825
|
+
break;
|
|
17826
|
+
case "BinOp":
|
|
17827
|
+
walk(current.left);
|
|
17828
|
+
walk(current.right);
|
|
17829
|
+
break;
|
|
17830
|
+
case "UnaryOp":
|
|
17831
|
+
walk(current.operand);
|
|
17832
|
+
break;
|
|
17833
|
+
case "Ternary":
|
|
17834
|
+
walk(current.cond);
|
|
17835
|
+
walk(current.then);
|
|
17836
|
+
walk(current.else);
|
|
17837
|
+
break;
|
|
17838
|
+
case "Member":
|
|
17839
|
+
walk(current.obj);
|
|
17840
|
+
break;
|
|
17841
|
+
case "Index":
|
|
17842
|
+
walk(current.obj);
|
|
17843
|
+
walk(current.index);
|
|
17844
|
+
break;
|
|
17845
|
+
case "Assign":
|
|
17846
|
+
walk(current.value);
|
|
17847
|
+
break;
|
|
17848
|
+
}
|
|
17849
|
+
};
|
|
17850
|
+
walk(node);
|
|
17851
|
+
}
|
|
17852
|
+
|
|
17853
|
+
// src/genui/lang/parser/builtins.ts
|
|
17854
|
+
function resolveField(obj, path) {
|
|
17855
|
+
if (!path || obj == null) return void 0;
|
|
17856
|
+
if (!path.includes(".")) return obj[path];
|
|
17857
|
+
let cur = obj;
|
|
17858
|
+
for (const p of path.split(".")) {
|
|
17859
|
+
if (cur == null) return void 0;
|
|
17860
|
+
cur = cur[p];
|
|
17861
|
+
}
|
|
17862
|
+
return cur;
|
|
17863
|
+
}
|
|
17864
|
+
function toNumber2(val) {
|
|
17865
|
+
if (typeof val === "number") return val;
|
|
17866
|
+
if (typeof val === "string") {
|
|
17867
|
+
const n = Number(val);
|
|
17868
|
+
return isNaN(n) ? 0 : n;
|
|
17869
|
+
}
|
|
17870
|
+
if (typeof val === "boolean") return val ? 1 : 0;
|
|
17871
|
+
return 0;
|
|
17872
|
+
}
|
|
17873
|
+
var BUILTINS = {
|
|
17874
|
+
Count: {
|
|
17875
|
+
name: "Count",
|
|
17876
|
+
signature: "Count(array) \u2192 number",
|
|
17877
|
+
description: "Returns array length",
|
|
17878
|
+
fn: (arr) => Array.isArray(arr) ? arr.length : 0
|
|
17879
|
+
},
|
|
17880
|
+
First: {
|
|
17881
|
+
name: "First",
|
|
17882
|
+
signature: "First(array) \u2192 element",
|
|
17883
|
+
description: "Returns first element of array",
|
|
17884
|
+
fn: (arr) => Array.isArray(arr) ? arr[0] ?? null : null
|
|
17885
|
+
},
|
|
17886
|
+
Last: {
|
|
17887
|
+
name: "Last",
|
|
17888
|
+
signature: "Last(array) \u2192 element",
|
|
17889
|
+
description: "Returns last element of array",
|
|
17890
|
+
fn: (arr) => Array.isArray(arr) ? arr[arr.length - 1] ?? null : null
|
|
17891
|
+
},
|
|
17892
|
+
Sum: {
|
|
17893
|
+
name: "Sum",
|
|
17894
|
+
signature: "Sum(numbers[]) \u2192 number",
|
|
17895
|
+
description: "Sum of numeric array",
|
|
17896
|
+
fn: (arr) => Array.isArray(arr) ? arr.reduce((a, b) => a + toNumber2(b), 0) : 0
|
|
17897
|
+
},
|
|
17898
|
+
Avg: {
|
|
17899
|
+
name: "Avg",
|
|
17900
|
+
signature: "Avg(numbers[]) \u2192 number",
|
|
17901
|
+
description: "Average of numeric array",
|
|
17902
|
+
fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((a, b) => a + toNumber2(b), 0) / arr.length : 0
|
|
17903
|
+
},
|
|
17904
|
+
Min: {
|
|
17905
|
+
name: "Min",
|
|
17906
|
+
signature: "Min(numbers[]) \u2192 number",
|
|
17907
|
+
description: "Minimum value in array",
|
|
17908
|
+
fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.min(acc, toNumber2(b)), toNumber2(arr[0])) : 0
|
|
17909
|
+
},
|
|
17910
|
+
Max: {
|
|
17911
|
+
name: "Max",
|
|
17912
|
+
signature: "Max(numbers[]) \u2192 number",
|
|
17913
|
+
description: "Maximum value in array",
|
|
17914
|
+
fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.max(acc, toNumber2(b)), toNumber2(arr[0])) : 0
|
|
17915
|
+
},
|
|
17916
|
+
Sort: {
|
|
17917
|
+
name: "Sort",
|
|
17918
|
+
signature: "Sort(array, field, direction?) \u2192 sorted array",
|
|
17919
|
+
description: 'Sort array by field. Direction: "asc" (default) or "desc"',
|
|
17920
|
+
fn: (arr, field, dir) => {
|
|
17921
|
+
if (!Array.isArray(arr)) return arr;
|
|
17922
|
+
const f = String(field ?? "");
|
|
17923
|
+
const desc = String(dir ?? "asc") === "desc";
|
|
17924
|
+
return [...arr].sort((a, b) => {
|
|
17925
|
+
const av = f ? resolveField(a, f) : a;
|
|
17926
|
+
const bv = f ? resolveField(b, f) : b;
|
|
17927
|
+
const aIsNumeric = typeof av === "number" || typeof av === "string" && !isNaN(Number(av)) && av !== "";
|
|
17928
|
+
const bIsNumeric = typeof bv === "number" || typeof bv === "string" && !isNaN(Number(bv)) && bv !== "";
|
|
17929
|
+
if (aIsNumeric && bIsNumeric) {
|
|
17930
|
+
const diff = toNumber2(av) - toNumber2(bv);
|
|
17931
|
+
return desc ? -diff : diff;
|
|
17932
|
+
}
|
|
17933
|
+
const cmp = String(av ?? "").localeCompare(String(bv ?? ""));
|
|
17934
|
+
return desc ? -cmp : cmp;
|
|
17935
|
+
});
|
|
17936
|
+
}
|
|
17937
|
+
},
|
|
17938
|
+
Filter: {
|
|
17939
|
+
name: "Filter",
|
|
17940
|
+
signature: 'Filter(array, field, operator: "==" | "!=" | ">" | "<" | ">=" | "<=" | "contains", value) \u2192 filtered array',
|
|
17941
|
+
description: "Filter array by field value",
|
|
17942
|
+
fn: (arr, field, op, value) => {
|
|
17943
|
+
if (!Array.isArray(arr)) return [];
|
|
17944
|
+
const f = String(field ?? "");
|
|
17945
|
+
const o = String(op ?? "==");
|
|
17946
|
+
return arr.filter((item) => {
|
|
17947
|
+
const v = f ? resolveField(item, f) : item;
|
|
17948
|
+
switch (o) {
|
|
17949
|
+
case "==":
|
|
17950
|
+
return v == value;
|
|
17951
|
+
case "!=":
|
|
17952
|
+
return v != value;
|
|
17953
|
+
case ">":
|
|
17954
|
+
return toNumber2(v) > toNumber2(value);
|
|
17955
|
+
case "<":
|
|
17956
|
+
return toNumber2(v) < toNumber2(value);
|
|
17957
|
+
case ">=":
|
|
17958
|
+
return toNumber2(v) >= toNumber2(value);
|
|
17959
|
+
case "<=":
|
|
17960
|
+
return toNumber2(v) <= toNumber2(value);
|
|
17961
|
+
case "contains":
|
|
17962
|
+
return String(v ?? "").includes(String(value ?? ""));
|
|
17963
|
+
default:
|
|
17964
|
+
return false;
|
|
17965
|
+
}
|
|
17966
|
+
});
|
|
17967
|
+
}
|
|
17968
|
+
},
|
|
17969
|
+
Round: {
|
|
17970
|
+
name: "Round",
|
|
17971
|
+
signature: "Round(number, decimals?) \u2192 number",
|
|
17972
|
+
description: "Round to N decimal places (default 0)",
|
|
17973
|
+
fn: (n, decimals) => {
|
|
17974
|
+
const num = toNumber2(n);
|
|
17975
|
+
const d = decimals != null ? toNumber2(decimals) : 0;
|
|
17976
|
+
const factor = Math.pow(10, d);
|
|
17977
|
+
return Math.round(num * factor) / factor;
|
|
17978
|
+
}
|
|
17979
|
+
},
|
|
17980
|
+
Abs: {
|
|
17981
|
+
name: "Abs",
|
|
17982
|
+
signature: "Abs(number) \u2192 number",
|
|
17983
|
+
description: "Absolute value",
|
|
17984
|
+
fn: (n) => Math.abs(toNumber2(n))
|
|
17985
|
+
},
|
|
17986
|
+
Floor: {
|
|
17987
|
+
name: "Floor",
|
|
17988
|
+
signature: "Floor(number) \u2192 number",
|
|
17989
|
+
description: "Round down to nearest integer",
|
|
17990
|
+
fn: (n) => Math.floor(toNumber2(n))
|
|
17991
|
+
},
|
|
17992
|
+
Ceil: {
|
|
17993
|
+
name: "Ceil",
|
|
17994
|
+
signature: "Ceil(number) \u2192 number",
|
|
17995
|
+
description: "Round up to nearest integer",
|
|
17996
|
+
fn: (n) => Math.ceil(toNumber2(n))
|
|
17997
|
+
}
|
|
17998
|
+
};
|
|
17999
|
+
var LAZY_BUILTINS = /* @__PURE__ */ new Set(["Each"]);
|
|
18000
|
+
var ACTION_STEPS = {
|
|
18001
|
+
Run: "run",
|
|
18002
|
+
ToAssistant: "continue_conversation",
|
|
18003
|
+
OpenUrl: "open_url",
|
|
18004
|
+
Set: "set",
|
|
18005
|
+
Reset: "reset"
|
|
18006
|
+
};
|
|
18007
|
+
var ACTION_NAMES = /* @__PURE__ */ new Set(["Action", ...Object.keys(ACTION_STEPS)]);
|
|
18008
|
+
var BUILTIN_NAMES = /* @__PURE__ */ new Set([
|
|
18009
|
+
...Object.keys(BUILTINS),
|
|
18010
|
+
...LAZY_BUILTINS,
|
|
18011
|
+
...ACTION_NAMES
|
|
18012
|
+
]);
|
|
18013
|
+
function isBuiltin(name) {
|
|
18014
|
+
return BUILTIN_NAMES.has(name);
|
|
18015
|
+
}
|
|
18016
|
+
var RESERVED_CALLS = { Query: "Query", Mutation: "Mutation" };
|
|
18017
|
+
function isReservedCall(name) {
|
|
18018
|
+
return name in RESERVED_CALLS;
|
|
18019
|
+
}
|
|
18020
|
+
|
|
18021
|
+
// src/genui/lang/parser/expressions.ts
|
|
18022
|
+
var PREC_TERNARY = 1;
|
|
18023
|
+
var PREC_OR = 2;
|
|
18024
|
+
var PREC_AND = 3;
|
|
18025
|
+
var PREC_EQ = 4;
|
|
18026
|
+
var PREC_CMP = 5;
|
|
18027
|
+
var PREC_ADD = 6;
|
|
18028
|
+
var PREC_MUL = 7;
|
|
18029
|
+
var PREC_UNARY = 8;
|
|
18030
|
+
var PREC_MEMBER = 9;
|
|
18031
|
+
function parseExpression(tokens) {
|
|
18032
|
+
let pos = 0;
|
|
18033
|
+
const cur = () => tokens[pos] ?? { t: 13 /* EOF */ };
|
|
18034
|
+
const adv = () => {
|
|
18035
|
+
const tok = cur();
|
|
18036
|
+
pos++;
|
|
18037
|
+
return tok;
|
|
18038
|
+
};
|
|
18039
|
+
const eat = (kind) => {
|
|
18040
|
+
if (cur().t === kind) pos++;
|
|
18041
|
+
};
|
|
18042
|
+
function getInfixPrec(tok) {
|
|
18043
|
+
switch (tok.t) {
|
|
18044
|
+
case 34 /* Question */:
|
|
18045
|
+
return PREC_TERNARY;
|
|
18046
|
+
case 32 /* Or */:
|
|
18047
|
+
return PREC_OR;
|
|
18048
|
+
case 31 /* And */:
|
|
18049
|
+
return PREC_AND;
|
|
18050
|
+
case 25 /* EqEq */:
|
|
18051
|
+
case 26 /* NotEq */:
|
|
18052
|
+
return PREC_EQ;
|
|
18053
|
+
case 27 /* Greater */:
|
|
18054
|
+
case 28 /* Less */:
|
|
18055
|
+
case 29 /* GreaterEq */:
|
|
18056
|
+
case 30 /* LessEq */:
|
|
18057
|
+
return PREC_CMP;
|
|
18058
|
+
case 20 /* Plus */:
|
|
18059
|
+
case 21 /* Minus */:
|
|
18060
|
+
return PREC_ADD;
|
|
18061
|
+
case 22 /* Star */:
|
|
18062
|
+
case 23 /* Slash */:
|
|
18063
|
+
case 24 /* Percent */:
|
|
18064
|
+
return PREC_MUL;
|
|
18065
|
+
case 19 /* Dot */:
|
|
18066
|
+
case 3 /* LBrack */:
|
|
18067
|
+
return PREC_MEMBER;
|
|
18068
|
+
default:
|
|
18069
|
+
return 0;
|
|
18070
|
+
}
|
|
18071
|
+
}
|
|
18072
|
+
function parseExpr(minPrec = 0) {
|
|
18073
|
+
let left = parsePrefix();
|
|
18074
|
+
while (getInfixPrec(cur()) > minPrec) {
|
|
18075
|
+
left = parseInfix(left);
|
|
18076
|
+
}
|
|
18077
|
+
return left;
|
|
18078
|
+
}
|
|
18079
|
+
function parsePrefix() {
|
|
18080
|
+
const tok = cur();
|
|
18081
|
+
if (tok.t === 14 /* Str */) {
|
|
18082
|
+
adv();
|
|
18083
|
+
return { k: "Str", v: tok.v };
|
|
18084
|
+
}
|
|
18085
|
+
if (tok.t === 15 /* Num */) {
|
|
18086
|
+
adv();
|
|
18087
|
+
return { k: "Num", v: tok.v };
|
|
18088
|
+
}
|
|
18089
|
+
if (tok.t === 10 /* True */) {
|
|
18090
|
+
adv();
|
|
18091
|
+
return { k: "Bool", v: true };
|
|
18092
|
+
}
|
|
18093
|
+
if (tok.t === 11 /* False */) {
|
|
18094
|
+
adv();
|
|
18095
|
+
return { k: "Bool", v: false };
|
|
18096
|
+
}
|
|
18097
|
+
if (tok.t === 12 /* Null */) {
|
|
18098
|
+
adv();
|
|
18099
|
+
return { k: "Null" };
|
|
18100
|
+
}
|
|
18101
|
+
if (tok.t === 3 /* LBrack */) return parseArr();
|
|
18102
|
+
if (tok.t === 5 /* LBrace */) return parseObj();
|
|
18103
|
+
if (tok.t === 18 /* StateVar */) {
|
|
18104
|
+
const name = tok.v;
|
|
18105
|
+
adv();
|
|
18106
|
+
if (cur().t === 9 /* Equals */) {
|
|
18107
|
+
adv();
|
|
18108
|
+
const value = parseExpr(0);
|
|
18109
|
+
return { k: "Assign", target: name, value };
|
|
18110
|
+
}
|
|
18111
|
+
return { k: "StateRef", n: name };
|
|
18112
|
+
}
|
|
18113
|
+
if (tok.t === 17 /* Type */) {
|
|
18114
|
+
const name = tok.v;
|
|
18115
|
+
if (tokens[pos + 1]?.t === 1 /* LParen */ && (!isBuiltin(name) || name === "Action"))
|
|
18116
|
+
return parseComp();
|
|
18117
|
+
adv();
|
|
18118
|
+
return { k: "Ref", n: name };
|
|
18119
|
+
}
|
|
18120
|
+
if (tok.t === 35 /* BuiltinCall */) {
|
|
18121
|
+
if (tokens[pos + 1]?.t === 1 /* LParen */) return parseComp();
|
|
18122
|
+
adv();
|
|
18123
|
+
return { k: "Ref", n: tok.v };
|
|
18124
|
+
}
|
|
18125
|
+
if (tok.t === 16 /* Ident */) {
|
|
18126
|
+
adv();
|
|
18127
|
+
return { k: "Ref", n: tok.v };
|
|
18128
|
+
}
|
|
18129
|
+
if (tok.t === 33 /* Not */) {
|
|
18130
|
+
adv();
|
|
18131
|
+
return { k: "UnaryOp", op: "!", operand: parseExpr(PREC_UNARY) };
|
|
18132
|
+
}
|
|
18133
|
+
if (tok.t === 21 /* Minus */) {
|
|
18134
|
+
adv();
|
|
18135
|
+
return { k: "UnaryOp", op: "-", operand: parseExpr(PREC_UNARY) };
|
|
18136
|
+
}
|
|
18137
|
+
if (tok.t === 1 /* LParen */) {
|
|
18138
|
+
adv();
|
|
18139
|
+
const inner = parseExpr(0);
|
|
18140
|
+
eat(2 /* RParen */);
|
|
18141
|
+
return inner;
|
|
18142
|
+
}
|
|
18143
|
+
adv();
|
|
18144
|
+
return { k: "Null" };
|
|
18145
|
+
}
|
|
18146
|
+
function parseInfix(left) {
|
|
18147
|
+
const tok = cur();
|
|
18148
|
+
if (tok.t === 20 /* Plus */) {
|
|
18149
|
+
adv();
|
|
18150
|
+
return { k: "BinOp", op: "+", left, right: parseExpr(PREC_ADD) };
|
|
18151
|
+
}
|
|
18152
|
+
if (tok.t === 21 /* Minus */) {
|
|
18153
|
+
adv();
|
|
18154
|
+
return { k: "BinOp", op: "-", left, right: parseExpr(PREC_ADD) };
|
|
18155
|
+
}
|
|
18156
|
+
if (tok.t === 22 /* Star */) {
|
|
18157
|
+
adv();
|
|
18158
|
+
return { k: "BinOp", op: "*", left, right: parseExpr(PREC_MUL) };
|
|
18159
|
+
}
|
|
18160
|
+
if (tok.t === 23 /* Slash */) {
|
|
18161
|
+
adv();
|
|
18162
|
+
return { k: "BinOp", op: "/", left, right: parseExpr(PREC_MUL) };
|
|
18163
|
+
}
|
|
18164
|
+
if (tok.t === 24 /* Percent */) {
|
|
18165
|
+
adv();
|
|
18166
|
+
return { k: "BinOp", op: "%", left, right: parseExpr(PREC_MUL) };
|
|
18167
|
+
}
|
|
18168
|
+
if (tok.t === 25 /* EqEq */) {
|
|
18169
|
+
adv();
|
|
18170
|
+
return { k: "BinOp", op: "==", left, right: parseExpr(PREC_EQ) };
|
|
18171
|
+
}
|
|
18172
|
+
if (tok.t === 26 /* NotEq */) {
|
|
18173
|
+
adv();
|
|
18174
|
+
return { k: "BinOp", op: "!=", left, right: parseExpr(PREC_EQ) };
|
|
18175
|
+
}
|
|
18176
|
+
if (tok.t === 27 /* Greater */) {
|
|
18177
|
+
adv();
|
|
18178
|
+
return { k: "BinOp", op: ">", left, right: parseExpr(PREC_CMP) };
|
|
18179
|
+
}
|
|
18180
|
+
if (tok.t === 28 /* Less */) {
|
|
18181
|
+
adv();
|
|
18182
|
+
return { k: "BinOp", op: "<", left, right: parseExpr(PREC_CMP) };
|
|
18183
|
+
}
|
|
18184
|
+
if (tok.t === 29 /* GreaterEq */) {
|
|
18185
|
+
adv();
|
|
18186
|
+
return { k: "BinOp", op: ">=", left, right: parseExpr(PREC_CMP) };
|
|
18187
|
+
}
|
|
18188
|
+
if (tok.t === 30 /* LessEq */) {
|
|
18189
|
+
adv();
|
|
18190
|
+
return { k: "BinOp", op: "<=", left, right: parseExpr(PREC_CMP) };
|
|
18191
|
+
}
|
|
18192
|
+
if (tok.t === 31 /* And */) {
|
|
18193
|
+
adv();
|
|
18194
|
+
return { k: "BinOp", op: "&&", left, right: parseExpr(PREC_AND) };
|
|
18195
|
+
}
|
|
18196
|
+
if (tok.t === 32 /* Or */) {
|
|
18197
|
+
adv();
|
|
18198
|
+
return { k: "BinOp", op: "||", left, right: parseExpr(PREC_OR) };
|
|
18199
|
+
}
|
|
18200
|
+
if (tok.t === 34 /* Question */) {
|
|
18201
|
+
adv();
|
|
18202
|
+
const then = parseExpr(0);
|
|
18203
|
+
eat(8 /* Colon */);
|
|
18204
|
+
const els = parseExpr(0);
|
|
18205
|
+
return { k: "Ternary", cond: left, then, else: els };
|
|
18206
|
+
}
|
|
18207
|
+
if (tok.t === 19 /* Dot */) {
|
|
18208
|
+
adv();
|
|
18209
|
+
const fieldTok = cur();
|
|
18210
|
+
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(), "?");
|
|
18211
|
+
return { k: "Member", obj: left, field };
|
|
18212
|
+
}
|
|
18213
|
+
if (tok.t === 3 /* LBrack */) {
|
|
18214
|
+
adv();
|
|
18215
|
+
const index = parseExpr(0);
|
|
18216
|
+
eat(4 /* RBrack */);
|
|
18217
|
+
return { k: "Index", obj: left, index };
|
|
18218
|
+
}
|
|
18219
|
+
return left;
|
|
18220
|
+
}
|
|
18221
|
+
function parseComp() {
|
|
18222
|
+
const name = cur().v;
|
|
18223
|
+
adv();
|
|
18224
|
+
eat(1 /* LParen */);
|
|
18225
|
+
const args = [];
|
|
18226
|
+
while (cur().t !== 2 /* RParen */ && cur().t !== 13 /* EOF */) {
|
|
18227
|
+
args.push(parseExpr(0));
|
|
18228
|
+
if (cur().t === 7 /* Comma */) adv();
|
|
18229
|
+
}
|
|
18230
|
+
eat(2 /* RParen */);
|
|
18231
|
+
return { k: "Comp", name, args };
|
|
18232
|
+
}
|
|
18233
|
+
function parseArr() {
|
|
18234
|
+
adv();
|
|
18235
|
+
const els = [];
|
|
18236
|
+
while (cur().t !== 4 /* RBrack */ && cur().t !== 13 /* EOF */) {
|
|
18237
|
+
els.push(parseExpr(0));
|
|
18238
|
+
if (cur().t === 7 /* Comma */) adv();
|
|
18239
|
+
}
|
|
18240
|
+
eat(4 /* RBrack */);
|
|
18241
|
+
return { k: "Arr", els };
|
|
18242
|
+
}
|
|
18243
|
+
function parseObj() {
|
|
18244
|
+
adv();
|
|
18245
|
+
const entries = [];
|
|
18246
|
+
while (cur().t !== 6 /* RBrace */ && cur().t !== 13 /* EOF */) {
|
|
18247
|
+
const kt = cur();
|
|
18248
|
+
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(), "?");
|
|
18249
|
+
eat(8 /* Colon */);
|
|
18250
|
+
entries.push([key, parseExpr(0)]);
|
|
18251
|
+
if (cur().t === 7 /* Comma */) adv();
|
|
18252
|
+
}
|
|
18253
|
+
eat(6 /* RBrace */);
|
|
18254
|
+
return { k: "Obj", entries };
|
|
18255
|
+
}
|
|
18256
|
+
return parseExpr(0);
|
|
18257
|
+
}
|
|
18258
|
+
|
|
18259
|
+
// src/genui/lang/parser/lexer.ts
|
|
18260
|
+
function tokenize(src) {
|
|
18261
|
+
const tokens = [];
|
|
18262
|
+
let i = 0;
|
|
18263
|
+
const n = src.length;
|
|
18264
|
+
while (i < n) {
|
|
18265
|
+
while (i < n && (src[i] === " " || src[i] === " " || src[i] === "\r")) i++;
|
|
18266
|
+
if (i >= n) break;
|
|
18267
|
+
const c = src[i];
|
|
18268
|
+
if (c === "\n") {
|
|
18269
|
+
tokens.push({ t: 0 /* Newline */ });
|
|
18270
|
+
i++;
|
|
18271
|
+
continue;
|
|
18272
|
+
}
|
|
18273
|
+
if (c === "(") {
|
|
18274
|
+
tokens.push({ t: 1 /* LParen */ });
|
|
18275
|
+
i++;
|
|
18276
|
+
continue;
|
|
18277
|
+
}
|
|
18278
|
+
if (c === ")") {
|
|
18279
|
+
tokens.push({ t: 2 /* RParen */ });
|
|
18280
|
+
i++;
|
|
18281
|
+
continue;
|
|
18282
|
+
}
|
|
18283
|
+
if (c === "[") {
|
|
18284
|
+
tokens.push({ t: 3 /* LBrack */ });
|
|
18285
|
+
i++;
|
|
18286
|
+
continue;
|
|
18287
|
+
}
|
|
18288
|
+
if (c === "]") {
|
|
18289
|
+
tokens.push({ t: 4 /* RBrack */ });
|
|
18290
|
+
i++;
|
|
18291
|
+
continue;
|
|
18292
|
+
}
|
|
18293
|
+
if (c === "{") {
|
|
18294
|
+
tokens.push({ t: 5 /* LBrace */ });
|
|
18295
|
+
i++;
|
|
18296
|
+
continue;
|
|
18297
|
+
}
|
|
18298
|
+
if (c === "}") {
|
|
18299
|
+
tokens.push({ t: 6 /* RBrace */ });
|
|
18300
|
+
i++;
|
|
18301
|
+
continue;
|
|
18302
|
+
}
|
|
18303
|
+
if (c === ",") {
|
|
18304
|
+
tokens.push({ t: 7 /* Comma */ });
|
|
18305
|
+
i++;
|
|
18306
|
+
continue;
|
|
18307
|
+
}
|
|
18308
|
+
if (c === ":") {
|
|
18309
|
+
tokens.push({ t: 8 /* Colon */ });
|
|
18310
|
+
i++;
|
|
18311
|
+
continue;
|
|
18312
|
+
}
|
|
18313
|
+
if (c === "=") {
|
|
18314
|
+
if (i + 1 < n && src[i + 1] === "=") {
|
|
18315
|
+
tokens.push({ t: 25 /* EqEq */ });
|
|
18316
|
+
i += 2;
|
|
18317
|
+
} else {
|
|
18318
|
+
tokens.push({ t: 9 /* Equals */ });
|
|
18319
|
+
i++;
|
|
18320
|
+
}
|
|
18321
|
+
continue;
|
|
18322
|
+
}
|
|
18323
|
+
if (c === "!") {
|
|
18324
|
+
if (i + 1 < n && src[i + 1] === "=") {
|
|
18325
|
+
tokens.push({ t: 26 /* NotEq */ });
|
|
18326
|
+
i += 2;
|
|
18327
|
+
} else {
|
|
18328
|
+
tokens.push({ t: 33 /* Not */ });
|
|
18329
|
+
i++;
|
|
18330
|
+
}
|
|
18331
|
+
continue;
|
|
18332
|
+
}
|
|
18333
|
+
if (c === ">") {
|
|
18334
|
+
if (i + 1 < n && src[i + 1] === "=") {
|
|
18335
|
+
tokens.push({ t: 29 /* GreaterEq */ });
|
|
18336
|
+
i += 2;
|
|
18337
|
+
} else {
|
|
18338
|
+
tokens.push({ t: 27 /* Greater */ });
|
|
18339
|
+
i++;
|
|
18340
|
+
}
|
|
18341
|
+
continue;
|
|
18342
|
+
}
|
|
18343
|
+
if (c === "<") {
|
|
18344
|
+
if (i + 1 < n && src[i + 1] === "=") {
|
|
18345
|
+
tokens.push({ t: 30 /* LessEq */ });
|
|
18346
|
+
i += 2;
|
|
18347
|
+
} else {
|
|
18348
|
+
tokens.push({ t: 28 /* Less */ });
|
|
18349
|
+
i++;
|
|
18350
|
+
}
|
|
18351
|
+
continue;
|
|
18352
|
+
}
|
|
18353
|
+
if (c === "&") {
|
|
18354
|
+
if (i + 1 < n && src[i + 1] === "&") {
|
|
18355
|
+
tokens.push({ t: 31 /* And */ });
|
|
18356
|
+
i += 2;
|
|
18357
|
+
} else {
|
|
18358
|
+
tokens.push({ t: 31 /* And */ });
|
|
18359
|
+
i++;
|
|
18360
|
+
}
|
|
18361
|
+
continue;
|
|
18362
|
+
}
|
|
18363
|
+
if (c === "|") {
|
|
18364
|
+
if (i + 1 < n && src[i + 1] === "|") {
|
|
18365
|
+
tokens.push({ t: 32 /* Or */ });
|
|
18366
|
+
i += 2;
|
|
18367
|
+
} else {
|
|
18368
|
+
tokens.push({ t: 32 /* Or */ });
|
|
18369
|
+
i++;
|
|
18370
|
+
}
|
|
18371
|
+
continue;
|
|
18372
|
+
}
|
|
18373
|
+
if (c === ".") {
|
|
18374
|
+
tokens.push({ t: 19 /* Dot */ });
|
|
18375
|
+
i++;
|
|
18376
|
+
continue;
|
|
18377
|
+
}
|
|
18378
|
+
if (c === "?") {
|
|
18379
|
+
tokens.push({ t: 34 /* Question */ });
|
|
18380
|
+
i++;
|
|
18381
|
+
continue;
|
|
18382
|
+
}
|
|
18383
|
+
if (c === "+") {
|
|
18384
|
+
tokens.push({ t: 20 /* Plus */ });
|
|
18385
|
+
i++;
|
|
18386
|
+
continue;
|
|
18387
|
+
}
|
|
18388
|
+
if (c === "*") {
|
|
18389
|
+
tokens.push({ t: 22 /* Star */ });
|
|
18390
|
+
i++;
|
|
18391
|
+
continue;
|
|
18392
|
+
}
|
|
18393
|
+
if (c === "/") {
|
|
18394
|
+
tokens.push({ t: 23 /* Slash */ });
|
|
18395
|
+
i++;
|
|
18396
|
+
continue;
|
|
18397
|
+
}
|
|
18398
|
+
if (c === "%") {
|
|
18399
|
+
tokens.push({ t: 24 /* Percent */ });
|
|
18400
|
+
i++;
|
|
18401
|
+
continue;
|
|
18402
|
+
}
|
|
18403
|
+
if (c === '"') {
|
|
18404
|
+
const start = i;
|
|
18405
|
+
i++;
|
|
18406
|
+
let isClosed = false;
|
|
18407
|
+
while (i < n) {
|
|
18408
|
+
if (src[i] === "\\") {
|
|
18409
|
+
i += 2;
|
|
18410
|
+
} else if (src[i] === '"') {
|
|
18411
|
+
i++;
|
|
18412
|
+
isClosed = true;
|
|
18413
|
+
break;
|
|
18414
|
+
} else {
|
|
18415
|
+
i++;
|
|
18416
|
+
}
|
|
18417
|
+
}
|
|
18418
|
+
const rawString = src.slice(start, i);
|
|
18419
|
+
try {
|
|
18420
|
+
const validJsonString = isClosed ? rawString : rawString + '"';
|
|
18421
|
+
tokens.push({ t: 14 /* Str */, v: JSON.parse(validJsonString) });
|
|
18422
|
+
} catch {
|
|
18423
|
+
const stripped = rawString.replace(/^"|"$/g, "");
|
|
18424
|
+
tokens.push({ t: 14 /* Str */, v: stripped });
|
|
18425
|
+
}
|
|
18426
|
+
continue;
|
|
18427
|
+
}
|
|
18428
|
+
if (c === "'") {
|
|
18429
|
+
i++;
|
|
18430
|
+
let result = "";
|
|
18431
|
+
while (i < n) {
|
|
18432
|
+
if (src[i] === "\\") {
|
|
18433
|
+
i++;
|
|
18434
|
+
if (i < n) {
|
|
18435
|
+
const esc = src[i];
|
|
18436
|
+
if (esc === "'") result += "'";
|
|
18437
|
+
else if (esc === "\\") result += "\\";
|
|
18438
|
+
else if (esc === "n") result += "\n";
|
|
18439
|
+
else if (esc === "t") result += " ";
|
|
18440
|
+
else result += esc;
|
|
18441
|
+
i++;
|
|
18442
|
+
}
|
|
18443
|
+
} else if (src[i] === "'") {
|
|
18444
|
+
i++;
|
|
18445
|
+
break;
|
|
18446
|
+
} else {
|
|
18447
|
+
result += src[i];
|
|
18448
|
+
i++;
|
|
18449
|
+
}
|
|
18450
|
+
}
|
|
18451
|
+
tokens.push({ t: 14 /* Str */, v: result });
|
|
18452
|
+
continue;
|
|
18453
|
+
}
|
|
18454
|
+
if (c === "-") {
|
|
18455
|
+
const prev = tokens.length > 0 ? tokens[tokens.length - 1] : null;
|
|
18456
|
+
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 */);
|
|
18457
|
+
if (!afterValue && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") ; else {
|
|
18458
|
+
tokens.push({ t: 21 /* Minus */ });
|
|
18459
|
+
i++;
|
|
18460
|
+
continue;
|
|
18461
|
+
}
|
|
18462
|
+
}
|
|
18463
|
+
const isDigit = c >= "0" && c <= "9";
|
|
18464
|
+
const isNegDigit = c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9";
|
|
18465
|
+
if (isDigit || isNegDigit) {
|
|
18466
|
+
const start = i;
|
|
18467
|
+
if (src[i] === "-") i++;
|
|
18468
|
+
while (i < n && src[i] >= "0" && src[i] <= "9") i++;
|
|
18469
|
+
if (i < n && src[i] === "." && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") {
|
|
18470
|
+
i++;
|
|
18471
|
+
while (i < n && src[i] >= "0" && src[i] <= "9") i++;
|
|
18472
|
+
}
|
|
18473
|
+
if (i < n && (src[i] === "e" || src[i] === "E")) {
|
|
18474
|
+
i++;
|
|
18475
|
+
if (i < n && (src[i] === "+" || src[i] === "-")) i++;
|
|
18476
|
+
while (i < n && src[i] >= "0" && src[i] <= "9") i++;
|
|
18477
|
+
}
|
|
18478
|
+
tokens.push({ t: 15 /* Num */, v: +src.slice(start, i) });
|
|
18479
|
+
continue;
|
|
18480
|
+
}
|
|
18481
|
+
if (c === "$" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
|
|
18482
|
+
const start = i;
|
|
18483
|
+
i++;
|
|
18484
|
+
while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
|
|
18485
|
+
i++;
|
|
18486
|
+
tokens.push({ t: 18 /* StateVar */, v: src.slice(start, i) });
|
|
18487
|
+
continue;
|
|
18488
|
+
}
|
|
18489
|
+
const isAlpha = c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
|
|
18490
|
+
if (isAlpha) {
|
|
18491
|
+
const start = i;
|
|
18492
|
+
while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
|
|
18493
|
+
i++;
|
|
18494
|
+
const word = src.slice(start, i);
|
|
18495
|
+
if (word === "true") {
|
|
18496
|
+
tokens.push({ t: 10 /* True */ });
|
|
18497
|
+
continue;
|
|
18498
|
+
}
|
|
18499
|
+
if (word === "false") {
|
|
18500
|
+
tokens.push({ t: 11 /* False */ });
|
|
18501
|
+
continue;
|
|
18502
|
+
}
|
|
18503
|
+
if (word === "null") {
|
|
18504
|
+
tokens.push({ t: 12 /* Null */ });
|
|
18505
|
+
continue;
|
|
18506
|
+
}
|
|
18507
|
+
const kind = c >= "A" && c <= "Z" ? 17 /* Type */ : 16 /* Ident */;
|
|
18508
|
+
tokens.push({ t: kind, v: word });
|
|
18509
|
+
continue;
|
|
18510
|
+
}
|
|
18511
|
+
if (c === "@" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
|
|
18512
|
+
i++;
|
|
18513
|
+
const start = i;
|
|
18514
|
+
while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
|
|
18515
|
+
i++;
|
|
18516
|
+
tokens.push({ t: 35 /* BuiltinCall */, v: src.slice(start, i) });
|
|
18517
|
+
continue;
|
|
18518
|
+
}
|
|
18519
|
+
i++;
|
|
18520
|
+
}
|
|
18521
|
+
tokens.push({ t: 13 /* EOF */ });
|
|
18522
|
+
return tokens;
|
|
18523
|
+
}
|
|
18524
|
+
|
|
18525
|
+
// src/genui/lang/parser/types.ts
|
|
18526
|
+
function isElementNode(value) {
|
|
18527
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
18528
|
+
const node = value;
|
|
18529
|
+
return node.type === "element" && typeof node.typeName === "string" && typeof node.props === "object" && node.props !== null && typeof node.partial === "boolean";
|
|
18530
|
+
}
|
|
18531
|
+
|
|
18532
|
+
// src/genui/lang/parser/materialize.ts
|
|
18533
|
+
function containsDynamicValue(v) {
|
|
18534
|
+
if (v == null || typeof v !== "object") return false;
|
|
18535
|
+
if (isASTNode(v)) return true;
|
|
18536
|
+
if (Array.isArray(v)) return v.some(containsDynamicValue);
|
|
18537
|
+
if (isElementNode(v)) {
|
|
18538
|
+
return Object.values(v.props).some(containsDynamicValue);
|
|
18539
|
+
}
|
|
18540
|
+
const obj = v;
|
|
18541
|
+
return Object.values(obj).some(containsDynamicValue);
|
|
18542
|
+
}
|
|
18543
|
+
function resolveRef(name, ctx, mode) {
|
|
18544
|
+
if (ctx.visited.has(name)) {
|
|
18545
|
+
ctx.unres.push(name);
|
|
18546
|
+
return mode === "expr" ? { k: "Ph", n: name } : null;
|
|
18547
|
+
}
|
|
18548
|
+
if (!ctx.syms.has(name)) {
|
|
18549
|
+
ctx.unres.push(name);
|
|
18550
|
+
return mode === "expr" ? { k: "Ph", n: name } : null;
|
|
18551
|
+
}
|
|
18552
|
+
const target = ctx.syms.get(name);
|
|
18553
|
+
ctx.unreached?.delete(name);
|
|
18554
|
+
if (target.k === "Comp" && isReservedCall(target.name)) {
|
|
18555
|
+
const refType = target.name === RESERVED_CALLS.Mutation ? "mutation" : "query";
|
|
18556
|
+
return { k: "RuntimeRef", n: name, refType };
|
|
18557
|
+
}
|
|
18558
|
+
ctx.visited.add(name);
|
|
18559
|
+
const prevStatementId = ctx.currentStatementId;
|
|
18560
|
+
ctx.currentStatementId = name;
|
|
18561
|
+
try {
|
|
18562
|
+
const result = mode === "value" ? materializeValue(target, ctx) : materializeExpr(target, ctx);
|
|
18563
|
+
if (mode === "value" && isElementNode(result)) {
|
|
18564
|
+
result.statementId = name;
|
|
18565
|
+
}
|
|
18566
|
+
return result;
|
|
18567
|
+
} finally {
|
|
18568
|
+
ctx.currentStatementId = prevStatementId;
|
|
18569
|
+
ctx.visited.delete(name);
|
|
18570
|
+
}
|
|
18571
|
+
}
|
|
18572
|
+
function materializeLazyBuiltin(node, ctx, scopedRefs) {
|
|
18573
|
+
if (!LAZY_BUILTINS.has(node.name) || node.args.length < 3) return null;
|
|
18574
|
+
const varArg = node.args[1];
|
|
18575
|
+
const varName = varArg.k === "Ref" ? varArg.n : varArg.k === "Str" ? varArg.v : null;
|
|
18576
|
+
if (!varName) return null;
|
|
18577
|
+
const nextScopedRefs = new Set(scopedRefs);
|
|
18578
|
+
nextScopedRefs.add(varName);
|
|
18579
|
+
const recursedArgs = node.args.map(
|
|
18580
|
+
(a, i) => i === 1 ? a : materializeExprInternal(a, ctx, nextScopedRefs)
|
|
18581
|
+
);
|
|
18582
|
+
return { ...node, args: recursedArgs };
|
|
18583
|
+
}
|
|
18584
|
+
function materializeExprInternal(node, ctx, scopedRefs) {
|
|
18585
|
+
switch (node.k) {
|
|
18586
|
+
case "Ref":
|
|
18587
|
+
return scopedRefs.has(node.n) ? node : resolveRef(node.n, ctx, "expr");
|
|
18588
|
+
case "Ph":
|
|
18589
|
+
return node;
|
|
18590
|
+
case "Comp": {
|
|
18591
|
+
const lazy2 = materializeLazyBuiltin(node, ctx, scopedRefs);
|
|
18592
|
+
if (lazy2) return lazy2;
|
|
18593
|
+
const recursedArgs = node.args.map((a) => materializeExprInternal(a, ctx, scopedRefs));
|
|
18594
|
+
if (isBuiltin(node.name) || isReservedCall(node.name)) {
|
|
18595
|
+
return { ...node, args: recursedArgs };
|
|
18596
|
+
}
|
|
18597
|
+
const def = ctx.cat?.get(node.name);
|
|
18598
|
+
if (def) {
|
|
18599
|
+
const mappedProps = {};
|
|
18600
|
+
for (let i = 0; i < def.params.length && i < recursedArgs.length; i++) {
|
|
18601
|
+
mappedProps[def.params[i].name] = recursedArgs[i];
|
|
18602
|
+
}
|
|
18603
|
+
return { ...node, args: recursedArgs, mappedProps };
|
|
18604
|
+
}
|
|
18605
|
+
ctx.errors.push({
|
|
18606
|
+
code: "unknown-component",
|
|
18607
|
+
component: node.name,
|
|
18608
|
+
path: "",
|
|
18609
|
+
message: `Unknown component "${node.name}" \u2014 not found in catalog or builtins`,
|
|
18610
|
+
statementId: ctx.currentStatementId
|
|
18611
|
+
});
|
|
18612
|
+
return { ...node, args: recursedArgs };
|
|
18613
|
+
}
|
|
18614
|
+
case "Arr":
|
|
18615
|
+
return { ...node, els: node.els.map((e) => materializeExprInternal(e, ctx, scopedRefs)) };
|
|
18616
|
+
case "Obj":
|
|
18617
|
+
return {
|
|
18618
|
+
...node,
|
|
18619
|
+
entries: node.entries.map(
|
|
18620
|
+
([k, v]) => [k, materializeExprInternal(v, ctx, scopedRefs)]
|
|
18621
|
+
)
|
|
18622
|
+
};
|
|
18623
|
+
case "BinOp":
|
|
18624
|
+
return {
|
|
18625
|
+
...node,
|
|
18626
|
+
left: materializeExprInternal(node.left, ctx, scopedRefs),
|
|
18627
|
+
right: materializeExprInternal(node.right, ctx, scopedRefs)
|
|
18628
|
+
};
|
|
18629
|
+
case "UnaryOp":
|
|
18630
|
+
return { ...node, operand: materializeExprInternal(node.operand, ctx, scopedRefs) };
|
|
18631
|
+
case "Ternary":
|
|
18632
|
+
return {
|
|
18633
|
+
...node,
|
|
18634
|
+
cond: materializeExprInternal(node.cond, ctx, scopedRefs),
|
|
18635
|
+
then: materializeExprInternal(node.then, ctx, scopedRefs),
|
|
18636
|
+
else: materializeExprInternal(node.else, ctx, scopedRefs)
|
|
18637
|
+
};
|
|
18638
|
+
case "Member":
|
|
18639
|
+
return { ...node, obj: materializeExprInternal(node.obj, ctx, scopedRefs) };
|
|
18640
|
+
case "Index":
|
|
18641
|
+
return {
|
|
18642
|
+
...node,
|
|
18643
|
+
obj: materializeExprInternal(node.obj, ctx, scopedRefs),
|
|
18644
|
+
index: materializeExprInternal(node.index, ctx, scopedRefs)
|
|
18645
|
+
};
|
|
18646
|
+
case "Assign":
|
|
18647
|
+
return { ...node, value: materializeExprInternal(node.value, ctx, scopedRefs) };
|
|
18648
|
+
// Literals, StateRef, RuntimeRef — pass through unchanged
|
|
18649
|
+
default:
|
|
18650
|
+
return node;
|
|
18651
|
+
}
|
|
18652
|
+
}
|
|
18653
|
+
function materializeExpr(node, ctx) {
|
|
18654
|
+
return materializeExprInternal(node, ctx, /* @__PURE__ */ new Set());
|
|
18655
|
+
}
|
|
18656
|
+
function materializeValue(node, ctx) {
|
|
18657
|
+
switch (node.k) {
|
|
18658
|
+
// ── Ref resolution ───────────────────────────────────────────────────
|
|
18659
|
+
case "Ref":
|
|
18660
|
+
return resolveRef(node.n, ctx, "value");
|
|
18661
|
+
// ── Literals → plain values ──────────────────────────────────────────
|
|
18662
|
+
case "Str":
|
|
18663
|
+
return node.v;
|
|
18664
|
+
case "Num":
|
|
18665
|
+
return node.v;
|
|
18666
|
+
case "Bool":
|
|
18667
|
+
return node.v;
|
|
18668
|
+
case "Null":
|
|
18669
|
+
return null;
|
|
18670
|
+
case "Ph":
|
|
18671
|
+
return null;
|
|
18672
|
+
// ── Collections ──────────────────────────────────────────────────────
|
|
18673
|
+
case "Arr": {
|
|
18674
|
+
const items = [];
|
|
18675
|
+
for (const e of node.els) {
|
|
18676
|
+
if (e.k === "Ph") continue;
|
|
18677
|
+
const value = materializeValue(e, ctx);
|
|
18678
|
+
if (value === null && (e.k === "Comp" || e.k === "Ref")) continue;
|
|
18679
|
+
items.push(value);
|
|
18680
|
+
}
|
|
18681
|
+
return items;
|
|
18682
|
+
}
|
|
18683
|
+
case "Obj": {
|
|
18684
|
+
const o = {};
|
|
18685
|
+
for (const [k, v] of node.entries) o[k] = materializeValue(v, ctx);
|
|
18686
|
+
return o;
|
|
18687
|
+
}
|
|
18688
|
+
// ── Component nodes ──────────────────────────────────────────────────
|
|
18689
|
+
case "Comp": {
|
|
18690
|
+
const { name, args } = node;
|
|
18691
|
+
if (isBuiltin(name)) {
|
|
18692
|
+
const lazy2 = materializeLazyBuiltin(node, ctx, /* @__PURE__ */ new Set());
|
|
18693
|
+
if (lazy2) return lazy2;
|
|
18694
|
+
return { ...node, args: args.map((a) => materializeExpr(a, ctx)) };
|
|
18695
|
+
}
|
|
18696
|
+
if (isReservedCall(name)) {
|
|
18697
|
+
ctx.errors.push({
|
|
18698
|
+
code: "inline-reserved",
|
|
18699
|
+
component: name,
|
|
18700
|
+
path: "",
|
|
18701
|
+
message: `${name}() must be declared as a top-level statement, not used inline as a value`,
|
|
18702
|
+
statementId: ctx.currentStatementId
|
|
18703
|
+
});
|
|
18704
|
+
return null;
|
|
18705
|
+
}
|
|
18706
|
+
const def = ctx.cat?.get(name);
|
|
18707
|
+
const props = {};
|
|
18708
|
+
if (def) {
|
|
18709
|
+
for (let i = 0; i < def.params.length && i < args.length; i++) {
|
|
18710
|
+
props[def.params[i].name] = materializeValue(args[i], ctx);
|
|
18711
|
+
}
|
|
18712
|
+
if (args.length > def.params.length) {
|
|
18713
|
+
const excessCount = args.length - def.params.length;
|
|
18714
|
+
ctx.errors.push({
|
|
18715
|
+
code: "excess-args",
|
|
18716
|
+
component: name,
|
|
18717
|
+
path: "",
|
|
18718
|
+
message: `${name} takes ${def.params.length} arg(s), got ${args.length} (${excessCount} excess dropped)`,
|
|
18719
|
+
statementId: ctx.currentStatementId
|
|
18720
|
+
});
|
|
18721
|
+
}
|
|
18722
|
+
const missingRequired = def.params.filter(
|
|
18723
|
+
(p) => p.required && (!(p.name in props) || props[p.name] === null)
|
|
18724
|
+
);
|
|
18725
|
+
if (missingRequired.length) {
|
|
18726
|
+
const stillInvalid = missingRequired.filter((p) => {
|
|
18727
|
+
if (p.defaultValue !== void 0) {
|
|
18728
|
+
props[p.name] = p.defaultValue;
|
|
18729
|
+
return false;
|
|
18730
|
+
}
|
|
18731
|
+
return true;
|
|
18732
|
+
});
|
|
18733
|
+
if (stillInvalid.length) {
|
|
18734
|
+
for (const p of stillInvalid) {
|
|
18735
|
+
const isNull = p.name in props;
|
|
18736
|
+
ctx.errors.push({
|
|
18737
|
+
code: isNull ? "null-required" : "missing-required",
|
|
18738
|
+
component: name,
|
|
18739
|
+
path: `/${p.name}`,
|
|
18740
|
+
message: isNull ? `required field "${p.name}" cannot be null` : `missing required field "${p.name}"`,
|
|
18741
|
+
statementId: ctx.currentStatementId
|
|
18742
|
+
});
|
|
18743
|
+
}
|
|
18744
|
+
return null;
|
|
18745
|
+
}
|
|
18746
|
+
}
|
|
18747
|
+
} else if (!isBuiltin(name) && !isReservedCall(name)) {
|
|
18748
|
+
ctx.errors.push({
|
|
18749
|
+
code: "unknown-component",
|
|
18750
|
+
component: name,
|
|
18751
|
+
path: "",
|
|
18752
|
+
message: `Unknown component "${name}" \u2014 not found in catalog or builtins`,
|
|
18753
|
+
statementId: ctx.currentStatementId
|
|
18754
|
+
});
|
|
18755
|
+
return null;
|
|
18756
|
+
}
|
|
18757
|
+
const hasDynamicProps = Object.values(props).some((v) => containsDynamicValue(v));
|
|
18758
|
+
return { type: "element", typeName: name, props, partial: ctx.partial, hasDynamicProps };
|
|
18759
|
+
}
|
|
18760
|
+
// ── Runtime expression nodes → preserve as ASTNode, normalize children ─
|
|
18761
|
+
default: {
|
|
18762
|
+
if (isRuntimeExpr(node)) {
|
|
18763
|
+
return materializeExpr(node, ctx);
|
|
18764
|
+
}
|
|
18765
|
+
return node;
|
|
18766
|
+
}
|
|
18767
|
+
}
|
|
18768
|
+
}
|
|
18769
|
+
|
|
18770
|
+
// src/genui/lang/parser/statements.ts
|
|
18771
|
+
function autoClose(input) {
|
|
18772
|
+
const stack = [];
|
|
18773
|
+
let inStr = false;
|
|
18774
|
+
let esc = false;
|
|
18775
|
+
for (let i = 0; i < input.length; i++) {
|
|
18776
|
+
const c = input[i];
|
|
18777
|
+
if (esc) {
|
|
18778
|
+
esc = false;
|
|
18779
|
+
continue;
|
|
18780
|
+
}
|
|
18781
|
+
if (c === "\\" && inStr) {
|
|
18782
|
+
esc = true;
|
|
18783
|
+
continue;
|
|
18784
|
+
}
|
|
18785
|
+
if (inStr) {
|
|
18786
|
+
if (c === inStr) inStr = false;
|
|
18787
|
+
continue;
|
|
18788
|
+
}
|
|
18789
|
+
if (c === '"' || c === "'") {
|
|
18790
|
+
inStr = c;
|
|
18791
|
+
continue;
|
|
18792
|
+
}
|
|
18793
|
+
if (c === "(" || c === "[" || c === "{") stack.push(c);
|
|
18794
|
+
else if (c === ")" && stack[stack.length - 1] === "(") stack.pop();
|
|
18795
|
+
else if (c === "]" && stack[stack.length - 1] === "[") stack.pop();
|
|
18796
|
+
else if (c === "}" && stack[stack.length - 1] === "{") stack.pop();
|
|
18797
|
+
}
|
|
18798
|
+
const wasIncomplete = !!inStr || stack.length > 0;
|
|
18799
|
+
if (!wasIncomplete) return { text: input, wasIncomplete: false };
|
|
18800
|
+
let out = input;
|
|
18801
|
+
if (inStr) {
|
|
18802
|
+
if (esc) out += "\\";
|
|
18803
|
+
out += inStr;
|
|
18804
|
+
}
|
|
18805
|
+
for (let j = stack.length - 1; j >= 0; j--)
|
|
18806
|
+
out += stack[j] === "(" ? ")" : stack[j] === "[" ? "]" : "}";
|
|
18807
|
+
return { text: out, wasIncomplete: true };
|
|
18808
|
+
}
|
|
18809
|
+
function split(tokens) {
|
|
18810
|
+
const stmts = [];
|
|
18811
|
+
let pos = 0;
|
|
18812
|
+
while (pos < tokens.length) {
|
|
18813
|
+
while (pos < tokens.length && tokens[pos].t === 0 /* Newline */) pos++;
|
|
18814
|
+
if (pos >= tokens.length || tokens[pos].t === 13 /* EOF */) break;
|
|
18815
|
+
const tok = tokens[pos];
|
|
18816
|
+
if (tok.t !== 16 /* Ident */ && tok.t !== 17 /* Type */ && tok.t !== 18 /* StateVar */) {
|
|
18817
|
+
while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
|
|
18818
|
+
continue;
|
|
18819
|
+
}
|
|
18820
|
+
const id = tok.v;
|
|
18821
|
+
const idTokenType = tok.t;
|
|
18822
|
+
pos++;
|
|
18823
|
+
if (pos >= tokens.length || tokens[pos].t !== 9 /* Equals */) {
|
|
18824
|
+
while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
|
|
18825
|
+
continue;
|
|
18826
|
+
}
|
|
18827
|
+
pos++;
|
|
18828
|
+
const expr = [];
|
|
18829
|
+
let depth = 0;
|
|
18830
|
+
let ternaryDepth = 0;
|
|
18831
|
+
while (pos < tokens.length && tokens[pos].t !== 13 /* EOF */) {
|
|
18832
|
+
const tt = tokens[pos].t;
|
|
18833
|
+
if (tt === 0 /* Newline */ && depth <= 0 && ternaryDepth <= 0) {
|
|
18834
|
+
let peek = pos + 1;
|
|
18835
|
+
while (peek < tokens.length && tokens[peek].t === 0 /* Newline */) peek++;
|
|
18836
|
+
const nextT = peek < tokens.length ? tokens[peek].t : 13 /* EOF */;
|
|
18837
|
+
if (nextT === 34 /* Question */ || nextT === 8 /* Colon */ && ternaryDepth > 0) {
|
|
18838
|
+
pos++;
|
|
18839
|
+
continue;
|
|
18840
|
+
}
|
|
18841
|
+
break;
|
|
18842
|
+
}
|
|
18843
|
+
if (tt === 0 /* Newline */) {
|
|
18844
|
+
pos++;
|
|
18845
|
+
continue;
|
|
18846
|
+
}
|
|
18847
|
+
if (tt === 1 /* LParen */ || tt === 3 /* LBrack */ || tt === 5 /* LBrace */) depth++;
|
|
18848
|
+
else if ((tt === 2 /* RParen */ || tt === 4 /* RBrack */ || tt === 6 /* RBrace */) && depth > 0) depth--;
|
|
18849
|
+
else if (tt === 34 /* Question */ && depth === 0) ternaryDepth++;
|
|
18850
|
+
else if (tt === 8 /* Colon */ && depth === 0 && ternaryDepth > 0) ternaryDepth--;
|
|
18851
|
+
expr.push(tokens[pos++]);
|
|
18852
|
+
}
|
|
18853
|
+
if (expr.length) stmts.push({ id, idTokenType, tokens: expr });
|
|
18854
|
+
}
|
|
18855
|
+
return stmts;
|
|
18856
|
+
}
|
|
18857
|
+
|
|
18858
|
+
// src/genui/lang/parser/parser.ts
|
|
18859
|
+
function emptyResult(incomplete = true) {
|
|
18860
|
+
return {
|
|
18861
|
+
root: null,
|
|
18862
|
+
meta: {
|
|
18863
|
+
incomplete,
|
|
18864
|
+
unresolved: [],
|
|
18865
|
+
orphaned: [],
|
|
18866
|
+
statementCount: 0,
|
|
18867
|
+
errors: []
|
|
18868
|
+
},
|
|
18869
|
+
stateDeclarations: {},
|
|
18870
|
+
queryStatements: [],
|
|
18871
|
+
mutationStatements: []
|
|
18872
|
+
};
|
|
18873
|
+
}
|
|
18874
|
+
function collectQueryDeps(node) {
|
|
18875
|
+
if (!isASTNode(node)) return [];
|
|
18876
|
+
const refs = /* @__PURE__ */ new Set();
|
|
18877
|
+
walkAST(node, (current) => {
|
|
18878
|
+
if (current.k === "StateRef") refs.add(current.n);
|
|
18879
|
+
});
|
|
18880
|
+
return [...refs];
|
|
18881
|
+
}
|
|
18882
|
+
function classifyStatement(raw, expr) {
|
|
18883
|
+
if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Query) {
|
|
18884
|
+
const deps = collectQueryDeps(expr.args[1]);
|
|
18885
|
+
return {
|
|
18886
|
+
kind: "query",
|
|
18887
|
+
id: raw.id,
|
|
18888
|
+
call: { callee: RESERVED_CALLS.Query, args: expr.args },
|
|
18889
|
+
expr,
|
|
18890
|
+
deps: deps.length > 0 ? deps : void 0
|
|
18891
|
+
};
|
|
18892
|
+
}
|
|
18893
|
+
if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Mutation) {
|
|
18894
|
+
return {
|
|
18895
|
+
kind: "mutation",
|
|
18896
|
+
id: raw.id,
|
|
18897
|
+
call: { callee: RESERVED_CALLS.Mutation, args: expr.args },
|
|
18898
|
+
expr
|
|
18899
|
+
};
|
|
18900
|
+
}
|
|
18901
|
+
if (raw.idTokenType === 18 /* StateVar */) {
|
|
18902
|
+
return { kind: "state", id: raw.id, init: expr };
|
|
18903
|
+
}
|
|
18904
|
+
return { kind: "value", id: raw.id, expr };
|
|
18905
|
+
}
|
|
18906
|
+
function extractStatements(stmts, ctx) {
|
|
18907
|
+
const stateDeclarations = {};
|
|
18908
|
+
const queryStatements = [];
|
|
18909
|
+
const mutationStatements = [];
|
|
18910
|
+
for (const stmt of stmts) {
|
|
18911
|
+
switch (stmt.kind) {
|
|
18912
|
+
case "state":
|
|
18913
|
+
stateDeclarations[stmt.id] = materializeValue(stmt.init, ctx);
|
|
18914
|
+
break;
|
|
18915
|
+
case "query":
|
|
18916
|
+
queryStatements.push({
|
|
18917
|
+
statementId: stmt.id,
|
|
18918
|
+
toolAST: stmt.call.args[0] ?? null,
|
|
18919
|
+
argsAST: stmt.call.args[1] ?? null,
|
|
18920
|
+
defaultsAST: stmt.call.args[2] ?? null,
|
|
18921
|
+
refreshAST: stmt.call.args[3] ?? null,
|
|
18922
|
+
deps: stmt.deps,
|
|
18923
|
+
complete: true
|
|
18924
|
+
});
|
|
18925
|
+
break;
|
|
18926
|
+
case "mutation":
|
|
18927
|
+
mutationStatements.push({
|
|
18928
|
+
statementId: stmt.id,
|
|
18929
|
+
toolAST: stmt.call.args[0] ?? null,
|
|
18930
|
+
argsAST: stmt.call.args[1] ?? null
|
|
18931
|
+
});
|
|
18932
|
+
break;
|
|
18933
|
+
}
|
|
18934
|
+
}
|
|
18935
|
+
for (const stmt of stmts) {
|
|
18936
|
+
const nodes = stmt.kind === "state" ? [stmt.init] : stmt.kind === "value" ? [stmt.expr] : stmt.kind === "query" || stmt.kind === "mutation" ? stmt.call.args : [];
|
|
18937
|
+
for (const node of nodes) {
|
|
18938
|
+
for (const dep of collectQueryDeps(node)) {
|
|
18939
|
+
if (!(dep in stateDeclarations)) {
|
|
18940
|
+
stateDeclarations[dep] = null;
|
|
18941
|
+
}
|
|
18942
|
+
}
|
|
18943
|
+
}
|
|
18944
|
+
}
|
|
18945
|
+
return { stateDeclarations, queryStatements, mutationStatements };
|
|
18946
|
+
}
|
|
18947
|
+
var DEFAULT_ROOT_STATEMENT_ID = "root";
|
|
18948
|
+
function isComponentStatement(stmt) {
|
|
18949
|
+
return stmt.kind === "value" && stmt.expr.k === "Comp" && !isBuiltin(stmt.expr.name) && stmt.expr.name !== RESERVED_CALLS.Query && stmt.expr.name !== RESERVED_CALLS.Mutation;
|
|
18950
|
+
}
|
|
18951
|
+
function pickEntryId(stmtMap, typedStmts, firstId, rootName) {
|
|
18952
|
+
if (stmtMap.has(DEFAULT_ROOT_STATEMENT_ID)) return DEFAULT_ROOT_STATEMENT_ID;
|
|
18953
|
+
if (rootName && stmtMap.has(rootName)) return rootName;
|
|
18954
|
+
const preferredComponent = rootName ? typedStmts.find((stmt) => isComponentStatement(stmt) && stmt.expr.name === rootName) : void 0;
|
|
18955
|
+
if (preferredComponent) return preferredComponent.id;
|
|
18956
|
+
const firstComponent = typedStmts.find(isComponentStatement);
|
|
18957
|
+
return firstComponent?.id ?? firstId;
|
|
18958
|
+
}
|
|
18959
|
+
function buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtCount, cat, rootName) {
|
|
18960
|
+
const entryId = pickEntryId(stmtMap, typedStmts, firstId, rootName);
|
|
18961
|
+
if (!stmtMap.has(entryId)) return emptyResult(wasIncomplete);
|
|
18962
|
+
const syms = /* @__PURE__ */ new Map();
|
|
18963
|
+
for (const [id, stmt] of stmtMap) {
|
|
18964
|
+
syms.set(id, stmt.kind === "state" ? stmt.init : stmt.expr);
|
|
18965
|
+
}
|
|
18966
|
+
const unres = [];
|
|
18967
|
+
const errors = [];
|
|
18968
|
+
const unreached = /* @__PURE__ */ new Set();
|
|
18969
|
+
for (const [id, stmt] of stmtMap) {
|
|
18970
|
+
if (id === entryId) continue;
|
|
18971
|
+
if (stmt.kind === "state" || stmt.kind === "query" || stmt.kind === "mutation") continue;
|
|
18972
|
+
unreached.add(id);
|
|
18973
|
+
}
|
|
18974
|
+
const ctx = {
|
|
18975
|
+
syms,
|
|
18976
|
+
cat,
|
|
18977
|
+
errors,
|
|
18978
|
+
unres,
|
|
18979
|
+
visited: /* @__PURE__ */ new Set(),
|
|
18980
|
+
partial: wasIncomplete,
|
|
18981
|
+
currentStatementId: entryId,
|
|
18982
|
+
unreached
|
|
18983
|
+
};
|
|
18984
|
+
const materialized = materializeValue(syms.get(entryId), ctx);
|
|
18985
|
+
const root = isElementNode(materialized) ? materialized : null;
|
|
18986
|
+
if (root) root.statementId = entryId;
|
|
18987
|
+
const { stateDeclarations, queryStatements, mutationStatements } = extractStatements(
|
|
18988
|
+
typedStmts,
|
|
18989
|
+
ctx
|
|
18990
|
+
);
|
|
18991
|
+
const orphaned = [...unreached];
|
|
18992
|
+
return {
|
|
18993
|
+
root,
|
|
18994
|
+
meta: {
|
|
18995
|
+
incomplete: wasIncomplete,
|
|
18996
|
+
unresolved: unres,
|
|
18997
|
+
orphaned,
|
|
18998
|
+
statementCount: stmtCount,
|
|
18999
|
+
errors
|
|
19000
|
+
},
|
|
19001
|
+
stateDeclarations,
|
|
19002
|
+
queryStatements,
|
|
19003
|
+
mutationStatements
|
|
19004
|
+
};
|
|
19005
|
+
}
|
|
19006
|
+
function skipString(input, start) {
|
|
19007
|
+
if (input[start] !== '"') return start;
|
|
19008
|
+
let i = start + 1;
|
|
19009
|
+
while (i < input.length) {
|
|
19010
|
+
const c = input[i];
|
|
19011
|
+
if (c === "\\") {
|
|
19012
|
+
i += 2;
|
|
19013
|
+
} else if (c === '"') {
|
|
19014
|
+
return i + 1;
|
|
19015
|
+
} else {
|
|
19016
|
+
i++;
|
|
19017
|
+
}
|
|
19018
|
+
}
|
|
19019
|
+
return i;
|
|
19020
|
+
}
|
|
19021
|
+
function stripFences(input) {
|
|
19022
|
+
const blocks = [];
|
|
19023
|
+
let i = 0;
|
|
19024
|
+
while (i < input.length) {
|
|
19025
|
+
let fenceStart = -1;
|
|
19026
|
+
while (i < input.length) {
|
|
19027
|
+
const nextI = skipString(input, i);
|
|
19028
|
+
if (nextI > i) {
|
|
19029
|
+
i = nextI;
|
|
19030
|
+
continue;
|
|
19031
|
+
}
|
|
19032
|
+
const c = input[i];
|
|
19033
|
+
if (c === "`" && i + 1 < input.length && input[i + 1] === "`" && i + 2 < input.length && input[i + 2] === "`") {
|
|
19034
|
+
fenceStart = i;
|
|
19035
|
+
break;
|
|
19036
|
+
}
|
|
19037
|
+
i++;
|
|
19038
|
+
}
|
|
19039
|
+
if (fenceStart === -1) break;
|
|
19040
|
+
let j = fenceStart + 3;
|
|
19041
|
+
while (j < input.length && input[j] !== "\n") j++;
|
|
19042
|
+
if (j >= input.length) {
|
|
19043
|
+
blocks.push(input.slice(fenceStart + 3).replace(/^[^\n]*\n?/, ""));
|
|
19044
|
+
i = input.length;
|
|
19045
|
+
break;
|
|
19046
|
+
}
|
|
19047
|
+
j++;
|
|
19048
|
+
let closePos = -1;
|
|
19049
|
+
let k = j;
|
|
19050
|
+
while (k < input.length) {
|
|
19051
|
+
const nextK = skipString(input, k);
|
|
19052
|
+
if (nextK > k) {
|
|
19053
|
+
k = nextK;
|
|
19054
|
+
continue;
|
|
19055
|
+
}
|
|
19056
|
+
const c = input[k];
|
|
19057
|
+
if (c === "`" && k + 1 < input.length && input[k + 1] === "`" && k + 2 < input.length && input[k + 2] === "`") {
|
|
19058
|
+
closePos = k;
|
|
19059
|
+
break;
|
|
19060
|
+
}
|
|
19061
|
+
k++;
|
|
19062
|
+
}
|
|
19063
|
+
if (closePos !== -1) {
|
|
19064
|
+
blocks.push(input.slice(j, closePos));
|
|
19065
|
+
i = closePos + 3;
|
|
19066
|
+
} else {
|
|
19067
|
+
blocks.push(input.slice(j));
|
|
19068
|
+
i = input.length;
|
|
19069
|
+
}
|
|
19070
|
+
}
|
|
19071
|
+
if (blocks.length > 0) return blocks.join("\n");
|
|
19072
|
+
if (input.startsWith("```")) {
|
|
19073
|
+
let j = 3;
|
|
19074
|
+
while (j < input.length && input[j] !== "\n") j++;
|
|
19075
|
+
const start = j < input.length ? j + 1 : 3;
|
|
19076
|
+
const body = input.slice(start);
|
|
19077
|
+
const trailingFence = body.lastIndexOf("```");
|
|
19078
|
+
if (trailingFence !== -1) {
|
|
19079
|
+
return body.slice(0, trailingFence);
|
|
19080
|
+
}
|
|
19081
|
+
return body;
|
|
19082
|
+
}
|
|
19083
|
+
return input;
|
|
19084
|
+
}
|
|
19085
|
+
function stripComments(input) {
|
|
19086
|
+
let inStr = false;
|
|
19087
|
+
return input.split("\n").map((line) => {
|
|
19088
|
+
for (let i = 0; i < line.length; i++) {
|
|
19089
|
+
const c = line[i];
|
|
19090
|
+
if (inStr) {
|
|
19091
|
+
if (c === "\\" && i + 1 < line.length) {
|
|
19092
|
+
i++;
|
|
19093
|
+
continue;
|
|
19094
|
+
}
|
|
19095
|
+
if (c === inStr) inStr = false;
|
|
19096
|
+
continue;
|
|
19097
|
+
}
|
|
19098
|
+
if (c === '"' || c === "'") {
|
|
19099
|
+
inStr = c;
|
|
19100
|
+
continue;
|
|
19101
|
+
}
|
|
19102
|
+
if (c === "/" && line[i + 1] === "/") {
|
|
19103
|
+
return line.substring(0, i).trimEnd();
|
|
19104
|
+
}
|
|
19105
|
+
if (c === "#") {
|
|
19106
|
+
return line.substring(0, i).trimEnd();
|
|
19107
|
+
}
|
|
19108
|
+
}
|
|
19109
|
+
return line;
|
|
19110
|
+
}).join("\n");
|
|
19111
|
+
}
|
|
19112
|
+
function preprocess(input) {
|
|
19113
|
+
return stripComments(stripFences(input.trim())).trim();
|
|
19114
|
+
}
|
|
19115
|
+
function parse(input, cat, rootName) {
|
|
19116
|
+
const trimmed = preprocess(input);
|
|
19117
|
+
if (!trimmed) return emptyResult();
|
|
19118
|
+
const { text, wasIncomplete } = autoClose(trimmed);
|
|
19119
|
+
const stmts = split(tokenize(text));
|
|
19120
|
+
if (!stmts.length) return emptyResult(wasIncomplete);
|
|
19121
|
+
const stmtMap = /* @__PURE__ */ new Map();
|
|
19122
|
+
let firstId = "";
|
|
19123
|
+
for (const s of stmts) {
|
|
19124
|
+
const expr = parseExpression(s.tokens);
|
|
19125
|
+
const stmt = classifyStatement(s, expr);
|
|
19126
|
+
stmtMap.set(s.id, stmt);
|
|
19127
|
+
if (!firstId) firstId = s.id;
|
|
19128
|
+
}
|
|
19129
|
+
const typedStmts = [...stmtMap.values()];
|
|
19130
|
+
return buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtMap.size, cat, rootName);
|
|
19131
|
+
}
|
|
19132
|
+
function createStreamParser(cat, rootName) {
|
|
19133
|
+
let buf = "";
|
|
19134
|
+
let cleaned = "";
|
|
19135
|
+
let completedEnd = 0;
|
|
19136
|
+
const completedStmtMap = /* @__PURE__ */ new Map();
|
|
19137
|
+
let firstId = "";
|
|
19138
|
+
function addStmt(text) {
|
|
19139
|
+
const t = text.trim();
|
|
19140
|
+
if (!t) return;
|
|
19141
|
+
for (const s of split(tokenize(t))) {
|
|
19142
|
+
const expr = parseExpression(s.tokens);
|
|
19143
|
+
const stmt = classifyStatement(s, expr);
|
|
19144
|
+
completedStmtMap.set(s.id, stmt);
|
|
19145
|
+
if (!firstId) firstId = s.id;
|
|
19146
|
+
}
|
|
19147
|
+
}
|
|
19148
|
+
function refreshCleaned() {
|
|
19149
|
+
const next = preprocess(buf);
|
|
19150
|
+
if (!next.startsWith(cleaned.slice(0, completedEnd))) {
|
|
19151
|
+
completedEnd = 0;
|
|
19152
|
+
completedStmtMap.clear();
|
|
19153
|
+
firstId = "";
|
|
19154
|
+
}
|
|
19155
|
+
cleaned = next;
|
|
19156
|
+
}
|
|
19157
|
+
function scanNewCompleted() {
|
|
19158
|
+
let depth = 0, ternaryDepth = 0, inStr = false, esc = false;
|
|
19159
|
+
let stmtStart = completedEnd;
|
|
19160
|
+
for (let i = completedEnd; i < cleaned.length; i++) {
|
|
19161
|
+
const c = cleaned[i];
|
|
19162
|
+
if (esc) {
|
|
19163
|
+
esc = false;
|
|
19164
|
+
continue;
|
|
19165
|
+
}
|
|
19166
|
+
if (c === "\\" && inStr) {
|
|
19167
|
+
esc = true;
|
|
19168
|
+
continue;
|
|
19169
|
+
}
|
|
19170
|
+
if (inStr) {
|
|
19171
|
+
if (c === inStr) inStr = false;
|
|
19172
|
+
continue;
|
|
19173
|
+
}
|
|
19174
|
+
if (c === '"' || c === "'") {
|
|
19175
|
+
inStr = c;
|
|
19176
|
+
continue;
|
|
19177
|
+
}
|
|
19178
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
19179
|
+
else if (c === ")" || c === "]" || c === "}") depth = Math.max(0, depth - 1);
|
|
19180
|
+
else if (c === "?" && depth === 0) ternaryDepth++;
|
|
19181
|
+
else if (c === ":" && depth === 0 && ternaryDepth > 0) ternaryDepth--;
|
|
19182
|
+
else if (c === "\n" && depth <= 0 && ternaryDepth <= 0) {
|
|
19183
|
+
let peek = i + 1;
|
|
19184
|
+
while (peek < cleaned.length && (cleaned[peek] === " " || cleaned[peek] === " " || cleaned[peek] === "\r" || cleaned[peek] === "\n"))
|
|
19185
|
+
peek++;
|
|
19186
|
+
if (peek < cleaned.length && (cleaned[peek] === "?" || cleaned[peek] === ":" && ternaryDepth > 0)) {
|
|
19187
|
+
continue;
|
|
19188
|
+
}
|
|
19189
|
+
const t = cleaned.slice(stmtStart, i).trim();
|
|
19190
|
+
if (t) addStmt(t);
|
|
19191
|
+
stmtStart = i + 1;
|
|
19192
|
+
completedEnd = i + 1;
|
|
19193
|
+
}
|
|
19194
|
+
}
|
|
19195
|
+
return stmtStart;
|
|
19196
|
+
}
|
|
19197
|
+
function currentResult() {
|
|
19198
|
+
refreshCleaned();
|
|
19199
|
+
const pendingStart = scanNewCompleted();
|
|
19200
|
+
const pendingText = cleaned.slice(pendingStart).trim();
|
|
19201
|
+
if (!pendingText) {
|
|
19202
|
+
if (completedStmtMap.size === 0) return emptyResult();
|
|
19203
|
+
return buildResult(
|
|
19204
|
+
completedStmtMap,
|
|
19205
|
+
[...completedStmtMap.values()],
|
|
19206
|
+
firstId,
|
|
19207
|
+
false,
|
|
19208
|
+
completedStmtMap.size,
|
|
19209
|
+
cat,
|
|
19210
|
+
rootName
|
|
19211
|
+
);
|
|
19212
|
+
}
|
|
19213
|
+
const { text: closed, wasIncomplete } = autoClose(pendingText);
|
|
19214
|
+
const stmts = split(tokenize(closed));
|
|
19215
|
+
if (!stmts.length) {
|
|
19216
|
+
if (completedStmtMap.size === 0) return emptyResult(wasIncomplete);
|
|
19217
|
+
return buildResult(
|
|
19218
|
+
completedStmtMap,
|
|
19219
|
+
[...completedStmtMap.values()],
|
|
19220
|
+
firstId,
|
|
19221
|
+
wasIncomplete,
|
|
19222
|
+
completedStmtMap.size,
|
|
19223
|
+
cat,
|
|
19224
|
+
rootName
|
|
19225
|
+
);
|
|
19226
|
+
}
|
|
19227
|
+
const allStmtMap = new Map(completedStmtMap);
|
|
19228
|
+
for (const s of stmts) {
|
|
19229
|
+
const expr = parseExpression(s.tokens);
|
|
19230
|
+
const stmt = classifyStatement(s, expr);
|
|
19231
|
+
allStmtMap.set(s.id, stmt);
|
|
19232
|
+
}
|
|
19233
|
+
const allTypedStmts = [...allStmtMap.values()];
|
|
19234
|
+
const fid = firstId || stmts[0].id;
|
|
19235
|
+
return buildResult(
|
|
19236
|
+
allStmtMap,
|
|
19237
|
+
allTypedStmts,
|
|
19238
|
+
fid,
|
|
19239
|
+
wasIncomplete,
|
|
19240
|
+
allStmtMap.size,
|
|
19241
|
+
cat,
|
|
19242
|
+
rootName
|
|
19243
|
+
);
|
|
19244
|
+
}
|
|
19245
|
+
function reset() {
|
|
19246
|
+
buf = "";
|
|
19247
|
+
cleaned = "";
|
|
19248
|
+
completedEnd = 0;
|
|
19249
|
+
completedStmtMap.clear();
|
|
19250
|
+
firstId = "";
|
|
19251
|
+
}
|
|
19252
|
+
return {
|
|
19253
|
+
push(chunk) {
|
|
19254
|
+
buf += chunk;
|
|
19255
|
+
return currentResult();
|
|
19256
|
+
},
|
|
19257
|
+
set(fullText) {
|
|
19258
|
+
if (fullText.length < buf.length || !fullText.startsWith(buf)) {
|
|
19259
|
+
reset();
|
|
19260
|
+
}
|
|
19261
|
+
const delta = fullText.slice(buf.length);
|
|
19262
|
+
if (delta) buf += delta;
|
|
19263
|
+
return currentResult();
|
|
19264
|
+
},
|
|
19265
|
+
getResult: currentResult
|
|
19266
|
+
};
|
|
19267
|
+
}
|
|
19268
|
+
function useGenUIProgram(text, catalog) {
|
|
19269
|
+
const parserRef = useRef(
|
|
19270
|
+
null
|
|
19271
|
+
);
|
|
19272
|
+
if (!parserRef.current || parserRef.current.catalog !== catalog) {
|
|
19273
|
+
parserRef.current = { catalog, parser: createStreamParser(catalog, "root") };
|
|
19274
|
+
}
|
|
19275
|
+
const lastGoodRef = useRef(null);
|
|
19276
|
+
return useMemo(() => {
|
|
19277
|
+
const result = parserRef.current.parser.set(text);
|
|
19278
|
+
if (result.root) {
|
|
19279
|
+
lastGoodRef.current = result.root;
|
|
19280
|
+
}
|
|
19281
|
+
return { result, root: result.root ?? lastGoodRef.current };
|
|
19282
|
+
}, [text, catalog]);
|
|
19283
|
+
}
|
|
19284
|
+
var noop = () => {
|
|
19285
|
+
};
|
|
19286
|
+
function childKey(value, index) {
|
|
19287
|
+
if (isElementNode(value) && value.statementId) return `s:${value.statementId}`;
|
|
19288
|
+
return `i:${index}`;
|
|
19289
|
+
}
|
|
19290
|
+
var RenderDepthContext = createContext(0);
|
|
19291
|
+
var MAX_DEPTH = 64;
|
|
19292
|
+
var GenUINode = memo(function GenUINode2({
|
|
19293
|
+
node,
|
|
19294
|
+
components,
|
|
19295
|
+
onAction,
|
|
19296
|
+
isStreaming,
|
|
19297
|
+
onTelemetry
|
|
19298
|
+
}) {
|
|
19299
|
+
const depth = useContext(RenderDepthContext);
|
|
19300
|
+
if (depth > MAX_DEPTH) {
|
|
19301
|
+
onTelemetry?.({ kind: "dropped-node", typeName: node.typeName });
|
|
19302
|
+
return null;
|
|
19303
|
+
}
|
|
19304
|
+
const renderer = components[node.typeName];
|
|
19305
|
+
if (!renderer) {
|
|
19306
|
+
onTelemetry?.({ kind: "unknown-component", typeName: node.typeName });
|
|
19307
|
+
return null;
|
|
19308
|
+
}
|
|
19309
|
+
const render = (value) => {
|
|
19310
|
+
if (value == null) return null;
|
|
19311
|
+
if (isElementNode(value)) {
|
|
19312
|
+
return /* @__PURE__ */ jsx(RenderDepthContext.Provider, { value: depth + 1, children: /* @__PURE__ */ jsx(
|
|
19313
|
+
GenUINode2,
|
|
19314
|
+
{
|
|
19315
|
+
node: value,
|
|
19316
|
+
components,
|
|
19317
|
+
onAction,
|
|
19318
|
+
isStreaming,
|
|
19319
|
+
onTelemetry
|
|
19320
|
+
}
|
|
19321
|
+
) });
|
|
19322
|
+
}
|
|
19323
|
+
if (Array.isArray(value)) {
|
|
19324
|
+
return value.map((item, index) => /* @__PURE__ */ jsx(React14.Fragment, { children: render(item) }, childKey(item, index)));
|
|
19325
|
+
}
|
|
19326
|
+
if (typeof value === "object" && isRuntimeExpr(value)) return null;
|
|
19327
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
19328
|
+
return null;
|
|
19329
|
+
};
|
|
19330
|
+
return renderer({ node, ctx: { render, onAction, isStreaming } });
|
|
19331
|
+
});
|
|
19332
|
+
function GenUIView({
|
|
19333
|
+
root,
|
|
19334
|
+
components,
|
|
19335
|
+
onAction = noop,
|
|
19336
|
+
isStreaming = false,
|
|
19337
|
+
onTelemetry,
|
|
19338
|
+
placeholder = null
|
|
19339
|
+
}) {
|
|
19340
|
+
if (!root) return placeholder;
|
|
19341
|
+
return /* @__PURE__ */ jsx(
|
|
19342
|
+
GenUINode,
|
|
19343
|
+
{
|
|
19344
|
+
node: root,
|
|
19345
|
+
components,
|
|
19346
|
+
onAction,
|
|
19347
|
+
isStreaming,
|
|
19348
|
+
onTelemetry
|
|
19349
|
+
}
|
|
19350
|
+
);
|
|
19351
|
+
}
|
|
19352
|
+
|
|
19353
|
+
// src/genui/registry.ts
|
|
19354
|
+
var CIMPLIFY_GENUI_REGISTRY = {
|
|
19355
|
+
version: "3",
|
|
19356
|
+
components: [
|
|
19357
|
+
{
|
|
19358
|
+
name: "Stack",
|
|
19359
|
+
description: 'Layout container. direction: "column" (default) or "row"; gap in points (default 12)',
|
|
19360
|
+
params: [{ name: "children", required: true }, { name: "direction" }, { name: "gap" }]
|
|
19361
|
+
},
|
|
19362
|
+
{
|
|
19363
|
+
name: "Card",
|
|
19364
|
+
description: "Elevated surface grouping related content, with an optional title",
|
|
19365
|
+
params: [{ name: "children", required: true }, { name: "title" }]
|
|
19366
|
+
},
|
|
19367
|
+
{
|
|
19368
|
+
name: "TextContent",
|
|
19369
|
+
description: 'Text block. size: "display", "heading", "title", "body" (default), "label", "caption"',
|
|
19370
|
+
params: [{ name: "text", required: true }, { name: "size" }]
|
|
19371
|
+
},
|
|
19372
|
+
{
|
|
19373
|
+
name: "Stat",
|
|
19374
|
+
description: "Compact stat: small label over a bold value, optional sub line and tone",
|
|
19375
|
+
params: [
|
|
19376
|
+
{ name: "label", required: true },
|
|
19377
|
+
{ name: "value", required: true },
|
|
19378
|
+
{ name: "sub" },
|
|
19379
|
+
{ name: "tone" }
|
|
19380
|
+
]
|
|
19381
|
+
},
|
|
19382
|
+
{
|
|
19383
|
+
name: "Metric",
|
|
19384
|
+
description: "KPI card with label, value, optional percent change and SF Symbol icon",
|
|
19385
|
+
params: [
|
|
19386
|
+
{ name: "label", required: true },
|
|
19387
|
+
{ name: "value", required: true },
|
|
19388
|
+
{ name: "change" },
|
|
19389
|
+
{ name: "icon" }
|
|
19390
|
+
]
|
|
19391
|
+
},
|
|
19392
|
+
{
|
|
19393
|
+
name: "KeyValue",
|
|
19394
|
+
description: "Label/value row; set mono true for numeric values",
|
|
19395
|
+
params: [
|
|
19396
|
+
{ name: "label", required: true },
|
|
19397
|
+
{ name: "value", required: true },
|
|
19398
|
+
{ name: "mono" }
|
|
19399
|
+
]
|
|
19400
|
+
},
|
|
19401
|
+
{
|
|
19402
|
+
name: "Badge",
|
|
19403
|
+
description: "Small status chip. tone: neutral, positive, info, indigo, violet, warning, danger",
|
|
19404
|
+
params: [{ name: "label", required: true }, { name: "tone" }]
|
|
19405
|
+
},
|
|
19406
|
+
{
|
|
19407
|
+
name: "Callout",
|
|
19408
|
+
description: "Inline alert with title, optional message. tone: info (default), success, warning, danger, accent, neutral",
|
|
19409
|
+
params: [{ name: "title", required: true }, { name: "message" }, { name: "tone" }]
|
|
19410
|
+
},
|
|
19411
|
+
{
|
|
19412
|
+
name: "Button",
|
|
19413
|
+
description: 'Action button. action: "action:name" string or Action([...]) steps. variant: primary, cta, secondary (default), ghost, destructive',
|
|
19414
|
+
params: [
|
|
19415
|
+
{ name: "label", required: true },
|
|
19416
|
+
{ name: "action", required: true },
|
|
19417
|
+
{ name: "variant" }
|
|
19418
|
+
]
|
|
19419
|
+
},
|
|
19420
|
+
{
|
|
19421
|
+
name: "Buttons",
|
|
19422
|
+
description: 'Row (default) or column of Buttons; direction: "row" or "column"',
|
|
19423
|
+
params: [{ name: "children", required: true }, { name: "direction" }]
|
|
19424
|
+
},
|
|
19425
|
+
{
|
|
19426
|
+
name: "Divider",
|
|
19427
|
+
description: "Hairline separator",
|
|
19428
|
+
params: []
|
|
19429
|
+
},
|
|
19430
|
+
{
|
|
19431
|
+
name: "Table",
|
|
19432
|
+
description: "Data table: headers is an array of strings, rows an array of cell arrays",
|
|
19433
|
+
params: [
|
|
19434
|
+
{ name: "headers", required: true },
|
|
19435
|
+
{ name: "rows", required: true }
|
|
19436
|
+
]
|
|
19437
|
+
},
|
|
19438
|
+
{
|
|
19439
|
+
name: "BarChart",
|
|
19440
|
+
description: "Bar chart: labels (x axis strings) and values (numbers), optional title",
|
|
19441
|
+
params: [
|
|
19442
|
+
{ name: "labels", required: true },
|
|
19443
|
+
{ name: "values", required: true },
|
|
19444
|
+
{ name: "title" }
|
|
19445
|
+
]
|
|
19446
|
+
},
|
|
19447
|
+
{
|
|
19448
|
+
name: "LineChart",
|
|
19449
|
+
description: "Line chart with area fill: labels and values, optional title",
|
|
19450
|
+
params: [
|
|
19451
|
+
{ name: "labels", required: true },
|
|
19452
|
+
{ name: "values", required: true },
|
|
19453
|
+
{ name: "title" }
|
|
19454
|
+
]
|
|
19455
|
+
},
|
|
19456
|
+
{
|
|
19457
|
+
name: "Money",
|
|
19458
|
+
description: "Formatted money amount, never abbreviated. currency: ISO code (default USD)",
|
|
19459
|
+
params: [{ name: "amount", required: true }, { name: "currency" }]
|
|
19460
|
+
},
|
|
19461
|
+
{
|
|
19462
|
+
name: "Image",
|
|
19463
|
+
description: "Remote image by URL with optional caption",
|
|
19464
|
+
params: [{ name: "url", required: true }, { name: "caption" }]
|
|
19465
|
+
},
|
|
19466
|
+
{
|
|
19467
|
+
name: "ListRow",
|
|
19468
|
+
description: "Entity row for orders/customers/products/invoices: initials avatar (or SF Symbol tile via symbol), title + subtitle, trailing value and badge with tone",
|
|
19469
|
+
params: [
|
|
19470
|
+
{ name: "title", required: true },
|
|
19471
|
+
{ name: "subtitle" },
|
|
19472
|
+
{ name: "value" },
|
|
19473
|
+
{ name: "badge" },
|
|
19474
|
+
{ name: "tone" },
|
|
19475
|
+
{ name: "symbol" }
|
|
19476
|
+
]
|
|
19477
|
+
},
|
|
19478
|
+
{
|
|
19479
|
+
name: "Timeline",
|
|
19480
|
+
description: "Event history (order journey, payment trail): events is an array of {title, detail?, time?, tone?} objects, newest first",
|
|
19481
|
+
params: [{ name: "events", required: true }]
|
|
19482
|
+
},
|
|
19483
|
+
{
|
|
19484
|
+
name: "Progress",
|
|
19485
|
+
description: "Amount toward a bound (goal, stock cover, capacity): numeric value and target (default 100), optional detail caption and tone",
|
|
19486
|
+
params: [
|
|
19487
|
+
{ name: "label", required: true },
|
|
19488
|
+
{ name: "value", required: true },
|
|
19489
|
+
{ name: "target" },
|
|
19490
|
+
{ name: "detail" },
|
|
19491
|
+
{ name: "tone" }
|
|
19492
|
+
]
|
|
19493
|
+
},
|
|
19494
|
+
{
|
|
19495
|
+
name: "DonutChart",
|
|
19496
|
+
description: "Share of a whole (sales by channel, revenue by category): labels + values, optional title and center display string (e.g. the formatted total)",
|
|
19497
|
+
params: [
|
|
19498
|
+
{ name: "labels", required: true },
|
|
19499
|
+
{ name: "values", required: true },
|
|
19500
|
+
{ name: "title" },
|
|
19501
|
+
{ name: "center" }
|
|
19502
|
+
]
|
|
19503
|
+
},
|
|
19504
|
+
{
|
|
19505
|
+
name: "StatusTrack",
|
|
19506
|
+
description: "Stage tracker (order fulfilment, invoice lifecycle): steps are stage names, current is the active step name or index",
|
|
19507
|
+
params: [
|
|
19508
|
+
{ name: "steps", required: true },
|
|
19509
|
+
{ name: "current", required: true }
|
|
19510
|
+
]
|
|
19511
|
+
},
|
|
19512
|
+
{
|
|
19513
|
+
name: "LineItems",
|
|
19514
|
+
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",
|
|
19515
|
+
params: [
|
|
19516
|
+
{ name: "items", required: true },
|
|
19517
|
+
{ name: "total", required: true },
|
|
19518
|
+
{ name: "adjustments" }
|
|
19519
|
+
]
|
|
19520
|
+
},
|
|
19521
|
+
{
|
|
19522
|
+
name: "Grid",
|
|
19523
|
+
description: "Tile layout for Stat/Metric children, columns default 2",
|
|
19524
|
+
params: [{ name: "children", required: true }, { name: "columns" }]
|
|
19525
|
+
},
|
|
19526
|
+
{
|
|
19527
|
+
name: "Banner",
|
|
19528
|
+
description: "Hero-stat moment on a tonal wash: big value, label, optional percent delta. tone: positive (default), warning, danger, info, neutral",
|
|
19529
|
+
params: [
|
|
19530
|
+
{ name: "value", required: true },
|
|
19531
|
+
{ name: "label", required: true },
|
|
19532
|
+
{ name: "delta" },
|
|
19533
|
+
{ name: "tone" }
|
|
19534
|
+
]
|
|
19535
|
+
},
|
|
19536
|
+
{
|
|
19537
|
+
name: "EmptyState",
|
|
19538
|
+
description: "Nothing-to-show state with an SF Symbol and optional message",
|
|
19539
|
+
params: [{ name: "title", required: true }, { name: "message" }, { name: "symbol" }]
|
|
19540
|
+
}
|
|
19541
|
+
]
|
|
19542
|
+
};
|
|
19543
|
+
|
|
19544
|
+
// src/genui/manifest.ts
|
|
19545
|
+
function genUIManifest(registry) {
|
|
19546
|
+
return {
|
|
19547
|
+
version: registry.version,
|
|
19548
|
+
components: registry.components.map((component) => ({
|
|
19549
|
+
name: component.name,
|
|
19550
|
+
description: component.description,
|
|
19551
|
+
params: component.params.map((param) => ({
|
|
19552
|
+
name: param.name,
|
|
19553
|
+
required: param.required ?? false
|
|
19554
|
+
}))
|
|
19555
|
+
}))
|
|
19556
|
+
};
|
|
19557
|
+
}
|
|
19558
|
+
function encodeString(value) {
|
|
19559
|
+
return JSON.stringify(value).replace(/\//g, "\\/");
|
|
19560
|
+
}
|
|
19561
|
+
function canonicalize(value) {
|
|
19562
|
+
if (value === null) return "null";
|
|
19563
|
+
if (typeof value === "string") return encodeString(value);
|
|
19564
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
19565
|
+
if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
|
|
19566
|
+
const keys = Object.keys(value).sort();
|
|
19567
|
+
const entries = keys.map((key) => `${encodeString(key)}:${canonicalize(value[key])}`);
|
|
19568
|
+
return `{${entries.join(",")}}`;
|
|
19569
|
+
}
|
|
19570
|
+
function genUIManifestJson(manifest) {
|
|
19571
|
+
return canonicalize(manifest);
|
|
19572
|
+
}
|
|
19573
|
+
async function genUIFingerprint(manifest) {
|
|
19574
|
+
const bytes = new TextEncoder().encode(genUIManifestJson(manifest));
|
|
19575
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
19576
|
+
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 16);
|
|
19577
|
+
}
|
|
19578
|
+
|
|
19579
|
+
export { AccommodationCard, AccountContent, AccountDashboardPage, AccountHero, AccountIdentityStrip, AccountLineItem, AccountLineItems, AccountNav, AccountOrderDetailPage, AccountOrderView, AccountOrdersPage, AccountShell, AccountSidebar, AccountSignedOutPrompt, AccountSubscriptionDetailPage, AccountSubscriptionsPage, Ad, AdProvider, AddOnSelector, AddressAutocomplete, AddressElement, AuthElement, AvailabilityBadge, BackLink, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingSummary, BookingsPage, BoxIcon24, 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, createPlacesSessionToken, defaultBoundaryKind, genUICatalog, genUIFingerprint, genUIManifest, genUIManifestJson, getBoundaryLabels, isImageAttachment, 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 };
|