@xom11/whiteboard 0.24.2 → 0.25.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.
Files changed (84) hide show
  1. package/README.md +84 -11
  2. package/dist/ai.d.mts +422 -566
  3. package/dist/ai.d.ts +422 -566
  4. package/dist/ai.js +1527 -407
  5. package/dist/ai.js.map +1 -1
  6. package/dist/ai.mjs +1008 -512
  7. package/dist/ai.mjs.map +1 -1
  8. package/dist/catalog.json +4 -4
  9. package/dist/{chunk-BKSXPNPQ.mjs → chunk-AYSFWUPK.mjs} +4 -3
  10. package/dist/chunk-AYSFWUPK.mjs.map +1 -0
  11. package/dist/chunk-B4NJJZFR.mjs +18 -0
  12. package/dist/chunk-B4NJJZFR.mjs.map +1 -0
  13. package/dist/{chunk-LVNCYP4J.mjs → chunk-CJBLJUWG.mjs} +5 -5
  14. package/dist/{chunk-LVNCYP4J.mjs.map → chunk-CJBLJUWG.mjs.map} +1 -1
  15. package/dist/{chunk-7WQXXEVR.mjs → chunk-ESVPQWHX.mjs} +5 -5
  16. package/dist/{chunk-7WQXXEVR.mjs.map → chunk-ESVPQWHX.mjs.map} +1 -1
  17. package/dist/{chunk-KRC2XOIG.mjs → chunk-I24QOHPU.mjs} +3 -3
  18. package/dist/{chunk-KRC2XOIG.mjs.map → chunk-I24QOHPU.mjs.map} +1 -1
  19. package/dist/{chunk-ZBJBQKJ2.mjs → chunk-IHUFOV7L.mjs} +4 -19
  20. package/dist/chunk-IHUFOV7L.mjs.map +1 -0
  21. package/dist/{chunk-AZIARTGX.mjs → chunk-M42TGYT6.mjs} +3 -3
  22. package/dist/{chunk-AZIARTGX.mjs.map → chunk-M42TGYT6.mjs.map} +1 -1
  23. package/dist/{chunk-45CGKJ7S.mjs → chunk-NDEZJKNY.mjs} +4 -4
  24. package/dist/{chunk-45CGKJ7S.mjs.map → chunk-NDEZJKNY.mjs.map} +1 -1
  25. package/dist/{chunk-BEZSQKPY.mjs → chunk-ONBCUWVI.mjs} +5 -4
  26. package/dist/chunk-ONBCUWVI.mjs.map +1 -0
  27. package/dist/{chunk-WM2VDYQA.mjs → chunk-REIJZDVZ.mjs} +4 -3
  28. package/dist/chunk-REIJZDVZ.mjs.map +1 -0
  29. package/dist/{chunk-2WF6KIGF.mjs → chunk-TB4CL25L.mjs} +9 -8
  30. package/dist/chunk-TB4CL25L.mjs.map +1 -0
  31. package/dist/chunk-VNCCIV6O.mjs +938 -0
  32. package/dist/chunk-VNCCIV6O.mjs.map +1 -0
  33. package/dist/{chunk-CGZZO4BX.mjs → chunk-VRHWDZ66.mjs} +5 -5
  34. package/dist/{chunk-CGZZO4BX.mjs.map → chunk-VRHWDZ66.mjs.map} +1 -1
  35. package/dist/{chunk-4DS3MKID.mjs → chunk-YSJOVBCD.mjs} +4 -4
  36. package/dist/{chunk-4DS3MKID.mjs.map → chunk-YSJOVBCD.mjs.map} +1 -1
  37. package/dist/geometry-2d.d.mts +2 -2
  38. package/dist/geometry-2d.d.ts +2 -2
  39. package/dist/geometry-2d.js +1383 -23
  40. package/dist/geometry-2d.js.map +1 -1
  41. package/dist/geometry-2d.mjs +6 -5
  42. package/dist/geometry-3d.d.mts +2 -2
  43. package/dist/geometry-3d.d.ts +2 -2
  44. package/dist/geometry-3d.js +2 -2
  45. package/dist/geometry-3d.js.map +1 -1
  46. package/dist/geometry-3d.mjs +5 -4
  47. package/dist/graph-2d.d.mts +2 -2
  48. package/dist/graph-2d.d.ts +2 -2
  49. package/dist/graph-2d.js +2 -2
  50. package/dist/graph-2d.js.map +1 -1
  51. package/dist/graph-2d.mjs +8 -7
  52. package/dist/{host-ZIQ77W33.mjs → host-A64ITWVX.mjs} +7 -6
  53. package/dist/host-A64ITWVX.mjs.map +1 -0
  54. package/dist/{host-EPZCNFLH.mjs → host-L7FMFZUW.mjs} +226 -29
  55. package/dist/host-L7FMFZUW.mjs.map +1 -0
  56. package/dist/{host-LKCMYEAV.mjs → host-QK53UYMD.mjs} +11 -10
  57. package/dist/host-QK53UYMD.mjs.map +1 -0
  58. package/dist/index.d.mts +3 -3
  59. package/dist/index.d.ts +3 -3
  60. package/dist/index.js +1414 -54
  61. package/dist/index.js.map +1 -1
  62. package/dist/index.mjs +18 -17
  63. package/dist/index.mjs.map +1 -1
  64. package/dist/latex.d.mts +2 -2
  65. package/dist/latex.d.ts +2 -2
  66. package/dist/render-3WTY7NZB.mjs +9 -0
  67. package/dist/{render-SA4JTOW3.mjs.map → render-3WTY7NZB.mjs.map} +1 -1
  68. package/dist/serialize-SRJVKYUG.mjs +8 -0
  69. package/dist/{serialize-JAVOU22E.mjs.map → serialize-SRJVKYUG.mjs.map} +1 -1
  70. package/dist/{types-vtvyKGAA.d.mts → types-DWRyCa2m.d.mts} +187 -2
  71. package/dist/{types-Crbefnfe.d.ts → types-DWRyCa2m.d.ts} +187 -2
  72. package/package.json +1 -1
  73. package/dist/chunk-2WF6KIGF.mjs.map +0 -1
  74. package/dist/chunk-BEZSQKPY.mjs.map +0 -1
  75. package/dist/chunk-BKSXPNPQ.mjs.map +0 -1
  76. package/dist/chunk-WM2VDYQA.mjs.map +0 -1
  77. package/dist/chunk-ZBJBQKJ2.mjs.map +0 -1
  78. package/dist/host-EPZCNFLH.mjs.map +0 -1
  79. package/dist/host-LKCMYEAV.mjs.map +0 -1
  80. package/dist/host-ZIQ77W33.mjs.map +0 -1
  81. package/dist/render-SA4JTOW3.mjs +0 -8
  82. package/dist/serialize-JAVOU22E.mjs +0 -7
  83. package/dist/types-DxlMPh-6.d.mts +0 -49
  84. package/dist/types-DxlMPh-6.d.ts +0 -49
