@helpai/elements 0.38.0 → 0.40.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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, H as HandshakeResponse, L as Link, S as ServerConfig, b as SiteConfig, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial } from './deployment-CNCSLVdz.js';
1
+ export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, H as HandshakeResponse, L as Link, S as ServerConfig, b as SiteConfig, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial } from './deployment-MbF78Pdq.js';
2
2
  import 'zod';
3
3
 
4
4
  /**
@@ -40,7 +40,7 @@ interface ClientStorage {
40
40
  * populated {@link Strings} map. UI components then read `strings.send` as a
41
41
  * normal property access — no per-render lookup, no per-render allocation.
42
42
  */
43
- type StringKey = "launcherOpen" | "panelTitle" | "composerPlaceholder" | "send" | "stop" | "attach" | "micStart" | "micStop" | "micUnsupported" | "expand" | "collapse" | "fullscreen" | "exitFullscreen" | "resizeHandle" | "scrollToBottom" | "popOut" | "close" | "moreActions" | "soundOn" | "soundOff" | "language" | "theme" | "themeAuto" | "themeLight" | "themeDark" | "textSize" | "textSizeSmall" | "textSizeNormal" | "textSizeLarge" | "history" | "historyTitle" | "historyEmpty" | "historyLoading" | "historyBack" | "historyContinue" | "conversationLoading" | "conversationClosed" | "startNewConversation" | "dateToday" | "dateYesterday" | "dateLastWeek" | "dateOlder" | "newConversation" | "dropZone" | "attachmentTooLarge" | "attachmentTooMany" | "attachmentMimeRejected" | "errorRetry" | "errorGeneric" | "errorRateLimited" | "loading" | "thinking" | "thoughts" | "usedTool" | "collapseSidebar" | "expandSidebar" | "formSubmit" | "formSkip" | "formSubmitted" | "formSkipped" | "formFillOut" | "formRequired" | "formInvalidEmail" | "formInvalidTel" | "formInvalidUrl" | "formInvalidNumber" | "formTooShort" | "formTooLong" | "formNumberTooSmall" | "formNumberTooLarge" | "formPatternMismatch" | "formChooseAtLeast" | "formChooseAtMost" | "formOther" | "formOtherPlaceholder" | "inputRequired" | "inputSubmit" | "inputSkip" | "inputSubmitted" | "approvalRequired" | "approve" | "reject" | "approved" | "rejected" | "approvalReason" | "stepNoLongerActive" | "tabHome" | "tabConversations" | "tabHelp" | "tabNews" | "modulesEmpty" | "moduleBack" | "contentLoading" | "homeGreeting" | "homeGreetingNamed" | "homeGreetingLead" | "homeSearchPlaceholder" | "homeContentTitle" | "homeStatus" | "helpTitle" | "helpSearchPlaceholder" | "helpEmpty" | "helpLoading" | "helpSearchEmpty" | "newsTitle" | "newsEmpty" | "newsLoading" | "newsBack" | "newsPublishedAt";
43
+ type StringKey = "launcherOpen" | "launcherLabel" | "panelTitle" | "composerPlaceholder" | "send" | "stop" | "attach" | "micStart" | "micStop" | "micUnsupported" | "expand" | "collapse" | "fullscreen" | "exitFullscreen" | "resizeHandle" | "scrollToBottom" | "popOut" | "close" | "moreActions" | "soundOn" | "soundOff" | "language" | "theme" | "themeAuto" | "themeLight" | "themeDark" | "textSize" | "textSizeSmall" | "textSizeNormal" | "textSizeLarge" | "history" | "historyTitle" | "historyEmpty" | "historyLoading" | "historyBack" | "historyContinue" | "conversationLoading" | "conversationClosed" | "startNewConversation" | "dateToday" | "dateYesterday" | "dateLastWeek" | "dateOlder" | "newConversation" | "dropZone" | "attachmentTooLarge" | "attachmentTooMany" | "attachmentMimeRejected" | "errorRetry" | "errorGeneric" | "errorRateLimited" | "loading" | "thinking" | "thoughts" | "usedTool" | "collapseSidebar" | "expandSidebar" | "formSubmit" | "formSkip" | "formSubmitted" | "formSkipped" | "formFillOut" | "formRequired" | "formInvalidEmail" | "formInvalidTel" | "formInvalidUrl" | "formInvalidNumber" | "formTooShort" | "formTooLong" | "formNumberTooSmall" | "formNumberTooLarge" | "formPatternMismatch" | "formChooseAtLeast" | "formChooseAtMost" | "formOther" | "formOtherPlaceholder" | "inputRequired" | "inputSubmit" | "inputSkip" | "inputSubmitted" | "approvalRequired" | "approve" | "reject" | "approved" | "rejected" | "approvalReason" | "stepNoLongerActive" | "tabHome" | "tabConversations" | "tabHelp" | "tabNews" | "modulesEmpty" | "moduleBack" | "contentLoading" | "homeGreeting" | "homeGreetingNamed" | "homeGreetingLead" | "homeSearchPlaceholder" | "homeContentTitle" | "homeStatus" | "helpTitle" | "helpSearchPlaceholder" | "helpEmpty" | "helpLoading" | "helpSearchEmpty" | "newsTitle" | "newsEmpty" | "newsLoading" | "newsBack" | "newsPublishedAt";
44
44
  /** A partial map for one locale — what overrides look like on the wire. */
