@xom11/whiteboard 0.29.0 → 0.31.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 (93) hide show
  1. package/dist/ai.d.mts +259 -33
  2. package/dist/ai.d.ts +259 -33
  3. package/dist/ai.js +5424 -470
  4. package/dist/ai.js.map +1 -1
  5. package/dist/ai.mjs +4971 -351
  6. package/dist/ai.mjs.map +1 -1
  7. package/dist/catalog.json +5 -5
  8. package/dist/{chunk-V3YJ6JFL.mjs → chunk-44JY2AKC.mjs} +3 -3
  9. package/dist/{chunk-V3YJ6JFL.mjs.map → chunk-44JY2AKC.mjs.map} +1 -1
  10. package/dist/{chunk-GEC2D2EQ.mjs → chunk-BMYC2ILT.mjs} +4 -4
  11. package/dist/{chunk-GEC2D2EQ.mjs.map → chunk-BMYC2ILT.mjs.map} +1 -1
  12. package/dist/{chunk-PPKHCRRE.mjs → chunk-C76SOFXF.mjs} +3 -3
  13. package/dist/{chunk-PPKHCRRE.mjs.map → chunk-C76SOFXF.mjs.map} +1 -1
  14. package/dist/{chunk-IHUFOV7L.mjs → chunk-CH6SFONH.mjs} +15 -3
  15. package/dist/chunk-CH6SFONH.mjs.map +1 -0
  16. package/dist/{chunk-E6EDOPGT.mjs → chunk-DWIEVCGK.mjs} +254 -16
  17. package/dist/chunk-DWIEVCGK.mjs.map +1 -0
  18. package/dist/{chunk-SZDAS7LK.mjs → chunk-IE2GGHNF.mjs} +131 -81
  19. package/dist/chunk-IE2GGHNF.mjs.map +1 -0
  20. package/dist/{chunk-ZTQBUKLJ.mjs → chunk-JJ4FPCBE.mjs} +142 -22
  21. package/dist/chunk-JJ4FPCBE.mjs.map +1 -0
  22. package/dist/{chunk-QRUAEXLR.mjs → chunk-K5BS2H56.mjs} +5 -5
  23. package/dist/{chunk-QRUAEXLR.mjs.map → chunk-K5BS2H56.mjs.map} +1 -1
  24. package/dist/{chunk-BNBOIDO5.mjs → chunk-K7VJU7LQ.mjs} +3 -3
  25. package/dist/{chunk-BNBOIDO5.mjs.map → chunk-K7VJU7LQ.mjs.map} +1 -1
  26. package/dist/{chunk-H22OZYTW.mjs → chunk-KOXOC2FI.mjs} +48 -39
  27. package/dist/chunk-KOXOC2FI.mjs.map +1 -0
  28. package/dist/{chunk-CXHNVYMD.mjs → chunk-KWDBVLST.mjs} +5 -5
  29. package/dist/{chunk-CXHNVYMD.mjs.map → chunk-KWDBVLST.mjs.map} +1 -1
  30. package/dist/{chunk-OQIQNKPQ.mjs → chunk-LTLLQUMN.mjs} +4 -4
  31. package/dist/{chunk-OQIQNKPQ.mjs.map → chunk-LTLLQUMN.mjs.map} +1 -1
  32. package/dist/chunk-QK6OVDLC.mjs +103 -0
  33. package/dist/chunk-QK6OVDLC.mjs.map +1 -0
  34. package/dist/{chunk-QGNU34T7.mjs → chunk-QLQ4MJNO.mjs} +10 -4
  35. package/dist/chunk-QLQ4MJNO.mjs.map +1 -0
  36. package/dist/{chunk-BU5KLO3P.mjs → chunk-T3N4BSJV.mjs} +4 -4
  37. package/dist/{chunk-BU5KLO3P.mjs.map → chunk-T3N4BSJV.mjs.map} +1 -1
  38. package/dist/{chunk-5JM35CXV.mjs → chunk-TMRFSOM7.mjs} +4 -4
  39. package/dist/{chunk-5JM35CXV.mjs.map → chunk-TMRFSOM7.mjs.map} +1 -1
  40. package/dist/geometry-2d.d.mts +1 -1
  41. package/dist/geometry-2d.d.ts +1 -1
  42. package/dist/geometry-2d.js +841 -204
  43. package/dist/geometry-2d.js.map +1 -1
  44. package/dist/geometry-2d.mjs +5 -5
  45. package/dist/geometry-3d.d.mts +1 -1
  46. package/dist/geometry-3d.d.ts +1 -1
  47. package/dist/geometry-3d.js +172 -22
  48. package/dist/geometry-3d.js.map +1 -1
  49. package/dist/geometry-3d.mjs +4 -4
  50. package/dist/graph-2d.d.mts +1 -1
  51. package/dist/graph-2d.d.ts +1 -1
  52. package/dist/graph-2d.js +307 -100
  53. package/dist/graph-2d.js.map +1 -1
  54. package/dist/graph-2d.mjs +7 -7
  55. package/dist/handleExtractProblem-BrDY9ifM.d.mts +58 -0
  56. package/dist/handleExtractProblem-BrDY9ifM.d.ts +58 -0
  57. package/dist/{host-HOSJHQ5H.mjs → host-4FIUNIDQ.mjs} +13 -12
  58. package/dist/host-4FIUNIDQ.mjs.map +1 -0
  59. package/dist/{host-2ISGVO7O.mjs → host-4ZB4XD4S.mjs} +9 -8
  60. package/dist/host-4ZB4XD4S.mjs.map +1 -0
  61. package/dist/{host-ZQCDAT6O.mjs → host-H2IGOKJU.mjs} +3 -3
  62. package/dist/{host-ZQCDAT6O.mjs.map → host-H2IGOKJU.mjs.map} +1 -1
  63. package/dist/{host-HKMZSCIT.mjs → host-KMWP7KBT.mjs} +286 -74
  64. package/dist/host-KMWP7KBT.mjs.map +1 -0
  65. package/dist/index.d.mts +3 -2
  66. package/dist/index.d.ts +3 -2
  67. package/dist/index.js +849 -206
  68. package/dist/index.js.map +1 -1
  69. package/dist/index.mjs +22 -21
  70. package/dist/index.mjs.map +1 -1
  71. package/dist/latex.d.mts +1 -1
  72. package/dist/latex.d.ts +1 -1
  73. package/dist/latex.js +8 -2
  74. package/dist/latex.js.map +1 -1
  75. package/dist/latex.mjs +1 -1
  76. package/dist/render-NMS7OAV6.mjs +10 -0
  77. package/dist/{render-ZX2O2IK7.mjs.map → render-NMS7OAV6.mjs.map} +1 -1
  78. package/dist/serialize-PGHQZEPV.mjs +9 -0
  79. package/dist/{serialize-N4G6RFBB.mjs.map → serialize-PGHQZEPV.mjs.map} +1 -1
  80. package/dist/{types-C3FjpoUi.d.ts → types-tePd94vW.d.mts} +8 -0
  81. package/dist/{types-C3FjpoUi.d.mts → types-tePd94vW.d.ts} +8 -0
  82. package/package.json +2 -1
  83. package/dist/chunk-E6EDOPGT.mjs.map +0 -1
  84. package/dist/chunk-H22OZYTW.mjs.map +0 -1
  85. package/dist/chunk-IHUFOV7L.mjs.map +0 -1
  86. package/dist/chunk-QGNU34T7.mjs.map +0 -1
  87. package/dist/chunk-SZDAS7LK.mjs.map +0 -1
  88. package/dist/chunk-ZTQBUKLJ.mjs.map +0 -1
  89. package/dist/host-2ISGVO7O.mjs.map +0 -1
  90. package/dist/host-HKMZSCIT.mjs.map +0 -1
  91. package/dist/host-HOSJHQ5H.mjs.map +0 -1
  92. package/dist/render-ZX2O2IK7.mjs +0 -10
  93. package/dist/serialize-N4G6RFBB.mjs +0 -9
