@xom11/whiteboard 0.28.0 → 0.29.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 (39) hide show
  1. package/dist/ai.d.mts +236 -295
  2. package/dist/ai.d.ts +236 -295
  3. package/dist/ai.js +6019 -7612
  4. package/dist/ai.js.map +1 -1
  5. package/dist/ai.mjs +4804 -5457
  6. package/dist/ai.mjs.map +1 -1
  7. package/dist/catalog.json +2 -2
  8. package/dist/{chunk-AJAHD35N.mjs → chunk-E6EDOPGT.mjs} +3 -105
  9. package/dist/chunk-E6EDOPGT.mjs.map +1 -0
  10. package/dist/{chunk-QCZVFEN4.mjs → chunk-GEC2D2EQ.mjs} +3 -3
  11. package/dist/{chunk-QCZVFEN4.mjs.map → chunk-GEC2D2EQ.mjs.map} +1 -1
  12. package/dist/geometry-2d.d.mts +1 -2
  13. package/dist/geometry-2d.d.ts +1 -2
  14. package/dist/geometry-2d.js +888 -3623
  15. package/dist/geometry-2d.js.map +1 -1
  16. package/dist/geometry-2d.mjs +1 -1
  17. package/dist/geometry-3d.d.mts +1 -2
  18. package/dist/geometry-3d.d.ts +1 -2
  19. package/dist/graph-2d.d.mts +1 -2
  20. package/dist/graph-2d.d.ts +1 -2
  21. package/dist/{host-4P766V4J.mjs → host-HKMZSCIT.mjs} +54 -313
  22. package/dist/host-HKMZSCIT.mjs.map +1 -0
  23. package/dist/index.d.mts +2 -4
  24. package/dist/index.d.ts +2 -4
  25. package/dist/index.js +880 -3619
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +3 -4
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/latex.d.mts +1 -2
  30. package/dist/latex.d.ts +1 -2
  31. package/dist/{types-BHYC2Fiw.d.mts → types-C3FjpoUi.d.mts} +1 -232
  32. package/dist/{types-BHYC2Fiw.d.ts → types-C3FjpoUi.d.ts} +1 -232
  33. package/package.json +1 -9
  34. package/dist/chunk-AJAHD35N.mjs.map +0 -1
  35. package/dist/chunk-T3SOHYB2.mjs +0 -851
  36. package/dist/chunk-T3SOHYB2.mjs.map +0 -1
  37. package/dist/handleExtractProblem-C-U5KluK.d.mts +0 -158
  38. package/dist/handleExtractProblem-C-U5KluK.d.ts +0 -158
  39. package/dist/host-4P766V4J.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- export { geometryStamp } from './chunk-QCZVFEN4.mjs';
2
+ export { geometryStamp } from './chunk-GEC2D2EQ.mjs';
3
3
  import './chunk-H22OZYTW.mjs';
4
4
  import './chunk-R5FL6S7L.mjs';
5
5
  import './chunk-SZDAS7LK.mjs';
@@ -1,5 +1,4 @@
1
- import { B as BaseStampCustomData, S as StampType } from './types-BHYC2Fiw.mjs';
2
- import 'zod';
1
+ import { B as BaseStampCustomData, S as StampType } from './types-C3FjpoUi.mjs';
3
2
  import 'react';
4
3
  import '@excalidraw/excalidraw/element/types';
5
4
 
@@ -1,5 +1,4 @@
1
- import { B as BaseStampCustomData, S as StampType } from './types-BHYC2Fiw.js';
2
- import 'zod';
1
+ import { B as BaseStampCustomData, S as StampType } from './types-C3FjpoUi.js';
3
2
  import 'react';
4
3
  import '@excalidraw/excalidraw/element/types';
5
4
 
@@ -1,5 +1,4 @@
1
- import { B as BaseStampCustomData, S as StampType } from './types-BHYC2Fiw.mjs';
2
- import 'zod';
1
+ import { B as BaseStampCustomData, S as StampType } from './types-C3FjpoUi.mjs';
3
2
  import 'react';
4
3
  import '@excalidraw/excalidraw/element/types';
