@xom11/whiteboard 0.29.0 → 0.30.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 +33 -1
- package/dist/ai.d.ts +33 -1
- package/dist/ai.js +179 -0
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +3 -1
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +2 -2
- package/dist/chunk-QK6OVDLC.mjs +103 -0
- package/dist/chunk-QK6OVDLC.mjs.map +1 -0
- package/dist/{chunk-E6EDOPGT.mjs → chunk-SF3U7ZF4.mjs} +76 -3
- package/dist/chunk-SF3U7ZF4.mjs.map +1 -0
- package/dist/{chunk-GEC2D2EQ.mjs → chunk-XVVLT6B3.mjs} +3 -3
- package/dist/{chunk-GEC2D2EQ.mjs.map → chunk-XVVLT6B3.mjs.map} +1 -1
- package/dist/geometry-2d.js +392 -32
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +1 -1
- package/dist/handleExtractProblem-BrDY9ifM.d.mts +58 -0
- package/dist/handleExtractProblem-BrDY9ifM.d.ts +58 -0
- package/dist/{host-HKMZSCIT.mjs → host-3UFGFMJ2.mjs} +199 -34
- package/dist/host-3UFGFMJ2.mjs.map +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +396 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-E6EDOPGT.mjs.map +0 -1
- package/dist/host-HKMZSCIT.mjs.map +0 -1
package/dist/geometry-2d.mjs
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
interface ImagePart {
|
|
2
|
+
/** Whitelist 3 format browser decode native được. */
|
|
3
|
+
mediaType: 'image/png' | 'image/jpeg' | 'image/webp';
|
|
4
|
+
/** Base64 không bao gồm "data:image/...;base64," prefix. */
|
|
5
|
+
base64: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ExtractProblemOptions {
|
|
9
|
+
/** Tesseract language. Default 'vie+eng' cho đề toán VN. */
|
|
10
|
+
tesseractLang?: string;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
interface ExtractProblemSuccess {
|
|
14
|
+
ok: true;
|
|
15
|
+
text: string;
|
|
16
|
+
confidence: 'high' | 'low';
|
|
17
|
+
usage: {
|
|
18
|
+
inputTokens: number;
|
|
19
|
+
outputTokens: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
interface ExtractProblemFailure {
|
|
23
|
+
ok: false;
|
|
24
|
+
reason: 'not-math' | 'unreadable' | 'empty' | 'unsupported';
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
type ExtractProblemOutcome = ExtractProblemSuccess | ExtractProblemFailure;
|
|
28
|
+
declare function extractProblemFromImage(image: ImagePart, opts?: ExtractProblemOptions): Promise<ExtractProblemOutcome>;
|
|
29
|
+
|
|
30
|
+
interface HandleExtractProblemOptions extends ExtractProblemOptions {
|
|
31
|
+
}
|
|
32
|
+
type ExtractUiResult = {
|
|
33
|
+
kind: 'success';
|
|
34
|
+
text: string;
|
|
35
|
+
usage: {
|
|
36
|
+
inputTokens: number;
|
|
37
|
+
outputTokens: number;
|
|
38
|
+
};
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'low-confidence';
|
|
41
|
+
text: string;
|
|
42
|
+
warning: string;
|
|
43
|
+
usage: {
|
|
44
|
+
inputTokens: number;
|
|
45
|
+
outputTokens: number;
|
|
46
|
+
};
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'refused';
|
|
49
|
+
reason: 'not-math';
|
|
50
|
+
message: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'error';
|
|
53
|
+
code: 'network' | 'unsupported' | 'unexpected' | 'empty';
|
|
54
|
+
message: string;
|
|
55
|
+
};
|
|
56
|
+
declare function handleExtractProblem(image: ImagePart, opts?: HandleExtractProblemOptions): Promise<ExtractUiResult>;
|
|
57
|
+
|
|
58
|
+
export { type ExtractUiResult as E, type HandleExtractProblemOptions as H, type ImagePart as I, type ExtractProblemOptions as a, type ExtractProblemOutcome as b, extractProblemFromImage as e, handleExtractProblem as h };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
interface ImagePart {
|
|
2
|
+
/** Whitelist 3 format browser decode native được. */
|
|
3
|
+
mediaType: 'image/png' | 'image/jpeg' | 'image/webp';
|
|
4
|
+
/** Base64 không bao gồm "data:image/...;base64," prefix. */
|
|
5
|
+
base64: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ExtractProblemOptions {
|
|
9
|
+
/** Tesseract language. Default 'vie+eng' cho đề toán VN. */
|
|
10
|
+
tesseractLang?: string;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
interface ExtractProblemSuccess {
|
|
14
|
+
ok: true;
|
|
15
|
+
text: string;
|
|
16
|
+
confidence: 'high' | 'low';
|
|
17
|
+
usage: {
|
|
18
|
+
inputTokens: number;
|
|
19
|
+
outputTokens: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
interface ExtractProblemFailure {
|
|
23
|
+
ok: false;
|
|
24
|
+
reason: 'not-math' | 'unreadable' | 'empty' | 'unsupported';
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
type ExtractProblemOutcome = ExtractProblemSuccess | ExtractProblemFailure;
|
|
28
|
+
declare function extractProblemFromImage(image: ImagePart, opts?: ExtractProblemOptions): Promise<ExtractProblemOutcome>;
|
|
29
|
+
|
|
30
|
+
interface HandleExtractProblemOptions extends ExtractProblemOptions {
|
|
31
|
+
}
|
|
32
|
+
type ExtractUiResult = {
|
|
33
|
+
kind: 'success';
|
|
34
|
+
text: string;
|
|
35
|
+
usage: {
|
|
36
|
+
inputTokens: number;
|
|
37
|
+
outputTokens: number;
|
|
38
|
+
};
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'low-confidence';
|
|
41
|
+
text: string;
|
|
42
|
+
warning: string;
|
|
43
|
+
usage: {
|
|
44
|
+
inputTokens: number;
|
|
45
|
+
outputTokens: number;
|
|
46
|
+
};
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'refused';
|
|
49
|
+
reason: 'not-math';
|
|
50
|
+
message: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'error';
|
|
53
|
+
code: 'network' | 'unsupported' | 'unexpected' | 'empty';
|
|
54
|
+
message: string;
|
|
55
|
+
};
|
|
56
|
+
declare function handleExtractProblem(image: ImagePart, opts?: HandleExtractProblemOptions): Promise<ExtractUiResult>;
|
|
57
|
+
|
|
58
|
+
export { type ExtractUiResult as E, type HandleExtractProblemOptions as H, type ImagePart as I, type ExtractProblemOptions as a, type ExtractProblemOutcome as b, extractProblemFromImage as e, handleExtractProblem as h };
|
|
@@ -8,7 +8,8 @@ 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 { describeDsl } from './chunk-
|
|
11
|
+
import { validateFile, fileToImagePart, describeDsl } from './chunk-SF3U7ZF4.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';
|
|
@@ -3353,6 +3354,20 @@ var ArrowUpIcon = (props) => /* @__PURE__ */ jsxs(
|
|
|
3353
3354
|
}
|
|
3354
3355
|
);
|
|
3355
3356
|
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" }) });
|
|
3357
|
+
var PaperclipIcon = (props) => /* @__PURE__ */ jsx(
|
|
3358
|
+
"svg",
|
|
3359
|
+
{
|
|
3360
|
+
viewBox: "0 0 24 24",
|
|
3361
|
+
fill: "none",
|
|
3362
|
+
stroke: "currentColor",
|
|
3363
|
+
strokeWidth: 1.75,
|
|
3364
|
+
strokeLinecap: "round",
|
|
3365
|
+
strokeLinejoin: "round",
|
|
3366
|
+
"aria-hidden": true,
|
|
3367
|
+
...props,
|
|
3368
|
+
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" })
|
|
3369
|
+
}
|
|
3370
|
+
);
|
|
3356
3371
|
function AiFigurePrompt({ generator, onGenerated }) {
|
|
3357
3372
|
const {
|
|
3358
3373
|
prompt,
|
|
@@ -3372,20 +3387,132 @@ function AiFigurePrompt({ generator, onGenerated }) {
|
|
|
3372
3387
|
const id = setInterval(() => setElapsed((s) => s + 1), 1e3);
|
|
3373
3388
|
return () => clearInterval(id);
|
|
3374
3389
|
}, [isLoading]);
|
|
3390
|
+
const [image, setImage] = useState(null);
|
|
3391
|
+
const [ocrLoading, setOcrLoading] = useState(false);
|
|
3392
|
+
const [ocrError, setOcrError] = useState(null);
|
|
3393
|
+
const [ocrWarning, setOcrWarning] = useState(null);
|
|
3394
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
3395
|
+
const fileInputRef = useRef(null);
|
|
3375
3396
|
const textareaRef = useRef(null);
|
|
3397
|
+
const imagePreview = image ? `data:${image.mediaType};base64,${image.base64}` : null;
|
|
3398
|
+
useEffect(() => {
|
|
3399
|
+
setOcrError(null);
|
|
3400
|
+
setOcrWarning(null);
|
|
3401
|
+
}, [image]);
|
|
3402
|
+
const handleFile = useCallback(
|
|
3403
|
+
async (file) => {
|
|
3404
|
+
if (isLoading || ocrLoading) return;
|
|
3405
|
+
const v = validateFile(file);
|
|
3406
|
+
if (!v.ok) {
|
|
3407
|
+
setOcrError(v.message);
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3410
|
+
try {
|
|
3411
|
+
const part = await fileToImagePart(file);
|
|
3412
|
+
setImage(part);
|
|
3413
|
+
} catch (e) {
|
|
3414
|
+
setOcrError(e instanceof Error ? e.message : "Kh\xF4ng decode \u0111\u01B0\u1EE3c \u1EA3nh");
|
|
3415
|
+
}
|
|
3416
|
+
},
|
|
3417
|
+
[isLoading, ocrLoading]
|
|
3418
|
+
);
|
|
3419
|
+
const handleFileInput = useCallback(
|
|
3420
|
+
(e) => {
|
|
3421
|
+
const file = e.target.files?.[0];
|
|
3422
|
+
if (file) void handleFile(file);
|
|
3423
|
+
e.target.value = "";
|
|
3424
|
+
},
|
|
3425
|
+
[handleFile]
|
|
3426
|
+
);
|
|
3427
|
+
const handlePaste = useCallback(
|
|
3428
|
+
(e) => {
|
|
3429
|
+
const item = Array.from(e.clipboardData.items).find(
|
|
3430
|
+
(it) => it.kind === "file" && it.type.startsWith("image/")
|
|
3431
|
+
);
|
|
3432
|
+
if (!item) return;
|
|
3433
|
+
const file = item.getAsFile();
|
|
3434
|
+
if (!file) return;
|
|
3435
|
+
e.preventDefault();
|
|
3436
|
+
void handleFile(file);
|
|
3437
|
+
},
|
|
3438
|
+
[handleFile]
|
|
3439
|
+
);
|
|
3440
|
+
const handleDrop = useCallback(
|
|
3441
|
+
(e) => {
|
|
3442
|
+
e.preventDefault();
|
|
3443
|
+
setIsDragOver(false);
|
|
3444
|
+
const file = Array.from(e.dataTransfer.files).find(
|
|
3445
|
+
(f) => f.type.startsWith("image/")
|
|
3446
|
+
);
|
|
3447
|
+
if (file) void handleFile(file);
|
|
3448
|
+
},
|
|
3449
|
+
[handleFile]
|
|
3450
|
+
);
|
|
3451
|
+
const runOcr = useCallback(async () => {
|
|
3452
|
+
if (!image) return;
|
|
3453
|
+
setOcrLoading(true);
|
|
3454
|
+
setOcrError(null);
|
|
3455
|
+
setOcrWarning(null);
|
|
3456
|
+
try {
|
|
3457
|
+
const r = await handleExtractProblem(image);
|
|
3458
|
+
if (r.kind === "success" || r.kind === "low-confidence") {
|
|
3459
|
+
setPrompt(r.text);
|
|
3460
|
+
if (r.kind === "low-confidence") setOcrWarning(r.warning);
|
|
3461
|
+
requestAnimationFrame(() => textareaRef.current?.focus());
|
|
3462
|
+
} else {
|
|
3463
|
+
setOcrError(r.message);
|
|
3464
|
+
}
|
|
3465
|
+
} finally {
|
|
3466
|
+
setOcrLoading(false);
|
|
3467
|
+
}
|
|
3468
|
+
}, [image, setPrompt]);
|
|
3376
3469
|
const handleSendClick = useCallback(async () => {
|
|
3470
|
+
if (image && !prompt.trim() && !ocrLoading) {
|
|
3471
|
+
await runOcr();
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3377
3474
|
const generated = await submit();
|
|
3378
3475
|
if (generated) onGenerated(generated);
|
|
3379
|
-
}, [submit, onGenerated]);
|
|
3476
|
+
}, [image, prompt, ocrLoading, runOcr, submit, onGenerated]);
|
|
3380
3477
|
const promptEmpty = !prompt.trim();
|
|
3381
|
-
const
|
|
3478
|
+
const willOcr = image != null && promptEmpty;
|
|
3479
|
+
const sendDisabled = !image && promptEmpty || ocrLoading || isLoading && !willOcr;
|
|
3480
|
+
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
3481
|
return /* @__PURE__ */ jsxs("div", { className: "border-b border-slate-200 bg-slate-50 px-3 py-3", children: [
|
|
3383
3482
|
/* @__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
3483
|
/* @__PURE__ */ jsxs(
|
|
3385
3484
|
"div",
|
|
3386
3485
|
{
|
|
3387
|
-
|
|
3486
|
+
onDragOver: (e) => {
|
|
3487
|
+
e.preventDefault();
|
|
3488
|
+
setIsDragOver(true);
|
|
3489
|
+
},
|
|
3490
|
+
onDragLeave: () => setIsDragOver(false),
|
|
3491
|
+
onDrop: handleDrop,
|
|
3492
|
+
onPaste: handlePaste,
|
|
3493
|
+
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
3494
|
children: [
|
|
3495
|
+
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: [
|
|
3496
|
+
/* @__PURE__ */ jsx(
|
|
3497
|
+
"img",
|
|
3498
|
+
{
|
|
3499
|
+
src: imagePreview,
|
|
3500
|
+
alt: "\u1EA2nh \u0111\u1EC1 b\xE0i",
|
|
3501
|
+
className: "max-h-48 max-w-full h-auto w-auto rounded-lg border border-slate-200 shadow-sm"
|
|
3502
|
+
}
|
|
3503
|
+
),
|
|
3504
|
+
/* @__PURE__ */ jsx(
|
|
3505
|
+
"button",
|
|
3506
|
+
{
|
|
3507
|
+
type: "button",
|
|
3508
|
+
onClick: () => setImage(null),
|
|
3509
|
+
disabled: ocrLoading || isLoading,
|
|
3510
|
+
"aria-label": "Xo\xE1 \u1EA3nh",
|
|
3511
|
+
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",
|
|
3512
|
+
children: "\xD7"
|
|
3513
|
+
}
|
|
3514
|
+
)
|
|
3515
|
+
] }) }),
|
|
3389
3516
|
/* @__PURE__ */ jsx(
|
|
3390
3517
|
"textarea",
|
|
3391
3518
|
{
|
|
@@ -3403,40 +3530,78 @@ function AiFigurePrompt({ generator, onGenerated }) {
|
|
|
3403
3530
|
},
|
|
3404
3531
|
disabled: isLoading,
|
|
3405
3532
|
rows: 2,
|
|
3406
|
-
placeholder
|
|
3533
|
+
placeholder,
|
|
3407
3534
|
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
3535
|
}
|
|
3409
3536
|
),
|
|
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
|
-
|
|
3537
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 pb-2 pt-1", children: [
|
|
3538
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3539
|
+
/* @__PURE__ */ jsx(
|
|
3540
|
+
"button",
|
|
3541
|
+
{
|
|
3542
|
+
type: "button",
|
|
3543
|
+
onClick: () => fileInputRef.current?.click(),
|
|
3544
|
+
disabled: isLoading || ocrLoading,
|
|
3545
|
+
"aria-label": "\u0110\xEDnh \u1EA3nh \u0111\u1EC1 b\xE0i",
|
|
3546
|
+
title: "\u0110\xEDnh \u1EA3nh (c\u0169ng c\xF3 th\u1EC3 d\xE1n b\u1EB1ng Ctrl+V ho\u1EB7c k\xE9o th\u1EA3)",
|
|
3547
|
+
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",
|
|
3548
|
+
children: /* @__PURE__ */ jsx(PaperclipIcon, { className: "h-[18px] w-[18px]" })
|
|
3549
|
+
}
|
|
3550
|
+
),
|
|
3551
|
+
/* @__PURE__ */ jsx(
|
|
3552
|
+
"input",
|
|
3553
|
+
{
|
|
3554
|
+
ref: fileInputRef,
|
|
3555
|
+
type: "file",
|
|
3556
|
+
accept: "image/png,image/jpeg,image/webp",
|
|
3557
|
+
className: "sr-only",
|
|
3558
|
+
onChange: handleFileInput,
|
|
3559
|
+
disabled: isLoading || ocrLoading,
|
|
3560
|
+
"aria-label": "Ch\u1ECDn \u1EA3nh \u0111\u1EC1 b\xE0i"
|
|
3561
|
+
}
|
|
3562
|
+
)
|
|
3563
|
+
] }),
|
|
3564
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3565
|
+
(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` }),
|
|
3566
|
+
isLoading ? /* @__PURE__ */ jsx(
|
|
3567
|
+
"button",
|
|
3568
|
+
{
|
|
3569
|
+
type: "button",
|
|
3570
|
+
onClick: cancel,
|
|
3571
|
+
"aria-label": "Hu\u1EF7 d\u1EF1ng h\xECnh AI",
|
|
3572
|
+
"data-testid": "geometry-ai-cancel",
|
|
3573
|
+
title: `\u0110ang d\u1EF1ng\u2026 ${elapsed}s \u2014 b\u1EA5m \u0111\u1EC3 hu\u1EF7`,
|
|
3574
|
+
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",
|
|
3575
|
+
children: /* @__PURE__ */ jsx(StopIcon, { className: "h-3.5 w-3.5" })
|
|
3576
|
+
}
|
|
3577
|
+
) : /* @__PURE__ */ jsx(
|
|
3578
|
+
"button",
|
|
3579
|
+
{
|
|
3580
|
+
type: "button",
|
|
3581
|
+
onClick: () => void handleSendClick(),
|
|
3582
|
+
disabled: sendDisabled,
|
|
3583
|
+
"aria-label": willOcr ? "\u0110\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh" : "D\u1EF1ng b\u1EB1ng AI",
|
|
3584
|
+
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)",
|
|
3585
|
+
"data-testid": willOcr ? "geometry-ai-ocr" : "geometry-ai-submit",
|
|
3586
|
+
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",
|
|
3587
|
+
children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-[18px] w-[18px]" })
|
|
3588
|
+
}
|
|
3589
|
+
)
|
|
3590
|
+
] })
|
|
3591
|
+
] }),
|
|
3592
|
+
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
3593
|
]
|
|
3438
3594
|
}
|
|
3439
3595
|
),
|
|
3596
|
+
ocrWarning && /* @__PURE__ */ jsx(
|
|
3597
|
+
"p",
|
|
3598
|
+
{
|
|
3599
|
+
className: "mt-1 px-1 text-xs text-amber-700",
|
|
3600
|
+
"data-testid": "geometry-ai-ocr-warning",
|
|
3601
|
+
children: ocrWarning
|
|
3602
|
+
}
|
|
3603
|
+
),
|
|
3604
|
+
ocrError && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: ocrError }),
|
|
3440
3605
|
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error })
|
|
3441
3606
|
] });
|
|
3442
3607
|
}
|
|
@@ -4034,5 +4199,5 @@ var GeometryStampHost = forwardRef(
|
|
|
4034
4199
|
);
|
|
4035
4200
|
|
|
4036
4201
|
export { GeometryStampHost };
|
|
4037
|
-
//# sourceMappingURL=host-
|
|
4038
|
-
//# sourceMappingURL=host-
|
|
4202
|
+
//# sourceMappingURL=host-3UFGFMJ2.mjs.map
|
|
4203
|
+
//# sourceMappingURL=host-3UFGFMJ2.mjs.map
|