@@ -1,18 +1,20 @@
1
1
  "use client";
2
2
  import { useChordShortcut } from './chunk-HNQLZIEP.mjs';
3
3
  import { useToolStateMachine } from './chunk-NVJ7K3DK.mjs';
4
- import { safeJsx, initJxgBoard, attachJxgWheelZoom, useToast, ToastHost, STAMP_PANEL_DESKTOP, ToastProvider, useStampStore, StampLeftPanel } from './chunk-2WF6KIGF.mjs';
5
- import { serializeBoard, renderGeometrySvgFromState, isGeometryCustomData, deserializeBoard } from './chunk-CGZZO4BX.mjs';
4
+ import { safeJsx, initJxgBoard, attachJxgWheelZoom, useToast, ToastHost, STAMP_PANEL_DESKTOP, ToastProvider, useStampStore, StampLeftPanel, ObjectRow } from './chunk-TB4CL25L.mjs';
5
+ import { serializeBoard, renderGeometrySvgFromState, isGeometryCustomData, deserializeBoard } from './chunk-VRHWDZ66.mjs';
6
6
  import { themeLabel, paletteFor, themeAxis, themeGrid } from './chunk-R5FL6S7L.mjs';
7
- import { JxgRenderer } from './chunk-BKSXPNPQ.mjs';
7
+ import { JxgRenderer } from './chunk-AYSFWUPK.mjs';
8
8
  import './chunk-ICR4CVOE.mjs';
