@modelnex/sdk 0.5.29 → 0.5.31

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/README.md CHANGED
@@ -86,7 +86,6 @@ Current behavior:
86
86
  | `serverUrl` | Backend base URL for chat, tags, tours, voice, and recording APIs |
87
87
  | `websiteId` | Tenant/integration identifier |
88
88
  | `userProfile` | End-user targeting data for tours/workflows |
89
- | `devMode` | Enables recording/studio tooling for your internal users |
90
89
 
91
90
  ## Chat Bubble Props
92
91
 
@@ -115,10 +114,7 @@ export function AppShell({ children, currentUser }) {
115
114
  return (
116
115
  <ModelNexProvider
117
116
  websiteId="prod_site_123"
118
-
119
- // Development setup
120
- devMode={process.env.NODE_ENV === 'development'}
121
-
117
+
122
118
  // User Targeting for Tours & Workflows
123
119
  userProfile={{
124
120
  userId: currentUser.id,
@@ -132,6 +128,16 @@ export function AppShell({ children, currentUser }) {
132
128
  }
133
129
  ```
134
130
 
131
+ ### Browser-Injected Dev Mode
132
+
133
+ ```html
134
+ <script>
135
+ window.__MODELNEX_DEV_MODE_KEY__ = 'dmk_live_xxxxxxxxx';
136
+ </script>
137
+ ```
138
+
139
+ The SDK now enables dev tooling only after it finds a browser-injected dev mode key and validates it with the backend for the current `websiteId`.
140
+
135
141
  ### Themed Chat Bubble
136
142
 
137
143
  ```tsx
@@ -0,0 +1,55 @@
1
+ // src/utils/dom-sync.ts
2
+ function waitForDomSettle(options = {}) {
3
+ const { timeoutMs = 5e3, debounceMs = 400, minWaitMs = 100 } = options;
4
+ return new Promise((resolve) => {
5
+ let debounceTimer = null;
6
+ let resolved = false;
7
+ const maxTimer = setTimeout(() => {
8
+ if (!resolved) {
9
+ resolved = true;
10
+ cleanup();
11
+ console.log("[DOM Sync] Forced resolution by max timeout");
12
+ resolve();
13
+ }
14
+ }, Math.max(timeoutMs, minWaitMs));
15
+ const finish = () => {
16
+ if (!resolved) {
17
+ resolved = true;
18
+ cleanup();
19
+ resolve();
20
+ }
21
+ };
22
+ const observer = new MutationObserver((mutations) => {
23
+ const hasSignificantMutations = mutations.some((m) => {
24
+ if (m.target instanceof HTMLElement) {
25
+ if (m.target.hasAttribute("data-modelnex-tour-highlight")) return false;
26
+ if (m.target.hasAttribute("data-modelnex-caption")) return false;
27
+ if (m.target.closest("#modelnex-studio-root")) return false;
28
+ if (m.target.closest("#modelnex-active-agent-root")) return false;
29
+ }
30
+ return true;
31
+ });
32
+ if (!hasSignificantMutations) return;
33
+ if (debounceTimer) clearTimeout(debounceTimer);
34
+ debounceTimer = setTimeout(finish, debounceMs);
35
+ });
36
+ const cleanup = () => {
37
+ observer.disconnect();
38
+ if (debounceTimer) clearTimeout(debounceTimer);
39
+ clearTimeout(maxTimer);
40
+ };
41
+ setTimeout(() => {
42
+ if (resolved) return;
43
+ observer.observe(document.body, {
44
+ childList: true,
45
+ subtree: true,
46
+ attributes: true,
47
+ characterData: true
48
+ });
49
+ debounceTimer = setTimeout(finish, debounceMs);
50
+ }, minWaitMs);
51
+ });
52
+ }
53
+ export {
54
+ waitForDomSettle
55
+ };
package/dist/index.d.mts CHANGED
@@ -845,15 +845,6 @@ interface ModelNexProviderProps {
845
845
  * Same-origin base for tour API (avoids CORS for ?modelnex_test_tour=)
846
846
  */
847
847
  toursApiBase?: string;
848
- /**
849
- * Enable SDK dev tools unconditionally (tour recording, studio mode)
850
- */
851
- devMode?: boolean;
852
- /**
853
- * Optional dev mode key. When present, the SDK validates it with the backend
854
- * and enables dev tooling when the key matches the project configuration.
855
- */
856
- devModeKey?: string;
857
848
  }
858
849
  declare const ModelNexProvider: React$1.FC<ModelNexProviderProps>;
859
850
 
package/dist/index.d.ts CHANGED
@@ -845,15 +845,6 @@ interface ModelNexProviderProps {
845
845
  * Same-origin base for tour API (avoids CORS for ?modelnex_test_tour=)
846
846
  */
847
847
  toursApiBase?: string;
848
- /**
849
- * Enable SDK dev tools unconditionally (tour recording, studio mode)
850
- */
851
- devMode?: boolean;
852
- /**
853
- * Optional dev mode key. When present, the SDK validates it with the backend
854
- * and enables dev tooling when the key matches the project configuration.
855
- */
856
- devModeKey?: string;
857
848
  }
858
849
  declare const ModelNexProvider: React$1.FC<ModelNexProviderProps>;
859
850
 
package/dist/index.js CHANGED
@@ -2766,9 +2766,7 @@ function normalizeDevModeKey(value) {
2766
2766
  const normalized = value.trim();
2767
2767
  return normalized || void 0;
2768
2768
  }
2769
- function resolveInjectedDevModeKey(explicitDevModeKey) {
2770
- const normalizedExplicitKey = normalizeDevModeKey(explicitDevModeKey);
2771
- if (normalizedExplicitKey) return normalizedExplicitKey;
2769
+ function resolveInjectedDevModeKey() {
2772
2770
  if (typeof window === "undefined") return void 0;
2773
2771
  const browserWindow = window;
2774
2772
  for (const globalName of DEV_MODE_KEY_GLOBAL_NAMES) {
@@ -8880,9 +8878,40 @@ var TOUR_THEME = {
8880
8878
  cardBorder: "var(--modelnex-tour-card-border, rgba(79, 70, 229, 0.12))",
8881
8879
  actionBg: "var(--modelnex-tour-action-bg, #1e1b4b)"
8882
8880
  };
8881
+ var DOCKED_DESKTOP_PANEL_HEIGHT_RATIO = 0.7;
8882
+ var UNDOCKED_DESKTOP_PANEL_HEIGHT_RATIO = 0.4;
8883
8883
  function shouldIgnoreTourGestureStart(target) {
8884
8884
  return target instanceof HTMLElement && Boolean(target.closest('[data-modelnex-review-toggle="true"]'));
8885
8885
  }
8886
+ function getViewportHeight() {
8887
+ if (typeof window === "undefined") return 0;
8888
+ const layoutViewportHeight = window.innerHeight || document.documentElement?.clientHeight || 0;
8889
+ const visualViewportHeight = window.visualViewport?.height;
8890
+ if (typeof visualViewportHeight === "number" && Number.isFinite(visualViewportHeight) && visualViewportHeight > 0) {
8891
+ return Math.round(Math.min(layoutViewportHeight || visualViewportHeight, visualViewportHeight));
8892
+ }
8893
+ return layoutViewportHeight;
8894
+ }
8895
+ function useViewportHeight() {
8896
+ const [viewportHeight, setViewportHeight] = (0, import_react18.useState)(() => getViewportHeight());
8897
+ (0, import_react18.useEffect)(() => {
8898
+ if (typeof window === "undefined") return;
8899
+ const updateViewportHeight = () => {
8900
+ setViewportHeight(getViewportHeight());
8901
+ };
8902
+ updateViewportHeight();
8903
+ const visualViewport = window.visualViewport;
8904
+ window.addEventListener("resize", updateViewportHeight, { passive: true });
8905
+ visualViewport?.addEventListener("resize", updateViewportHeight);
8906
+ visualViewport?.addEventListener("scroll", updateViewportHeight);
8907
+ return () => {
8908
+ window.removeEventListener("resize", updateViewportHeight);
8909
+ visualViewport?.removeEventListener("resize", updateViewportHeight);
8910
+ visualViewport?.removeEventListener("scroll", updateViewportHeight);
8911
+ };
8912
+ }, []);
8913
+ return viewportHeight;
8914
+ }
8886
8915
  function useMediaQuery(query) {
8887
8916
  const [matches, setMatches] = (0, import_react18.useState)(false);
8888
8917
  (0, import_react18.useEffect)(() => {
@@ -9778,6 +9807,7 @@ function ModelNexChatBubble({
9778
9807
  e.stopPropagation();
9779
9808
  };
9780
9809
  const isMobile = useMediaQuery("(max-width: 640px)");
9810
+ const viewportHeight = useViewportHeight();
9781
9811
  const themeStyles = (0, import_react18.useMemo)(() => {
9782
9812
  if (!theme) return null;
9783
9813
  const styles = {};
@@ -9807,13 +9837,13 @@ function ModelNexChatBubble({
9807
9837
  fontFamily: "var(--modelnex-font)",
9808
9838
  ...themeStyles
9809
9839
  };
9810
- const desktopPanelHeight = docked ? "min(calc(100vh - 48px), calc(var(--modelnex-panel-max-height, 600px) + 180px))" : "var(--modelnex-panel-max-height, 600px)";
9811
- const desktopPanelMaxHeight = docked ? "calc(100vh - 48px)" : "calc(100vh - 120px)";
9840
+ const desktopPanelHeightRatio = docked ? DOCKED_DESKTOP_PANEL_HEIGHT_RATIO : UNDOCKED_DESKTOP_PANEL_HEIGHT_RATIO;
9841
+ const desktopPanelHeight = viewportHeight > 0 ? `${Math.round(viewportHeight * desktopPanelHeightRatio)}px` : docked ? "70vh" : "40vh";
9812
9842
  const panelStyle = {
9813
9843
  width: isMobile ? "100%" : "var(--modelnex-panel-width, 380px)",
9814
9844
  maxWidth: "calc(100vw - 32px)",
9815
9845
  height: isMobile ? "100%" : desktopPanelHeight,
9816
- maxHeight: isMobile ? "100%" : desktopPanelMaxHeight,
9846
+ maxHeight: isMobile ? "100%" : desktopPanelHeight,
9817
9847
  borderRadius: isMobile ? "0" : "var(--modelnex-radius-panel, 20px)",
9818
9848
  border: isMobile ? "none" : "1px solid var(--modelnex-border, #e4e4e7)",
9819
9849
  background: "var(--modelnex-bg, #ffffff)",
@@ -11741,8 +11771,6 @@ var ModelNexProvider = ({
11741
11771
  websiteId,
11742
11772
  userProfile,
11743
11773
  toursApiBase,
11744
- devMode,
11745
- devModeKey,
11746
11774
  serverUrl: serverUrlProp
11747
11775
  }) => {
11748
11776
  const serverUrl = serverUrlProp ?? DEFAULT_MODELNEX_SERVER_URL;
@@ -11766,7 +11794,7 @@ var ModelNexProvider = ({
11766
11794
  const [socketId, setSocketId] = (0, import_react21.useState)(null);
11767
11795
  const [actions, setActions] = (0, import_react21.useState)(/* @__PURE__ */ new Map());
11768
11796
  const [validatedBrowserDevMode, setValidatedBrowserDevMode] = (0, import_react21.useState)(false);
11769
- const resolvedDevModeKey = (0, import_react21.useMemo)(() => resolveInjectedDevModeKey(devModeKey), [devModeKey]);
11797
+ const resolvedDevModeKey = (0, import_react21.useMemo)(() => resolveInjectedDevModeKey(), []);
11770
11798
  (0, import_react21.useEffect)(() => {
11771
11799
  let cancelled = false;
11772
11800
  if (!websiteId || !resolvedDevModeKey) {
@@ -11783,7 +11811,7 @@ var ModelNexProvider = ({
11783
11811
  cancelled = true;
11784
11812
  };
11785
11813
  }, [resolvedDevModeKey, serverUrl, websiteId]);
11786
- const effectiveDevMode = Boolean(devMode) || validatedBrowserDevMode;
11814
+ const effectiveDevMode = validatedBrowserDevMode;
11787
11815
  const registerAction = (0, import_react21.useCallback)((action) => {
11788
11816
  setActions((prev) => {
11789
11817
  const next = new Map(prev);
package/dist/index.mjs CHANGED
@@ -2557,9 +2557,7 @@ function normalizeDevModeKey(value) {
2557
2557
  const normalized = value.trim();
2558
2558
  return normalized || void 0;
2559
2559
  }
2560
- function resolveInjectedDevModeKey(explicitDevModeKey) {
2561
- const normalizedExplicitKey = normalizeDevModeKey(explicitDevModeKey);
2562
- if (normalizedExplicitKey) return normalizedExplicitKey;
2560
+ function resolveInjectedDevModeKey() {
2563
2561
  if (typeof window === "undefined") return void 0;
2564
2562
  const browserWindow = window;
2565
2563
  for (const globalName of DEV_MODE_KEY_GLOBAL_NAMES) {
@@ -8670,9 +8668,40 @@ var TOUR_THEME = {
8670
8668
  cardBorder: "var(--modelnex-tour-card-border, rgba(79, 70, 229, 0.12))",
8671
8669
  actionBg: "var(--modelnex-tour-action-bg, #1e1b4b)"
8672
8670
  };
8671
+ var DOCKED_DESKTOP_PANEL_HEIGHT_RATIO = 0.7;
8672
+ var UNDOCKED_DESKTOP_PANEL_HEIGHT_RATIO = 0.4;
8673
8673
  function shouldIgnoreTourGestureStart(target) {
8674
8674
  return target instanceof HTMLElement && Boolean(target.closest('[data-modelnex-review-toggle="true"]'));
8675
8675
  }
8676
+ function getViewportHeight() {
8677
+ if (typeof window === "undefined") return 0;
8678
+ const layoutViewportHeight = window.innerHeight || document.documentElement?.clientHeight || 0;
8679
+ const visualViewportHeight = window.visualViewport?.height;
8680
+ if (typeof visualViewportHeight === "number" && Number.isFinite(visualViewportHeight) && visualViewportHeight > 0) {
8681
+ return Math.round(Math.min(layoutViewportHeight || visualViewportHeight, visualViewportHeight));
8682
+ }
8683
+ return layoutViewportHeight;
8684
+ }
8685
+ function useViewportHeight() {
8686
+ const [viewportHeight, setViewportHeight] = useState13(() => getViewportHeight());
8687
+ useEffect17(() => {
8688
+ if (typeof window === "undefined") return;
8689
+ const updateViewportHeight = () => {
8690
+ setViewportHeight(getViewportHeight());
8691
+ };
8692
+ updateViewportHeight();
8693
+ const visualViewport = window.visualViewport;
8694
+ window.addEventListener("resize", updateViewportHeight, { passive: true });
8695
+ visualViewport?.addEventListener("resize", updateViewportHeight);
8696
+ visualViewport?.addEventListener("scroll", updateViewportHeight);
8697
+ return () => {
8698
+ window.removeEventListener("resize", updateViewportHeight);
8699
+ visualViewport?.removeEventListener("resize", updateViewportHeight);
8700
+ visualViewport?.removeEventListener("scroll", updateViewportHeight);
8701
+ };
8702
+ }, []);
8703
+ return viewportHeight;
8704
+ }
8676
8705
  function useMediaQuery(query) {
8677
8706
  const [matches, setMatches] = useState13(false);
8678
8707
  useEffect17(() => {
@@ -9568,6 +9597,7 @@ function ModelNexChatBubble({
9568
9597
  e.stopPropagation();
9569
9598
  };
9570
9599
  const isMobile = useMediaQuery("(max-width: 640px)");
9600
+ const viewportHeight = useViewportHeight();
9571
9601
  const themeStyles = useMemo3(() => {
9572
9602
  if (!theme) return null;
9573
9603
  const styles = {};
@@ -9597,13 +9627,13 @@ function ModelNexChatBubble({
9597
9627
  fontFamily: "var(--modelnex-font)",
9598
9628
  ...themeStyles
9599
9629
  };
9600
- const desktopPanelHeight = docked ? "min(calc(100vh - 48px), calc(var(--modelnex-panel-max-height, 600px) + 180px))" : "var(--modelnex-panel-max-height, 600px)";
9601
- const desktopPanelMaxHeight = docked ? "calc(100vh - 48px)" : "calc(100vh - 120px)";
9630
+ const desktopPanelHeightRatio = docked ? DOCKED_DESKTOP_PANEL_HEIGHT_RATIO : UNDOCKED_DESKTOP_PANEL_HEIGHT_RATIO;
9631
+ const desktopPanelHeight = viewportHeight > 0 ? `${Math.round(viewportHeight * desktopPanelHeightRatio)}px` : docked ? "70vh" : "40vh";
9602
9632
  const panelStyle = {
9603
9633
  width: isMobile ? "100%" : "var(--modelnex-panel-width, 380px)",
9604
9634
  maxWidth: "calc(100vw - 32px)",
9605
9635
  height: isMobile ? "100%" : desktopPanelHeight,
9606
- maxHeight: isMobile ? "100%" : desktopPanelMaxHeight,
9636
+ maxHeight: isMobile ? "100%" : desktopPanelHeight,
9607
9637
  borderRadius: isMobile ? "0" : "var(--modelnex-radius-panel, 20px)",
9608
9638
  border: isMobile ? "none" : "1px solid var(--modelnex-border, #e4e4e7)",
9609
9639
  background: "var(--modelnex-bg, #ffffff)",
@@ -11531,8 +11561,6 @@ var ModelNexProvider = ({
11531
11561
  websiteId,
11532
11562
  userProfile,
11533
11563
  toursApiBase,
11534
- devMode,
11535
- devModeKey,
11536
11564
  serverUrl: serverUrlProp
11537
11565
  }) => {
11538
11566
  const serverUrl = serverUrlProp ?? DEFAULT_MODELNEX_SERVER_URL;
@@ -11556,7 +11584,7 @@ var ModelNexProvider = ({
11556
11584
  const [socketId, setSocketId] = useState15(null);
11557
11585
  const [actions, setActions] = useState15(/* @__PURE__ */ new Map());
11558
11586
  const [validatedBrowserDevMode, setValidatedBrowserDevMode] = useState15(false);
11559
- const resolvedDevModeKey = useMemo5(() => resolveInjectedDevModeKey(devModeKey), [devModeKey]);
11587
+ const resolvedDevModeKey = useMemo5(() => resolveInjectedDevModeKey(), []);
11560
11588
  useEffect19(() => {
11561
11589
  let cancelled = false;
11562
11590
  if (!websiteId || !resolvedDevModeKey) {
@@ -11573,7 +11601,7 @@ var ModelNexProvider = ({
11573
11601
  cancelled = true;
11574
11602
  };
11575
11603
  }, [resolvedDevModeKey, serverUrl, websiteId]);
11576
- const effectiveDevMode = Boolean(devMode) || validatedBrowserDevMode;
11604
+ const effectiveDevMode = validatedBrowserDevMode;
11577
11605
  const registerAction = useCallback14((action) => {
11578
11606
  setActions((prev) => {
11579
11607
  const next = new Map(prev);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelnex/sdk",
3
- "version": "0.5.29",
3
+ "version": "0.5.31",
4
4
  "description": "React SDK for natural language control of web apps via AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -20,6 +20,15 @@
20
20
  "dist",
21
21
  "README.md"
22
22
  ],
23
+ "scripts": {
24
+ "prepublishOnly": "npm run build",
25
+ "build": "npm exec -- tsup src/index.ts --format cjs,esm --dts",
26
+ "dev": "npm exec -- tsup src/index.ts --format cjs,esm --watch --dts",
27
+ "lint": "eslint src/",
28
+ "test": "npm run build && node --test tests/*.test.js",
29
+ "test:ci": "npm run test && npm run test:unit",
30
+ "test:unit": "node --import tsx --test tests/*.test.ts"
31
+ },
23
32
  "peerDependencies": {
24
33
  "react": ">=17.0.0",
25
34
  "react-dom": ">=17.0.0",
@@ -58,13 +67,5 @@
58
67
  "bugs": {
59
68
  "url": "https://github.com/sharunaraksha/modelnex-sdk/issues"
60
69
  },
61
- "homepage": "https://github.com/sharunaraksha/modelnex-sdk#readme",
62
- "scripts": {
63
- "build": "npm exec -- tsup src/index.ts --format cjs,esm --dts",
64
- "dev": "npm exec -- tsup src/index.ts --format cjs,esm --watch --dts",
65
- "lint": "eslint src/",
66
- "test": "npm run build && node --test tests/*.test.js",
67
- "test:ci": "npm run test && npm run test:unit",
68
- "test:unit": "node --import tsx --test tests/*.test.ts"
69
- }
70
- }
70
+ "homepage": "https://github.com/sharunaraksha/modelnex-sdk#readme"
71
+ }