5
4
 
@@ -1,5 +1,4 @@
1
- import { B as BaseStampCustomData, S as StampType } from './types-BHYC2Fiw.js';
2
- import 'zod';
1
+ import { B as BaseStampCustomData, S as StampType } from './types-C3FjpoUi.js';
3
2
  import 'react';
4
3
  import '@excalidraw/excalidraw/element/types';
5
4
 
@@ -8,15 +8,14 @@ import { JxgRenderer } from './chunk-SZDAS7LK.mjs';
8
8
  import './chunk-ICR4CVOE.mjs';
9
9
  import { nextLabel, useEditorState, listObjects } from './chunk-ZTQBUKLJ.mjs';
10
10
  import './chunk-IHUFOV7L.mjs';
11
- import { validateFile, fileToImagePart, describeDsl, serializeState } from './chunk-AJAHD35N.mjs';
12
- import { handleExtractProblem } from './chunk-T3SOHYB2.mjs';
11
+ import { describeDsl } from './chunk-E6EDOPGT.mjs';
13
12
  import { DEFAULT_VIEW_2D } from './chunk-73Q7ADVL.mjs';
14
13
  import './chunk-B4NJJZFR.mjs';
15
14
  import { useIsMobile } from './chunk-P2AOIF7S.mjs';
16
15
  import { insertStampImage } from './chunk-QGNU34T7.mjs';
17
16
  import './chunk-5UTGXHLJ.mjs';
18
17
  import { __export } from './chunk-J5LGTIGS.mjs';
19
- import { forwardRef, useRef, useId, useState, useEffect, useCallback, useImperativeHandle, useSyncExternalStore, useMemo, useLayoutEffect } from 'react';
18
+ import { forwardRef, useRef, useId, useState, useEffect, useCallback, useImperativeHandle, useMemo, useLayoutEffect } from 'react';
20
19
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
21
20
  import { createPortal } from 'react-dom';
22
21
 
@@ -3268,41 +3267,13 @@ var TransformParamPopover = ({ kind, anchor, defaultValue, onConfirm, onCancel,
3268
3267
  );
3269
3268
  return createPortal(node, document.body);
3270
3269
  };
3271
- function useAiFigure(generator, options = {}) {
3272
- const { currentState } = options;
3270
+ function useAiFigure(generator) {
3273
3271
  const [prompt, setPrompt] = useState("");
3274
3272
  const [isLoading, setIsLoading] = useState(false);
3275
3273
  const [error, setError] = useState(null);
3276
3274
  const [tokens, setTokens] = useState(0);
3277
3275
  const abortRef = useRef(null);
3278
3276
  const requestIdRef = useRef(0);
3279
- const { dsl: currentDsl, unsupported, entityCount, hasContent } = useMemo(() => {
3280
- if (!currentState || currentState.order.length === 0) {
3281
- return {
3282
- dsl: null,
3283
- unsupported: [],
3284
- entityCount: { points: 0, shapes: 0 },
3285
- hasContent: false
3286
- };
3287
- }
3288
- const { dsl, unsupported: unsupported2 } = serializeState(currentState);
3289
- return {
3290
- dsl,
3291
- unsupported: unsupported2,
3292
- entityCount: { points: dsl.points.length, shapes: dsl.shapes.length },
3293
- hasContent: true
3294
- };
3295
- }, [currentState]);
3296
- const hasUnsupported = unsupported.length > 0;
3297
- const initialMode = hasContent && !hasUnsupported ? "refine" : "build";
3298
- const [mode, setModeInternal] = useState(initialMode);
3299
- useEffect(() => {
3300
- if (!hasContent && mode === "refine") setModeInternal("build");
3301
- if (hasUnsupported && mode === "refine") setModeInternal("build");
3302
- }, [hasContent, hasUnsupported, mode]);
3303
- const setMode = useCallback((next) => {
3304
- setModeInternal(next);
3305
- }, []);
3306
3277
  useEffect(() => () => abortRef.current?.abort(), []);
3307
3278
  const submit = useCallback(async () => {
3308
3279
  const problem = prompt.trim();
@@ -3326,8 +3297,7 @@ function useAiFigure(generator, options = {}) {
3326
3297
  signal: controller.signal,
3327
3298
  onProgress: (info) => {
3328
3299
  if (requestId === requestIdRef.current) setTokens(info.tokens);
3329
- },
3330
- ...mode === "refine" && currentDsl ? { currentDsl } : {}
3300
+ }
3331
3301
  });
3332
3302
  if (controller.signal.aborted || requestId !== requestIdRef.current) return null;
3333
3303
  if (!generated.ok) {
@@ -3351,7 +3321,7 @@ function useAiFigure(generator, options = {}) {
3351
3321
  setIsLoading(false);
3352
3322
  }
3353
3323
  }
3354
- }, [generator, prompt, mode, currentDsl]);
3324
+ }, [generator, prompt]);
3355
3325
  const cancel = useCallback(() => {
3356
3326
  abortRef.current?.abort();
3357
3327
  }, []);