9
- import { nextLabel, useEditorState, listObjects } from './chunk-WM2VDYQA.mjs';
10
- import './chunk-ZBJBQKJ2.mjs';
9
+ import { nextLabel, useEditorState, listObjects } from './chunk-REIJZDVZ.mjs';
10
+ import './chunk-IHUFOV7L.mjs';
11
+ import { describeDsl, serializeState } from './chunk-VNCCIV6O.mjs';
11
12
  import { DEFAULT_VIEW_2D } from './chunk-73Q7ADVL.mjs';
13
+ import './chunk-B4NJJZFR.mjs';
12
14
  import { useIsMobile } from './chunk-P2AOIF7S.mjs';
13
15
  import { insertStampImage } from './chunk-QGNU34T7.mjs';
14
16
  import './chunk-5UTGXHLJ.mjs';
15
- import { forwardRef, useRef, useId, useState, useEffect, useCallback, useImperativeHandle, useLayoutEffect, useMemo } from 'react';
17
+ import { forwardRef, useRef, useId, useState, useEffect, useCallback, useImperativeHandle, useSyncExternalStore, useMemo, useLayoutEffect } from 'react';
16
18
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
19
  import { createPortal } from 'react-dom';
18
20
 
@@ -2620,12 +2622,41 @@ var TransformParamPopover = ({ kind, anchor, defaultValue, onConfirm, onCancel,
2620
2622
  );
2621
2623
  return createPortal(node, document.body);
2622
2624
  };