@@ -1,18 +1,19 @@
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, ObjectRow } from './chunk-OQIQNKPQ.mjs';
5
- import { serializeBoard, renderGeometrySvgFromState, isGeometryCustomData, deserializeBoard } from './chunk-H22OZYTW.mjs';
4
+ import { safeJsx, initJxgBoard, attachJxgWheelZoom, useToast, ToastHost, STAMP_PANEL_DESKTOP, ToastProvider, useStampStore, StampLeftPanel, ObjectRow } from './chunk-LTLLQUMN.mjs';
5
+ import { isDefaultBbox, autoFitBoardToContent, serializeBoard, renderGeometrySvgFromState, isGeometryCustomData, deserializeBoard } from './chunk-KOXOC2FI.mjs';
6
6
  import { themeLabel, paletteFor, themeAxis, themeGrid } from './chunk-R5FL6S7L.mjs';
7
- import { JxgRenderer } from './chunk-SZDAS7LK.mjs';
7
+ import { JxgRenderer } from './chunk-IE2GGHNF.mjs';
8
8
  import './chunk-ICR4CVOE.mjs';
9
- import { nextLabel, useEditorState, listObjects } from './chunk-ZTQBUKLJ.mjs';
10
- import './chunk-IHUFOV7L.mjs';
11
- import { describeDsl } from './chunk-E6EDOPGT.mjs';
9
+ import { nextLabel, useEditorState, listObjects } from './chunk-JJ4FPCBE.mjs';
10
+ import './chunk-CH6SFONH.mjs';
11
+ import { validateFile, fileToImagePart, describeDsl } from './chunk-DWIEVCGK.mjs';
12
+ import { handleExtractProblem } from './chunk-QK6OVDLC.mjs';
12
13
  import { DEFAULT_VIEW_2D } from './chunk-73Q7ADVL.mjs';
13
14
  import './chunk-B4NJJZFR.mjs';
14
15
  import { useIsMobile } from './chunk-P2AOIF7S.mjs';
15
- import { insertStampImage } from './chunk-QGNU34T7.mjs';
16
+ import { insertStampImage } from './chunk-QLQ4MJNO.mjs';
16
17
  import './chunk-5UTGXHLJ.mjs';