@@ -3362,27 +3332,9 @@ function useAiFigure(generator, options = {}) {
3362
3332
  error,
3363
3333
  submit,
3364
3334
  cancel,
3365
- tokens,
3366
- mode,
3367
- setMode,
3368
- entityCount,
3369
- hasUnsupported
3335
+ tokens
3370
3336
  };
3371
3337
  }
3372
- var PaperclipIcon = (props) => /* @__PURE__ */ jsx(
3373
- "svg",
3374
- {
3375
- viewBox: "0 0 24 24",
3376
- fill: "none",
3377
- stroke: "currentColor",
3378
- strokeWidth: 1.75,
3379
- strokeLinecap: "round",
3380
- strokeLinejoin: "round",
3381
- "aria-hidden": true,
3382
- ...props,
3383
- children: /* @__PURE__ */ jsx("path", { d: "M21.44 11.05 12.25 20.24a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66L9.41 17.41a2 2 0 1 1-2.83-2.83l8.49-8.49" })
3384
- }
3385
- );
3386
3338
  var ArrowUpIcon = (props) => /* @__PURE__ */ jsxs(
3387
3339
  "svg",
3388
3340
  {
@@ -3401,12 +3353,7 @@ var ArrowUpIcon = (props) => /* @__PURE__ */ jsxs(
3401
3353
  }
3402
3354
  );
3403
3355
  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" }) });
3404
- function AiFigurePrompt({
3405
- generator,
3406
- onGenerated,
3407
- currentState,
3408
- extractProblem = handleExtractProblem
3409
- }) {
3356
+ function AiFigurePrompt({ generator, onGenerated }) {
3410
3357
  const {
3411
3358
  prompt,
3412
3359
  setPrompt,
@@ -3414,12 +3361,8 @@ function AiFigurePrompt({
3414
3361
  error,
3415
3362
  submit,
3416
3363
  cancel,
3417
- tokens,
3418
- mode,
3419
- setMode,
3420
- entityCount,
3421
- hasUnsupported
3422
- } = useAiFigure(generator, { currentState });
3364
+ tokens
3365
+ } = useAiFigure(generator);
3423
3366
  const [elapsed, setElapsed] = useState(0);
3424
3367
  useEffect(() => {
3425
3368
  if (!isLoading) {
@@ -3429,184 +3372,20 @@ function AiFigurePrompt({
3429
3372
  const id = setInterval(() => setElapsed((s) => s + 1), 1e3);
3430
3373
  return () => clearInterval(id);
3431
3374
  }, [isLoading]);
3432
- const [image, setImage] = useState(null);
3433
- const [ocrLoading, setOcrLoading] = useState(false);
3434
- const [ocrError, setOcrError] = useState(null);
3435
- const [ocrWarning, setOcrWarning] = useState(null);
3436
- const [isDragOver, setIsDragOver] = useState(false);
3437
- const fileInputRef = useRef(null);
3438
3375
  const textareaRef = useRef(null);
3439
- const imagePreview = image ? `data:${image.mediaType};base64,${image.base64}` : null;
3440
- useEffect(() => {
3441
- setOcrError(null);
3442
- setOcrWarning(null);
3443
- }, [image]);
3444
- const handleFile = useCallback(
3445
- async (file) => {
3446
- if (isLoading || ocrLoading) return;
3447
- const v = validateFile(file);
3448
- if (!v.ok) {
3449
- setOcrError(v.message);
3450
- return;
3451
- }
3452
- try {
3453
- const part = await fileToImagePart(file);
3454
- setImage(part);
3455
- } catch (e) {
3456
- setOcrError(e instanceof Error ? e.message : "Kh\xF4ng decode \u0111\u01B0\u1EE3c \u1EA3nh");
3457
- }
3458
- },
3459
- [isLoading, ocrLoading]
3460
- );
3461
- const handleFileInput = useCallback(
3462
- (e) => {
3463
- const file = e.target.files?.[0];
3464
- if (file) void handleFile(file);
3465
- e.target.value = "";
3466
- },
3467
- [handleFile]
3468
- );
3469
- const handlePaste = useCallback(
3470
- (e) => {
3471
- const item = Array.from(e.clipboardData.items).find(
3472
- (it) => it.kind === "file" && it.type.startsWith("image/")
3473
- );
3474
- if (!item) return;
3475
- const file = item.getAsFile();
3476
- if (!file) return;
3477
- e.preventDefault();
3478
- void handleFile(file);
3479
- },
3480
- [handleFile]
3481
- );
3482
- const handleDrop = useCallback(
3483
- (e) => {
3484
- e.preventDefault();
3485
- setIsDragOver(false);
3486
- const file = Array.from(e.dataTransfer.files).find(
3487
- (f) => f.type.startsWith("image/")
3488
- );
3489
- if (file) void handleFile(file);
3490
- },
3491
- [handleFile]
3492
- );
3493
- const runOcr = useCallback(async () => {
3494
- if (!image) return;
3495
- setOcrLoading(true);
3496
- setOcrError(null);
3497
- setOcrWarning(null);
3498
- try {
3499
- const r = await extractProblem(image);
3500
- if (r.kind === "success" || r.kind === "low-confidence") {
3501
- setPrompt(r.text);
3502
- if (r.kind === "low-confidence") setOcrWarning(r.warning);
3503
- requestAnimationFrame(() => textareaRef.current?.focus());
3504
- } else {
3505
- setOcrError(r.message);
3506
- }
3507
- } finally {
3508
- setOcrLoading(false);
3509
- }
3510
- }, [image, setPrompt, extractProblem]);
3511
3376
  const handleSendClick = useCallback(async () => {
3512
- if (image && !prompt.trim() && !ocrLoading) {
3513
- await runOcr();
3514
- return;
3515
- }
3516
3377
  const generated = await submit();
3517
3378
  if (generated) onGenerated(generated);
3518
- }, [image, prompt, ocrLoading, runOcr, submit, onGenerated]);
3519
- const handleSwitchToBuild = useCallback(() => {
3520
- if (currentState && currentState.order.length > 0) {
3521
- const ok = window.confirm(
3522
- "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?"
3523
- );
3524
- if (!ok) return;
3525
- }
3526
- setMode("build");
3527
- }, [currentState, setMode]);
3528
- const hasContent = currentState != null && currentState.order.length > 0;
3379
+ }, [submit, onGenerated]);
3529
3380
  const promptEmpty = !prompt.trim();
3530
- const willOcr = image != null && promptEmpty;
3531
- const sendDisabled = !image && promptEmpty || ocrLoading || isLoading && !willOcr;
3532
- const refineChipLabel = entityCount.points + entityCount.shapes > 0 ? `Th\xEAm v\xE0o \xB7 ${entityCount.points}\u0111, ${entityCount.shapes}\u0111o\u1EA1n` : "Th\xEAm v\xE0o";
3533
- 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" : mode === "refine" ? "M\xF4 t\u1EA3 ph\u1EA7n c\u1EA7n th\xEAm (vd: trung \u0111i\u1EC3m M c\u1EE7a BC). C\xF3 th\u1EC3 d\xE1n \u1EA3nh \u0111\u1EC1 (Ctrl+V)." : "M\xF4 t\u1EA3 \u0111\u1EC1 b\xE0i c\u1EA7n d\u1EF1ng \u2014 ho\u1EB7c d\xE1n/\u0111\xEDnh \u1EA3nh \u0111\u1EC1 (Ctrl+V).";
3381
+ const sendDisabled = promptEmpty || isLoading;
3534
3382
  return /* @__PURE__ */ jsxs("div", { className: "border-b border-slate-200 bg-slate-50 px-3 py-3", children: [
3535
- /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
3536
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium tracking-wide text-slate-600", children: "D\u1EF1ng h\xECnh b\u1EB1ng AI" }),
3537
- hasContent && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", role: "tablist", "aria-label": "Ch\u1EBF \u0111\u1ED9 AI", children: [
3538
- /* @__PURE__ */ jsx(
3539
- "button",
3540
- {
3541
- type: "button",
3542
- role: "tab",
3543
- "aria-selected": mode === "refine",
3544
- "data-testid": "geometry-ai-mode-refine",
3545
- onClick: () => setMode("refine"),
3546
- disabled: isLoading || hasUnsupported,
3547
- title: hasUnsupported ? "H\xECnh c\xF3 \u0111\u1ED1i t\u01B0\u1EE3ng ngo\xE0i DSL \u2014 ch\u1EC9 d\u1EF1ng m\u1EDBi \u0111\u01B0\u1EE3c" : refineChipLabel,
3548
- className: `rounded-full px-2.5 py-0.5 text-[11px] transition ${mode === "refine" ? "bg-emerald-600 text-white shadow-sm" : "text-slate-500 hover:text-emerald-700"} ${hasUnsupported ? "cursor-not-allowed opacity-40" : ""}`,
3549
- children: refineChipLabel
3550
- }
3551
- ),
3552
- /* @__PURE__ */ jsx(
3553
- "button",
3554
- {
3555
- type: "button",
3556
- role: "tab",
3557
- "aria-selected": mode === "build",
3558
- "data-testid": "geometry-ai-mode-build",
3559
- onClick: handleSwitchToBuild,
3560
- disabled: isLoading,
3561
- className: `rounded-full px-2.5 py-0.5 text-[11px] transition ${mode === "build" ? "bg-emerald-600 text-white shadow-sm" : "text-slate-500 hover:text-emerald-700"}`,
3562
- children: "D\u1EF1ng m\u1EDBi"
3563
- }
3564
- )
3565
- ] })
3566
- ] }),
3567
- hasUnsupported && /* @__PURE__ */ jsx(
3568
- "p",
3569
- {
3570
- className: "mb-1.5 text-[10px] text-amber-700",
3571
- "data-testid": "geometry-ai-unsupported-warning",
3572
- children: "H\xECnh c\xF3 \u0111\u1ED1i t\u01B0\u1EE3ng ngo\xE0i DSL \u2014 ch\u1EC9 d\u1EF1ng m\u1EDBi \u0111\u01B0\u1EE3c"
3573
- }
3574
- ),
3383
+ /* @__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" }) }),
3575
3384
  /* @__PURE__ */ jsxs(
3576
3385
  "div",
3577
3386
  {
3578
- onDragOver: (e) => {
3579
- e.preventDefault();
3580
- setIsDragOver(true);
3581
- },
3582
- onDragLeave: () => setIsDragOver(false),
3583
- onDrop: handleDrop,
3584
- onPaste: handlePaste,
3585
- "aria-label": "Khu v\u1EF1c k\xE9o th\u1EA3 \u1EA3nh",
3586
- role: "region",
3587
- 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" : ""),
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",
3588
3388
  children: [
3589
- 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: [
3590
- /* @__PURE__ */ jsx(
3591
- "img",
3592
- {
3593
- src: imagePreview,
3594
- alt: "\u1EA2nh \u0111\u1EC1 b\xE0i",
3595
- className: "max-h-48 max-w-full h-auto w-auto rounded-lg border border-slate-200 shadow-sm"
3596
- }
3597
- ),
3598
- /* @__PURE__ */ jsx(
3599
- "button",
3600
- {
3601
- type: "button",
3602
- onClick: () => setImage(null),
3603
- disabled: ocrLoading || isLoading,
3604
- "aria-label": "Xo\xE1 \u1EA3nh",
3605
- 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",
3606
- children: "\xD7"
3607
- }
3608
- )
3609
- ] }) }),
3610
3389
  /* @__PURE__ */ jsx(
3611
3390
  "textarea",
3612
3391
  {
@@ -3624,77 +3403,41 @@ function AiFigurePrompt({
3624
3403
  },
3625
3404
  disabled: isLoading,
3626
3405
  rows: 2,
3627
- placeholder,
3406
+ placeholder: "M\xF4 t\u1EA3 \u0111\u1EC1 b\xE0i c\u1EA7n d\u1EF1ng.",
3628
3407
  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"
3629
3408
  }
3630
3409
  ),
3631
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 pb-2 pt-1", children: [
3632
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
3633
- /* @__PURE__ */ jsx(
3634
- "button",
3635
- {
3636
- type: "button",
3637
- onClick: () => fileInputRef.current?.click(),
3638
- disabled: isLoading || ocrLoading,
3639
- "aria-label": "\u0110\xEDnh \u1EA3nh \u0111\u1EC1 b\xE0i",
3640
- title: "\u0110\xEDnh \u1EA3nh (c\u0169ng c\xF3 th\u1EC3 d\xE1n b\u1EB1ng Ctrl+V ho\u1EB7c k\xE9o th\u1EA3)",
3641
- 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",
3642
- children: /* @__PURE__ */ jsx(PaperclipIcon, { className: "h-[18px] w-[18px]" })
3643
- }
3644
- ),
3645
- /* @__PURE__ */ jsx(
3646
- "input",
3647
- {
3648
- ref: fileInputRef,
3649
- type: "file",
3650
- accept: "image/png,image/jpeg,image/webp",
3651
- className: "sr-only",
3652
- onChange: handleFileInput,
3653
- disabled: isLoading || ocrLoading,
3654
- "aria-label": "Ch\u1ECDn \u1EA3nh \u0111\u1EC1 b\xE0i"
3655
- }
3656
- )
3657
- ] }),
3658
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3659
- (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` }),
3660
- isLoading ? /* @__PURE__ */ jsx(
3661
- "button",
3662
- {
3663
- type: "button",
3664
- onClick: cancel,
3665
- "aria-label": "Hu\u1EF7 d\u1EF1ng h\xECnh AI",
3666
- "data-testid": "geometry-ai-cancel",
3667
- title: `\u0110ang d\u1EF1ng\u2026 ${elapsed}s \u2014 b\u1EA5m \u0111\u1EC3 hu\u1EF7`,
3668
- 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",
3669
- children: /* @__PURE__ */ jsx(StopIcon, { className: "h-3.5 w-3.5" })
3670
- }
3671
- ) : /* @__PURE__ */ jsx(
3672
- "button",
3673
- {
3674
- type: "button",
3675
- onClick: () => void handleSendClick(),
3676
- disabled: sendDisabled,
3677
- "aria-label": willOcr ? "\u0110\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh" : "D\u1EF1ng b\u1EB1ng AI",
3678
- 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)",
3679
- "data-testid": willOcr ? "geometry-ai-ocr" : "geometry-ai-submit",
3680
- 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",
3681
- children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-[18px] w-[18px]" })
3682
- }
3683
- )
3684
- ] })
3685
- ] }),
3686
- 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" })
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
+ ] }) })
3687
3437
  ]
3688
3438
  }
3689
3439
  ),
3690
- /* @__PURE__ */ jsxs("p", { className: "mt-1.5 px-1 text-[10px] text-slate-500", children: [
3691
- "D\xE1n \u1EA3nh (Ctrl+V), k\xE9o th\u1EA3, ho\u1EB7c b\u1EA5m ",
3692
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u{1F4CE}" }),
3693
- " \u0111\u1EC3 \u0111\xEDnh \u1EA3nh \u0111\u1EC1."
3694
- ] }),
3695
- error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error }),
3696
- ocrError && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: ocrError }),
3697
- ocrWarning && /* @__PURE__ */ jsx("p", { className: "mt-1 rounded bg-amber-50 px-2 py-1 text-[11px] text-amber-700", children: ocrWarning })
3440
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error })
3698
3441
  ] });
3699
3442
  }
3700
3443
 
@@ -3815,8 +3558,6 @@ var GeometryEditorPanelInner = forwardRef(
3815
3558
  }, [onGeometryDraft]);
3816
3559
  useEditorState({ store, onHistoryChange });
3817
3560
  useGeometryDraftEmit({ store, handleRef, api, showAxis, showGrid, onGeometryDraft });
3818
- const snap = () => store.getState();
3819
- const currentSceneState = useSyncExternalStore((cb) => store.subscribe(cb), snap, snap);
3820
3561
  useEffect(() => {
3821
3562
  const sync = () => setHasContent(Object.keys(store.getState().objects).length > 0);
3822
3563
  sync();
@@ -3826,22 +3567,22 @@ var GeometryEditorPanelInner = forwardRef(
3826
3567
  const h = handleRef.current;
3827
3568
  if (!h) return;
3828
3569
  setReady(true);
3829
- h.onSelect((snap2) => {
3830
- setPropsPopover(snap2);
3570
+ h.onSelect((snap) => {
3571
+ setPropsPopover(snap);
3831
3572
  setMultiSelection(null);
3832
- onSelectionChangeRef.current?.(snap2.id);
3573
+ onSelectionChangeRef.current?.(snap.id);
3833
3574
  });
3834
3575
  h.onTransformParam((info) => setTransformPopover(info));
3835
- h.onSelectionState((snap2) => {
3836
- if (!snap2 || snap2.ids.length === 0) {
3576
+ h.onSelectionState((snap) => {
3577
+ if (!snap || snap.ids.length === 0) {
3837
3578
  setPropsPopover(null);
3838
3579
  setMultiSelection(null);
3839
3580
  onSelectionChangeRef.current?.(void 0);
3840
3581
  return;
3841
3582
  }
3842
- if (snap2.ids.length === 1) {
3843
- const id = snap2.ids[0];
3844
- const single = buildObjectSnapshot(store.getState(), id, snap2.anchor);
3583
+ if (snap.ids.length === 1) {
3584
+ const id = snap.ids[0];
3585
+ const single = buildObjectSnapshot(store.getState(), id, snap.anchor);
3845
3586
  if (single) {
3846
3587
  setPropsPopover(single);
3847
3588
  setMultiSelection(null);
@@ -3849,7 +3590,7 @@ var GeometryEditorPanelInner = forwardRef(
3849
3590
  }
3850
3591
  return;
3851
3592
  }
3852
- setMultiSelection(snap2);
3593
+ setMultiSelection(snap);
3853
3594
  setPropsPopover(null);
3854
3595
  onSelectionChangeRef.current?.(void 0);
3855
3596
  });
@@ -4008,7 +3749,7 @@ var GeometryEditorPanelInner = forwardRef(
4008
3749
  /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
4009
3750
  ] }) })
4010
3751
  ] }),
4011
- generateGeometryFigure && /* @__PURE__ */ jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure, currentState: currentSceneState }),
3752
+ generateGeometryFigure && /* @__PURE__ */ jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure }),
4012
3753
  /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1", children: /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
4013
3754
  MiniBoard2D,
4014
3755
  {
@@ -4293,5 +4034,5 @@ var GeometryStampHost = forwardRef(
4293
4034
  );
4294
4035
 
4295
4036
  export { GeometryStampHost };
4296
- //# sourceMappingURL=host-4P766V4J.mjs.map
4297
- //# sourceMappingURL=host-4P766V4J.mjs.map
4037
+ //# sourceMappingURL=host-HKMZSCIT.mjs.map
4038
+ //# sourceMappingURL=host-HKMZSCIT.mjs.map