2623
- function useAiFigure(generator) {
2625
+ function useAiFigure(generator, options = {}) {
2626
+ const { currentState } = options;
2624
2627
  const [prompt, setPrompt] = useState("");
2625
2628
  const [isLoading, setIsLoading] = useState(false);
2626
2629
  const [error, setError] = useState(null);
2630
+ const [tokens, setTokens] = useState(0);
2627
2631
  const abortRef = useRef(null);
2628
2632
  const requestIdRef = useRef(0);
2633
+ const { dsl: currentDsl, unsupported, entityCount, hasContent } = useMemo(() => {
2634
+ if (!currentState || currentState.order.length === 0) {
2635
+ return {
2636
+ dsl: null,
2637
+ unsupported: [],
2638
+ entityCount: { points: 0, shapes: 0 },
2639
+ hasContent: false
2640
+ };
2641
+ }
2642
+ const { dsl, unsupported: unsupported2 } = serializeState(currentState);
2643
+ return {
2644
+ dsl,
2645
+ unsupported: unsupported2,
2646
+ entityCount: { points: dsl.points.length, shapes: dsl.shapes.length },
2647
+ hasContent: true
2648
+ };
2649
+ }, [currentState]);
2650
+ const hasUnsupported = unsupported.length > 0;
2651
+ const initialMode = hasContent && !hasUnsupported ? "refine" : "build";
2652
+ const [mode, setModeInternal] = useState(initialMode);
2653
+ useEffect(() => {
2654
+ if (!hasContent && mode === "refine") setModeInternal("build");
2655
+ if (hasUnsupported && mode === "refine") setModeInternal("build");
2656
+ }, [hasContent, hasUnsupported, mode]);
2657
+ const setMode = useCallback((next) => {
2658
+ setModeInternal(next);
2659
+ }, []);
2629
2660
  useEffect(() => () => abortRef.current?.abort(), []);
2630
2661
  const submit = useCallback(async () => {
2631
2662
  const problem = prompt.trim();
@@ -2643,8 +2674,15 @@ function useAiFigure(generator) {
2643
2674
  abortRef.current = controller;
2644
2675
  setIsLoading(true);
2645
2676
  setError(null);
2677
+ setTokens(0);
2646
2678
  try {
2647
- const generated = await generator(problem, { signal: controller.signal });
2679
+ const generated = await generator(problem, {
2680
+ signal: controller.signal,
2681
+ onProgress: (info) => {
2682
+ if (requestId === requestIdRef.current) setTokens(info.tokens);
2683
+ },
2684
+ ...mode === "refine" && currentDsl ? { currentDsl } : {}
2685
+ });
2648
2686
  if (controller.signal.aborted || requestId !== requestIdRef.current) return null;
2649
2687
  if (!generated.ok) {
2650
2688
  setError(generated.message);
@@ -2656,7 +2694,9 @@ function useAiFigure(generator) {
2656
2694
  return null;
2657
2695
  }
2658
2696
  if (requestId === requestIdRef.current) {
2659
- setError(caught instanceof Error && caught.message ? caught.message : "Kh\xF4ng th\u1EC3 d\u1EF1ng h\xECnh b\u1EB1ng AI.");
2697
+ setError(
2698
+ caught instanceof Error && caught.message ? caught.message : "Kh\xF4ng th\u1EC3 d\u1EF1ng h\xECnh b\u1EB1ng AI."
2699
+ );
2660
2700
  }
2661
2701
  return null;
2662
2702
  } finally {
@@ -2665,22 +2705,81 @@ function useAiFigure(generator) {
2665
2705
  setIsLoading(false);
2666
2706
  }
2667
2707
  }
2668
- }, [generator, prompt]);
2669
- return { prompt, setPrompt, isLoading, error, submit };
2708
+ }, [generator, prompt, mode, currentDsl]);
2709
+ const cancel = useCallback(() => {
2710
+ abortRef.current?.abort();
2711
+ }, []);
2712
+ return {
2713
+ prompt,
2714
+ setPrompt,
2715
+ isLoading,
2716
+ error,
2717
+ submit,
2718
+ cancel,
2719
+ tokens,
2720
+ mode,
2721
+ setMode,
2722
+ entityCount,
2723
+ hasUnsupported
2724
+ };
2670
2725
  }
2671
- function AiFigurePrompt({ generator, onGenerated }) {
2726
+ var BUILD_EXAMPLES = [
2727
+ "Tam gi\xE1c ABC, d\u1EF1ng trung \u0111i\u1EC3m M c\u1EE7a BC",
2728
+ "Tam gi\xE1c ABC vu\xF4ng t\u1EA1i A, AH l\xE0 \u0111\u01B0\u1EDDng cao xu\u1ED1ng BC",
2729
+ "H\xECnh thoi ABCD, hai \u0111\u01B0\u1EDDng ch\xE9o c\u1EAFt nhau t\u1EA1i O",
2730
+ "T\u1EEB \u0111i\u1EC3m M ngo\xE0i \u0111\u01B0\u1EDDng tr\xF2n (O), k\u1EBB hai ti\u1EBFp tuy\u1EBFn"
2731
+ ];
2732
+ var REFINE_EXAMPLES = [
2733
+ "Th\xEAm trung \u0111i\u1EC3m M c\u1EE7a BC",
2734
+ "D\u1EF1ng \u0111\u01B0\u1EDDng cao AH xu\u1ED1ng BC",
2735
+ "V\u1EBD \u0111\u01B0\u1EDDng tr\xF2n ngo\u1EA1i ti\u1EBFp",
2736
+ "Th\xEAm ti\u1EBFp tuy\u1EBFn t\u1EA1i A"
2737
+ ];
2738
+ function AiFigurePrompt({ generator, onGenerated, currentState }) {
2672
2739
  const {
2673
2740
  prompt,
2674
2741
  setPrompt,
2675
2742
  isLoading,
2676
2743
  error,
2677
- submit
2678
- } = useAiFigure(generator);
2679
- const handleSubmit = useCallback(async (event) => {
2680
- event.preventDefault();
2681
- const generated = await submit();
2682
- if (generated) onGenerated(generated);
2683
- }, [onGenerated, submit]);
2744
+ submit,
2745
+ cancel,
2746
+ tokens,
2747
+ mode,
2748
+ setMode,
2749
+ entityCount,
2750
+ hasUnsupported
2751
+ } = useAiFigure(generator, { currentState });
2752
+ const [elapsed, setElapsed] = useState(0);
2753
+ useEffect(() => {
2754
+ if (!isLoading) {
2755
+ setElapsed(0);
2756
+ return;
2757
+ }
2758
+ setElapsed(0);
2759
+ const id = setInterval(() => setElapsed((s) => s + 1), 1e3);
2760
+ return () => clearInterval(id);
2761
+ }, [isLoading]);
2762
+ const handleSubmit = useCallback(
2763
+ async (event) => {
2764
+ event.preventDefault();
2765
+ const generated = await submit();
2766
+ if (generated) onGenerated(generated);
2767
+ },
2768
+ [onGenerated, submit]
2769
+ );
2770
+ const handleSwitchToBuild = useCallback(() => {
2771
+ if (currentState && currentState.order.length > 0) {
2772
+ const ok = window.confirm(
2773
+ "D\u1EF1ng m\u1EDBi s\u1EBD thay to\xE0n b\u1ED9 h\xECnh hi\u1EC7n t\u1EA1i b\u1EB1ng h\xECnh m\u1EDBi t\u1EEB AI. Ti\u1EBFp t\u1EE5c?"
2774
+ );
2775
+ if (!ok) return;
2776
+ }
2777
+ setMode("build");
2778
+ }, [currentState, setMode]);
2779
+ const primaryLabel = isLoading ? tokens > 0 ? `\u0110ang d\u1EF1ng ${tokens}tok / ${elapsed}s \u2014 Hu\u1EF7` : `\u0110ang d\u1EF1ng... ${elapsed}s \u2014 Hu\u1EF7` : "D\u1EF1ng b\u1EB1ng AI";
2780
+ const hasContent = currentState != null && currentState.order.length > 0;
2781
+ const examples = mode === "refine" ? REFINE_EXAMPLES : BUILD_EXAMPLES;
2782
+ const refineChipLabel = entityCount.points + entityCount.shapes > 0 ? `Th\xEAm v\xE0o \xB7 ${entityCount.points}\u0111, ${entityCount.shapes}\u0111o\u1EA1n` : "Th\xEAm v\xE0o";
2684
2783
  return /* @__PURE__ */ jsxs(
2685
2784
  "form",
2686
2785
  {
@@ -2690,7 +2789,47 @@ function AiFigurePrompt({ generator, onGenerated }) {
2690
2789
  },
2691
2790
  className: "border-b border-slate-200 bg-slate-50 px-3 py-2",
2692
2791
  children: [
2693
- /* @__PURE__ */ jsx("label", { htmlFor: "geometry-ai-prompt", className: "mb-1 block text-xs font-medium text-slate-600", children: "D\u1EF1ng h\xECnh b\u1EB1ng AI" }),
2792
+ /* @__PURE__ */ jsx(
2793
+ "label",
2794
+ {
2795
+ htmlFor: "geometry-ai-prompt",
2796
+ className: "mb-1 block text-xs font-medium text-slate-600",
2797
+ children: "D\u1EF1ng h\xECnh b\u1EB1ng AI"
2798
+ }
2799
+ ),
2800
+ hasContent && /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2", children: [
2801
+ /* @__PURE__ */ jsx(
2802
+ "button",
2803
+ {
2804
+ type: "button",
2805
+ "data-testid": "geometry-ai-mode-refine",
2806
+ onClick: () => setMode("refine"),
2807
+ disabled: isLoading || hasUnsupported,
2808
+ className: `rounded-full border px-2 py-0.5 text-[11px] transition ${mode === "refine" ? "border-emerald-600 bg-emerald-100 text-emerald-800" : "border-slate-300 bg-white text-slate-600 hover:border-emerald-400"} ${hasUnsupported ? "cursor-not-allowed opacity-50" : ""}`,
2809
+ title: hasUnsupported ? "H\xECnh hi\u1EC7n t\u1EA1i c\xF3 \u0111\u1ED1i t\u01B0\u1EE3ng ngo\xE0i DSL \u2014 ch\u1EC9 d\u1EF1ng m\u1EDBi \u0111\u01B0\u1EE3c" : refineChipLabel,
2810
+ children: refineChipLabel
2811
+ }
2812
+ ),
2813
+ /* @__PURE__ */ jsx(
2814
+ "button",
2815
+ {
2816
+ type: "button",
2817
+ "data-testid": "geometry-ai-mode-build",
2818
+ onClick: handleSwitchToBuild,
2819
+ disabled: isLoading,
2820
+ className: `rounded-full border px-2 py-0.5 text-[11px] transition ${mode === "build" ? "border-emerald-600 bg-emerald-100 text-emerald-800" : "border-slate-300 bg-white text-slate-600 hover:border-emerald-400"}`,
2821
+ children: "D\u1EF1ng m\u1EDBi"
2822
+ }
2823
+ ),
2824
+ hasUnsupported && /* @__PURE__ */ jsx(
2825
+ "span",
2826
+ {
2827
+ className: "text-[10px] text-amber-700",
2828
+ "data-testid": "geometry-ai-unsupported-warning",
2829
+ children: "H\xECnh c\xF3 \u0111\u1ED1i t\u01B0\u1EE3ng ngo\xE0i DSL"
2830
+ }
2831
+ )
2832
+ ] }),
2694
2833
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
2695
2834
  /* @__PURE__ */ jsx(
2696
2835
  "textarea",
@@ -2701,21 +2840,42 @@ function AiFigurePrompt({ generator, onGenerated }) {
2701
2840
  onChange: (event) => setPrompt(event.target.value),
2702
2841
  disabled: isLoading,
2703
2842
  rows: 2,
2704
- placeholder: "V\xED d\u1EE5: Cho tam gi\xE1c ABC, d\u1EF1ng \u0111\u01B0\u1EDDng cao AH.",
2843
+ placeholder: mode === "refine" ? "V\xED d\u1EE5: th\xEAm trung \u0111i\u1EC3m M c\u1EE7a BC" : "V\xED d\u1EE5: Cho tam gi\xE1c ABC, d\u1EF1ng \u0111\u01B0\u1EDDng cao AH.",
2705
2844
  className: "min-h-12 flex-1 resize-none rounded border border-slate-300 bg-white px-2 py-1.5 text-xs text-slate-800 outline-none focus:border-emerald-500 disabled:opacity-60"
2706
2845
  }
2707
2846
  ),
2708
- /* @__PURE__ */ jsx(
2847
+ isLoading ? /* @__PURE__ */ jsx(
2848
+ "button",
2849
+ {
2850
+ type: "button",
2851
+ onClick: cancel,
2852
+ className: "rounded bg-amber-600 px-3 py-2 text-xs font-medium text-white transition hover:bg-amber-700",
2853
+ children: primaryLabel
2854
+ }
2855
+ ) : /* @__PURE__ */ jsx(
2709
2856
  "button",
2710
2857
  {
2711
2858
  type: "submit",
2712
- disabled: isLoading || !prompt.trim(),
2859
+ disabled: !prompt.trim(),
2713
2860
  className: "rounded bg-emerald-600 px-3 py-2 text-xs font-medium text-white transition hover:bg-emerald-700 disabled:opacity-50",
2714
- children: isLoading ? "\u0110ang d\u1EF1ng..." : "D\u1EF1ng b\u1EB1ng AI"
2861
+ children: primaryLabel
2715
2862
  }
2716
2863
  )
2717
2864
  ] }),
