@xom11/whiteboard 0.24.1 → 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 (85) hide show
  1. package/README.md +85 -12
  2. package/dist/ai.d.mts +472 -0
  3. package/dist/ai.d.ts +472 -0
  4. package/dist/ai.js +2156 -0
  5. package/dist/ai.js.map +1 -0
  6. package/dist/ai.mjs +1495 -0
  7. package/dist/ai.mjs.map +1 -0
  8. package/dist/catalog.json +4 -4
  9. package/dist/chunk-73Q7ADVL.mjs +35 -0
  10. package/dist/chunk-73Q7ADVL.mjs.map +1 -0
  11. package/dist/{chunk-BKSXPNPQ.mjs → chunk-AYSFWUPK.mjs} +4 -3
  12. package/dist/chunk-AYSFWUPK.mjs.map +1 -0
  13. package/dist/chunk-B4NJJZFR.mjs +18 -0
  14. package/dist/chunk-B4NJJZFR.mjs.map +1 -0
  15. package/dist/{chunk-LVNCYP4J.mjs → chunk-CJBLJUWG.mjs} +5 -5
  16. package/dist/{chunk-LVNCYP4J.mjs.map → chunk-CJBLJUWG.mjs.map} +1 -1
  17. package/dist/{chunk-YIPI3WUL.mjs → chunk-ESVPQWHX.mjs} +5 -5
  18. package/dist/{chunk-YIPI3WUL.mjs.map → chunk-ESVPQWHX.mjs.map} +1 -1
  19. package/dist/{chunk-IBTRMWD6.mjs → chunk-I24QOHPU.mjs} +3 -3
  20. package/dist/{chunk-IBTRMWD6.mjs.map → chunk-I24QOHPU.mjs.map} +1 -1
  21. package/dist/{chunk-ZBJBQKJ2.mjs → chunk-IHUFOV7L.mjs} +4 -19
  22. package/dist/chunk-IHUFOV7L.mjs.map +1 -0
  23. package/dist/{chunk-AZIARTGX.mjs → chunk-M42TGYT6.mjs} +3 -3
  24. package/dist/{chunk-AZIARTGX.mjs.map → chunk-M42TGYT6.mjs.map} +1 -1
  25. package/dist/{chunk-WWMQ2VHZ.mjs → chunk-NDEZJKNY.mjs} +4 -4
  26. package/dist/{chunk-WWMQ2VHZ.mjs.map → chunk-NDEZJKNY.mjs.map} +1 -1
  27. package/dist/{chunk-CSCF3YFZ.mjs → chunk-ONBCUWVI.mjs} +6 -4
  28. package/dist/chunk-ONBCUWVI.mjs.map +1 -0
  29. package/dist/{chunk-6V4SH4JJ.mjs → chunk-REIJZDVZ.mjs} +6 -35
  30. package/dist/chunk-REIJZDVZ.mjs.map +1 -0
  31. package/dist/{chunk-4D5CSIJO.mjs → chunk-TB4CL25L.mjs} +10 -8
  32. package/dist/chunk-TB4CL25L.mjs.map +1 -0
  33. package/dist/chunk-VNCCIV6O.mjs +938 -0
  34. package/dist/chunk-VNCCIV6O.mjs.map +1 -0
  35. package/dist/{chunk-MFOGFFIL.mjs → chunk-VRHWDZ66.mjs} +6 -5
  36. package/dist/chunk-VRHWDZ66.mjs.map +1 -0
  37. package/dist/{chunk-CRAPWQKJ.mjs → chunk-YSJOVBCD.mjs} +4 -4
  38. package/dist/{chunk-CRAPWQKJ.mjs.map → chunk-YSJOVBCD.mjs.map} +1 -1
  39. package/dist/geometry-2d.d.mts +2 -1
  40. package/dist/geometry-2d.d.ts +2 -1
  41. package/dist/geometry-2d.js +1383 -23
  42. package/dist/geometry-2d.js.map +1 -1
  43. package/dist/geometry-2d.mjs +7 -5
  44. package/dist/geometry-3d.d.mts +2 -1
  45. package/dist/geometry-3d.d.ts +2 -1
  46. package/dist/geometry-3d.js +2 -2
  47. package/dist/geometry-3d.js.map +1 -1
  48. package/dist/geometry-3d.mjs +6 -4
  49. package/dist/graph-2d.d.mts +2 -1
  50. package/dist/graph-2d.d.ts +2 -1
  51. package/dist/graph-2d.js +2 -2
  52. package/dist/graph-2d.js.map +1 -1
  53. package/dist/graph-2d.mjs +9 -7
  54. package/dist/{host-TLIXN4CF.mjs → host-A64ITWVX.mjs} +8 -6
  55. package/dist/host-A64ITWVX.mjs.map +1 -0
  56. package/dist/{host-DOAYVL35.mjs → host-L7FMFZUW.mjs} +228 -30
  57. package/dist/host-L7FMFZUW.mjs.map +1 -0
  58. package/dist/{host-GKNQBBUE.mjs → host-QK53UYMD.mjs} +12 -10
  59. package/dist/host-QK53UYMD.mjs.map +1 -0
  60. package/dist/index.d.mts +4 -616
  61. package/dist/index.d.ts +4 -616
  62. package/dist/index.js +8889 -8529
  63. package/dist/index.js.map +1 -1
  64. package/dist/index.mjs +21 -1012
  65. package/dist/index.mjs.map +1 -1
  66. package/dist/latex.d.mts +2 -1
  67. package/dist/latex.d.ts +2 -1
  68. package/dist/render-3WTY7NZB.mjs +9 -0
  69. package/dist/{render-SA4JTOW3.mjs.map → render-3WTY7NZB.mjs.map} +1 -1
  70. package/dist/serialize-SRJVKYUG.mjs +8 -0
  71. package/dist/{serialize-3NZS6A6Q.mjs.map → serialize-SRJVKYUG.mjs.map} +1 -1
  72. package/dist/{types-rA4slL08.d.ts → types-DWRyCa2m.d.mts} +139 -1
  73. package/dist/{types-rA4slL08.d.mts → types-DWRyCa2m.d.ts} +139 -1
  74. package/package.json +6 -1
  75. package/dist/chunk-4D5CSIJO.mjs.map +0 -1
  76. package/dist/chunk-6V4SH4JJ.mjs.map +0 -1
  77. package/dist/chunk-BKSXPNPQ.mjs.map +0 -1
  78. package/dist/chunk-CSCF3YFZ.mjs.map +0 -1
  79. package/dist/chunk-MFOGFFIL.mjs.map +0 -1
  80. package/dist/chunk-ZBJBQKJ2.mjs.map +0 -1
  81. package/dist/host-DOAYVL35.mjs.map +0 -1
  82. package/dist/host-GKNQBBUE.mjs.map +0 -1
  83. package/dist/host-TLIXN4CF.mjs.map +0 -1
  84. package/dist/render-SA4JTOW3.mjs +0 -8
  85. package/dist/serialize-3NZS6A6Q.mjs +0 -6