17
18
  import { __export } from './chunk-J5LGTIGS.mjs';
18
19
  import { forwardRef, useRef, useId, useState, useEffect, useCallback, useImperativeHandle, useMemo, useLayoutEffect } from 'react';
@@ -330,41 +331,48 @@ var Icon = {
330
331
  /* @__PURE__ */ jsx("circle", { cx: "4", cy: "4", r: "1.7", fill: C_POINT })
331
332
  ] }),
332
333
  // ===== Nâng cao / kind chưa có icon =====
333
- excenter: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
334
- /* @__PURE__ */ jsx("path", { d: "M5 16 L12 5 L19 16 Z", stroke: "currentColor", strokeWidth: "1.3" }),
335
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "20", r: "3", fill: "none", stroke: C_CONSTRUCT, strokeWidth: "1.3" }),
336
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "20", r: "1.4", fill: C_CONSTRUCT })
334
+ // Hệ màu đồng bộ: XANH = input click sẵn, ĐỎ = output suy ra. Toạ độ tính để
335
+ // điểm nằm ĐÚNG trên đường tròn/cung/tiếp tuyến (không trôi nổi).
336
+ excenter: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", children: [
337
+ /* @__PURE__ */ jsx("polygon", { points: "12,3 15,15 9,15", stroke: "currentColor", strokeWidth: "1.4", fill: "none" }),
338
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "15", x2: "8", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
339
+ /* @__PURE__ */ jsx("line", { x1: "15", y1: "15", x2: "16", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
340
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "18.9", r: "3.8", stroke: "currentColor", strokeWidth: "1.3" }),
341
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "3", r: "1.6", fill: C_POINT }),
342
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "15", r: "1.6", fill: C_POINT }),
343
+ /* @__PURE__ */ jsx("circle", { cx: "15", cy: "15", r: "1.6", fill: C_POINT }),
344
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "18.9", r: "2.2", fill: C_CONSTRUCT })
337
345
  ] }),
338
- tangencyPoint: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
339
- /* @__PURE__ */ jsx("circle", { cx: "10", cy: "12", r: "6.5", stroke: "currentColor", strokeWidth: "1.3" }),
340
- /* @__PURE__ */ jsx("line", { x1: "16.5", y1: "3", x2: "16.5", y2: "21", stroke: "currentColor", strokeWidth: "1.3" }),
341
- /* @__PURE__ */ jsx("circle", { cx: "16.5", cy: "12", r: "2.2", fill: C_CONSTRUCT })
346
+ tangencyPoint: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
347
+ /* @__PURE__ */ jsx("circle", { cx: "9.5", cy: "12", r: "7", stroke: "currentColor", strokeWidth: "1.4" }),
348
+ /* @__PURE__ */ jsx("line", { x1: "16.5", y1: "3.5", x2: "16.5", y2: "20.5", stroke: "currentColor", strokeWidth: "1.4" }),
349
+ /* @__PURE__ */ jsx("circle", { cx: "16.5", cy: "12", r: "2.3", fill: C_CONSTRUCT })
342
350
  ] }),
343
- secondIntersection: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
344
- /* @__PURE__ */ jsx("circle", { cx: "11", cy: "12", r: "6.5", stroke: "currentColor", strokeWidth: "1.3" }),
345
- /* @__PURE__ */ jsx("line", { x1: "2", y1: "8", x2: "22", y2: "16", stroke: "currentColor", strokeWidth: "1.3" }),
346
- /* @__PURE__ */ jsx("circle", { cx: "6.2", cy: "9.6", r: "1.6", fill: "currentColor" }),
347
- /* @__PURE__ */ jsx("circle", { cx: "15.8", cy: "14.4", r: "2.4", fill: C_CONSTRUCT })
351
+ secondIntersection: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
352
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "12", r: "6.5", stroke: "currentColor", strokeWidth: "1.4" }),
353
+ /* @__PURE__ */ jsx("line", { x1: "2.4", y1: "8.2", x2: "20.4", y2: "11.4", stroke: "currentColor", strokeWidth: "1.4" }),
354
+ /* @__PURE__ */ jsx("circle", { cx: "5.37", cy: "8.75", r: "1.8", fill: C_POINT }),
355
+ /* @__PURE__ */ jsx("circle", { cx: "17.4", cy: "10.87", r: "2.4", fill: C_CONSTRUCT })
348
356
  ] }),
349
357
  arcMidpoint: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
350
- /* @__PURE__ */ jsx("path", { d: "M4 17 A 9 9 0 0 1 20 17", stroke: "currentColor", strokeWidth: "1.4" }),
351
- /* @__PURE__ */ jsx("circle", { cx: "4", cy: "17", r: "1.6", fill: "currentColor" }),
352
- /* @__PURE__ */ jsx("circle", { cx: "20", cy: "17", r: "1.6", fill: "currentColor" }),
353
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8.2", r: "2.4", fill: C_CONSTRUCT })
358
+ /* @__PURE__ */ jsx("path", { d: "M4 17 A 8.5 8.5 0 0 1 20 17", stroke: "currentColor", strokeWidth: "1.6", fill: "none" }),
359
+ /* @__PURE__ */ jsx("circle", { cx: "4", cy: "17", r: "1.8", fill: C_POINT }),
360
+ /* @__PURE__ */ jsx("circle", { cx: "20", cy: "17", r: "1.8", fill: C_POINT }),
361
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "11.4", r: "2.3", fill: C_CONSTRUCT })
354
362
  ] }),