2718
- error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-xs text-red-600", children: error })
2865
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-xs text-red-600", children: error }),
2866
+ !isLoading && !prompt.trim() && !error && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1", children: [
2867
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-slate-500", children: "G\u1EE3i \xFD:" }),
2868
+ examples.map((ex) => /* @__PURE__ */ jsx(
2869
+ "button",
2870
+ {
2871
+ type: "button",
2872
+ onClick: () => setPrompt(ex),
2873
+ className: "rounded-full border border-slate-300 bg-white px-2 py-0.5 text-[10px] text-slate-600 transition hover:border-emerald-400 hover:bg-emerald-50 hover:text-emerald-700",
2874
+ children: ex
2875
+ },
2876
+ ex
2877
+ ))
2878
+ ] })
2719
2879
  ]
2720
2880
  }
2721
2881
  );
@@ -2752,6 +2912,11 @@ var GeometryEditorPanelInner = forwardRef(
2752
2912
  onSelectionChangeRef.current = onSelectionChange;
2753
2913
  }, [onSelectionChange]);
2754
2914
  useEditorState({ store, onHistoryChange });
2915
+ const currentSceneState = useSyncExternalStore(
2916
+ (cb) => store.subscribe(cb),
2917
+ () => store.getState(),
2918
+ () => store.getState()
2919
+ );
2755
2920
  useEffect(() => {
2756
2921
  const sync = () => setHasContent(Object.keys(store.getState().objects).length > 0);
2757
2922
  sync();
@@ -2945,7 +3110,7 @@ var GeometryEditorPanelInner = forwardRef(
2945
3110
  /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
2946
3111
  ] }) })
