sunpeak 0.20.36 → 0.20.47

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.
Files changed (64) hide show
  1. package/README.md +4 -2
  2. package/bin/commands/inspect.mjs +291 -64
  3. package/bin/commands/test-init.mjs +6 -0
  4. package/bin/lib/eval/eval-runner.mjs +53 -1
  5. package/bin/lib/eval/eval-types.d.mts +27 -0
  6. package/bin/lib/eval/model-registry.mjs +2 -2
  7. package/dist/chatgpt/index.cjs +1 -1
  8. package/dist/chatgpt/index.js +1 -1
  9. package/dist/claude/index.cjs +1 -1
  10. package/dist/claude/index.js +1 -1
  11. package/dist/embed.css +1 -1
  12. package/dist/hooks/tool-data-store.d.ts +26 -0
  13. package/dist/hooks/use-tool-data.d.ts +3 -9
  14. package/dist/host/chatgpt/index.cjs +1 -1
  15. package/dist/host/chatgpt/index.js +1 -1
  16. package/dist/index.cjs +35 -21
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.js +35 -21
  19. package/dist/index.js.map +1 -1
  20. package/dist/inspector/index.cjs +1 -1
  21. package/dist/inspector/index.js +1 -1
  22. package/dist/inspector/inspector.d.ts +7 -0
  23. package/dist/inspector/use-inspector-state.d.ts +28 -0
  24. package/dist/{inspector-CiuT_2yA.js → inspector-BSha-CAW.js} +216 -75
  25. package/dist/inspector-BSha-CAW.js.map +1 -0
  26. package/dist/{inspector-BNWla95w.cjs → inspector-Chhc2GNO.cjs} +216 -75
  27. package/dist/inspector-Chhc2GNO.cjs.map +1 -0
  28. package/dist/lib/utils.d.ts +8 -7
  29. package/dist/mcp/index.cjs +5 -3
  30. package/dist/mcp/index.cjs.map +1 -1
  31. package/dist/mcp/index.js +5 -3
  32. package/dist/mcp/index.js.map +1 -1
  33. package/dist/mcp/server.d.ts +12 -1
  34. package/dist/style.css +22 -0
  35. package/dist/{use-app-Duar2Ipu.js → use-app-CtKy52kw.js} +62 -1
  36. package/dist/use-app-CtKy52kw.js.map +1 -0
  37. package/dist/{use-app-DUdnDLP5.cjs → use-app-xaiN0HAd.cjs} +62 -1
  38. package/dist/use-app-xaiN0HAd.cjs.map +1 -0
  39. package/package.json +8 -8
  40. package/template/dist/albums/albums.html +3 -3
  41. package/template/dist/albums/albums.json +1 -1
  42. package/template/dist/carousel/carousel.html +3 -3
  43. package/template/dist/carousel/carousel.json +1 -1
  44. package/template/dist/map/map.html +4 -4
  45. package/template/dist/map/map.json +1 -1
  46. package/template/dist/review/review.html +3 -3
  47. package/template/dist/review/review.json +1 -1
  48. package/template/node_modules/.bin/tsc +2 -2
  49. package/template/node_modules/.bin/tsserver +2 -2
  50. package/template/node_modules/.bin/vitest +2 -2
  51. package/template/node_modules/.vite/deps/_metadata.json +3 -3
  52. package/template/node_modules/.vite-mcp/deps/_metadata.json +20 -20
  53. package/template/node_modules/.vite-mcp/deps/vitest.js +7 -7
  54. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  55. package/template/package.json +1 -1
  56. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-darwin.png +0 -0
  57. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-linux.png +0 -0
  58. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-darwin.png +0 -0
  59. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-linux.png +0 -0
  60. package/template/tsconfig.json +2 -0
  61. package/dist/inspector-BNWla95w.cjs.map +0 -1
  62. package/dist/inspector-CiuT_2yA.js.map +0 -1
  63. package/dist/use-app-DUdnDLP5.cjs.map +0 -1
  64. package/dist/use-app-Duar2Ipu.js.map +0 -1