355
363
  circleIntersection: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
356
- /* @__PURE__ */ jsx("circle", { cx: "9", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.3" }),
357
- /* @__PURE__ */ jsx("circle", { cx: "15", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.3" }),
358
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "7.6", r: "2", fill: C_CONSTRUCT }),
359
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "16.4", r: "2", fill: C_CONSTRUCT })
364
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.4" }),
365
+ /* @__PURE__ */ jsx("circle", { cx: "15", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.4" }),
366
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "6.8", r: "2", fill: C_CONSTRUCT }),
367
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "17.2", r: "2", fill: C_CONSTRUCT })
360
368
  ] }),
361
369
  tangentPointExt: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
362
- /* @__PURE__ */ jsx("circle", { cx: "14", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.3" }),
363
- /* @__PURE__ */ jsx("circle", { cx: "3.5", cy: "12", r: "1.8", fill: C_POINT }),
364
- /* @__PURE__ */ jsx("line", { x1: "3.5", y1: "12", x2: "17.5", y2: "7.5", stroke: "currentColor", strokeWidth: "1.2" }),
365
- /* @__PURE__ */ jsx("line", { x1: "3.5", y1: "12", x2: "17.5", y2: "16.5", stroke: "currentColor", strokeWidth: "1.2" }),
366
- /* @__PURE__ */ jsx("circle", { cx: "17.5", cy: "7.5", r: "1.8", fill: C_CONSTRUCT }),
367
- /* @__PURE__ */ jsx("circle", { cx: "17.5", cy: "16.5", r: "1.8", fill: C_CONSTRUCT })
370
+ /* @__PURE__ */ jsx("circle", { cx: "13", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.4" }),
371
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "11.8", y2: "18.6", stroke: "currentColor", strokeWidth: "1.3" }),
372
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "11.8", y2: "5.4", stroke: "currentColor", strokeWidth: "1.3" }),
373
+ /* @__PURE__ */ jsx("circle", { cx: "3", cy: "12", r: "2", fill: C_POINT }),
374
+ /* @__PURE__ */ jsx("circle", { cx: "9.4", cy: "16.8", r: "1.8", fill: C_CONSTRUCT }),
375
+ /* @__PURE__ */ jsx("circle", { cx: "9.4", cy: "7.2", r: "1.8", fill: C_CONSTRUCT })
368
376
  ] }),
369
377
  circleCR: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
370
378
  /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "7.5", stroke: "currentColor", strokeWidth: "1.3" }),
@@ -375,9 +383,11 @@ var Icon = {
375
383
  /* @__PURE__ */ jsx("path", { d: "M3 19 L12 4 L21 19 Z", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }),
376
384
  /* @__PURE__ */ jsx("circle", { cx: "12", cy: "14", r: "4.2", fill: "none", stroke: C_CONSTRUCT, strokeWidth: "1.3" })
377
385
  ] }),
378
- excircle: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
379
- /* @__PURE__ */ jsx("path", { d: "M6 9 L14 4 L18 13 Z", stroke: "currentColor", strokeWidth: "1.2" }),
380
- /* @__PURE__ */ jsx("circle", { cx: "9", cy: "17", r: "4.6", fill: "none", stroke: C_CONSTRUCT, strokeWidth: "1.3" })
386
+ excircle: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", children: [
387
+ /* @__PURE__ */ jsx("polygon", { points: "12,3 15,15 9,15", stroke: "currentColor", strokeWidth: "1.4", fill: "none" }),
388
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "15", x2: "8", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
389
+ /* @__PURE__ */ jsx("line", { x1: "15", y1: "15", x2: "16", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
390
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "18.9", r: "3.8", stroke: C_CONSTRUCT, strokeWidth: "1.5", fill: C_CONSTRUCT, fillOpacity: "0.12" })
381
391
  ] }),
382
392
  pointOn: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
383
393
  /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "7.5", stroke: "currentColor", strokeWidth: "1.3" }),