2947
3112
  ] }),
2948
- generateGeometryFigure && /* @__PURE__ */ jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure }),
3113
+ generateGeometryFigure && /* @__PURE__ */ jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure, currentState: currentSceneState }),
2949
3114
  /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1", children: /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
2950
3115
  MiniBoard2D,
2951
3116
  {
@@ -3070,6 +3235,36 @@ var GeometryEditorPanel = forwardRef(
3070
3235
  return /* @__PURE__ */ jsx(ToastProvider, { children: /* @__PURE__ */ jsx(GeometryEditorPanelInner, { ...props, ref }) });
3071
3236
  }
3072
3237
  );
3238
+ function makeDslRenderRow(store) {
3239
+ return function renderDslRow(obj, defaults) {
3240
+ const state = store.getState();
3241
+ const noop = () => {
3242
+ };
3243
+ return /* @__PURE__ */ jsx(
3244
+ ObjectRow,
3245
+ {
3246
+ obj,
3247
+ state,
3248
+ selected: defaults.selected,
3249
+ onSelect: defaults.onClick,
3250
+ onToggleVisible: (id) => {
3251
+ const o = state.objects[id];
3252
+ if (!o) return;
3253
+ store.dispatch({ type: "UPDATE", payload: { id, patch: { visible: !o.visible } } });
3254
+ },
3255
+ onToggleLocked: (id) => {
3256
+ const o = state.objects[id];
3257
+ if (!o) return;
3258
+ store.dispatch({ type: "UPDATE", payload: { id, patch: { locked: !o.locked } } });
3259
+ },
3260
+ onRename: noop,
3261
+ onChangeColor: noop,
3262
+ onDelete: (id) => store.dispatch({ type: "DELETE", payload: { id } }),
3263
+ describe: describeDsl
3264
+ }
3265
+ );
3266
+ };
3267
+ }
3073
3268
  function parseInitialState(data) {
3074
3269
  if (!isGeometryCustomData(data)) return null;
3075
3270
  return deserializeBoard(data.jsonState);
@@ -3100,6 +3295,7 @@ var GeometryStampHost = forwardRef(
3100
3295
  onSelect: (key) => setSelectedTool(key),
3101
3296
  enabled: !isMobile
3102
3297
  });
3298
+ const renderRow = useMemo(() => makeDslRenderRow(sceneStore), [sceneStore]);
3103
3299
  const handleInsert = useCallback(
3104
3300
  async (jsonState, svgString) => {
3105
3301
  if (!api) return;
@@ -3161,7 +3357,8 @@ var GeometryStampHost = forwardRef(
3161
3357
  onObjectSelect: (id) => {
3162
3358
  setSelectedObjectId(id ?? void 0);
3163
3359
  panelRef.current?.selectObject(id);
3164
- }
3360
+ },
3361
+ renderRow
3165
3362
  },
3166
3363
  isMobile,
3167
3364
  drawerOpen,
@@ -3196,5 +3393,5 @@ var GeometryStampHost = forwardRef(
3196
3393
  );
3197
3394
 
3198
3395
  export { GeometryStampHost };
3199
- //# sourceMappingURL=host-EPZCNFLH.mjs.map
3200
- //# sourceMappingURL=host-EPZCNFLH.mjs.map
3396
+ //# sourceMappingURL=host-L7FMFZUW.mjs.map
3397
+ //# sourceMappingURL=host-L7FMFZUW.mjs.map