@@ -3481,20 +3481,50 @@ var twMerge = /* @__PURE__ */ createTailwindMerge(getDefaultConfig);
3481
3481
  function cn(...inputs) {
3482
3482
  return twMerge(clsx(inputs));
3483
3483
  }
3484
+ function currentPageIsLoopback() {
3485
+ if (typeof window === "undefined") return true;
3486
+ return isLocalNetworkHostname(window.location.hostname);
3487
+ }
3488
+ function normalizeHostname(hostname) {
3489
+ return hostname.toLowerCase().replace(/^\[(.*)\]$/, "$1");
3490
+ }
3491
+ function isLocalNetworkHostname(hostname) {
3492
+ const host = normalizeHostname(hostname);
3493
+ if (host === "localhost" || host === "0.0.0.0" || host === "::1") return true;
3494
+ if (host.startsWith("127.")) return true;
3495
+ const ipv4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
3496
+ if (ipv4) {
3497
+ const octets = ipv4.slice(1).map(Number);
3498
+ if (octets.some((octet) => octet < 0 || octet > 255)) return false;
3499
+ const [a, b] = octets;
3500
+ return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
3501
+ }
3502
+ return host.startsWith("fc") || host.startsWith("fd") || host.startsWith("fe80:");
3503
+ }
3484
3504
  /**
3485
3505
  * Returns true when `icon` is safe to use as the `src` of an `<img>` rendered
3486
- * inside the inspector chrome. Accepts http(s) URLs and `data:image/*` URIs
3487
- * for raster image types only. SVG data URIs are rejected because they can
3488
- * include `<script>`/event handlers that execute when the document parses
3489
- * the inline document (the `<img>` tag itself does not run scripts in modern
3490
- * browsers, but adjacent <object>/<embed>/<iframe> renders would). Anything
3491
- * else (emoji, plain text, javascript:, file:, etc.) falls through to the
3492
- * text-rendering path that already handles emoji icons.
3506
+ * inside the inspector chrome. Accepts https URLs, local http URLs while the
3507
+ * inspector itself is running locally, and `data:image/*` URIs for raster image
3508
+ * types only. SVG data URIs are rejected because they can include
3509
+ * `<script>`/event handlers that execute when the document parses the inline
3510
+ * document (the `<img>` tag itself does not run scripts in modern browsers,
3511
+ * but adjacent <object>/<embed>/<iframe> renders would). Anything else (emoji,
3512
+ * plain text, javascript:, file:, etc.) falls through to the text-rendering
3513
+ * path that already handles emoji icons.
3493
3514
  */
3494
3515
  function isAllowedIconUrl(icon) {
3495
- if (icon.startsWith("https://") || icon.startsWith("http://")) return true;
3496
- if (icon.startsWith("data:image/png") || icon.startsWith("data:image/jpeg") || icon.startsWith("data:image/gif") || icon.startsWith("data:image/webp")) return true;
3497
- return false;
3516
+ if (/^data:image\/(?:png|jpeg|gif|webp)(?:[;,]|$)/i.test(icon)) return true;
3517
+ let url;
3518
+ try {
3519
+ url = new URL(icon);
3520
+ } catch {
3521
+ return false;
3522
+ }
3523
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
3524
+ const inspectorIsLocal = currentPageIsLoopback();
3525
+ if (isLocalNetworkHostname(url.hostname) && !inspectorIsLocal) return false;
3526
+ if (url.protocol === "http:" && !inspectorIsLocal) return false;
3527
+ return true;
3498
3528
  }
3499
3529
  //#endregion
3500
3530
  //#region src/inspector/hosts.ts