@@ -2122,11 +2132,11 @@ function buildObjectSnapshot(state, id, anchorScreen) {
2122
2132
  const obj = state.objects[id];
2123
2133
  if (!obj) return null;
2124
2134
  const k = obj.kind;
2125
- if (k !== "point" && k !== "line" && k !== "circle" && k !== "segment" && k !== "ray" && k !== "vector") {
2135
+ if (k !== "point" && k !== "line" && k !== "circle" && k !== "segment" && k !== "ray" && k !== "vector" && k !== "intersection") {
2126
2136
  return null;
2127
2137
  }
2128
2138
  const a = obj.attrs;
2129
- const jKind = k === "point" ? "point" : k === "circle" ? "circle" : "line";
2139
+ const jKind = k === "point" || k === "intersection" ? "point" : k === "circle" ? "circle" : "line";
2130
2140
  return {
2131
2141
  id,
2132
2142
  kind: jKind,
@@ -2702,6 +2712,11 @@ var MiniBoard2D = forwardRef(function MiniBoard2D2({ onReady, store, selectedToo
2702
2712
  rendererRef.current = new JxgRenderer(store, board, {
2703
2713
  theme: paletteFor(isDarkRef.current)
2704
2714
  });
2715
+ if (isDefaultBbox(initialView.bbox)) {
2716
+ const el = containerRef.current;
2717
+ const aspect = el && el.clientHeight > 0 ? el.clientWidth / el.clientHeight : 1;
2718
+ safeJsx("MiniBoard.autoFit", () => autoFitBoardToContent(board, aspect));
2719
+ }
2705
2720
  if (containerRef.current) {
2706
2721
  wheelCleanup = attachJxgWheelZoom(containerRef.current, board, "MiniBoard.2d");
2707
2722
  }
@@ -3271,6 +3286,7 @@ function useAiFigure(generator) {
3271
3286
  const [prompt, setPrompt] = useState("");
3272
3287
  const [isLoading, setIsLoading] = useState(false);
3273
3288
  const [error, setError] = useState(null);
3289
+ const [notice, setNotice] = useState(null);
3274
3290
  const [tokens, setTokens] = useState(0);
3275
3291
  const abortRef = useRef(null);
3276
3292
  const requestIdRef = useRef(0);
@@ -3291,6 +3307,7 @@ function useAiFigure(generator) {
3291
3307
  abortRef.current = controller;
3292
3308
  setIsLoading(true);
3293
3309
  setError(null);
3310
+ setNotice(null);
3294
3311
  setTokens(0);
3295
3312
  try {
3296
3313
  const generated = await generator(problem, {
@@ -3304,6 +3321,7 @@ function useAiFigure(generator) {
3304
3321
  setError(generated.message);
3305
3322
  return null;
3306
3323
  }
3324
+ if (generated.partial) setNotice(generated.partial.message);
3307
3325
  return generated.state;
3308
3326
  } catch (caught) {
3309
3327
  if (controller.signal.aborted || caught instanceof DOMException && caught.name === "AbortError") {
@@ -3330,6 +3348,7 @@ function useAiFigure(generator) {
3330
3348
  setPrompt,
3331
3349
  isLoading,
3332
3350
  error,
3351
+ notice,
3333
3352
  submit,
3334
3353
  cancel,
3335
3354
  tokens
@@ -3353,12 +3372,27 @@ var ArrowUpIcon = (props) => /* @__PURE__ */ jsxs(
3353
3372
  }
3354
3373
  );
3355
3374
  var StopIcon = (props) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) });
3375
+ var PaperclipIcon = (props) => /* @__PURE__ */ jsx(
3376
+ "svg",
3377
+ {
3378
+ viewBox: "0 0 24 24",
3379
+ fill: "none",
3380
+ stroke: "currentColor",
3381
+ strokeWidth: 1.75,
3382
+ strokeLinecap: "round",
3383
+ strokeLinejoin: "round",
3384
+ "aria-hidden": true,
3385
+ ...props,
3386
+ children: /* @__PURE__ */ jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" })
3387
+ }
3388
+ );
3356
3389
  function AiFigurePrompt({ generator, onGenerated }) {
3357
3390
  const {
3358
3391
  prompt,
3359
3392
  setPrompt,
3360
3393
  isLoading,
3361
3394
  error,
3395
+ notice,
3362
3396
  submit,
3363
3397
  cancel,
3364
3398
  tokens
@@ -3372,20 +3406,136 @@ function AiFigurePrompt({ generator, onGenerated }) {
3372
3406
  const id = setInterval(() => setElapsed((s) => s + 1), 1e3);
3373
3407
  return () => clearInterval(id);
3374
3408
  }, [isLoading]);
3409
+ const [noticeDismissed, setNoticeDismissed] = useState(false);
3410
+ useEffect(() => {
3411
+ setNoticeDismissed(false);
3412
+ }, [notice]);
3413
+ const [image, setImage] = useState(null);
3414
+ const [ocrLoading, setOcrLoading] = useState(false);
3415
+ const [ocrError, setOcrError] = useState(null);
3416
+ const [ocrWarning, setOcrWarning] = useState(null);
3417
+ const [isDragOver, setIsDragOver] = useState(false);
3418
+ const fileInputRef = useRef(null);
3375
3419
  const textareaRef = useRef(null);
3420
+ const imagePreview = image ? `data:${image.mediaType};base64,${image.base64}` : null;
3421
+ useEffect(() => {
3422
+ setOcrError(null);
3423
+ setOcrWarning(null);
3424
+ }, [image]);
3425
+ const handleFile = useCallback(
3426
+ async (file) => {
3427
+ if (isLoading || ocrLoading) return;
3428
+ const v = validateFile(file);
3429
+ if (!v.ok) {
3430
+ setOcrError(v.message);
3431
+ return;
3432
+ }
3433
+ try {
3434
+ const part = await fileToImagePart(file);
3435
+ setImage(part);
3436
+ } catch (e) {
3437
+ setOcrError(e instanceof Error ? e.message : "Kh\xF4ng decode \u0111\u01B0\u1EE3c \u1EA3nh");
3438
+ }
3439
+ },
3440
+ [isLoading, ocrLoading]
3441
+ );
3442
+ const handleFileInput = useCallback(
3443
+ (e) => {
3444
+ const file = e.target.files?.[0];
3445
+ if (file) void handleFile(file);
3446
+ e.target.value = "";
3447
+ },
3448
+ [handleFile]
3449
+ );
3450
+ const handlePaste = useCallback(
3451
+ (e) => {
3452
+ const item = Array.from(e.clipboardData.items).find(
3453
+ (it) => it.kind === "file" && it.type.startsWith("image/")
3454
+ );
3455
+ if (!item) return;
3456
+ const file = item.getAsFile();
3457
+ if (!file) return;
3458
+ e.preventDefault();
3459
+ void handleFile(file);
3460
+ },
3461
+ [handleFile]
3462
+ );
3463
+ const handleDrop = useCallback(
3464
+ (e) => {
3465
+ e.preventDefault();
3466
+ setIsDragOver(false);
3467
+ const file = Array.from(e.dataTransfer.files).find(
3468
+ (f) => f.type.startsWith("image/")
3469
+ );
3470
+ if (file) void handleFile(file);
3471
+ },
3472
+ [handleFile]
3473
+ );
3474
+ const runOcr = useCallback(async () => {
3475
+ if (!image) return;
3476
+ setOcrLoading(true);
3477
+ setOcrError(null);
3478
+ setOcrWarning(null);
3479
+ try {
3480
+ const r = await handleExtractProblem(image);
3481
+ if (r.kind === "success" || r.kind === "low-confidence") {
3482
+ setPrompt(r.text);
3483
+ if (r.kind === "low-confidence") setOcrWarning(r.warning);
3484
+ requestAnimationFrame(() => textareaRef.current?.focus());
3485
+ } else {
3486
+ setOcrError(r.message);
3487
+ }
3488
+ } finally {
3489
+ setOcrLoading(false);
3490
+ }
3491
+ }, [image, setPrompt]);
3376
3492
  const handleSendClick = useCallback(async () => {
3493
+ if (image && !prompt.trim() && !ocrLoading) {
3494
+ await runOcr();
3495
+ return;
3496
+ }
3377
3497
  const generated = await submit();
3378
3498
  if (generated) onGenerated(generated);
3379
- }, [submit, onGenerated]);
3499
+ }, [image, prompt, ocrLoading, runOcr, submit, onGenerated]);
3380
3500
  const promptEmpty = !prompt.trim();
3381
- const sendDisabled = promptEmpty || isLoading;
3501
+ const willOcr = image != null && promptEmpty;
3502
+ const sendDisabled = !image && promptEmpty || ocrLoading || isLoading && !willOcr;
3503
+ const placeholder = willOcr ? "B\u1EA5m g\u1EEDi \u0111\u1EC3 \u0111\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh \u2014 ho\u1EB7c t\u1EF1 g\xF5 \u1EDF \u0111\xE2y\u2026" : "M\xF4 t\u1EA3 \u0111\u1EC1 b\xE0i c\u1EA7n d\u1EF1ng \u2014 ho\u1EB7c d\xE1n/\u0111\xEDnh \u1EA3nh \u0111\u1EC1 (Ctrl+V).";
3382
3504
  return /* @__PURE__ */ jsxs("div", { className: "border-b border-slate-200 bg-slate-50 px-3 py-3", children: [
3383
3505
  /* @__PURE__ */ jsx("div", { className: "mb-2 flex items-center justify-between gap-2", children: /* @__PURE__ */ jsx("span", { className: "text-xs font-medium tracking-wide text-slate-600", children: "D\u1EF1ng h\xECnh b\u1EB1ng AI" }) }),
3384
3506
  /* @__PURE__ */ jsxs(
3385
3507
  "div",
3386
3508
  {
3387
- className: "group relative flex flex-col rounded-2xl bg-white shadow-sm transition-all duration-150 ring-1 ring-slate-200 focus-within:ring-2 focus-within:ring-emerald-400/70 focus-within:shadow-md",
3509
+ onDragOver: (e) => {
3510
+ e.preventDefault();
3511
+ setIsDragOver(true);
3512
+ },
3513
+ onDragLeave: () => setIsDragOver(false),
3514
+ onDrop: handleDrop,
3515
+ onPaste: handlePaste,
3516
+ className: "group relative flex flex-col rounded-2xl bg-white shadow-sm transition-all duration-150 ring-1 ring-slate-200 focus-within:ring-2 focus-within:ring-emerald-400/70 focus-within:shadow-md " + (isDragOver ? "ring-2 ring-emerald-500 bg-emerald-50/40" : ""),
3388
3517
  children: [
3518
+ image && imagePreview && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 px-3 pt-2.5", children: /* @__PURE__ */ jsxs("div", { className: "group/chip relative", children: [
3519
+ /* @__PURE__ */ jsx(
3520
+ "img",
3521
+ {
3522
+ src: imagePreview,
3523
+ alt: "\u1EA2nh \u0111\u1EC1 b\xE0i",
3524
+ className: "max-h-48 max-w-full h-auto w-auto rounded-lg border border-slate-200 shadow-sm"
3525
+ }
3526
+ ),
3527
+ /* @__PURE__ */ jsx(
3528
+ "button",
3529
+ {
3530
+ type: "button",
3531
+ onClick: () => setImage(null),
3532
+ disabled: ocrLoading || isLoading,
3533
+ "aria-label": "Xo\xE1 \u1EA3nh",
3534
+ className: "absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-slate-900/85 text-[11px] font-medium text-white shadow ring-2 ring-white transition hover:bg-slate-900 disabled:opacity-50",
3535
+ children: "\xD7"
3536
+ }
3537
+ )
3538
+ ] }) }),
3389
3539
  /* @__PURE__ */ jsx(
3390
3540
  "textarea",
3391
3541
  {
@@ -3403,41 +3553,102 @@ function AiFigurePrompt({ generator, onGenerated }) {
3403
3553
  },
3404
3554
  disabled: isLoading,
3405
3555
  rows: 2,
3406
- placeholder: "M\xF4 t\u1EA3 \u0111\u1EC1 b\xE0i c\u1EA7n d\u1EF1ng.",
3556
+ placeholder,
3407
3557
  className: "block w-full resize-none rounded-2xl bg-transparent px-3.5 pt-2.5 pb-1 text-sm leading-relaxed text-slate-800 placeholder:text-slate-400 outline-none disabled:opacity-60 field-sizing-content max-h-44"
3408
3558
  }
3409
3559
  ),
3410
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-2 px-2 pb-2 pt-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3411
- isLoading && /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] tabular-nums text-slate-500", children: tokens > 0 ? `${tokens}tok \xB7 ${elapsed}s` : `${elapsed}s` }),
3412
- isLoading ? /* @__PURE__ */ jsx(
3413
- "button",
3414
- {
3415
- type: "button",
3416
- onClick: cancel,
3417
- "aria-label": "Hu\u1EF7 d\u1EF1ng h\xECnh AI",
3418
- "data-testid": "geometry-ai-cancel",
3419
- title: `\u0110ang d\u1EF1ng\u2026 ${elapsed}s \u2014 b\u1EA5m \u0111\u1EC3 hu\u1EF7`,
3420
- className: "flex h-8 w-8 items-center justify-center rounded-full bg-amber-500 text-white shadow-sm transition hover:scale-105 hover:bg-amber-600 active:scale-95",
3421
- children: /* @__PURE__ */ jsx(StopIcon, { className: "h-3.5 w-3.5" })
3422
- }
3423
- ) : /* @__PURE__ */ jsx(
3424
- "button",
3425
- {
3426
- type: "button",
3427
- onClick: () => void handleSendClick(),
3428
- disabled: sendDisabled,
3429
- "aria-label": "D\u1EF1ng b\u1EB1ng AI",
3430
- title: "D\u1EF1ng b\u1EB1ng AI (Ctrl/\u2318+Enter)",
3431
- "data-testid": "geometry-ai-submit",
3432
- className: "flex h-8 w-8 items-center justify-center rounded-full bg-emerald-600 text-white shadow-sm transition hover:scale-105 hover:bg-emerald-700 active:scale-95 disabled:cursor-not-allowed disabled:bg-slate-300 disabled:hover:scale-100",
3433
- children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-[18px] w-[18px]" })
3434
- }
3435
- )
3436
- ] }) })
3560
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 pb-2 pt-1", children: [
3561
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
3562
+ /* @__PURE__ */ jsx(
3563
+ "button",
3564
+ {
3565
+ type: "button",
3566
+ onClick: () => fileInputRef.current?.click(),
3567
+ disabled: isLoading || ocrLoading,
3568
+ "aria-label": "\u0110\xEDnh \u1EA3nh \u0111\u1EC1 b\xE0i",
3569
+ title: "\u0110\xEDnh \u1EA3nh (c\u0169ng c\xF3 th\u1EC3 d\xE1n b\u1EB1ng Ctrl+V ho\u1EB7c k\xE9o th\u1EA3)",
3570
+ className: "flex h-8 w-8 items-center justify-center rounded-full text-slate-500 transition hover:bg-slate-100 hover:text-emerald-700 disabled:opacity-40",
3571
+ children: /* @__PURE__ */ jsx(PaperclipIcon, { className: "h-[18px] w-[18px]" })
3572
+ }
3573
+ ),
3574
+ /* @__PURE__ */ jsx(
3575
+ "input",
3576
+ {
3577
+ ref: fileInputRef,
3578
+ type: "file",
3579
+ accept: "image/png,image/jpeg,image/webp",
3580
+ className: "sr-only",
3581
+ onChange: handleFileInput,
3582
+ disabled: isLoading || ocrLoading,
3583
+ "aria-label": "Ch\u1ECDn \u1EA3nh \u0111\u1EC1 b\xE0i"
3584
+ }
3585
+ )
3586
+ ] }),
3587
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3588
+ (isLoading || ocrLoading) && /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] tabular-nums text-slate-500", children: ocrLoading ? "\u0111\u1ECDc \u1EA3nh\u2026" : tokens > 0 ? `${tokens}tok \xB7 ${elapsed}s` : `${elapsed}s` }),
3589
+ isLoading ? /* @__PURE__ */ jsx(
3590
+ "button",
3591
+ {
3592
+ type: "button",
3593
+ onClick: cancel,
3594
+ "aria-label": "Hu\u1EF7 d\u1EF1ng h\xECnh AI",
3595
+ "data-testid": "geometry-ai-cancel",
3596
+ title: `\u0110ang d\u1EF1ng\u2026 ${elapsed}s \u2014 b\u1EA5m \u0111\u1EC3 hu\u1EF7`,
3597
+ className: "flex h-8 w-8 items-center justify-center rounded-full bg-amber-500 text-white shadow-sm transition hover:scale-105 hover:bg-amber-600 active:scale-95",
3598
+ children: /* @__PURE__ */ jsx(StopIcon, { className: "h-3.5 w-3.5" })
3599
+ }
3600
+ ) : /* @__PURE__ */ jsx(
3601
+ "button",
3602
+ {
3603
+ type: "button",
3604
+ onClick: () => void handleSendClick(),
3605
+ disabled: sendDisabled,
3606
+ "aria-label": willOcr ? "\u0110\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh" : "D\u1EF1ng b\u1EB1ng AI",
3607
+ title: willOcr ? "\u0110\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh (s\u1EBD \u0111i\u1EC1n v\xE0o \xF4 chat)" : "D\u1EF1ng b\u1EB1ng AI (Ctrl/\u2318+Enter)",
3608
+ "data-testid": willOcr ? "geometry-ai-ocr" : "geometry-ai-submit",
3609
+ className: "flex h-8 w-8 items-center justify-center rounded-full bg-emerald-600 text-white shadow-sm transition hover:scale-105 hover:bg-emerald-700 active:scale-95 disabled:cursor-not-allowed disabled:bg-slate-300 disabled:hover:scale-100",
3610
+ children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-[18px] w-[18px]" })
3611
+ }
3612
+ )
3613
+ ] })
3614
+ ] }),
3615
+ isDragOver && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center rounded-2xl bg-emerald-50/60 text-xs font-medium text-emerald-700", children: "Th\u1EA3 \u1EA3nh v\xE0o \u0111\xE2y" })
3437
3616
  ]