@@ -1,17 +1,20 @@
1
1
  "use client";
2
- import { useToolStateMachine } from './chunk-NVJ7K3DK.mjs';
3
- import { serializeBoard, renderGeometrySvgFromState, isGeometryCustomData, deserializeBoard } from './chunk-MFOGFFIL.mjs';
4
- import { JxgRenderer } from './chunk-BKSXPNPQ.mjs';
5
2
  import { useChordShortcut } from './chunk-HNQLZIEP.mjs';
6
- import { safeJsx, initJxgBoard, attachJxgWheelZoom, useToast, ToastHost, STAMP_PANEL_DESKTOP, ToastProvider, useStampStore, StampLeftPanel } from './chunk-4D5CSIJO.mjs';
3
+ import { useToolStateMachine } from './chunk-NVJ7K3DK.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';
7
6
  import { themeLabel, paletteFor, themeAxis, themeGrid } from './chunk-R5FL6S7L.mjs';
7
+ import { JxgRenderer } from './chunk-AYSFWUPK.mjs';
8
8
  import './chunk-ICR4CVOE.mjs';
9
- import { DEFAULT_VIEW_2D, nextLabel, useEditorState, listObjects } from './chunk-6V4SH4JJ.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';
12
+ import { DEFAULT_VIEW_2D } from './chunk-73Q7ADVL.mjs';
13
+ import './chunk-B4NJJZFR.mjs';
11
14
  import { useIsMobile } from './chunk-P2AOIF7S.mjs';