@@ -5992,16 +6022,12 @@ var SUNPEAK_INLINE_HELPER_SCRIPT = `
5992
6022
  //#region src/inspector/iframe-resource.tsx
5993
6023
  /**
5994
6024
  * Allowed origins for cross-origin script loading.
5995
- * - Local development: localhost, 127.0.0.1, file://
5996
6025
  * - Production: sunpeak-prod-app-storage.s3.us-east-2.amazonaws.com (serves user scripts)
6026
+ *
6027
+ * Loopback script URLs are handled separately in isAllowedUrl() so hosted
6028
+ * inspectors cannot be tricked into loading scripts from a visitor's machine.
5997
6029
  */
5998
- var ALLOWED_SCRIPT_ORIGINS = [
5999
- "https://sunpeak-prod-app-storage.s3.us-east-2.amazonaws.com",
6000
- "http://localhost",
6001
- "https://localhost",
6002
- "http://127.0.0.1",
6003
- "https://127.0.0.1"
6004
- ];
6030
+ var ALLOWED_SCRIPT_ORIGINS = ["https://sunpeak-prod-app-storage.s3.us-east-2.amazonaws.com"];
6005
6031
  /**
6006
6032
  * Escapes HTML special characters to prevent XSS via attribute injection.
6007
6033
  */
@@ -6539,6 +6565,9 @@ var VALID_SCREEN_WIDTHS = new Set([
6539
6565
  "tablet",
6540
6566
  "full"
6541
6567
  ]);
6568
+ function isSafeStoredString(value) {
6569
+ return typeof value === "string" && value.length <= 200 && !/[\u0000-\u001f\u007f]/.test(value);
6570
+ }
6542
6571
  function sanitizeStoredPrefs(raw) {
6543
6572
  if (!raw || typeof raw !== "object") return {};
6544
6573
  const obj = raw;
@@ -6546,6 +6575,8 @@ function sanitizeStoredPrefs(raw) {
6546
6575
  if (typeof obj.theme === "string" && VALID_THEMES.has(obj.theme)) prefs.theme = obj.theme;
6547
6576
  if (typeof obj.locale === "string") prefs.locale = obj.locale;
6548
6577
  if (typeof obj.displayMode === "string" && VALID_DISPLAY_MODES.has(obj.displayMode)) prefs.displayMode = obj.displayMode;
6578
+ if (typeof obj.containerHeight === "number" && Number.isFinite(obj.containerHeight)) prefs.containerHeight = obj.containerHeight;
6579
+ if (typeof obj.containerWidth === "number" && Number.isFinite(obj.containerWidth)) prefs.containerWidth = obj.containerWidth;
6549
6580
  if (typeof obj.containerMaxHeight === "number" && Number.isFinite(obj.containerMaxHeight)) prefs.containerMaxHeight = obj.containerMaxHeight;
6550
6581
  if (typeof obj.containerMaxWidth === "number" && Number.isFinite(obj.containerMaxWidth)) prefs.containerMaxWidth = obj.containerMaxWidth;
6551
6582
  if (obj.safeAreaInsets && typeof obj.safeAreaInsets === "object") {
@@ -6564,6 +6595,10 @@ function sanitizeStoredPrefs(raw) {
6564
6595
  if (typeof obj.screenWidth === "string" && VALID_SCREEN_WIDTHS.has(obj.screenWidth)) prefs.screenWidth = obj.screenWidth;
6565
6596
  if (typeof obj.sidebarWidth === "number" && Number.isFinite(obj.sidebarWidth)) prefs.sidebarWidth = Math.max(DEFAULT_SIDEBAR_WIDTH$1, Math.round(obj.sidebarWidth));
6566
6597
  if (typeof obj.rightSidebarWidth === "number" && Number.isFinite(obj.rightSidebarWidth)) prefs.rightSidebarWidth = Math.max(DEFAULT_SIDEBAR_WIDTH$1, Math.round(obj.rightSidebarWidth));
6598
+ if (typeof obj.timeZone === "string") prefs.timeZone = obj.timeZone;
6599
+ if (typeof obj.prodResources === "boolean") prefs.prodResources = obj.prodResources;
6600
+ if (isSafeStoredString(obj.modelProvider)) prefs.modelProvider = obj.modelProvider;
6601
+ if (isSafeStoredString(obj.modelId)) prefs.modelId = obj.modelId;
6567
6602
  return prefs;
6568
6603
  }
6569
6604
  function readStoredPrefs() {
@@ -6576,6 +6611,12 @@ function readStoredPrefs() {
6576
6611
  return {};
6577
6612
  }
6578
6613
  }
6614
+ function writeStoredPrefs(prefs) {
6615
+ if (typeof window === "undefined") return;
6616
+ try {
6617
+ localStorage.setItem(PREFS_KEY, JSON.stringify(prefs));
6618
+ } catch {}
6619
+ }
6579
6620
  function deriveContainerDimensions({ displayMode, containerHeight, containerWidth, containerMaxHeight, containerMaxWidth, measuredContentWidth, viewportHeight = 800, viewportWidth = 1280 }) {
6580
6621
  if (containerHeight != null || containerWidth != null || containerMaxHeight != null || containerMaxWidth != null) return {
6581
6622
  ...containerHeight != null ? { height: containerHeight } : {},
@@ -6624,8 +6665,8 @@ function useInspectorState({ simulations, defaultHost = "chatgpt", preserveToolD
6624
6665
  const [theme, setTheme] = (0, react.useState)(urlParams.theme ?? storedPrefs.theme ?? DEFAULT_THEME);
6625
6666
  const [displayMode, _setDisplayMode] = (0, react.useState)(urlParams.displayMode ?? storedPrefs.displayMode ?? DEFAULT_DISPLAY_MODE);
6626
6667
  const [locale, setLocale] = (0, react.useState)(urlParams.locale ?? storedPrefs.locale ?? "en-US");
6627
- const [containerHeight, setContainerHeight] = (0, react.useState)(void 0);
6628
- const [containerWidth, setContainerWidth] = (0, react.useState)(void 0);
6668
+ const [containerHeight, setContainerHeight] = (0, react.useState)(storedPrefs.containerHeight);
6669
+ const [containerWidth, setContainerWidth] = (0, react.useState)(storedPrefs.containerWidth);
6629
6670
  const [containerMaxHeight, setContainerMaxHeight] = (0, react.useState)(urlParams.containerMaxHeight ?? storedPrefs.containerMaxHeight);
6630
6671
  const [containerMaxWidth, setContainerMaxWidth] = (0, react.useState)(urlParams.containerMaxWidth ?? storedPrefs.containerMaxWidth);
6631
6672
  const [platform, setPlatform] = (0, react.useState)(urlParams.platform ?? storedPrefs.platform ?? DEFAULT_PLATFORM);
@@ -6637,7 +6678,7 @@ function useInspectorState({ simulations, defaultHost = "chatgpt", preserveToolD
6637
6678
  left: 0,
6638
6679
  right: 0
6639
6680
  });
6640
- const [timeZone, setTimeZone] = (0, react.useState)(() => Intl.DateTimeFormat().resolvedOptions().timeZone);
6681
+ const [timeZone, setTimeZone] = (0, react.useState)(() => storedPrefs.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone);
6641
6682
  const isFirstRender = (0, react.useRef)(true);
6642
6683
  (0, react.useEffect)(() => {
6643
6684
  if (isFirstRender.current) {
@@ -6645,29 +6686,32 @@ function useInspectorState({ simulations, defaultHost = "chatgpt", preserveToolD
6645
6686
  return;
6646
6687
  }
6647
6688
  if (autoRun) return;
6648
- try {
6649
- const prefs = {
6650
- theme,
6651
- locale,
6652
- displayMode,
6653
- containerMaxHeight,
6654
- containerMaxWidth,
6655
- safeAreaInsets,
6656
- activeHost,
6657
- platform,
6658
- hover,
6659
- touch,
6660
- screenWidth,
6661
- sidebarWidth,
6662
- rightSidebarWidth
6663
- };
6664
- localStorage.setItem(PREFS_KEY, JSON.stringify(prefs));
6665
- } catch {}
6689
+ writeStoredPrefs({
6690
+ ...readStoredPrefs(),
6691
+ theme,
6692
+ locale,
6693
+ displayMode,
6694
+ containerHeight,
6695
+ containerWidth,
6696
+ containerMaxHeight,
6697
+ containerMaxWidth,
6698
+ safeAreaInsets,
6699
+ activeHost,
6700
+ platform,
6701
+ hover,
6702
+ touch,
6703
+ screenWidth,
6704
+ sidebarWidth,
6705
+ rightSidebarWidth,
6706
+ timeZone
6707
+ });
6666
6708
  }, [
6667
6709
  autoRun,
6668
6710
  theme,
6669
6711
  locale,
6670
6712
  displayMode,
6713
+ containerHeight,
6714
+ containerWidth,
6671
6715
  containerMaxHeight,
6672
6716
  containerMaxWidth,
6673
6717
  safeAreaInsets,
@@ -6677,7 +6721,8 @@ function useInspectorState({ simulations, defaultHost = "chatgpt", preserveToolD
6677
6721
  touch,
6678
6722
  screenWidth,
6679
6723
  sidebarWidth,
6680
- rightSidebarWidth
6724
+ rightSidebarWidth,
6725
+ timeZone
6681
6726
  ]);
6682
6727
  const [measuredContentWidth, setMeasuredContentWidth] = (0, react.useState)(void 0);
6683
6728
  const handleContentWidthChange = (0, react.useCallback)((width) => {
@@ -7590,12 +7635,16 @@ var DOCS_BASE_URL = "https://sunpeak.ai/docs";
7590
7635
  var DEFAULT_MODEL_PROVIDERS = [{
7591
7636
  id: "openai",
7592
7637
  label: "OpenAI",
7593
- defaultModel: "gpt-4o"
7638
+ defaultModel: "gpt-5.5"
7594
7639
  }, {
7595
7640
  id: "anthropic",
7596
7641
  label: "Anthropic",
7597
- defaultModel: "claude-3-5-sonnet-20241022"
7642
+ defaultModel: "claude-sonnet-4-20250514"
7598
7643
  }];
7644
+ function createModelConversationId() {
7645
+ const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2);
7646
+ return `model-chat-${Date.now()}-${random}`;
7647
+ }
7599
7648
  function splitCssArgs(value) {
7600
7649
  const args = [];
7601
7650
  let depth = 0;
@@ -7688,6 +7737,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7688
7737
  autoRun: params.get("autoRun") === "true"
7689
7738
  };
7690
7739
  }, []);
7740
+ const storedPrefs = react.useMemo(() => initUrlParams.autoRun ? {} : readStoredPrefs(), [initUrlParams.autoRun]);
7691
7741
  const [selectedToolName, setSelectedToolName] = react.useState(() => {
7692
7742
  if (initUrlParams.tool && toolMap.has(initUrlParams.tool)) return initUrlParams.tool;
7693
7743
  if (initUrlParams.simulation) {
@@ -7737,7 +7787,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7737
7787
  const [oauthStatus, setOauthStatus] = react.useState("none");
7738
7788
  const [oauthError, setOauthError] = react.useState();
7739
7789
  const connection = useMcpConnection(isEmbedded ? void 0 : mcpServerUrl || void 0, inspectorApiBaseUrl);
7740
- const [prodResources, setProdResources] = react.useState(state.urlProdResources ?? defaultProdResources);
7790
+ const [prodResources, setProdResources] = react.useState(state.urlProdResources ?? storedPrefs.prodResources ?? defaultProdResources);
7741
7791
  const showSidebar = state.urlSidebar !== false;
7742
7792
  const showDevOverlay = state.urlDevOverlay !== false;
7743
7793
  const [isRunning, setIsRunning] = react.useState(false);
@@ -7759,6 +7809,15 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7759
7809
  if (requested && modelProviderOptions.some((provider) => provider.id === requested)) return requested;
7760
7810
  return modelProviderOptions[0]?.id ?? "openai";
7761
7811
  }, [modelChat?.defaultProvider, modelProviderOptions]);
7812
+ const initialModelProvider = react.useMemo(() => {
7813
+ const stored = storedPrefs.modelProvider;
7814
+ if (stored && modelProviderOptions.some((provider) => provider.id === stored)) return stored;
7815
+ return defaultModelProvider;
7816
+ }, [
7817
+ defaultModelProvider,
7818
+ modelProviderOptions,
7819
+ storedPrefs.modelProvider
7820
+ ]);
7762
7821
  const getDefaultModelId = react.useCallback((provider) => {
7763
7822
  const option = modelProviderOptions.find((item) => item.id === provider);
7764
7823
  const candidates = [
@@ -7770,8 +7829,20 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7770
7829
  if (providerModels.length === 0) return candidates.find(Boolean) ?? "";
7771
7830
  return candidates.find((model) => model && providerModels.includes(model)) ?? providerModels[0];
7772
7831
  }, [modelChat?.defaultModel, modelProviderOptions]);
7773
- const [modelProvider, setModelProvider] = react.useState(defaultModelProvider);
7774
- const [modelId, setModelId] = react.useState(() => getDefaultModelId(defaultModelProvider));
7832
+ const getInitialModelId = react.useCallback((provider) => {
7833
+ const stored = storedPrefs.modelId;
7834
+ if (storedPrefs.modelProvider && storedPrefs.modelProvider !== provider) return getDefaultModelId(provider);
7835
+ const providerModels = modelProviderOptions.find((item) => item.id === provider)?.models ?? [];
7836
+ if (stored && (providerModels.length === 0 || providerModels.includes(stored))) return stored;
7837
+ return getDefaultModelId(provider);
7838
+ }, [
7839
+ getDefaultModelId,
7840
+ modelProviderOptions,
7841
+ storedPrefs.modelId,
7842
+ storedPrefs.modelProvider
7843
+ ]);
7844
+ const [modelProvider, setModelProvider] = react.useState(initialModelProvider);
7845
+ const [modelId, setModelId] = react.useState(() => getInitialModelId(initialModelProvider));
7775
7846
  const [apiKeyDraft, setApiKeyDraft] = react.useState("");
7776
7847
  const [keyStatus, setKeyStatus] = react.useState({ hasKey: false });
7777
7848
  const [isKeyStatusLoading, setIsKeyStatusLoading] = react.useState(usesApiKeyUi);
@@ -7780,6 +7851,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7780
7851
  const [chatInput, setChatInput] = react.useState("");
7781
7852
  const [isChatting, setIsChatting] = react.useState(false);
7782
7853
  const [chatStatus, setChatStatus] = react.useState("");
7854
+ const modelConversationIdRef = react.useRef(createModelConversationId());
7783
7855
  const currentModelProvider = react.useMemo(() => modelProviderOptions.find((provider) => provider.id === modelProvider), [modelProvider, modelProviderOptions]);
7784
7856
  const selectedProviderModelOptions = react.useMemo(() => currentModelProvider?.models ?? [], [currentModelProvider?.models]);
7785
7857
  const modelCallableTools = react.useMemo(() => {
@@ -7790,6 +7862,25 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7790
7862
  }
7791
7863
  return Array.from(map.values());
7792
7864
  }, [simulations]);
7865
+ const isFirstInspectorPrefsRender = react.useRef(true);
7866
+ react.useEffect(() => {
7867
+ if (isFirstInspectorPrefsRender.current) {
7868
+ isFirstInspectorPrefsRender.current = false;
7869
+ return;
7870
+ }
7871
+ if (initUrlParams.autoRun) return;
7872
+ writeStoredPrefs({
7873
+ ...readStoredPrefs(),
7874
+ prodResources,
7875
+ modelProvider,
7876
+ modelId
7877
+ });
7878
+ }, [
7879
+ initUrlParams.autoRun,
7880
+ modelId,
7881
+ modelProvider,
7882
+ prodResources
7883
+ ]);
7793
7884
  react.useEffect(() => {
7794
7885
  setServerUrl(mcpServerUrl ?? "");
7795
7886
  }, [mcpServerUrl]);
@@ -7899,6 +7990,13 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7899
7990
  usesApiKeyUi,
7900
7991
  usesLocalModelEndpoints
7901
7992
  ]);
7993
+ const handleResetModelConversation = react.useCallback(() => {
7994
+ modelConversationIdRef.current = createModelConversationId();
7995
+ setChatMessages([]);
7996
+ setChatInput("");
7997
+ setChatStatus("");
7998
+ setIsChatting(false);
7999
+ }, []);
7902
8000
  react.useEffect(() => {
7903
8001
  state.setSelectedSimulationName(effectiveSimulationName);
7904
8002
  }, [effectiveSimulationName]);
@@ -8159,15 +8257,18 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8159
8257
  state.setToolResultJson("");
8160
8258
  state.setToolResultError("");
8161
8259
  setHasRun(false);
8260
+ const requestConversationId = modelConversationIdRef.current;
8162
8261
  try {
8163
8262
  const messages = nextMessages.map((message) => ({
8164
8263
  role: message.role,
8165
- content: message.content
8166
- }));
8264
+ content: message.content.trim()
8265
+ })).filter((message) => message.content.length > 0);
8167
8266
  let data;
8168
8267
  if (modelChatHandler) data = await modelChatHandler({
8268
+ conversationId: requestConversationId,
8169
8269
  provider: modelProvider,
8170
8270
  modelId,
8271
+ host: state.activeHost,
8171
8272
  messages,
8172
8273
  tools: modelCallableTools,
8173
8274
  appContext: state.modelAppContext ?? void 0
@@ -8178,8 +8279,10 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8178
8279
  method: "POST",
8179
8280
  headers: { "Content-Type": "application/json" },
8180
8281
  body: JSON.stringify({
8282
+ conversationId: requestConversationId,
8181
8283
  provider: modelProvider,
8182
8284
  modelId,
8285
+ host: state.activeHost,
8183
8286
  messages,
8184
8287
  appContext: state.modelAppContext ?? void 0
8185
8288
  })
@@ -8188,6 +8291,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8188
8291
  if (!res.ok || data.error) throw new Error(data.error ?? `Model request failed (${res.status})`);
8189
8292
  }
8190
8293
  if (data.error) throw new Error(data.error);
8294
+ if (requestConversationId !== modelConversationIdRef.current) return;
8191
8295
  let rendersApp = false;
8192
8296
  const toolCalls = data.toolCalls ?? [];
8193
8297
  for (let index = toolCalls.length - 1; index >= 0; index--) {
@@ -8225,6 +8329,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8225
8329
  });
8226
8330
  setChatStatus("");
8227
8331
  } catch (err) {
8332
+ if (requestConversationId !== modelConversationIdRef.current) return;
8228
8333
  const message = err instanceof Error ? err.message : String(err);
8229
8334
  setChatMessages((messages) => [...messages, {
8230
8335
  id: `assistant-error-${Date.now()}`,
@@ -8233,7 +8338,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8233
8338
  }]);
8234
8339
  setChatStatus(message);
8235
8340
  } finally {
8236
- setIsChatting(false);
8341
+ if (requestConversationId === modelConversationIdRef.current) setIsChatting(false);
8237
8342
  }
8238
8343
  }, [
8239
8344
  canUseModelChat,
@@ -8847,33 +8952,69 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8847
8952
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8848
8953
  className: "space-y-1",
8849
8954
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8850
- className: "grid grid-cols-2 gap-2",
8851
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
8852
- label: "Provider",
8853
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
8854
- value: modelProvider,
8855
- onChange: handleModelProviderChange,
8856
- options: modelProviderOptions.map((provider) => ({
8857
- value: provider.id,
8858
- label: provider.label ?? provider.id
8859
- }))
8860
- })
8861
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
8862
- label: "Model",
8863
- children: selectedProviderModelOptions.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
8864
- value: modelId,
8865
- onChange: setModelId,
8866
- options: selectedProviderModelOptions.map((model) => ({
8867
- value: model,
8868
- label: model
8869
- }))
8870
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
8871
- value: modelId,
8872
- onChange: setModelId,
8873
- applyOnBlur: true,
8874
- placeholder: getDefaultModelId(modelProvider)
8955
+ className: "grid grid-cols-[0.7fr_minmax(0,1fr)_1.75rem] items-end gap-2",
8956
+ children: [
8957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
8958
+ label: "Provider",
8959
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
8960
+ value: modelProvider,
8961
+ onChange: handleModelProviderChange,
8962
+ options: modelProviderOptions.map((provider) => ({
8963
+ value: provider.id,
8964
+ label: provider.label ?? provider.id
8965
+ }))
8966
+ })
8967
+ }),
8968
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
8969
+ label: "Model",
8970
+ children: selectedProviderModelOptions.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
8971
+ value: modelId,
8972
+ onChange: setModelId,
8973
+ options: selectedProviderModelOptions.map((model) => ({
8974
+ value: model,
8975
+ label: model
8976
+ }))
8977
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
8978
+ value: modelId,
8979
+ onChange: setModelId,
8980
+ applyOnBlur: true,
8981
+ placeholder: getDefaultModelId(modelProvider)
8982
+ })
8983
+ }),
8984
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8985
+ className: "group relative flex h-7 items-center self-end",
8986
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8987
+ type: "button",
8988
+ onClick: handleResetModelConversation,
8989
+ disabled: chatMessages.length === 0 && !isChatting && !chatStatus,
8990
+ "aria-label": "Reset model conversation",
8991
+ "aria-describedby": "reset-model-conversation-tooltip",
8992
+ title: "Reset conversation",
8993
+ className: "flex h-7 w-7 cursor-pointer items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-40",
8994
+ style: {
8995
+ backgroundColor: "var(--color-background-primary)",
8996
+ color: "var(--color-text-primary)"
8997
+ },
8998
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
8999
+ width: "18",
9000
+ height: "18",
9001
+ viewBox: "0 0 24 24",
9002
+ fill: "currentColor",
9003
+ "aria-hidden": "true",
9004
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-8 3.58-8 8s3.58 8 8 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h8V3z" })
9005
+ })
9006
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9007
+ id: "reset-model-conversation-tooltip",
9008
+ role: "tooltip",
9009
+ className: "pointer-events-none absolute right-0 top-full z-[1000] mt-1 hidden whitespace-nowrap rounded px-2 py-1 text-[11px] font-normal leading-tight group-focus-within:block group-hover:block",
9010
+ style: {
9011
+ backgroundColor: "var(--color-text-primary)",
9012
+ color: "var(--color-background-primary)"
9013
+ },
9014
+ children: "Reset conversation"
9015
+ })]
8875
9016
  })
8876
- })]
9017
+ ]
8877
9018
  }), usesApiKeyUi && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SidebarControl, {
8878
9019
  label: "API Key",
8879
9020
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -9404,4 +9545,4 @@ Object.defineProperty(exports, "useThemeContext", {
9404
9545
  }
9405
9546
  });
9406
9547
 
9407
- //# sourceMappingURL=inspector-BNWla95w.cjs.map
9548
+ //# sourceMappingURL=inspector-Chhc2GNO.cjs.map