3438
3617
  }
3439
3618
  ),
3440
- error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error })
3619
+ ocrWarning && /* @__PURE__ */ jsx(
3620
+ "p",
3621
+ {
3622
+ className: "mt-1 px-1 text-xs text-amber-700",
3623
+ "data-testid": "geometry-ai-ocr-warning",
3624
+ children: ocrWarning
3625
+ }
3626
+ ),
3627
+ ocrError && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: ocrError }),
3628
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error }),
3629
+ notice && !noticeDismissed && /* @__PURE__ */ jsxs(
3630
+ "div",
3631
+ {
3632
+ role: "status",
3633
+ "data-testid": "geometry-ai-partial-notice",
3634
+ className: "relative mt-2 whitespace-pre-wrap rounded-lg border border-amber-200 bg-amber-50 py-2 pl-3 pr-8 text-xs leading-relaxed text-amber-800",
3635
+ children: [
3636
+ notice,
3637
+ /* @__PURE__ */ jsx(
3638
+ "button",
3639
+ {
3640
+ type: "button",
3641
+ onClick: () => setNoticeDismissed(true),
3642
+ "aria-label": "\u0110\xF3ng th\xF4ng b\xE1o",
3643
+ title: "\u0110\xF3ng th\xF4ng b\xE1o",
3644
+ "data-testid": "geometry-ai-partial-dismiss",
3645
+ className: "absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded text-amber-500 transition hover:bg-amber-100 hover:text-amber-800",
3646
+ children: "\xD7"
3647
+ }
3648
+ )
3649
+ ]
3650
+ }
3651
+ )
3441
3652
  ] });
3442
3653
  }
3443
3654
 
@@ -3946,7 +4157,8 @@ var GeometryStampHost = forwardRef(
3946
4157
  version: 1,
3947
4158
  jsonState
3948
4159
  }),
3949
- editingElementId: editingElement?.id ?? null
4160
+ editingElementId: editingElement?.id ?? null,
4161
+ preserveExistingSize: true
3950
4162
  });
3951
4163
  } catch (err) {
3952
4164
  console.error("Geometry insert failed:", err);
@@ -4034,5 +4246,5 @@ var GeometryStampHost = forwardRef(
4034
4246
  );
4035
4247
 
4036
4248
  export { GeometryStampHost };
4037
- //# sourceMappingURL=host-HKMZSCIT.mjs.map
4038
- //# sourceMappingURL=host-HKMZSCIT.mjs.map
4249
+ //# sourceMappingURL=host-KMWP7KBT.mjs.map
4250
+ //# sourceMappingURL=host-KMWP7KBT.mjs.map