@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.
- package/dist/ai.d.mts +259 -33
- package/dist/ai.d.ts +259 -33
- package/dist/ai.js +5424 -470
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +4971 -351
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +5 -5
- package/dist/{chunk-V3YJ6JFL.mjs → chunk-44JY2AKC.mjs} +3 -3
- package/dist/{chunk-V3YJ6JFL.mjs.map → chunk-44JY2AKC.mjs.map} +1 -1
- package/dist/{chunk-GEC2D2EQ.mjs → chunk-BMYC2ILT.mjs} +4 -4
- package/dist/{chunk-GEC2D2EQ.mjs.map → chunk-BMYC2ILT.mjs.map} +1 -1
- package/dist/{chunk-PPKHCRRE.mjs → chunk-C76SOFXF.mjs} +3 -3
- package/dist/{chunk-PPKHCRRE.mjs.map → chunk-C76SOFXF.mjs.map} +1 -1
- package/dist/{chunk-IHUFOV7L.mjs → chunk-CH6SFONH.mjs} +15 -3
- package/dist/chunk-CH6SFONH.mjs.map +1 -0
- package/dist/{chunk-E6EDOPGT.mjs → chunk-DWIEVCGK.mjs} +254 -16
- package/dist/chunk-DWIEVCGK.mjs.map +1 -0
- package/dist/{chunk-SZDAS7LK.mjs → chunk-IE2GGHNF.mjs} +131 -81
- package/dist/chunk-IE2GGHNF.mjs.map +1 -0
- package/dist/{chunk-ZTQBUKLJ.mjs → chunk-JJ4FPCBE.mjs} +142 -22
- package/dist/chunk-JJ4FPCBE.mjs.map +1 -0
- package/dist/{chunk-QRUAEXLR.mjs → chunk-K5BS2H56.mjs} +5 -5
- package/dist/{chunk-QRUAEXLR.mjs.map → chunk-K5BS2H56.mjs.map} +1 -1
- package/dist/{chunk-BNBOIDO5.mjs → chunk-K7VJU7LQ.mjs} +3 -3
- package/dist/{chunk-BNBOIDO5.mjs.map → chunk-K7VJU7LQ.mjs.map} +1 -1
- package/dist/{chunk-H22OZYTW.mjs → chunk-KOXOC2FI.mjs} +48 -39
- package/dist/chunk-KOXOC2FI.mjs.map +1 -0
- package/dist/{chunk-CXHNVYMD.mjs → chunk-KWDBVLST.mjs} +5 -5
- package/dist/{chunk-CXHNVYMD.mjs.map → chunk-KWDBVLST.mjs.map} +1 -1
- package/dist/{chunk-OQIQNKPQ.mjs → chunk-LTLLQUMN.mjs} +4 -4
- package/dist/{chunk-OQIQNKPQ.mjs.map → chunk-LTLLQUMN.mjs.map} +1 -1
- package/dist/chunk-QK6OVDLC.mjs +103 -0
- package/dist/chunk-QK6OVDLC.mjs.map +1 -0
- package/dist/{chunk-QGNU34T7.mjs → chunk-QLQ4MJNO.mjs} +10 -4
- package/dist/chunk-QLQ4MJNO.mjs.map +1 -0
- package/dist/{chunk-BU5KLO3P.mjs → chunk-T3N4BSJV.mjs} +4 -4
- package/dist/{chunk-BU5KLO3P.mjs.map → chunk-T3N4BSJV.mjs.map} +1 -1
- package/dist/{chunk-5JM35CXV.mjs → chunk-TMRFSOM7.mjs} +4 -4
- package/dist/{chunk-5JM35CXV.mjs.map → chunk-TMRFSOM7.mjs.map} +1 -1
- package/dist/geometry-2d.d.mts +1 -1
- package/dist/geometry-2d.d.ts +1 -1
- package/dist/geometry-2d.js +841 -204
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +5 -5
- package/dist/geometry-3d.d.mts +1 -1
- package/dist/geometry-3d.d.ts +1 -1
- package/dist/geometry-3d.js +172 -22
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +4 -4
- package/dist/graph-2d.d.mts +1 -1
- package/dist/graph-2d.d.ts +1 -1
- package/dist/graph-2d.js +307 -100
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +7 -7
- package/dist/handleExtractProblem-BrDY9ifM.d.mts +58 -0
- package/dist/handleExtractProblem-BrDY9ifM.d.ts +58 -0
- package/dist/{host-HOSJHQ5H.mjs → host-4FIUNIDQ.mjs} +13 -12
- package/dist/host-4FIUNIDQ.mjs.map +1 -0
- package/dist/{host-2ISGVO7O.mjs → host-4ZB4XD4S.mjs} +9 -8
- package/dist/host-4ZB4XD4S.mjs.map +1 -0
- package/dist/{host-ZQCDAT6O.mjs → host-H2IGOKJU.mjs} +3 -3
- package/dist/{host-ZQCDAT6O.mjs.map → host-H2IGOKJU.mjs.map} +1 -1
- package/dist/{host-HKMZSCIT.mjs → host-KMWP7KBT.mjs} +286 -74
- package/dist/host-KMWP7KBT.mjs.map +1 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +849 -206
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +22 -21
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +1 -1
- package/dist/latex.d.ts +1 -1
- package/dist/latex.js +8 -2
- package/dist/latex.js.map +1 -1
- package/dist/latex.mjs +1 -1
- package/dist/render-NMS7OAV6.mjs +10 -0
- package/dist/{render-ZX2O2IK7.mjs.map → render-NMS7OAV6.mjs.map} +1 -1
- package/dist/serialize-PGHQZEPV.mjs +9 -0
- package/dist/{serialize-N4G6RFBB.mjs.map → serialize-PGHQZEPV.mjs.map} +1 -1
- package/dist/{types-C3FjpoUi.d.ts → types-tePd94vW.d.mts} +8 -0
- package/dist/{types-C3FjpoUi.d.mts → types-tePd94vW.d.ts} +8 -0
- package/package.json +2 -1
- package/dist/chunk-E6EDOPGT.mjs.map +0 -1
- package/dist/chunk-H22OZYTW.mjs.map +0 -1
- package/dist/chunk-IHUFOV7L.mjs.map +0 -1
- package/dist/chunk-QGNU34T7.mjs.map +0 -1
- package/dist/chunk-SZDAS7LK.mjs.map +0 -1
- package/dist/chunk-ZTQBUKLJ.mjs.map +0 -1
- package/dist/host-2ISGVO7O.mjs.map +0 -1
- package/dist/host-HKMZSCIT.mjs.map +0 -1
- package/dist/host-HOSJHQ5H.mjs.map +0 -1
- package/dist/render-ZX2O2IK7.mjs +0 -10
- 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-
|
|
5
|
-
import { serializeBoard, renderGeometrySvgFromState, isGeometryCustomData, deserializeBoard } from './chunk-
|
|
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-
|
|
7
|
+
import { JxgRenderer } from './chunk-IE2GGHNF.mjs';
|
|
8
8
|
import './chunk-ICR4CVOE.mjs';
|
|
9
|
-
import { nextLabel, useEditorState, listObjects } from './chunk-
|
|
10
|
-
import './chunk-
|
|
11
|
-
import { describeDsl } from './chunk-
|
|
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-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
/* @__PURE__ */ jsx("
|
|
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: "
|
|
340
|
-
/* @__PURE__ */ jsx("line", { x1: "16.5", y1: "3", x2: "16.5", y2: "
|
|
341
|
-
/* @__PURE__ */ jsx("circle", { cx: "16.5", cy: "12", r: "2.
|
|
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.
|
|
345
|
-
/* @__PURE__ */ jsx("line", { x1: "2", y1: "8", x2: "
|
|
346
|
-
/* @__PURE__ */ jsx("circle", { cx: "
|
|
347
|
-
/* @__PURE__ */ jsx("circle", { cx: "
|
|
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
|
|
351
|
-
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "17", r: "1.
|
|
352
|
-
/* @__PURE__ */ jsx("circle", { cx: "20", cy: "17", r: "1.
|
|
353
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "
|
|
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.
|
|
357
|
-
/* @__PURE__ */ jsx("circle", { cx: "15", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.
|
|
358
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "
|
|
359
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "
|
|
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: "
|
|
363
|
-
/* @__PURE__ */ jsx("
|
|
364
|
-
/* @__PURE__ */ jsx("line", { x1: "3
|
|
365
|
-
/* @__PURE__ */ jsx("
|
|
366
|
-
/* @__PURE__ */ jsx("circle", { cx: "
|
|
367
|
-
/* @__PURE__ */ jsx("circle", { cx: "
|
|
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("
|
|
380
|
-
/* @__PURE__ */ jsx("
|
|
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
|
|
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
|
-
|
|
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
|
|
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__ */
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
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
|
-
|
|
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-
|
|
4038
|
-
//# sourceMappingURL=host-
|
|
4249
|
+
//# sourceMappingURL=host-KMWP7KBT.mjs.map
|
|
4250
|
+
//# sourceMappingURL=host-KMWP7KBT.mjs.map
|