12
15
  import { insertStampImage } from './chunk-QGNU34T7.mjs';
13
16
  import './chunk-5UTGXHLJ.mjs';
14
- 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';
15
18
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
16
19
  import { createPortal } from 'react-dom';
17
20
 
@@ -2619,12 +2622,41 @@ var TransformParamPopover = ({ kind, anchor, defaultValue, onConfirm, onCancel,
2619
2622
  );
2620
2623
  return createPortal(node, document.body);
2621
2624
  };
2622
- function useAiFigure(generator) {
2625
+ function useAiFigure(generator, options = {}) {
2626
+ const { currentState } = options;
2623
2627
  const [prompt, setPrompt] = useState("");
2624
2628
  const [isLoading, setIsLoading] = useState(false);
2625
2629
  const [error, setError] = useState(null);
2630
+ const [tokens, setTokens] = useState(0);
2626
2631
  const abortRef = useRef(null);
2627
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
+ }, []);
2628
2660
  useEffect(() => () => abortRef.current?.abort(), []);
2629
2661
  const submit = useCallback(async () => {
2630
2662
  const problem = prompt.trim();
@@ -2642,8 +2674,15 @@ function useAiFigure(generator) {
2642
2674
  abortRef.current = controller;
2643
2675
  setIsLoading(true);
2644
2676
  setError(null);
2677
+ setTokens(0);
2645
2678
  try {
2646
- 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
+ });
2647
2686
  if (controller.signal.aborted || requestId !== requestIdRef.current) return null;
2648
2687
  if (!generated.ok) {
2649
2688
  setError(generated.message);
@@ -2655,7 +2694,9 @@ function useAiFigure(generator) {
2655
2694
  return null;
2656
2695
  }
2657
2696
  if (requestId === requestIdRef.current) {
2658
- 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
+ );
2659
2700
  }
2660
2701
  return null;
2661
2702
  } finally {
@@ -2664,22 +2705,81 @@ function useAiFigure(generator) {
2664
2705
  setIsLoading(false);
2665
2706
  }
2666
2707
  }
2667
- }, [generator, prompt]);
2668
- 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
+ };
2669
2725
  }