45
45
  type LocaleStrings = Partial<Record<StringKey, string>>;
46
46
  /**
@@ -89,6 +89,11 @@ type StringsOverride = LocaleStrings | Partial<Record<string, LocaleStrings>>;
89
89
  * `visitorId` is the deliberate exception: it stays global so the same browser
90
90
  * is one anonymous visitor everywhere (`${brand.cssPrefix}.visitorId`).
91
91
  *
92
+ * A `storageVersion` (when set) folds a `.v<version>` segment into EVERY key —
93
+ * including the global `visitorId` — so bumping it force-resets the entire
94
+ * client-side namespace for all users (a full fresh start: new visitor id,
95
+ * conversation, prefs, panel state). Unset → the unversioned keys.
96
+ *
92
97
  * Storage is pluggable via {@link ClientStorage} — pass a custom adapter
93
98
  * to add encryption, logging, or use a non-localStorage backend.
94
99
  */
@@ -1248,6 +1253,14 @@ interface ResolvedOptions {
1248
1253
  interface ServerConfig {
1249
1254
  /** Data-API base override — adopted only when the host didn't set one. */
1250
1255
  dataApiBaseUrl?: string;
1256
+ /**
1257
+ * Storage-schema version — the deployment-owned force-reset lever, pushed ONLY
1258
+ * via the handshake (deliberately not an embed attribute / `init` option).
1259
+ * Pushing a new value abandons every client's prior storage namespace
1260
+ * (visitor id, conversation, prefs, panel state, caches) on next load. The
1261
+ * widget stashes it and the next mount folds `.v<version>` into every key.
1262
+ */
1263
+ storageVersion?: string;
1251
1264
  presentation?: PresentationOptions;
1252
1265
  behavior?: BehaviorOptions;
1253
1266
  theme?: ThemePreference | ThemeOverrides;
package/index.mjs CHANGED
@@ -62,6 +62,7 @@ var STRINGS_EN = {
62
62
  historyTitle: "Conversation history",
63
63
  language: "Language",
64
64
  launcherOpen: "Open chat",
65
+ launcherLabel: "Chat with us",
65
66
  micStart: "Start voice input",
66
67
  micStop: "Stop voice input",
67
68
  micUnsupported: "Voice input is not supported in this browser",
@@ -175,6 +176,7 @@ var STRINGS_FR = {
175
176
  historyTitle: "Historique des conversations",
176
177
  language: "Langue",
177
178
  launcherOpen: "Ouvrir le chat",
179
+ launcherLabel: "Discuter avec nous",
178
180
  micStart: "D\xE9marrer la saisie vocale",
179
181
  micStop: "Arr\xEAter la saisie vocale",
180
182
  micUnsupported: "La saisie vocale n'est pas prise en charge par ce navigateur",
@@ -1047,8 +1049,11 @@ function fillRandom(view) {
1047
1049
  var log2 = logger.scope("persistence");
1048
1050
  function createPersistence(widgetId, storage = defaultStorage, deploymentId) {
1049
1051
  const scope = deploymentId ? `${widgetId}.${deploymentId}` : widgetId;
1050
- const prefix = `${BRAND.cssPrefix}.${scope}`;
1051
- const KEY_VISITOR = `${BRAND.cssPrefix}.visitorId`;
1052
+ const KEY_STORAGE_VERSION = `${BRAND.cssPrefix}.storageVersion`;
1053
+ const activeVersion = storage.get(KEY_STORAGE_VERSION) ?? void 0;
1054
+ const version2 = activeVersion ? `.v${activeVersion}` : "";
1055
+ const prefix = `${BRAND.cssPrefix}.${scope}${version2}`;
1056
+ const KEY_VISITOR = `${BRAND.cssPrefix}${version2}.visitorId`;
1052
1057
  const KEY_CONVERSATION = `${prefix}.conversationId`;
1053
1058
  const KEY_USER_PREFS = `${prefix}.userPrefs`;
1054
1059
  const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
@@ -1159,6 +1164,12 @@ function createPersistence(widgetId, storage = defaultStorage, deploymentId) {
1159
1164
  log2.warn("saveFormDone serialise failed", { error });
1160
1165
  }
1161
1166
  },
1167
+ rememberStorageVersion(serverVersion) {
1168
+ if (serverVersion && serverVersion !== storage.get(KEY_STORAGE_VERSION)) {
1169
+ log2.debug("rememberStorageVersion (force-reset on next load)", { version: serverVersion });
1170
+ storage.set(KEY_STORAGE_VERSION, serverVersion);
1171
+ }
1172
+ },
1162
1173
  clearConversation() {
1163
1174
  storage.remove(KEY_CONVERSATION);
1164
1175
  }
@@ -1395,7 +1406,7 @@ import { h, render as renderPreact } from "preact";
1395
1406
 
1396
1407
  // src/ui/app.tsx
1397
1408
  import { useCallback as useCallback6, useEffect as useEffect17, useMemo as useMemo3, useRef as useRef9, useState as useState13 } from "preact/hooks";
1398
- import { useComputed as useComputed7, useSignal as useSignal2 } from "@preact/signals";
1409
+ import { batch, useComputed as useComputed7, useSignal as useSignal2 } from "@preact/signals";
1399
1410
 
1400
1411
  // src/core/handshake-shape.ts
1401
1412
  function isPlainObject2(raw) {
@@ -3195,7 +3206,7 @@ function Launcher({ onToggle, launcher, strings, leaving, edgeTab }) {
3195
3206
  "aria-label": strings.launcherOpen,
3196
3207
  children: [
3197
3208
  renderIcon(launcher.icon),
3198
- showLabel ? /* @__PURE__ */ jsx2("span", { class: `${p3}-fab-label`, children: launcher.label }) : null
3209
+ showLabel ? /* @__PURE__ */ jsx2("span", { class: `${p3}-fab-label`, children: localizeText(strings, launcher.label ?? "") }) : null
3199
3210
  ]
3200
3211
  }
3201
3212
  );
@@ -6736,6 +6747,9 @@ function useLauncherCallout({ callout, persistence }) {
6736
6747
  import { jsx as jsx36, jsxs as jsxs31 } from "preact/jsx-runtime";
6737
6748
  var log17 = logger.scope("app");
6738
6749
  var p32 = BRAND.cssPrefix;
6750
+ function makeLocalizedWelcome(w, strings) {
6751
+ return makeInstantWelcomeMessage({ ...w, text: localizeText(strings, w.text) });
6752
+ }
6739
6753
  function App({ options, hostElement, bus }) {
6740
6754
  const [persistence] = useState13(
6741
6755
  () => createPersistence(options.widgetId, options.storage, options.aiAgentDeploymentId)
@@ -6806,6 +6820,7 @@ function App({ options, hostElement, bus }) {
6806
6820
  const [agent, setAgent] = useState13(null);
6807
6821
  const [suggestions, setSuggestions] = useState13([]);
6808
6822
  const [activeCancel, setActiveCancel] = useState13(null);
6823
+ const stringsRef = useRef9(options.strings);
6809
6824
  const [parsedSite, setParsedSite] = useState13(void 0);
6810
6825
  const [parsedBlocks, setParsedBlocks] = useState13(void 0);
6811
6826
  const [sidebarCollapsed, setSidebarCollapsed] = useState13(() => persistence.loadSidebarCollapsed() ?? false);
@@ -6890,7 +6905,7 @@ function App({ options, hostElement, bus }) {
6890
6905
  const playWelcome = useCallback6(
6891
6906
  (welcomeMessages) => {
6892
6907
  if (!welcomeMessages || welcomeMessages.length === 0) return;
6893
- messagesSig.value = welcomeMessages.map(makeInstantWelcomeMessage);
6908
+ messagesSig.value = welcomeMessages.map((w) => makeLocalizedWelcome(w, stringsRef.current));
6894
6909
  },
6895
6910
  [messagesSig]
6896
6911
  );
@@ -6923,7 +6938,7 @@ function App({ options, hostElement, bus }) {
6923
6938
  const welcomeAnchor = (timelineStamps.length ? Math.min(...timelineStamps) : Date.now()) - 1;
6924
6939
  const loadedIds = new Set(loaded.map((m) => m.id));
6925
6940
  const welcomeRows = (welcomeRef.current?.messages ?? []).filter((w) => !loadedIds.has(w.id)).map((w) => {
6926
- const row = makeInstantWelcomeMessage(w);
6941
+ const row = makeLocalizedWelcome(w, stringsRef.current);
6927
6942
  row.createdAt = welcomeAnchor;
6928
6943
  return row;
6929
6944
  });
@@ -7010,7 +7025,6 @@ function App({ options, hostElement, bus }) {
7010
7025
  });
7011
7026
  }
7012
7027
  if (res.agent) setAgent(res.agent);
7013
- if (res.welcome?.suggestions) setSuggestions(res.welcome.suggestions);
7014
7028
  if (options.mode === "page") {
7015
7029
  if (res.site && isSiteConfigShape(res.site)) setParsedSite(res.site);
7016
7030
  else if (res.site) log17.warn("invalid site config, using fallback", { site: res.site });
@@ -7025,6 +7039,17 @@ function App({ options, hostElement, bus }) {
7025
7039
  if (res.userPrefs.textSize) setActiveTextSize(res.userPrefs.textSize);
7026
7040
  }
7027
7041
  welcomeRef.current = res.welcome;
7042
+ if (!newConversation) bus.emit("handshake", res);
7043
+ if (res.welcome?.suggestions) {
7044
+ const strings = stringsRef.current;
7045
+ setSuggestions(
7046
+ res.welcome.suggestions.map((sg) => ({
7047
+ ...sg,
7048
+ label: localizeText(strings, sg.label),
7049
+ ...sg.text === void 0 ? {} : { text: localizeText(strings, sg.text) }
7050
+ }))
7051
+ );
7052
+ }
7028
7053
  const isResume = !newConversation && persistedChatId && res.conversationId === persistedChatId;
7029
7054
  if (isResume) {
7030
7055
  if (activatedRef.current) void loadThread(persistedChatId);
@@ -7034,10 +7059,7 @@ function App({ options, hostElement, bus }) {
7034
7059
  persistence.saveConversationId(res.conversationId);
7035
7060
  playWelcome(res.welcome?.messages);
7036
7061
  }
7037
- if (!newConversation) {
7038
- bus.emit("handshake", res);
7039
- setConversationReady(true);
7040
- }
7062
+ if (!newConversation) setConversationReady(true);
7041
7063
  },
7042
7064
  [
7043
7065
  transport,
@@ -7425,8 +7447,10 @@ function App({ options, hostElement, bus }) {
7425
7447
  log17.info("clear \u2192 new conversation", { wasStreaming: streaming });
7426
7448
  bus.emit("clear", void 0);
7427
7449
  if (streaming) activeCancel?.();
7428
- messagesSig.value = [];
7429
- conversationIdSig.value = void 0;
7450
+ batch(() => {
7451
+ messagesSig.value = [];
7452
+ conversationIdSig.value = void 0;
7453
+ });
7430
7454
  persistence.clearConversation();
7431
7455
  setCanSend(true);
7432
7456
  setFormMarkers([]);
@@ -7525,9 +7549,11 @@ function App({ options, hostElement, bus }) {
7525
7549
  fetchFormMarkers(targetConversationId)
7526
7550
  ]);
7527
7551
  const hydrated = res.messages.map(fromWireMessage);
7528
- messagesSig.value = withWelcomeRows(hydrated, markers);
7552
+ batch(() => {
7553
+ messagesSig.value = withWelcomeRows(hydrated, markers);
7554
+ conversationIdSig.value = res.conversationId;
7555
+ });
7529
7556
  setFormMarkers(markers);
7530
- conversationIdSig.value = res.conversationId;
7531
7557
  persistence.saveConversationId(res.conversationId);
7532
7558
  if (res.agent) setAgent(res.agent);
7533
7559
  setCanSend(res.canContinue);
@@ -7581,6 +7607,7 @@ function App({ options, hostElement, bus }) {
7581
7607
  () => applyOptionOverrides(options, activeLocale, activeThemeMode, activeTextSize),
7582
7608
  [options, activeLocale, activeThemeMode, activeTextSize]
7583
7609
  );
7610
+ stringsRef.current = effectiveOptions.strings;
7584
7611
  const enrichedFormMarkers = useMemo3(
7585
7612
  () => formMarkers.map((m) => {
7586
7613
  const def = effectiveForms.list.find((f) => f.id === m.formId);
@@ -7901,6 +7928,7 @@ function createWidgetRuntime(params) {
7901
7928
  };
7902
7929
  bus.on("handshake", (response) => {
7903
7930
  if (!response.config) return;
7931
+ if (response.config.storageVersion) persistence.rememberStorageVersion(response.config.storageVersion);
7904
7932
  serverConfig = response.config;
7905
7933
  recompute();
7906
7934
  render2();
package/package.json CHANGED
@@ -80,5 +80,5 @@
80
80
  ],
81
81
  "type": "module",
82
82
  "types": "./index.d.ts",
83
- "version": "0.38.0"
83
+ "version": "0.40.0"
84
84
  }
package/schema.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, E as Endpoints, H as HandshakeResponse, L as Link, P as PAGE_AREA_SUGGESTIONS, f as PageContext, S as ServerConfig, b as SiteConfig, U as UserContext, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial, g as assetSchema, h as blocksConfigSchema, i as connectionConfigPartialSchema, j as connectionConfigSchema, k as cssColorSchema, l as cssLengthSchema, m as endpointsSchema, n as handshakeResponseSchema, o as linkSchema, p as localeSchema, q as pageContextSchema, s as serverConfigSchema, r as siteConfigSchema, u as userContextSchema, t as uuid7Schema, w as widgetConfigPartialSchema, v as widgetConfigSchema, x as widgetSettingsPartialSchema, y as widgetSettingsSchema } from './deployment-CNCSLVdz.js';
1
+ export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, E as Endpoints, H as HandshakeResponse, L as Link, P as PAGE_AREA_SUGGESTIONS, f as PageContext, S as ServerConfig, b as SiteConfig, U as UserContext, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial, g as assetSchema, h as blocksConfigSchema, i as connectionConfigPartialSchema, j as connectionConfigSchema, k as cssColorSchema, l as cssLengthSchema, m as endpointsSchema, n as handshakeResponseSchema, o as linkSchema, p as localeSchema, q as pageContextSchema, s as serverConfigSchema, r as siteConfigSchema, u as userContextSchema, t as uuid7Schema, w as widgetConfigPartialSchema, v as widgetConfigSchema, x as widgetSettingsPartialSchema, y as widgetSettingsSchema } from './deployment-MbF78Pdq.js';
2
2
  import { z } from 'zod';
3
3
 
4
4
  /**
@@ -56,9 +56,9 @@ declare const presentationSchema: z.ZodObject<{
56
56
  inset: z.ZodOptional<z.ZodString>;
57
57
  initialSize: z.ZodDefault<z.ZodEnum<{
58
58
  fullscreen: "fullscreen";
59
- normal: "normal";
60
59
  expanded: "expanded";
61
60
  auto: "auto";
61
+ normal: "normal";
62
62
  }>>;
63
63
  autoSizeBreakpoint: z.ZodDefault<z.ZodNumber>;
64
64
  }, z.core.$loose>>;
@@ -240,9 +240,9 @@ type LauncherOptions = z.infer<typeof launcherOptionsSchema>;
240
240
 
241
241
  declare const initialSizeSchema: z.ZodEnum<{
242
242
  fullscreen: "fullscreen";
243
- normal: "normal";
244
243
  expanded: "expanded";
245
244
  auto: "auto";
245
+ normal: "normal";
246
246
  }>;
247
247
  declare const resizeOptionsSchema: z.ZodObject<{
248
248
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -269,9 +269,9 @@ declare const sizeOptionsSchema: z.ZodObject<{
269
269
  inset: z.ZodOptional<z.ZodString>;
270
270
  initialSize: z.ZodDefault<z.ZodEnum<{
271
271
  fullscreen: "fullscreen";
272
- normal: "normal";
273
272
  expanded: "expanded";
274
273
  auto: "auto";
274
+ normal: "normal";
275
275
  }>>;
276
276
  autoSizeBreakpoint: z.ZodDefault<z.ZodNumber>;
277
277
  }, z.core.$loose>;
@@ -323,43 +323,43 @@ type FeatureFlags = z.infer<typeof featureFlagsSchema>;
323
323
  */
324
324
 
325
325
  declare const actionNameSchema: z.ZodEnum<{
326
+ close: "close";
326
327
  expand: "expand";
327
328
  fullscreen: "fullscreen";
328
329
  popOut: "popOut";
329
- close: "close";
330
- language: "language";
330
+ clear: "clear";
331
331
  theme: "theme";
332
+ language: "language";
332
333
  textSize: "textSize";
333
334
  history: "history";
334
- clear: "clear";
335
335
  sound: "sound";
336
336
  }>;
337
337
  type ActionName = z.infer<typeof actionNameSchema>;
338
338
  declare const headerActionsSchema: z.ZodArray<z.ZodEnum<{
339
+ close: "close";
339
340
  expand: "expand";
340
341
  fullscreen: "fullscreen";
341
342
  popOut: "popOut";
342
- close: "close";
343
- language: "language";
343
+ clear: "clear";
344
344
  theme: "theme";
345
+ language: "language";
345
346
  textSize: "textSize";
346
347
  history: "history";
347
- clear: "clear";
348
348
  sound: "sound";
349
349
  }>>;
350
350
  type HeaderActions = z.infer<typeof headerActionsSchema>;
351
351
  /** Section wrapper — `actions` list wrapped under `header` in the dashboard form. */
352
352
  declare const headerSchema: z.ZodObject<{
353
353
  actions: z.ZodOptional<z.ZodArray<z.ZodEnum<{
354
+ close: "close";
354
355
  expand: "expand";
355
356
  fullscreen: "fullscreen";
356
357
  popOut: "popOut";
357
- close: "close";
358
- language: "language";
358
+ clear: "clear";
359
359
  theme: "theme";
360
+ language: "language";
360
361
  textSize: "textSize";
361
362
  history: "history";
362
- clear: "clear";
363
363
  sound: "sound";
364
364
  }>>>;
365
365
  }, z.core.$loose>;
@@ -375,33 +375,33 @@ type HeaderOptions = z.infer<typeof headerSchema>;
375
375
  */
376
376
 
377
377
  declare const feedbackEventSchema: z.ZodEnum<{
378
+ voiceStart: "voiceStart";
379
+ voiceStop: "voiceStop";
378
380
  error: "error";
379
381
  messageReceived: "messageReceived";
380
382
  messageSent: "messageSent";
381
- voiceStart: "voiceStart";
382
- voiceStop: "voiceStop";
383
383
  }>;
384
384
  type FeedbackEvent = z.infer<typeof feedbackEventSchema>;
385
385
  declare const soundOptionsSchema: z.ZodObject<{
386
386
  enabled: z.ZodDefault<z.ZodBoolean>;
387
387
  volume: z.ZodDefault<z.ZodNumber>;
388
388
  events: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
389
+ voiceStart: "voiceStart";
390
+ voiceStop: "voiceStop";
389
391
  error: "error";
390
392
  messageReceived: "messageReceived";
391
393
  messageSent: "messageSent";
392
- voiceStart: "voiceStart";
393
- voiceStop: "voiceStop";
394
394
  }> & z.core.$partial, z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>>;
395
395
  }, z.core.$loose>;
396
396
  type SoundOptions = z.infer<typeof soundOptionsSchema>;
397
397
  declare const hapticsOptionsSchema: z.ZodObject<{
398
398
  enabled: z.ZodDefault<z.ZodBoolean>;
399
399
  events: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
400
+ voiceStart: "voiceStart";
401
+ voiceStop: "voiceStop";
400
402
  error: "error";
401
403
  messageReceived: "messageReceived";
402
404
  messageSent: "messageSent";
403
- voiceStart: "voiceStart";
404
- voiceStop: "voiceStop";
405
405
  }> & z.core.$partial, z.ZodUnion<readonly [z.ZodBoolean, z.ZodNumber, z.ZodArray<z.ZodNumber>]>>>;
406
406
  }, z.core.$loose>;
407
407
  type HapticsOptions = z.infer<typeof hapticsOptionsSchema>;
@@ -410,21 +410,21 @@ declare const feedbackSchema: z.ZodObject<{
410
410
  enabled: z.ZodDefault<z.ZodBoolean>;
411
411
  volume: z.ZodDefault<z.ZodNumber>;
412
412
  events: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
413
+ voiceStart: "voiceStart";
414
+ voiceStop: "voiceStop";
413
415
  error: "error";
414
416
  messageReceived: "messageReceived";
415
417
  messageSent: "messageSent";
416
- voiceStart: "voiceStart";
417
- voiceStop: "voiceStop";
418
418
  }> & z.core.$partial, z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>>;
419
419
  }, z.core.$loose>>;
420
420
  haptics: z.ZodOptional<z.ZodObject<{
421
421
  enabled: z.ZodDefault<z.ZodBoolean>;
422
422
  events: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
423
+ voiceStart: "voiceStart";
424
+ voiceStop: "voiceStop";
423
425
  error: "error";
424
426
  messageReceived: "messageReceived";
425
427
  messageSent: "messageSent";
426
- voiceStart: "voiceStart";
427
- voiceStop: "voiceStop";
428
428
  }> & z.core.$partial, z.ZodUnion<readonly [z.ZodBoolean, z.ZodNumber, z.ZodArray<z.ZodNumber>]>>>;
429
429
  }, z.core.$loose>>;
430
430
  }, z.core.$loose>;
@@ -859,18 +859,18 @@ type I18nOptions = z.infer<typeof i18nSchema>;
859
859
  */
860
860
 
861
861
  declare const moduleLayoutSchema: z.ZodEnum<{
862
+ home: "home";
862
863
  chat: "chat";
863
864
  help: "help";
864
- home: "home";
865
865
  news: "news";
866
866
  }>;
867
867
  type ModuleLayout = z.infer<typeof moduleLayoutSchema>;
868
868
  declare const moduleSchema: z.ZodObject<{
869
869
  label: z.ZodString;
870
870
  layout: z.ZodEnum<{
871
+ home: "home";
871
872
  chat: "chat";
872
873
  help: "help";
873
- home: "home";
874
874
  news: "news";
875
875
  }>;
876
876
  contentTags: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -896,9 +896,9 @@ type ModuleOptions = z.infer<typeof moduleSchema>;
896
896
  declare const modulesSchema: z.ZodArray<z.ZodObject<{
897
897
  label: z.ZodString;
898
898
  layout: z.ZodEnum<{
899
+ home: "home";
899
900
  chat: "chat";
900
901
  help: "help";
901
- home: "home";
902
902
  news: "news";
903
903
  }>;
904
904
  contentTags: z.ZodOptional<z.ZodArray<z.ZodString>>;
package/schema.json CHANGED
@@ -3403,6 +3403,11 @@
3403
3403
  "type": "string",
3404
3404
  "minLength": 1,
3405
3405
  "maxLength": 2048
3406
+ },
3407
+ "storageVersion": {
3408
+ "type": "string",
3409
+ "minLength": 1,
3410
+ "maxLength": 64
3406
3411
  }
3407
3412
  },
3408
3413
  "additionalProperties": {}
package/schema.mjs CHANGED
@@ -758,7 +758,8 @@ var connectionConfigPartialSchema = connectionConfigSchema.partial();
758
758
  // src/schema/deployment.ts
759
759
  import { z as z17 } from "zod";
760
760
  var serverConfigSchema = widgetSettingsSchema.partial().omit({ forms: true }).extend({
761
- dataApiBaseUrl: z17.string().min(1).max(2048).optional()
761
+ dataApiBaseUrl: z17.string().min(1).max(2048).optional(),
762
+ storageVersion: z17.string().min(1).max(64).optional()
762
763
  });
763
764
  var siteConfigSchema = z17.object({
764
765
  title: z17.string().min(1).max(120).optional().describe("Brand / site name. Used as the logo's alt-text fallback."),
package/web-component.mjs CHANGED
@@ -62,6 +62,7 @@ var STRINGS_EN = {
62
62
  historyTitle: "Conversation history",
63
63
  language: "Language",
64
64
  launcherOpen: "Open chat",
65
+ launcherLabel: "Chat with us",
65
66
  micStart: "Start voice input",
66
67
  micStop: "Stop voice input",
67
68
  micUnsupported: "Voice input is not supported in this browser",
@@ -175,6 +176,7 @@ var STRINGS_FR = {
175
176
  historyTitle: "Historique des conversations",
176
177
  language: "Langue",
177
178
  launcherOpen: "Ouvrir le chat",
179
+ launcherLabel: "Discuter avec nous",
178
180
  micStart: "D\xE9marrer la saisie vocale",
179
181
  micStop: "Arr\xEAter la saisie vocale",
180
182
  micUnsupported: "La saisie vocale n'est pas prise en charge par ce navigateur",
@@ -1085,8 +1087,11 @@ function fillRandom(view) {
1085
1087
  var log2 = logger.scope("persistence");
1086
1088
  function createPersistence(widgetId, storage = defaultStorage, deploymentId) {
1087
1089
  const scope = deploymentId ? `${widgetId}.${deploymentId}` : widgetId;
1088
- const prefix = `${BRAND.cssPrefix}.${scope}`;
1089
- const KEY_VISITOR = `${BRAND.cssPrefix}.visitorId`;
1090
+ const KEY_STORAGE_VERSION = `${BRAND.cssPrefix}.storageVersion`;
1091
+ const activeVersion = storage.get(KEY_STORAGE_VERSION) ?? void 0;
1092
+ const version = activeVersion ? `.v${activeVersion}` : "";
1093
+ const prefix = `${BRAND.cssPrefix}.${scope}${version}`;
1094
+ const KEY_VISITOR = `${BRAND.cssPrefix}${version}.visitorId`;
1090
1095
  const KEY_CONVERSATION = `${prefix}.conversationId`;
1091
1096
  const KEY_USER_PREFS = `${prefix}.userPrefs`;
1092
1097
  const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
@@ -1197,6 +1202,12 @@ function createPersistence(widgetId, storage = defaultStorage, deploymentId) {
1197
1202
  log2.warn("saveFormDone serialise failed", { error });
1198
1203
  }
1199
1204
  },
1205
+ rememberStorageVersion(serverVersion) {
1206
+ if (serverVersion && serverVersion !== storage.get(KEY_STORAGE_VERSION)) {
1207
+ log2.debug("rememberStorageVersion (force-reset on next load)", { version: serverVersion });
1208
+ storage.set(KEY_STORAGE_VERSION, serverVersion);
1209
+ }
1210
+ },
1200
1211
  clearConversation() {
1201
1212
  storage.remove(KEY_CONVERSATION);
1202
1213
  }
@@ -1337,7 +1348,7 @@ import { h, render as renderPreact } from "preact";
1337
1348
 
1338
1349
  // src/ui/app.tsx
1339
1350
  import { useCallback as useCallback6, useEffect as useEffect17, useMemo as useMemo3, useRef as useRef9, useState as useState13 } from "preact/hooks";
1340
- import { useComputed as useComputed7, useSignal as useSignal2 } from "@preact/signals";
1351
+ import { batch, useComputed as useComputed7, useSignal as useSignal2 } from "@preact/signals";
1341
1352
 
1342
1353
  // src/core/handshake-shape.ts
1343
1354
  function isPlainObject2(raw) {
@@ -3154,7 +3165,7 @@ function Launcher({ onToggle, launcher, strings, leaving, edgeTab }) {
3154
3165
  "aria-label": strings.launcherOpen,
3155
3166
  children: [
3156
3167
  renderIcon(launcher.icon),
3157
- showLabel ? /* @__PURE__ */ jsx2("span", { class: `${p3}-fab-label`, children: launcher.label }) : null
3168
+ showLabel ? /* @__PURE__ */ jsx2("span", { class: `${p3}-fab-label`, children: localizeText(strings, launcher.label ?? "") }) : null
3158
3169
  ]
3159
3170
  }
3160
3171
  );
@@ -6695,6 +6706,9 @@ function useLauncherCallout({ callout, persistence }) {
6695
6706
  import { jsx as jsx36, jsxs as jsxs31 } from "preact/jsx-runtime";
6696
6707
  var log16 = logger.scope("app");
6697
6708
  var p32 = BRAND.cssPrefix;
6709
+ function makeLocalizedWelcome(w, strings) {
6710
+ return makeInstantWelcomeMessage({ ...w, text: localizeText(strings, w.text) });
6711
+ }
6698
6712
  function App({ options, hostElement, bus }) {
6699
6713
  const [persistence] = useState13(
6700
6714
  () => createPersistence(options.widgetId, options.storage, options.aiAgentDeploymentId)
@@ -6765,6 +6779,7 @@ function App({ options, hostElement, bus }) {
6765
6779
  const [agent, setAgent] = useState13(null);
6766
6780
  const [suggestions, setSuggestions] = useState13([]);
6767
6781
  const [activeCancel, setActiveCancel] = useState13(null);
6782
+ const stringsRef = useRef9(options.strings);
6768
6783
  const [parsedSite, setParsedSite] = useState13(void 0);
6769
6784
  const [parsedBlocks, setParsedBlocks] = useState13(void 0);
6770
6785
  const [sidebarCollapsed, setSidebarCollapsed] = useState13(() => persistence.loadSidebarCollapsed() ?? false);
@@ -6849,7 +6864,7 @@ function App({ options, hostElement, bus }) {
6849
6864
  const playWelcome = useCallback6(
6850
6865
  (welcomeMessages) => {
6851
6866
  if (!welcomeMessages || welcomeMessages.length === 0) return;
6852
- messagesSig.value = welcomeMessages.map(makeInstantWelcomeMessage);
6867
+ messagesSig.value = welcomeMessages.map((w) => makeLocalizedWelcome(w, stringsRef.current));
6853
6868
  },
6854
6869
  [messagesSig]
6855
6870
  );
@@ -6882,7 +6897,7 @@ function App({ options, hostElement, bus }) {
6882
6897
  const welcomeAnchor = (timelineStamps.length ? Math.min(...timelineStamps) : Date.now()) - 1;
6883
6898
  const loadedIds = new Set(loaded.map((m) => m.id));
6884
6899
  const welcomeRows = (welcomeRef.current?.messages ?? []).filter((w) => !loadedIds.has(w.id)).map((w) => {
6885
- const row = makeInstantWelcomeMessage(w);
6900
+ const row = makeLocalizedWelcome(w, stringsRef.current);
6886
6901
  row.createdAt = welcomeAnchor;
6887
6902
  return row;
6888
6903
  });
@@ -6969,7 +6984,6 @@ function App({ options, hostElement, bus }) {
6969
6984
  });
6970
6985
  }
6971
6986
  if (res.agent) setAgent(res.agent);
6972
- if (res.welcome?.suggestions) setSuggestions(res.welcome.suggestions);
6973
6987
  if (options.mode === "page") {
6974
6988
  if (res.site && isSiteConfigShape(res.site)) setParsedSite(res.site);
6975
6989
  else if (res.site) log16.warn("invalid site config, using fallback", { site: res.site });
@@ -6984,6 +6998,17 @@ function App({ options, hostElement, bus }) {
6984
6998
  if (res.userPrefs.textSize) setActiveTextSize(res.userPrefs.textSize);
6985
6999
  }
6986
7000
  welcomeRef.current = res.welcome;
7001
+ if (!newConversation) bus.emit("handshake", res);
7002
+ if (res.welcome?.suggestions) {
7003
+ const strings = stringsRef.current;
7004
+ setSuggestions(
7005
+ res.welcome.suggestions.map((sg) => ({
7006
+ ...sg,
7007
+ label: localizeText(strings, sg.label),
7008
+ ...sg.text === void 0 ? {} : { text: localizeText(strings, sg.text) }
7009
+ }))
7010
+ );
7011
+ }
6987
7012
  const isResume = !newConversation && persistedChatId && res.conversationId === persistedChatId;
6988
7013
  if (isResume) {
6989
7014
  if (activatedRef.current) void loadThread(persistedChatId);
@@ -6993,10 +7018,7 @@ function App({ options, hostElement, bus }) {
6993
7018
  persistence.saveConversationId(res.conversationId);
6994
7019
  playWelcome(res.welcome?.messages);
6995
7020
  }
6996
- if (!newConversation) {
6997
- bus.emit("handshake", res);
6998
- setConversationReady(true);
6999
- }
7021
+ if (!newConversation) setConversationReady(true);
7000
7022
  },
7001
7023
  [
7002
7024
  transport,
@@ -7384,8 +7406,10 @@ function App({ options, hostElement, bus }) {
7384
7406
  log16.info("clear \u2192 new conversation", { wasStreaming: streaming });
7385
7407
  bus.emit("clear", void 0);
7386
7408
  if (streaming) activeCancel?.();
7387
- messagesSig.value = [];
7388
- conversationIdSig.value = void 0;
7409
+ batch(() => {
7410
+ messagesSig.value = [];
7411
+ conversationIdSig.value = void 0;
7412
+ });
7389
7413
  persistence.clearConversation();
7390
7414
  setCanSend(true);
7391
7415
  setFormMarkers([]);
@@ -7484,9 +7508,11 @@ function App({ options, hostElement, bus }) {
7484
7508
  fetchFormMarkers(targetConversationId)
7485
7509
  ]);
7486
7510
  const hydrated = res.messages.map(fromWireMessage);
7487
- messagesSig.value = withWelcomeRows(hydrated, markers);
7511
+ batch(() => {
7512
+ messagesSig.value = withWelcomeRows(hydrated, markers);
7513
+ conversationIdSig.value = res.conversationId;
7514
+ });
7488
7515
  setFormMarkers(markers);
7489
- conversationIdSig.value = res.conversationId;
7490
7516
  persistence.saveConversationId(res.conversationId);
7491
7517
  if (res.agent) setAgent(res.agent);
7492
7518
  setCanSend(res.canContinue);
@@ -7540,6 +7566,7 @@ function App({ options, hostElement, bus }) {
7540
7566
  () => applyOptionOverrides(options, activeLocale, activeThemeMode, activeTextSize),
7541
7567
  [options, activeLocale, activeThemeMode, activeTextSize]
7542
7568
  );
7569
+ stringsRef.current = effectiveOptions.strings;
7543
7570
  const enrichedFormMarkers = useMemo3(
7544
7571
  () => formMarkers.map((m) => {
7545
7572
  const def = effectiveForms.list.find((f) => f.id === m.formId);
@@ -7860,6 +7887,7 @@ function createWidgetRuntime(params) {
7860
7887
  };
7861
7888
  bus.on("handshake", (response) => {
7862
7889
  if (!response.config) return;
7890
+ if (response.config.storageVersion) persistence.rememberStorageVersion(response.config.storageVersion);
7863
7891
  serverConfig = response.config;
7864
7892
  recompute();
7865
7893
  render2();