2670
- 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 }) {
2671
2739
  const {
2672
2740
  prompt,
2673
2741
  setPrompt,
2674
2742
  isLoading,
2675
2743
  error,
2676
- submit
2677
- } = useAiFigure(generator);
2678
- const handleSubmit = useCallback(async (event) => {
2679
- event.preventDefault();
2680
- const generated = await submit();
2681
- if (generated) onGenerated(generated);
2682
- }, [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";
2683
2783
  return /* @__PURE__ */ jsxs(
2684
2784
  "form",
2685
2785
  {
@@ -2689,7 +2789,47 @@ function AiFigurePrompt({ generator, onGenerated }) {
2689
2789
  },
2690
2790
  className: "border-b border-slate-200 bg-slate-50 px-3 py-2",
2691
2791
  children: [
2692
- /* @__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
+ ] }),
2693
2833
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
2694
2834
  /* @__PURE__ */ jsx(
2695
2835
  "textarea",
@@ -2700,21 +2840,42 @@ function AiFigurePrompt({ generator, onGenerated }) {
2700
2840
  onChange: (event) => setPrompt(event.target.value),
2701
2841
  disabled: isLoading,
2702
2842
  rows: 2,
2703
- 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.",
2704
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"
2705
2845
  }
2706
2846
  ),
2707
- /* @__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(
2708
2856
  "button",
2709
2857
  {
2710
2858
  type: "submit",
2711
- disabled: isLoading || !prompt.trim(),
2859
+ disabled: !prompt.trim(),
2712
2860
  className: "rounded bg-emerald-600 px-3 py-2 text-xs font-medium text-white transition hover:bg-emerald-700 disabled:opacity-50",
2713
- children: isLoading ? "\u0110ang d\u1EF1ng..." : "D\u1EF1ng b\u1EB1ng AI"
2861
+ children: primaryLabel
2714
2862
  }
2715
2863
  )
2716
2864
  ] }),
2717
- 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
+ ] })
2718
2879
  ]
2719
2880
  }
2720
2881
  );
@@ -2751,6 +2912,11 @@ var GeometryEditorPanelInner = forwardRef(
2751
2912
  onSelectionChangeRef.current = onSelectionChange;
2752
2913
  }, [onSelectionChange]);
2753
2914
  useEditorState({ store, onHistoryChange });
2915
+ const currentSceneState = useSyncExternalStore(
2916
+ (cb) => store.subscribe(cb),
2917
+ () => store.getState(),
2918
+ () => store.getState()
2919
+ );
2754
2920
  useEffect(() => {
2755
2921
  const sync = () => setHasContent(Object.keys(store.getState().objects).length > 0);
2756
2922
  sync();
@@ -2944,7 +3110,7 @@ var GeometryEditorPanelInner = forwardRef(
2944
3110
  /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
2945
3111
  ] }) })
2946
3112
  ] }),
2947
- generateGeometryFigure && /* @__PURE__ */ jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure }),
3113
+ generateGeometryFigure && /* @__PURE__ */ jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure, currentState: currentSceneState }),
2948
3114
  /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1", children: /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
2949
3115
  MiniBoard2D,
2950
3116
  {
@@ -3069,6 +3235,36 @@ var GeometryEditorPanel = forwardRef(
3069
3235
  return /* @__PURE__ */ jsx(ToastProvider, { children: /* @__PURE__ */ jsx(GeometryEditorPanelInner, { ...props, ref }) });
3070
3236
  }
3071
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
+ }
3072
3268
  function parseInitialState(data) {
3073
3269
  if (!isGeometryCustomData(data)) return null;
3074
3270
  return deserializeBoard(data.jsonState);
@@ -3099,6 +3295,7 @@ var GeometryStampHost = forwardRef(
3099
3295
  onSelect: (key) => setSelectedTool(key),
3100
3296
  enabled: !isMobile
3101
3297
  });
3298
+ const renderRow = useMemo(() => makeDslRenderRow(sceneStore), [sceneStore]);
3102
3299
  const handleInsert = useCallback(
3103
3300
  async (jsonState, svgString) => {
3104
3301
  if (!api) return;
@@ -3160,7 +3357,8 @@ var GeometryStampHost = forwardRef(
3160
3357
  onObjectSelect: (id) => {
3161
3358
  setSelectedObjectId(id ?? void 0);
3162
3359
  panelRef.current?.selectObject(id);
3163
- }
3360
+ },
3361
+ renderRow
3164
3362
  },
3165
3363
  isMobile,
3166
3364
  drawerOpen,
@@ -3195,5 +3393,5 @@ var GeometryStampHost = forwardRef(
3195
3393
  );
3196
3394
 
3197
3395
  export { GeometryStampHost };
3198
- //# sourceMappingURL=host-DOAYVL35.mjs.map
3199
- //# sourceMappingURL=host-DOAYVL35.mjs.map
3396
+ //# sourceMappingURL=host-L7FMFZUW.mjs.map
3397
+ //# sourceMappingURL=host-L7FMFZUW.mjs.map