@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.
- package/dist/ai.d.mts +236 -295
- package/dist/ai.d.ts +236 -295
- package/dist/ai.js +6019 -7612
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +4804 -5457
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +2 -2
- package/dist/{chunk-AJAHD35N.mjs → chunk-E6EDOPGT.mjs} +3 -105
- package/dist/chunk-E6EDOPGT.mjs.map +1 -0
- package/dist/{chunk-QCZVFEN4.mjs → chunk-GEC2D2EQ.mjs} +3 -3
- package/dist/{chunk-QCZVFEN4.mjs.map → chunk-GEC2D2EQ.mjs.map} +1 -1
- package/dist/geometry-2d.d.mts +1 -2
- package/dist/geometry-2d.d.ts +1 -2
- package/dist/geometry-2d.js +888 -3623
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +1 -1
- package/dist/geometry-3d.d.mts +1 -2
- package/dist/geometry-3d.d.ts +1 -2
- package/dist/graph-2d.d.mts +1 -2
- package/dist/graph-2d.d.ts +1 -2
- package/dist/{host-4P766V4J.mjs → host-HKMZSCIT.mjs} +54 -313
- package/dist/host-HKMZSCIT.mjs.map +1 -0
- package/dist/index.d.mts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +880 -3619
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -4
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +1 -2
- package/dist/latex.d.ts +1 -2
- package/dist/{types-BHYC2Fiw.d.mts → types-C3FjpoUi.d.mts} +1 -232
- package/dist/{types-BHYC2Fiw.d.ts → types-C3FjpoUi.d.ts} +1 -232
- package/package.json +1 -9
- package/dist/chunk-AJAHD35N.mjs.map +0 -1
- package/dist/chunk-T3SOHYB2.mjs +0 -851
- package/dist/chunk-T3SOHYB2.mjs.map +0 -1
- package/dist/handleExtractProblem-C-U5KluK.d.mts +0 -158
- package/dist/handleExtractProblem-C-U5KluK.d.ts +0 -158
- package/dist/host-4P766V4J.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -6,14 +6,9 @@ var immer = require('immer');
|
|
|
6
6
|
var React19 = require('react');
|
|
7
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
8
8
|
var reactDom = require('react-dom');
|
|
9
|
-
var zod = require('zod');
|
|
10
|
-
var Anthropic = require('@anthropic-ai/sdk');
|
|
11
|
-
var zodToJsonSchema = require('zod-to-json-schema');
|
|
12
9
|
var excalidraw = require('@excalidraw/excalidraw');
|
|
13
10
|
require('@excalidraw/excalidraw/index.css');
|
|
14
11
|
|
|
15
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
|
-
|
|
17
12
|
function _interopNamespace(e) {
|
|
18
13
|
if (e && e.__esModule) return e;
|
|
19
14
|
var n = Object.create(null);
|
|
@@ -33,7 +28,6 @@ function _interopNamespace(e) {
|
|
|
33
28
|
}
|
|
34
29
|
|
|
35
30
|
var React19__namespace = /*#__PURE__*/_interopNamespace(React19);
|
|
36
|
-
var Anthropic__default = /*#__PURE__*/_interopDefault(Anthropic);
|
|
37
31
|
|
|
38
32
|
var __defProp = Object.defineProperty;
|
|
39
33
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -9264,1465 +9258,814 @@ var init_Toast2 = __esm({
|
|
|
9264
9258
|
init_useToast();
|
|
9265
9259
|
}
|
|
9266
9260
|
});
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9261
|
+
function useAiFigure(generator) {
|
|
9262
|
+
const [prompt, setPrompt] = React19.useState("");
|
|
9263
|
+
const [isLoading, setIsLoading] = React19.useState(false);
|
|
9264
|
+
const [error, setError] = React19.useState(null);
|
|
9265
|
+
const [tokens, setTokens] = React19.useState(0);
|
|
9266
|
+
const abortRef = React19.useRef(null);
|
|
9267
|
+
const requestIdRef = React19.useRef(0);
|
|
9268
|
+
React19.useEffect(() => () => abortRef.current?.abort(), []);
|
|
9269
|
+
const submit = React19.useCallback(async () => {
|
|
9270
|
+
const problem = prompt.trim();
|
|
9271
|
+
if (!problem) {
|
|
9272
|
+
setError("Nh\u1EADp \u0111\u1EC1 b\xE0i c\u1EA7n d\u1EF1ng h\xECnh.");
|
|
9273
|
+
return null;
|
|
9274
|
+
}
|
|
9275
|
+
if (!generator) {
|
|
9276
|
+
setError("T\xEDnh n\u0103ng d\u1EF1ng h\xECnh AI ch\u01B0a \u0111\u01B0\u1EE3c c\u1EA5u h\xECnh.");
|
|
9277
|
+
return null;
|
|
9278
|
+
}
|
|
9279
|
+
abortRef.current?.abort();
|
|
9280
|
+
const controller = new AbortController();
|
|
9281
|
+
const requestId = ++requestIdRef.current;
|
|
9282
|
+
abortRef.current = controller;
|
|
9283
|
+
setIsLoading(true);
|
|
9284
|
+
setError(null);
|
|
9285
|
+
setTokens(0);
|
|
9286
|
+
try {
|
|
9287
|
+
const generated = await generator(problem, {
|
|
9288
|
+
signal: controller.signal,
|
|
9289
|
+
onProgress: (info) => {
|
|
9290
|
+
if (requestId === requestIdRef.current) setTokens(info.tokens);
|
|
9291
|
+
}
|
|
9292
|
+
});
|
|
9293
|
+
if (controller.signal.aborted || requestId !== requestIdRef.current) return null;
|
|
9294
|
+
if (!generated.ok) {
|
|
9295
|
+
setError(generated.message);
|
|
9296
|
+
return null;
|
|
9297
|
+
}
|
|
9298
|
+
return generated.state;
|
|
9299
|
+
} catch (caught) {
|
|
9300
|
+
if (controller.signal.aborted || caught instanceof DOMException && caught.name === "AbortError") {
|
|
9301
|
+
return null;
|
|
9302
|
+
}
|
|
9303
|
+
if (requestId === requestIdRef.current) {
|
|
9304
|
+
setError(
|
|
9305
|
+
caught instanceof Error && caught.message ? caught.message : "Kh\xF4ng th\u1EC3 d\u1EF1ng h\xECnh b\u1EB1ng AI."
|
|
9306
|
+
);
|
|
9307
|
+
}
|
|
9308
|
+
return null;
|
|
9309
|
+
} finally {
|
|
9310
|
+
if (requestId === requestIdRef.current) {
|
|
9311
|
+
abortRef.current = null;
|
|
9312
|
+
setIsLoading(false);
|
|
9313
|
+
}
|
|
9314
|
+
}
|
|
9315
|
+
}, [generator, prompt]);
|
|
9316
|
+
const cancel = React19.useCallback(() => {
|
|
9317
|
+
abortRef.current?.abort();
|
|
9318
|
+
}, []);
|
|
9319
|
+
return {
|
|
9320
|
+
prompt,
|
|
9321
|
+
setPrompt,
|
|
9322
|
+
isLoading,
|
|
9323
|
+
error,
|
|
9324
|
+
submit,
|
|
9325
|
+
cancel,
|
|
9326
|
+
tokens
|
|
9327
|
+
};
|
|
9328
|
+
}
|
|
9329
|
+
var init_useAiFigure = __esm({
|
|
9330
|
+
"src/stamps/geometry-2d/editor/useAiFigure.ts"() {
|
|
9331
|
+
"use client";
|
|
9271
9332
|
}
|
|
9272
9333
|
});
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9334
|
+
function AiFigurePrompt({ generator, onGenerated }) {
|
|
9335
|
+
const {
|
|
9336
|
+
prompt,
|
|
9337
|
+
setPrompt,
|
|
9338
|
+
isLoading,
|
|
9339
|
+
error,
|
|
9340
|
+
submit,
|
|
9341
|
+
cancel,
|
|
9342
|
+
tokens
|
|
9343
|
+
} = useAiFigure(generator);
|
|
9344
|
+
const [elapsed, setElapsed] = React19.useState(0);
|
|
9345
|
+
React19.useEffect(() => {
|
|
9346
|
+
if (!isLoading) {
|
|
9347
|
+
setElapsed(0);
|
|
9348
|
+
return;
|
|
9349
|
+
}
|
|
9350
|
+
const id = setInterval(() => setElapsed((s) => s + 1), 1e3);
|
|
9351
|
+
return () => clearInterval(id);
|
|
9352
|
+
}, [isLoading]);
|
|
9353
|
+
const textareaRef = React19.useRef(null);
|
|
9354
|
+
const handleSendClick = React19.useCallback(async () => {
|
|
9355
|
+
const generated = await submit();
|
|
9356
|
+
if (generated) onGenerated(generated);
|
|
9357
|
+
}, [submit, onGenerated]);
|
|
9358
|
+
const promptEmpty = !prompt.trim();
|
|
9359
|
+
const sendDisabled = promptEmpty || isLoading;
|
|
9360
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-slate-200 bg-slate-50 px-3 py-3", children: [
|
|
9361
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 flex items-center justify-between gap-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium tracking-wide text-slate-600", children: "D\u1EF1ng h\xECnh b\u1EB1ng AI" }) }),
|
|
9362
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9363
|
+
"div",
|
|
9364
|
+
{
|
|
9365
|
+
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",
|
|
9366
|
+
children: [
|
|
9367
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9368
|
+
"textarea",
|
|
9369
|
+
{
|
|
9370
|
+
ref: textareaRef,
|
|
9371
|
+
id: "geometry-ai-prompt",
|
|
9372
|
+
"aria-label": "\u0110\u1EC1 b\xE0i cho AI",
|
|
9373
|
+
"data-testid": "geometry-ai-textarea",
|
|
9374
|
+
value: prompt,
|
|
9375
|
+
onChange: (e) => setPrompt(e.target.value),
|
|
9376
|
+
onKeyDown: (e) => {
|
|
9377
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !sendDisabled) {
|
|
9378
|
+
e.preventDefault();
|
|
9379
|
+
void handleSendClick();
|
|
9380
|
+
}
|
|
9381
|
+
},
|
|
9382
|
+
disabled: isLoading,
|
|
9383
|
+
rows: 2,
|
|
9384
|
+
placeholder: "M\xF4 t\u1EA3 \u0111\u1EC1 b\xE0i c\u1EA7n d\u1EF1ng.",
|
|
9385
|
+
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"
|
|
9386
|
+
}
|
|
9387
|
+
),
|
|
9388
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end gap-2 px-2 pb-2 pt-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
9389
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-[10px] tabular-nums text-slate-500", children: tokens > 0 ? `${tokens}tok \xB7 ${elapsed}s` : `${elapsed}s` }),
|
|
9390
|
+
isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
9391
|
+
"button",
|
|
9392
|
+
{
|
|
9393
|
+
type: "button",
|
|
9394
|
+
onClick: cancel,
|
|
9395
|
+
"aria-label": "Hu\u1EF7 d\u1EF1ng h\xECnh AI",
|
|
9396
|
+
"data-testid": "geometry-ai-cancel",
|
|
9397
|
+
title: `\u0110ang d\u1EF1ng\u2026 ${elapsed}s \u2014 b\u1EA5m \u0111\u1EC3 hu\u1EF7`,
|
|
9398
|
+
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",
|
|
9399
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(StopIcon, { className: "h-3.5 w-3.5" })
|
|
9400
|
+
}
|
|
9401
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
9402
|
+
"button",
|
|
9403
|
+
{
|
|
9404
|
+
type: "button",
|
|
9405
|
+
onClick: () => void handleSendClick(),
|
|
9406
|
+
disabled: sendDisabled,
|
|
9407
|
+
"aria-label": "D\u1EF1ng b\u1EB1ng AI",
|
|
9408
|
+
title: "D\u1EF1ng b\u1EB1ng AI (Ctrl/\u2318+Enter)",
|
|
9409
|
+
"data-testid": "geometry-ai-submit",
|
|
9410
|
+
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",
|
|
9411
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ArrowUpIcon, { className: "h-[18px] w-[18px]" })
|
|
9412
|
+
}
|
|
9413
|
+
)
|
|
9414
|
+
] }) })
|
|
9415
|
+
]
|
|
9416
|
+
}
|
|
9417
|
+
),
|
|
9418
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error })
|
|
9419
|
+
] });
|
|
9277
9420
|
}
|
|
9278
|
-
var
|
|
9279
|
-
|
|
9421
|
+
var ArrowUpIcon, StopIcon;
|
|
9422
|
+
var init_AiFigurePrompt = __esm({
|
|
9423
|
+
"src/stamps/geometry-2d/editor/AiFigurePrompt.tsx"() {
|
|
9424
|
+
"use client";
|
|
9425
|
+
init_useAiFigure();
|
|
9426
|
+
ArrowUpIcon = (props) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9427
|
+
"svg",
|
|
9428
|
+
{
|
|
9429
|
+
viewBox: "0 0 24 24",
|
|
9430
|
+
fill: "none",
|
|
9431
|
+
stroke: "currentColor",
|
|
9432
|
+
strokeWidth: 2.25,
|
|
9433
|
+
strokeLinecap: "round",
|
|
9434
|
+
strokeLinejoin: "round",
|
|
9435
|
+
"aria-hidden": true,
|
|
9436
|
+
...props,
|
|
9437
|
+
children: [
|
|
9438
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 19V5" }),
|
|
9439
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m5 12 7-7 7 7" })
|
|
9440
|
+
]
|
|
9441
|
+
}
|
|
9442
|
+
);
|
|
9443
|
+
StopIcon = (props) => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) });
|
|
9280
9444
|
}
|
|
9281
9445
|
});
|
|
9282
9446
|
|
|
9283
|
-
// src/stamps/geometry-2d/
|
|
9284
|
-
function
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
attrs: { constraint }
|
|
9292
|
-
};
|
|
9447
|
+
// src/stamps/geometry-2d/draft.ts
|
|
9448
|
+
function svgIntrinsicSize(svg) {
|
|
9449
|
+
const w = svg.match(/<svg[^>]*\swidth="([\d.]+)"/);
|
|
9450
|
+
const h = svg.match(/<svg[^>]*\sheight="([\d.]+)"/);
|
|
9451
|
+
if (w && h) return { width: parseFloat(w[1]), height: parseFloat(h[1]) };
|
|
9452
|
+
const vb = svg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/);
|
|
9453
|
+
if (vb) return { width: parseFloat(vb[1]), height: parseFloat(vb[2]) };
|
|
9454
|
+
return { width: 300, height: 200 };
|
|
9293
9455
|
}
|
|
9294
|
-
function
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
schemaVersion: 1
|
|
9311
|
-
};
|
|
9312
|
-
}
|
|
9313
|
-
});
|
|
9314
|
-
var freeModule;
|
|
9315
|
-
var init_free2 = __esm({
|
|
9316
|
-
"src/stamps/geometry-2d/dsl/kinds/points/free.ts"() {
|
|
9317
|
-
init_names();
|
|
9318
|
-
init_types5();
|
|
9319
|
-
init_shared3();
|
|
9320
|
-
freeModule = defineModule({
|
|
9321
|
-
kind: "free",
|
|
9322
|
-
role: "point",
|
|
9323
|
-
category: "points",
|
|
9324
|
-
prefix: "p",
|
|
9325
|
-
schema: zod.z.object({
|
|
9326
|
-
name: NameZ,
|
|
9327
|
-
kind: zod.z.literal("free"),
|
|
9328
|
-
x: zod.z.number().finite(),
|
|
9329
|
-
y: zod.z.number().finite()
|
|
9330
|
-
}),
|
|
9331
|
-
collectRefs: () => [],
|
|
9332
|
-
emit: (e, ctx) => [{
|
|
9333
|
-
role: "primary",
|
|
9334
|
-
object: emitPointObject(ctx.resolveId(e.name), e.name, { kind: "free", x: e.x, y: e.y })
|
|
9335
|
-
}]
|
|
9336
|
-
});
|
|
9456
|
+
function draftFromViewport(svg, appState, seq) {
|
|
9457
|
+
const { width, height } = svgIntrinsicSize(svg);
|
|
9458
|
+
const zoom = appState.zoom?.value ?? 1;
|
|
9459
|
+
const vw = appState.width ?? 800;
|
|
9460
|
+
const vh = appState.height ?? 600;
|
|
9461
|
+
const cx = appState.scrollX + vw / 2 / zoom;
|
|
9462
|
+
const cy = appState.scrollY + vh / 2 / zoom;
|
|
9463
|
+
return { svg, width, height, x: cx - width / 2, y: cy - height / 2, seq };
|
|
9464
|
+
}
|
|
9465
|
+
function didStateChange(seen, jsonState) {
|
|
9466
|
+
if (seen.last === jsonState) return false;
|
|
9467
|
+
seen.last = jsonState;
|
|
9468
|
+
return true;
|
|
9469
|
+
}
|
|
9470
|
+
var init_draft = __esm({
|
|
9471
|
+
"src/stamps/geometry-2d/draft.ts"() {
|
|
9337
9472
|
}
|
|
9338
9473
|
});
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
|
|
9474
|
+
function useGeometryDraftEmit({
|
|
9475
|
+
store,
|
|
9476
|
+
handleRef,
|
|
9477
|
+
api,
|
|
9478
|
+
showAxis,
|
|
9479
|
+
showGrid,
|
|
9480
|
+
onGeometryDraft,
|
|
9481
|
+
debounceMs = 350
|
|
9482
|
+
}) {
|
|
9483
|
+
const seqRef = React19.useRef(0);
|
|
9484
|
+
const seenRef = React19.useRef({ last: null });
|
|
9485
|
+
const timerRef = React19.useRef(null);
|
|
9486
|
+
const cbRef = React19.useRef(onGeometryDraft);
|
|
9487
|
+
cbRef.current = onGeometryDraft;
|
|
9488
|
+
React19.useEffect(() => {
|
|
9489
|
+
if (!cbRef.current) return;
|
|
9490
|
+
const emit = () => {
|
|
9491
|
+
const h = handleRef.current;
|
|
9492
|
+
if (!h) return;
|
|
9493
|
+
const state = h.getState();
|
|
9494
|
+
if (Object.keys(state.objects).length === 0) {
|
|
9495
|
+
if (seenRef.current.last !== null) {
|
|
9496
|
+
seenRef.current.last = null;
|
|
9497
|
+
cbRef.current?.(null);
|
|
9498
|
+
}
|
|
9499
|
+
return;
|
|
9500
|
+
}
|
|
9501
|
+
const bbox = h.getBbox();
|
|
9502
|
+
const jsonState = serializeBoard(state, { bbox, showAxis, showGrid });
|
|
9503
|
+
if (!didStateChange(seenRef.current, jsonState)) return;
|
|
9504
|
+
void (async () => {
|
|
9505
|
+
try {
|
|
9506
|
+
const svg = await renderGeometrySvgFromState(jsonState);
|
|
9507
|
+
const appState = api?.getAppState?.() ?? { scrollX: 0, scrollY: 0, width: 800, height: 600, zoom: { value: 1 } };
|
|
9508
|
+
seqRef.current += 1;
|
|
9509
|
+
cbRef.current?.(draftFromViewport(svg, appState, seqRef.current));
|
|
9510
|
+
} catch (err) {
|
|
9511
|
+
console.warn("[geometry] draft render failed:", err);
|
|
9512
|
+
}
|
|
9513
|
+
})();
|
|
9514
|
+
};
|
|
9515
|
+
const schedule = () => {
|
|
9516
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
9517
|
+
timerRef.current = setTimeout(emit, debounceMs);
|
|
9518
|
+
};
|
|
9519
|
+
const unsub = store.subscribe(schedule);
|
|
9520
|
+
return () => {
|
|
9521
|
+
unsub();
|
|
9522
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
9523
|
+
cbRef.current?.(null);
|
|
9524
|
+
};
|
|
9525
|
+
}, [store, handleRef, api, showAxis, showGrid, debounceMs]);
|
|
9526
|
+
}
|
|
9527
|
+
var init_useGeometryDraftEmit = __esm({
|
|
9528
|
+
"src/stamps/geometry-2d/editor/useGeometryDraftEmit.ts"() {
|
|
9529
|
+
init_serialize();
|
|
9530
|
+
init_render();
|
|
9531
|
+
init_draft();
|
|
9368
9532
|
}
|
|
9369
9533
|
});
|
|
9370
|
-
var
|
|
9371
|
-
var
|
|
9372
|
-
"src/stamps/geometry-2d/
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
)
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
role: "primary",
|
|
9448
|
-
object: emitPointObject(
|
|
9449
|
-
ctx.resolveId(e.name),
|
|
9450
|
-
e.name,
|
|
9451
|
-
{ kind: "onCircle", circleId: ctx.resolveId(e.circleId), theta: e.theta }
|
|
9452
|
-
)
|
|
9453
|
-
}]
|
|
9454
|
-
});
|
|
9455
|
-
}
|
|
9456
|
-
});
|
|
9457
|
-
var perpFootModule;
|
|
9458
|
-
var init_perpFoot2 = __esm({
|
|
9459
|
-
"src/stamps/geometry-2d/dsl/kinds/points/perpFoot.ts"() {
|
|
9460
|
-
init_names();
|
|
9461
|
-
init_types5();
|
|
9462
|
-
init_shared3();
|
|
9463
|
-
perpFootModule = defineModule({
|
|
9464
|
-
kind: "perpFoot",
|
|
9465
|
-
role: "point",
|
|
9466
|
-
category: "points",
|
|
9467
|
-
prefix: "p",
|
|
9468
|
-
schema: zod.z.object({
|
|
9469
|
-
name: NameZ,
|
|
9470
|
-
kind: zod.z.literal("perpFoot"),
|
|
9471
|
-
from: NameZ,
|
|
9472
|
-
onLine: NameZ
|
|
9473
|
-
}),
|
|
9474
|
-
collectRefs: (e) => [e.from, e.onLine],
|
|
9475
|
-
emit: (e, ctx) => [{
|
|
9476
|
-
role: "primary",
|
|
9477
|
-
object: emitPointObject(
|
|
9478
|
-
ctx.resolveId(e.name),
|
|
9479
|
-
e.name,
|
|
9480
|
-
{ kind: "perpFoot", from: ctx.resolveId(e.from), onLine: ctx.resolveId(e.onLine) }
|
|
9481
|
-
)
|
|
9482
|
-
}]
|
|
9483
|
-
});
|
|
9484
|
-
}
|
|
9485
|
-
});
|
|
9486
|
-
var circumcenterModule;
|
|
9487
|
-
var init_circumcenter2 = __esm({
|
|
9488
|
-
"src/stamps/geometry-2d/dsl/kinds/points/circumcenter.ts"() {
|
|
9489
|
-
init_names();
|
|
9490
|
-
init_types5();
|
|
9491
|
-
init_shared3();
|
|
9492
|
-
circumcenterModule = defineModule({
|
|
9493
|
-
kind: "circumcenter",
|
|
9494
|
-
role: "point",
|
|
9495
|
-
category: "points",
|
|
9496
|
-
prefix: "p",
|
|
9497
|
-
schema: zod.z.object({
|
|
9498
|
-
name: NameZ,
|
|
9499
|
-
kind: zod.z.literal("circumcenter"),
|
|
9500
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
9501
|
-
}),
|
|
9502
|
-
collectRefs: (e) => [...e.vertices],
|
|
9503
|
-
emit: (e, ctx) => [{
|
|
9504
|
-
role: "primary",
|
|
9505
|
-
object: emitPointObject(
|
|
9506
|
-
ctx.resolveId(e.name),
|
|
9507
|
-
e.name,
|
|
9508
|
-
{ kind: "circumcenter", vertices: resolveTriangleVertices(ctx, e.vertices) }
|
|
9509
|
-
)
|
|
9510
|
-
}]
|
|
9511
|
-
});
|
|
9512
|
-
}
|
|
9513
|
-
});
|
|
9514
|
-
var incenterModule;
|
|
9515
|
-
var init_incenter2 = __esm({
|
|
9516
|
-
"src/stamps/geometry-2d/dsl/kinds/points/incenter.ts"() {
|
|
9517
|
-
init_names();
|
|
9518
|
-
init_types5();
|
|
9519
|
-
init_shared3();
|
|
9520
|
-
incenterModule = defineModule({
|
|
9521
|
-
kind: "incenter",
|
|
9522
|
-
role: "point",
|
|
9523
|
-
category: "points",
|
|
9524
|
-
prefix: "p",
|
|
9525
|
-
schema: zod.z.object({
|
|
9526
|
-
name: NameZ,
|
|
9527
|
-
kind: zod.z.literal("incenter"),
|
|
9528
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
9529
|
-
}),
|
|
9530
|
-
collectRefs: (e) => [...e.vertices],
|
|
9531
|
-
emit: (e, ctx) => [{
|
|
9532
|
-
role: "primary",
|
|
9533
|
-
object: emitPointObject(
|
|
9534
|
-
ctx.resolveId(e.name),
|
|
9535
|
-
e.name,
|
|
9536
|
-
{ kind: "incenter", vertices: resolveTriangleVertices(ctx, e.vertices) }
|
|
9537
|
-
)
|
|
9538
|
-
}]
|
|
9539
|
-
});
|
|
9540
|
-
}
|
|
9541
|
-
});
|
|
9542
|
-
var centroidModule;
|
|
9543
|
-
var init_centroid2 = __esm({
|
|
9544
|
-
"src/stamps/geometry-2d/dsl/kinds/points/centroid.ts"() {
|
|
9545
|
-
init_names();
|
|
9546
|
-
init_types5();
|
|
9547
|
-
init_shared3();
|
|
9548
|
-
centroidModule = defineModule({
|
|
9549
|
-
kind: "centroid",
|
|
9550
|
-
role: "point",
|
|
9551
|
-
category: "points",
|
|
9552
|
-
prefix: "p",
|
|
9553
|
-
schema: zod.z.object({
|
|
9554
|
-
name: NameZ,
|
|
9555
|
-
kind: zod.z.literal("centroid"),
|
|
9556
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
9557
|
-
}),
|
|
9558
|
-
collectRefs: (e) => [...e.vertices],
|
|
9559
|
-
emit: (e, ctx) => [{
|
|
9560
|
-
role: "primary",
|
|
9561
|
-
object: emitPointObject(
|
|
9562
|
-
ctx.resolveId(e.name),
|
|
9563
|
-
e.name,
|
|
9564
|
-
{ kind: "centroid", vertices: resolveTriangleVertices(ctx, e.vertices) }
|
|
9565
|
-
)
|
|
9566
|
-
}]
|
|
9567
|
-
});
|
|
9568
|
-
}
|
|
9569
|
-
});
|
|
9570
|
-
var orthocenterModule;
|
|
9571
|
-
var init_orthocenter2 = __esm({
|
|
9572
|
-
"src/stamps/geometry-2d/dsl/kinds/points/orthocenter.ts"() {
|
|
9573
|
-
init_names();
|
|
9574
|
-
init_types5();
|
|
9575
|
-
init_shared3();
|
|
9576
|
-
orthocenterModule = defineModule({
|
|
9577
|
-
kind: "orthocenter",
|
|
9578
|
-
role: "point",
|
|
9579
|
-
category: "points",
|
|
9580
|
-
prefix: "p",
|
|
9581
|
-
schema: zod.z.object({
|
|
9582
|
-
name: NameZ,
|
|
9583
|
-
kind: zod.z.literal("orthocenter"),
|
|
9584
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
9585
|
-
}),
|
|
9586
|
-
collectRefs: (e) => [...e.vertices],
|
|
9587
|
-
emit: (e, ctx) => [{
|
|
9588
|
-
role: "primary",
|
|
9589
|
-
object: emitPointObject(
|
|
9590
|
-
ctx.resolveId(e.name),
|
|
9591
|
-
e.name,
|
|
9592
|
-
{ kind: "orthocenter", vertices: resolveTriangleVertices(ctx, e.vertices) }
|
|
9593
|
-
)
|
|
9594
|
-
}]
|
|
9595
|
-
});
|
|
9596
|
-
}
|
|
9597
|
-
});
|
|
9598
|
-
var intersectionModule;
|
|
9599
|
-
var init_intersection2 = __esm({
|
|
9600
|
-
"src/stamps/geometry-2d/dsl/kinds/points/intersection.ts"() {
|
|
9601
|
-
init_names();
|
|
9602
|
-
init_types5();
|
|
9603
|
-
init_shared3();
|
|
9604
|
-
intersectionModule = defineModule({
|
|
9605
|
-
kind: "intersection",
|
|
9606
|
-
role: "point",
|
|
9607
|
-
category: "points",
|
|
9608
|
-
prefix: "i",
|
|
9609
|
-
schema: zod.z.object({
|
|
9610
|
-
name: NameZ,
|
|
9611
|
-
kind: zod.z.literal("intersection"),
|
|
9612
|
-
ref1: NameZ,
|
|
9613
|
-
ref2: NameZ,
|
|
9614
|
-
branch: zod.z.union([zod.z.literal(0), zod.z.literal(1)]).optional()
|
|
9615
|
-
}),
|
|
9616
|
-
collectRefs: (e) => [e.ref1, e.ref2],
|
|
9617
|
-
emit: (e, ctx) => {
|
|
9618
|
-
const r1IsCircle = ctx.hintOf(e.ref1) === "circle";
|
|
9619
|
-
const r2IsCircle = ctx.hintOf(e.ref2) === "circle";
|
|
9620
|
-
let intersectKind;
|
|
9621
|
-
if (r1IsCircle && r2IsCircle) intersectKind = "circleCircle";
|
|
9622
|
-
else if (r1IsCircle || r2IsCircle) intersectKind = "lineCircle";
|
|
9623
|
-
else intersectKind = "lineLine";
|
|
9624
|
-
const attrs = {
|
|
9625
|
-
kind: intersectKind,
|
|
9626
|
-
ref1: ctx.resolveId(e.ref1),
|
|
9627
|
-
ref2: ctx.resolveId(e.ref2)
|
|
9628
|
-
};
|
|
9629
|
-
if (intersectKind !== "lineLine") {
|
|
9630
|
-
attrs.branch = e.branch ?? 0;
|
|
9631
|
-
}
|
|
9632
|
-
return [{
|
|
9633
|
-
role: "primary",
|
|
9634
|
-
object: {
|
|
9635
|
-
id: ctx.resolveId(e.name),
|
|
9636
|
-
kind: "intersection",
|
|
9637
|
-
label: e.name,
|
|
9638
|
-
...POINT_BASE_FIELDS,
|
|
9639
|
-
attrs
|
|
9640
|
-
}
|
|
9641
|
-
}];
|
|
9642
|
-
}
|
|
9643
|
-
});
|
|
9644
|
-
}
|
|
9645
|
-
});
|
|
9646
|
-
var segmentModule;
|
|
9647
|
-
var init_segment2 = __esm({
|
|
9648
|
-
"src/stamps/geometry-2d/dsl/kinds/lines/segment.ts"() {
|
|
9649
|
-
init_names();
|
|
9650
|
-
init_types5();
|
|
9651
|
-
init_shared3();
|
|
9652
|
-
segmentModule = defineModule({
|
|
9653
|
-
kind: "segment",
|
|
9654
|
-
role: "segment",
|
|
9655
|
-
category: "lines",
|
|
9656
|
-
prefix: "s",
|
|
9657
|
-
schema: zod.z.object({
|
|
9658
|
-
name: NameZ,
|
|
9659
|
-
kind: zod.z.literal("segment"),
|
|
9660
|
-
p1: NameZ,
|
|
9661
|
-
p2: NameZ
|
|
9662
|
-
}),
|
|
9663
|
-
collectRefs: (e) => [e.p1, e.p2],
|
|
9664
|
-
emit: (e, ctx) => [{
|
|
9665
|
-
role: "primary",
|
|
9666
|
-
object: {
|
|
9667
|
-
id: ctx.resolveId(e.name),
|
|
9668
|
-
kind: "segment",
|
|
9669
|
-
label: e.name,
|
|
9670
|
-
...SHAPE_BASE_FIELDS,
|
|
9671
|
-
attrs: { p1: ctx.resolveId(e.p1), p2: ctx.resolveId(e.p2) }
|
|
9672
|
-
}
|
|
9673
|
-
}]
|
|
9674
|
-
});
|
|
9675
|
-
}
|
|
9676
|
-
});
|
|
9677
|
-
var lineModule;
|
|
9678
|
-
var init_line2 = __esm({
|
|
9679
|
-
"src/stamps/geometry-2d/dsl/kinds/lines/line.ts"() {
|
|
9680
|
-
init_names();
|
|
9681
|
-
init_types5();
|
|
9682
|
-
init_shared3();
|
|
9683
|
-
lineModule = defineModule({
|
|
9684
|
-
kind: "line",
|
|
9685
|
-
role: "line",
|
|
9686
|
-
category: "lines",
|
|
9687
|
-
prefix: "l",
|
|
9688
|
-
schema: zod.z.object({
|
|
9689
|
-
name: NameZ,
|
|
9690
|
-
kind: zod.z.literal("line"),
|
|
9691
|
-
p1: NameZ,
|
|
9692
|
-
p2: NameZ
|
|
9693
|
-
}),
|
|
9694
|
-
collectRefs: (e) => [e.p1, e.p2],
|
|
9695
|
-
emit: (e, ctx) => [{
|
|
9696
|
-
role: "primary",
|
|
9697
|
-
object: {
|
|
9698
|
-
id: ctx.resolveId(e.name),
|
|
9699
|
-
kind: "line",
|
|
9700
|
-
label: e.name,
|
|
9701
|
-
...SHAPE_BASE_FIELDS,
|
|
9702
|
-
attrs: { p1: ctx.resolveId(e.p1), p2: ctx.resolveId(e.p2) }
|
|
9703
|
-
}
|
|
9704
|
-
}]
|
|
9705
|
-
});
|
|
9706
|
-
}
|
|
9707
|
-
});
|
|
9708
|
-
var rayModule;
|
|
9709
|
-
var init_ray2 = __esm({
|
|
9710
|
-
"src/stamps/geometry-2d/dsl/kinds/lines/ray.ts"() {
|
|
9711
|
-
init_names();
|
|
9712
|
-
init_types5();
|
|
9713
|
-
init_shared3();
|
|
9714
|
-
rayModule = defineModule({
|
|
9715
|
-
kind: "ray",
|
|
9716
|
-
role: "ray",
|
|
9717
|
-
category: "lines",
|
|
9718
|
-
prefix: "r",
|
|
9719
|
-
schema: zod.z.object({
|
|
9720
|
-
name: NameZ,
|
|
9721
|
-
kind: zod.z.literal("ray"),
|
|
9722
|
-
origin: NameZ,
|
|
9723
|
-
through: NameZ
|
|
9724
|
-
}),
|
|
9725
|
-
collectRefs: (e) => [e.origin, e.through],
|
|
9726
|
-
emit: (e, ctx) => [{
|
|
9727
|
-
role: "primary",
|
|
9728
|
-
object: {
|
|
9729
|
-
id: ctx.resolveId(e.name),
|
|
9730
|
-
kind: "ray",
|
|
9731
|
-
label: e.name,
|
|
9732
|
-
...SHAPE_BASE_FIELDS,
|
|
9733
|
-
attrs: { origin: ctx.resolveId(e.origin), through: ctx.resolveId(e.through) }
|
|
9734
|
-
}
|
|
9735
|
-
}]
|
|
9736
|
-
});
|
|
9737
|
-
}
|
|
9738
|
-
});
|
|
9739
|
-
var perpendicularModule;
|
|
9740
|
-
var init_perpendicular = __esm({
|
|
9741
|
-
"src/stamps/geometry-2d/dsl/kinds/lines/perpendicular.ts"() {
|
|
9742
|
-
init_names();
|
|
9743
|
-
init_types5();
|
|
9744
|
-
init_shared3();
|
|
9745
|
-
perpendicularModule = defineModule({
|
|
9746
|
-
kind: "perpendicular",
|
|
9747
|
-
role: "lineConstruction",
|
|
9748
|
-
category: "lines",
|
|
9749
|
-
prefix: "l",
|
|
9750
|
-
schema: zod.z.object({
|
|
9751
|
-
name: NameZ,
|
|
9752
|
-
kind: zod.z.literal("perpendicular"),
|
|
9753
|
-
throughPoint: NameZ,
|
|
9754
|
-
toLine: NameZ
|
|
9755
|
-
}),
|
|
9756
|
-
collectRefs: (e) => [e.throughPoint, e.toLine],
|
|
9757
|
-
emit: (e, ctx) => [{
|
|
9758
|
-
role: "primary",
|
|
9759
|
-
object: {
|
|
9760
|
-
id: ctx.resolveId(e.name),
|
|
9761
|
-
kind: "line",
|
|
9762
|
-
label: e.name,
|
|
9763
|
-
...SHAPE_BASE_FIELDS,
|
|
9764
|
-
attrs: {
|
|
9765
|
-
construction: {
|
|
9766
|
-
kind: "perpendicular",
|
|
9767
|
-
throughPoint: ctx.resolveId(e.throughPoint),
|
|
9768
|
-
toLine: ctx.resolveId(e.toLine)
|
|
9769
|
-
}
|
|
9770
|
-
}
|
|
9771
|
-
}
|
|
9772
|
-
}]
|
|
9773
|
-
});
|
|
9774
|
-
}
|
|
9775
|
-
});
|
|
9776
|
-
var parallelModule;
|
|
9777
|
-
var init_parallel = __esm({
|
|
9778
|
-
"src/stamps/geometry-2d/dsl/kinds/lines/parallel.ts"() {
|
|
9779
|
-
init_names();
|
|
9780
|
-
init_types5();
|
|
9781
|
-
init_shared3();
|
|
9782
|
-
parallelModule = defineModule({
|
|
9783
|
-
kind: "parallel",
|
|
9784
|
-
role: "lineConstruction",
|
|
9785
|
-
category: "lines",
|
|
9786
|
-
prefix: "l",
|
|
9787
|
-
schema: zod.z.object({
|
|
9788
|
-
name: NameZ,
|
|
9789
|
-
kind: zod.z.literal("parallel"),
|
|
9790
|
-
throughPoint: NameZ,
|
|
9791
|
-
toLine: NameZ
|
|
9792
|
-
}),
|
|
9793
|
-
collectRefs: (e) => [e.throughPoint, e.toLine],
|
|
9794
|
-
emit: (e, ctx) => [{
|
|
9795
|
-
role: "primary",
|
|
9796
|
-
object: {
|
|
9797
|
-
id: ctx.resolveId(e.name),
|
|
9798
|
-
kind: "line",
|
|
9799
|
-
label: e.name,
|
|
9800
|
-
...SHAPE_BASE_FIELDS,
|
|
9801
|
-
attrs: {
|
|
9802
|
-
construction: {
|
|
9803
|
-
kind: "parallel",
|
|
9804
|
-
throughPoint: ctx.resolveId(e.throughPoint),
|
|
9805
|
-
toLine: ctx.resolveId(e.toLine)
|
|
9806
|
-
}
|
|
9807
|
-
}
|
|
9808
|
-
}
|
|
9809
|
-
}]
|
|
9810
|
-
});
|
|
9811
|
-
}
|
|
9812
|
-
});
|
|
9813
|
-
var perpBisectorModule;
|
|
9814
|
-
var init_perpBisector = __esm({
|
|
9815
|
-
"src/stamps/geometry-2d/dsl/kinds/lines/perpBisector.ts"() {
|
|
9816
|
-
init_names();
|
|
9817
|
-
init_types5();
|
|
9818
|
-
init_shared3();
|
|
9819
|
-
perpBisectorModule = defineModule({
|
|
9820
|
-
kind: "perpBisector",
|
|
9821
|
-
role: "lineConstruction",
|
|
9822
|
-
category: "lines",
|
|
9823
|
-
prefix: "l",
|
|
9824
|
-
schema: zod.z.object({
|
|
9825
|
-
name: NameZ,
|
|
9826
|
-
kind: zod.z.literal("perpBisector"),
|
|
9827
|
-
p1: NameZ,
|
|
9828
|
-
p2: NameZ
|
|
9829
|
-
}),
|
|
9830
|
-
collectRefs: (e) => [e.p1, e.p2],
|
|
9831
|
-
emit: (e, ctx) => [{
|
|
9832
|
-
role: "primary",
|
|
9833
|
-
object: {
|
|
9834
|
-
id: ctx.resolveId(e.name),
|
|
9835
|
-
kind: "line",
|
|
9836
|
-
label: e.name,
|
|
9837
|
-
...SHAPE_BASE_FIELDS,
|
|
9838
|
-
attrs: {
|
|
9839
|
-
construction: {
|
|
9840
|
-
kind: "perpBisector",
|
|
9841
|
-
p1: ctx.resolveId(e.p1),
|
|
9842
|
-
p2: ctx.resolveId(e.p2)
|
|
9534
|
+
var GeometryEditorPanelInner, GeometryEditorPanel;
|
|
9535
|
+
var init_EditorPanel = __esm({
|
|
9536
|
+
"src/stamps/geometry-2d/editor/EditorPanel.tsx"() {
|
|
9537
|
+
"use client";
|
|
9538
|
+
init_MiniBoard();
|
|
9539
|
+
init_serialize();
|
|
9540
|
+
init_render();
|
|
9541
|
+
init_PropertiesPopover();
|
|
9542
|
+
init_MultiPropertiesPopover();
|
|
9543
|
+
init_TransformParamPopover();
|
|
9544
|
+
init_snapshot();
|
|
9545
|
+
init_icons();
|
|
9546
|
+
init_scene();
|
|
9547
|
+
init_constants();
|
|
9548
|
+
init_Toast2();
|
|
9549
|
+
init_AiFigurePrompt();
|
|
9550
|
+
init_useGeometryDraftEmit();
|
|
9551
|
+
GeometryEditorPanelInner = React19.forwardRef(
|
|
9552
|
+
function GeometryEditorPanelInner2({
|
|
9553
|
+
store,
|
|
9554
|
+
onInsert,
|
|
9555
|
+
onClose,
|
|
9556
|
+
withLeftPanel = false,
|
|
9557
|
+
selectedTool,
|
|
9558
|
+
showAxis,
|
|
9559
|
+
showGrid,
|
|
9560
|
+
onHistoryChange,
|
|
9561
|
+
isDark,
|
|
9562
|
+
isMobile = false,
|
|
9563
|
+
onOpenDrawer,
|
|
9564
|
+
onUndo,
|
|
9565
|
+
onRedo,
|
|
9566
|
+
canUndo,
|
|
9567
|
+
canRedo,
|
|
9568
|
+
onSelectionChange,
|
|
9569
|
+
generateGeometryFigure,
|
|
9570
|
+
api,
|
|
9571
|
+
onGeometryDraft
|
|
9572
|
+
}, ref) {
|
|
9573
|
+
const { showToast } = useToast();
|
|
9574
|
+
const handleRef = React19.useRef(null);
|
|
9575
|
+
const [ready, setReady] = React19.useState(false);
|
|
9576
|
+
const [hasContent, setHasContent] = React19.useState(false);
|
|
9577
|
+
const [propsPopover, setPropsPopover] = React19.useState(null);
|
|
9578
|
+
const [multiSelection, setMultiSelection] = React19.useState(null);
|
|
9579
|
+
const [transformPopover, setTransformPopover] = React19.useState(null);
|
|
9580
|
+
const onSelectionChangeRef = React19.useRef(onSelectionChange);
|
|
9581
|
+
React19.useEffect(() => {
|
|
9582
|
+
onSelectionChangeRef.current = onSelectionChange;
|
|
9583
|
+
}, [onSelectionChange]);
|
|
9584
|
+
const onGeometryDraftRef = React19.useRef(onGeometryDraft);
|
|
9585
|
+
React19.useEffect(() => {
|
|
9586
|
+
onGeometryDraftRef.current = onGeometryDraft;
|
|
9587
|
+
}, [onGeometryDraft]);
|
|
9588
|
+
useEditorState({ store, onHistoryChange });
|
|
9589
|
+
useGeometryDraftEmit({ store, handleRef, api, showAxis, showGrid, onGeometryDraft });
|
|
9590
|
+
React19.useEffect(() => {
|
|
9591
|
+
const sync = () => setHasContent(Object.keys(store.getState().objects).length > 0);
|
|
9592
|
+
sync();
|
|
9593
|
+
return store.subscribe(sync);
|
|
9594
|
+
}, [store]);
|
|
9595
|
+
const handleReady = React19.useCallback(() => {
|
|
9596
|
+
const h = handleRef.current;
|
|
9597
|
+
if (!h) return;
|
|
9598
|
+
setReady(true);
|
|
9599
|
+
h.onSelect((snap) => {
|
|
9600
|
+
setPropsPopover(snap);
|
|
9601
|
+
setMultiSelection(null);
|
|
9602
|
+
onSelectionChangeRef.current?.(snap.id);
|
|
9603
|
+
});
|
|
9604
|
+
h.onTransformParam((info) => setTransformPopover(info));
|
|
9605
|
+
h.onSelectionState((snap) => {
|
|
9606
|
+
if (!snap || snap.ids.length === 0) {
|
|
9607
|
+
setPropsPopover(null);
|
|
9608
|
+
setMultiSelection(null);
|
|
9609
|
+
onSelectionChangeRef.current?.(void 0);
|
|
9610
|
+
return;
|
|
9843
9611
|
}
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
init_names();
|
|
9854
|
-
init_types5();
|
|
9855
|
-
init_shared3();
|
|
9856
|
-
angleBisectorModule = defineModule({
|
|
9857
|
-
kind: "angleBisector",
|
|
9858
|
-
role: "lineConstruction",
|
|
9859
|
-
category: "lines",
|
|
9860
|
-
prefix: "l",
|
|
9861
|
-
schema: zod.z.object({
|
|
9862
|
-
name: NameZ,
|
|
9863
|
-
kind: zod.z.literal("angleBisector"),
|
|
9864
|
-
p1: NameZ,
|
|
9865
|
-
vertex: NameZ,
|
|
9866
|
-
p2: NameZ
|
|
9867
|
-
}),
|
|
9868
|
-
collectRefs: (e) => [e.p1, e.vertex, e.p2],
|
|
9869
|
-
emit: (e, ctx) => [{
|
|
9870
|
-
role: "primary",
|
|
9871
|
-
object: {
|
|
9872
|
-
id: ctx.resolveId(e.name),
|
|
9873
|
-
kind: "line",
|
|
9874
|
-
label: e.name,
|
|
9875
|
-
...SHAPE_BASE_FIELDS,
|
|
9876
|
-
attrs: {
|
|
9877
|
-
construction: {
|
|
9878
|
-
kind: "angleBisector",
|
|
9879
|
-
p1: ctx.resolveId(e.p1),
|
|
9880
|
-
vertex: ctx.resolveId(e.vertex),
|
|
9881
|
-
p2: ctx.resolveId(e.p2)
|
|
9612
|
+
if (snap.ids.length === 1) {
|
|
9613
|
+
const id = snap.ids[0];
|
|
9614
|
+
const single = buildObjectSnapshot(store.getState(), id, snap.anchor);
|
|
9615
|
+
if (single) {
|
|
9616
|
+
setPropsPopover(single);
|
|
9617
|
+
setMultiSelection(null);
|
|
9618
|
+
onSelectionChangeRef.current?.(id);
|
|
9619
|
+
}
|
|
9620
|
+
return;
|
|
9882
9621
|
}
|
|
9622
|
+
setMultiSelection(snap);
|
|
9623
|
+
setPropsPopover(null);
|
|
9624
|
+
onSelectionChangeRef.current?.(void 0);
|
|
9625
|
+
});
|
|
9626
|
+
}, [store]);
|
|
9627
|
+
const dismissPropsPopover = React19.useCallback(() => {
|
|
9628
|
+
setPropsPopover(null);
|
|
9629
|
+
onSelectionChangeRef.current?.(void 0);
|
|
9630
|
+
}, []);
|
|
9631
|
+
const dismissMultiPopover = React19.useCallback(() => {
|
|
9632
|
+
setMultiSelection(null);
|
|
9633
|
+
handleRef.current?.clearSelection();
|
|
9634
|
+
onSelectionChangeRef.current?.(void 0);
|
|
9635
|
+
}, []);
|
|
9636
|
+
const applyMultiColor = React19.useCallback((color) => {
|
|
9637
|
+
const ids = multiSelection?.ids ?? [];
|
|
9638
|
+
const h = handleRef.current;
|
|
9639
|
+
if (!h) return;
|
|
9640
|
+
for (const id of ids) {
|
|
9641
|
+
h.mutateObject(id, { attrs: { strokeColor: color, color } });
|
|
9883
9642
|
}
|
|
9884
|
-
}
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
"src/stamps/geometry-2d/dsl/kinds/lines/lineThrough.ts"() {
|
|
9892
|
-
init_names();
|
|
9893
|
-
init_types5();
|
|
9894
|
-
init_shared3();
|
|
9895
|
-
lineThroughModule = defineModule({
|
|
9896
|
-
kind: "lineThrough",
|
|
9897
|
-
role: "lineConstruction",
|
|
9898
|
-
category: "lines",
|
|
9899
|
-
prefix: "l",
|
|
9900
|
-
schema: zod.z.object({
|
|
9901
|
-
name: NameZ,
|
|
9902
|
-
kind: zod.z.literal("lineThrough"),
|
|
9903
|
-
points: zod.z.array(NameZ).min(2)
|
|
9904
|
-
}),
|
|
9905
|
-
collectRefs: (e) => [...e.points],
|
|
9906
|
-
refSpecs: [{ field: "points", role: "point", many: true }],
|
|
9907
|
-
emit: (e, ctx) => [{
|
|
9908
|
-
role: "primary",
|
|
9909
|
-
object: {
|
|
9910
|
-
id: ctx.resolveId(e.name),
|
|
9911
|
-
kind: "line",
|
|
9912
|
-
label: e.name,
|
|
9913
|
-
...SHAPE_BASE_FIELDS,
|
|
9914
|
-
attrs: {
|
|
9915
|
-
construction: {
|
|
9916
|
-
kind: "lineThrough",
|
|
9917
|
-
points: e.points.map((p) => ctx.resolveId(p))
|
|
9918
|
-
}
|
|
9643
|
+
}, [multiSelection]);
|
|
9644
|
+
const applyMultiDelete = React19.useCallback(() => {
|
|
9645
|
+
const ids = multiSelection?.ids ?? [];
|
|
9646
|
+
const h = handleRef.current;
|
|
9647
|
+
if (!h) return;
|
|
9648
|
+
for (const id of ids) {
|
|
9649
|
+
h.mutateObject(id, { remove: true });
|
|
9919
9650
|
}
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
kind: zod.z.literal("radicalAxis"),
|
|
9939
|
-
circle1: NameZ,
|
|
9940
|
-
circle2: NameZ
|
|
9941
|
-
}),
|
|
9942
|
-
collectRefs: (e) => [e.circle1, e.circle2],
|
|
9943
|
-
refSpecs: [
|
|
9944
|
-
{ field: "circle1", role: "circle" },
|
|
9945
|
-
{ field: "circle2", role: "circle" }
|
|
9946
|
-
],
|
|
9947
|
-
emit: (e, ctx) => [{
|
|
9948
|
-
role: "primary",
|
|
9949
|
-
object: {
|
|
9950
|
-
id: ctx.resolveId(e.name),
|
|
9951
|
-
kind: "line",
|
|
9952
|
-
label: e.name,
|
|
9953
|
-
...SHAPE_BASE_FIELDS,
|
|
9954
|
-
attrs: {
|
|
9955
|
-
construction: {
|
|
9956
|
-
kind: "radicalAxis",
|
|
9957
|
-
circle1: ctx.resolveId(e.circle1),
|
|
9958
|
-
circle2: ctx.resolveId(e.circle2)
|
|
9959
|
-
}
|
|
9960
|
-
}
|
|
9961
|
-
}
|
|
9962
|
-
}]
|
|
9963
|
-
});
|
|
9964
|
-
}
|
|
9965
|
-
});
|
|
9966
|
-
var tangentModule;
|
|
9967
|
-
var init_tangent = __esm({
|
|
9968
|
-
"src/stamps/geometry-2d/dsl/kinds/lines/tangent.ts"() {
|
|
9969
|
-
init_names();
|
|
9970
|
-
init_types5();
|
|
9971
|
-
init_shared3();
|
|
9972
|
-
tangentModule = defineModule({
|
|
9973
|
-
kind: "tangent",
|
|
9974
|
-
role: "lineConstruction",
|
|
9975
|
-
category: "lines",
|
|
9976
|
-
prefix: "l",
|
|
9977
|
-
schema: zod.z.object({
|
|
9978
|
-
name: NameZ,
|
|
9979
|
-
kind: zod.z.literal("tangent"),
|
|
9980
|
-
throughPoint: NameZ,
|
|
9981
|
-
toCircle: NameZ,
|
|
9982
|
-
branch: zod.z.union([zod.z.literal(0), zod.z.literal(1), zod.z.literal("on")]).optional()
|
|
9983
|
-
}),
|
|
9984
|
-
collectRefs: (e) => [e.throughPoint, e.toCircle],
|
|
9985
|
-
emit: (e, ctx) => {
|
|
9986
|
-
const construction = {
|
|
9987
|
-
kind: "tangent",
|
|
9988
|
-
throughPoint: ctx.resolveId(e.throughPoint),
|
|
9989
|
-
toCircle: ctx.resolveId(e.toCircle)
|
|
9990
|
-
};
|
|
9991
|
-
if (e.branch !== void 0) construction.branch = e.branch;
|
|
9992
|
-
return [{
|
|
9993
|
-
role: "primary",
|
|
9994
|
-
object: {
|
|
9995
|
-
id: ctx.resolveId(e.name),
|
|
9996
|
-
kind: "line",
|
|
9997
|
-
label: e.name,
|
|
9998
|
-
...SHAPE_BASE_FIELDS,
|
|
9999
|
-
attrs: { construction }
|
|
10000
|
-
}
|
|
10001
|
-
}];
|
|
10002
|
-
}
|
|
10003
|
-
});
|
|
10004
|
-
}
|
|
10005
|
-
});
|
|
10006
|
-
var polygonModule;
|
|
10007
|
-
var init_polygon3 = __esm({
|
|
10008
|
-
"src/stamps/geometry-2d/dsl/kinds/polygons/polygon.ts"() {
|
|
10009
|
-
init_names();
|
|
10010
|
-
init_types5();
|
|
10011
|
-
init_shared3();
|
|
10012
|
-
polygonModule = defineModule({
|
|
10013
|
-
kind: "polygon",
|
|
10014
|
-
role: "polygon",
|
|
10015
|
-
category: "polygons",
|
|
10016
|
-
prefix: "poly",
|
|
10017
|
-
schema: zod.z.object({
|
|
10018
|
-
name: NameZ,
|
|
10019
|
-
kind: zod.z.literal("polygon"),
|
|
10020
|
-
vertices: zod.z.array(NameZ).min(3)
|
|
10021
|
-
}),
|
|
10022
|
-
collectRefs: (e) => [...e.vertices],
|
|
10023
|
-
emit: (e, ctx) => [{
|
|
10024
|
-
role: "primary",
|
|
10025
|
-
object: {
|
|
10026
|
-
id: ctx.resolveId(e.name),
|
|
10027
|
-
kind: "polygon",
|
|
10028
|
-
label: e.name,
|
|
10029
|
-
...SHAPE_BASE_FIELDS,
|
|
10030
|
-
attrs: { vertices: e.vertices.map((v) => ctx.resolveId(v)) }
|
|
10031
|
-
}
|
|
10032
|
-
}]
|
|
10033
|
-
});
|
|
10034
|
-
}
|
|
10035
|
-
});
|
|
10036
|
-
var circleCPModule;
|
|
10037
|
-
var init_circleCP = __esm({
|
|
10038
|
-
"src/stamps/geometry-2d/dsl/kinds/circles/circleCP.ts"() {
|
|
10039
|
-
init_names();
|
|
10040
|
-
init_types5();
|
|
10041
|
-
init_shared3();
|
|
10042
|
-
circleCPModule = defineModule({
|
|
10043
|
-
kind: "circleCP",
|
|
10044
|
-
role: "circle",
|
|
10045
|
-
category: "circles",
|
|
10046
|
-
prefix: "c",
|
|
10047
|
-
schema: zod.z.object({
|
|
10048
|
-
name: NameZ,
|
|
10049
|
-
kind: zod.z.literal("circleCP"),
|
|
10050
|
-
center: NameZ,
|
|
10051
|
-
surfacePoint: NameZ,
|
|
10052
|
-
visible: zod.z.boolean().optional()
|
|
10053
|
-
}),
|
|
10054
|
-
collectRefs: (e) => [e.center, e.surfacePoint],
|
|
10055
|
-
emit: (e, ctx) => [{
|
|
10056
|
-
role: "primary",
|
|
10057
|
-
object: {
|
|
10058
|
-
id: ctx.resolveId(e.name),
|
|
10059
|
-
kind: "circle",
|
|
10060
|
-
label: e.name,
|
|
10061
|
-
...SHAPE_BASE_FIELDS,
|
|
10062
|
-
visible: e.visible ?? true,
|
|
10063
|
-
attrs: { center: ctx.resolveId(e.center), surfacePoint: ctx.resolveId(e.surfacePoint) }
|
|
10064
|
-
}
|
|
10065
|
-
}]
|
|
10066
|
-
});
|
|
10067
|
-
}
|
|
10068
|
-
});
|
|
10069
|
-
var circle3Module;
|
|
10070
|
-
var init_circle3 = __esm({
|
|
10071
|
-
"src/stamps/geometry-2d/dsl/kinds/circles/circle3.ts"() {
|
|
10072
|
-
init_names();
|
|
10073
|
-
init_types5();
|
|
10074
|
-
init_shared3();
|
|
10075
|
-
circle3Module = defineModule({
|
|
10076
|
-
kind: "circle3",
|
|
10077
|
-
role: "circle",
|
|
10078
|
-
category: "circles",
|
|
10079
|
-
prefix: "c",
|
|
10080
|
-
schema: zod.z.object({
|
|
10081
|
-
name: NameZ,
|
|
10082
|
-
kind: zod.z.literal("circle3"),
|
|
10083
|
-
p1: NameZ,
|
|
10084
|
-
p2: NameZ,
|
|
10085
|
-
p3: NameZ
|
|
10086
|
-
}),
|
|
10087
|
-
collectRefs: (e) => [e.p1, e.p2, e.p3],
|
|
10088
|
-
emit: (e, ctx) => [{
|
|
10089
|
-
role: "primary",
|
|
10090
|
-
object: {
|
|
10091
|
-
id: ctx.resolveId(e.name),
|
|
10092
|
-
kind: "circle",
|
|
10093
|
-
label: e.name,
|
|
10094
|
-
...SHAPE_BASE_FIELDS,
|
|
10095
|
-
attrs: {
|
|
10096
|
-
construction: {
|
|
10097
|
-
kind: "circumscribed",
|
|
10098
|
-
p1: ctx.resolveId(e.p1),
|
|
10099
|
-
p2: ctx.resolveId(e.p2),
|
|
10100
|
-
p3: ctx.resolveId(e.p3)
|
|
9651
|
+
h.clearSelection();
|
|
9652
|
+
setMultiSelection(null);
|
|
9653
|
+
onSelectionChangeRef.current?.(void 0);
|
|
9654
|
+
}, [multiSelection]);
|
|
9655
|
+
const performInsert = React19.useCallback(() => {
|
|
9656
|
+
if (!handleRef.current) return false;
|
|
9657
|
+
const h = handleRef.current;
|
|
9658
|
+
const state = h.getState();
|
|
9659
|
+
if (Object.keys(state.objects).length === 0) return false;
|
|
9660
|
+
const bbox = h.getBbox();
|
|
9661
|
+
const jsonState = serializeBoard(state, { bbox, showAxis, showGrid });
|
|
9662
|
+
void (async () => {
|
|
9663
|
+
try {
|
|
9664
|
+
const svgString = await renderGeometrySvgFromState(jsonState);
|
|
9665
|
+
onInsert(jsonState, svgString);
|
|
9666
|
+
onGeometryDraftRef.current?.(null);
|
|
9667
|
+
} catch (err) {
|
|
9668
|
+
console.error("Geometry insert failed:", err);
|
|
10101
9669
|
}
|
|
9670
|
+
})();
|
|
9671
|
+
return true;
|
|
9672
|
+
}, [onInsert, showAxis, showGrid]);
|
|
9673
|
+
const loadAiFigure = React19.useCallback((generated) => {
|
|
9674
|
+
handleRef.current?.clearSelection();
|
|
9675
|
+
setPropsPopover(null);
|
|
9676
|
+
setMultiSelection(null);
|
|
9677
|
+
setTransformPopover(null);
|
|
9678
|
+
onSelectionChangeRef.current?.(void 0);
|
|
9679
|
+
const current = store.getState();
|
|
9680
|
+
store.dispatch({
|
|
9681
|
+
type: "LOAD",
|
|
9682
|
+
payload: { state: { ...generated, meta: current.meta } }
|
|
9683
|
+
});
|
|
9684
|
+
}, [store]);
|
|
9685
|
+
React19.useImperativeHandle(ref, () => ({
|
|
9686
|
+
insert: performInsert,
|
|
9687
|
+
hasContent: () => Object.keys(handleRef.current?.getState().objects ?? {}).length > 0,
|
|
9688
|
+
selectObject: (id) => handleRef.current?.highlight(id)
|
|
9689
|
+
}), [performInsert]);
|
|
9690
|
+
const wrapperStyle = isMobile ? { position: "fixed", inset: 0, zIndex: 40 } : {
|
|
9691
|
+
position: "absolute",
|
|
9692
|
+
top: "50%",
|
|
9693
|
+
left: withLeftPanel ? "calc(50% + 120px)" : "50%",
|
|
9694
|
+
transform: "translate(-50%, -50%)",
|
|
9695
|
+
zIndex: 40
|
|
9696
|
+
};
|
|
9697
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9698
|
+
"div",
|
|
9699
|
+
{
|
|
9700
|
+
role: "dialog",
|
|
9701
|
+
"aria-label": "D\u1EF1ng h\xECnh h\u1ECDc",
|
|
9702
|
+
"data-testid": "geometry-editor-panel",
|
|
9703
|
+
"data-stamp-area": "true",
|
|
9704
|
+
"data-mobile-editor": isMobile ? "true" : void 0,
|
|
9705
|
+
style: wrapperStyle,
|
|
9706
|
+
className: [
|
|
9707
|
+
isDark ? "theme--dark " : "",
|
|
9708
|
+
"relative flex flex-col overflow-hidden bg-white",
|
|
9709
|
+
isMobile ? "h-full w-full" : `${STAMP_PANEL_DESKTOP} rounded-lg border border-slate-300 shadow-2xl ring-1 ring-black/5`
|
|
9710
|
+
].join(" "),
|
|
9711
|
+
children: [
|
|
9712
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex items-center gap-2 border-b border-slate-200 bg-gradient-to-r from-emerald-600 to-teal-600 px-3 py-2 text-white", children: [
|
|
9713
|
+
isMobile && /* @__PURE__ */ jsxRuntime.jsx(
|
|
9714
|
+
"button",
|
|
9715
|
+
{
|
|
9716
|
+
type: "button",
|
|
9717
|
+
onClick: onOpenDrawer,
|
|
9718
|
+
"aria-label": "M\u1EDF ng\u0103n c\xF4ng c\u1EE5",
|
|
9719
|
+
className: "-ml-1 inline-flex h-10 w-10 items-center justify-center rounded transition hover:bg-white/15",
|
|
9720
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
9721
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "6", x2: "20", y2: "6" }),
|
|
9722
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "12", x2: "20", y2: "12" }),
|
|
9723
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "18", x2: "20", y2: "18" })
|
|
9724
|
+
] })
|
|
9725
|
+
}
|
|
9726
|
+
),
|
|
9727
|
+
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "flex flex-1 items-center gap-2 text-sm font-semibold", children: [
|
|
9728
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
9729
|
+
/* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "3,18 12,3 21,18" }),
|
|
9730
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "3", r: "1.5", fill: "currentColor" }),
|
|
9731
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "3", cy: "18", r: "1.5", fill: "currentColor" }),
|
|
9732
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "21", cy: "18", r: "1.5", fill: "currentColor" })
|
|
9733
|
+
] }),
|
|
9734
|
+
"D\u1EF1ng h\xECnh h\u1ECDc"
|
|
9735
|
+
] }),
|
|
9736
|
+
isMobile && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9737
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9738
|
+
"button",
|
|
9739
|
+
{
|
|
9740
|
+
type: "button",
|
|
9741
|
+
onClick: onUndo,
|
|
9742
|
+
disabled: !canUndo,
|
|
9743
|
+
"aria-label": "Ho\xE0n t\xE1c",
|
|
9744
|
+
title: "Ho\xE0n t\xE1c (Ctrl/Cmd+Z)",
|
|
9745
|
+
"data-testid": "undo-btn-mobile",
|
|
9746
|
+
className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15 disabled:opacity-40",
|
|
9747
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(UndoIcon3, {})
|
|
9748
|
+
}
|
|
9749
|
+
),
|
|
9750
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9751
|
+
"button",
|
|
9752
|
+
{
|
|
9753
|
+
type: "button",
|
|
9754
|
+
onClick: onRedo,
|
|
9755
|
+
disabled: !canRedo,
|
|
9756
|
+
"aria-label": "L\xE0m l\u1EA1i",
|
|
9757
|
+
title: "L\xE0m l\u1EA1i (Ctrl/Cmd+Shift+Z)",
|
|
9758
|
+
"data-testid": "redo-btn-mobile",
|
|
9759
|
+
className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15 disabled:opacity-40",
|
|
9760
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(RedoIcon3, {})
|
|
9761
|
+
}
|
|
9762
|
+
),
|
|
9763
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9764
|
+
"button",
|
|
9765
|
+
{
|
|
9766
|
+
type: "button",
|
|
9767
|
+
onClick: performInsert,
|
|
9768
|
+
disabled: !ready || !hasContent,
|
|
9769
|
+
title: !hasContent ? "V\u1EBD \xEDt nh\u1EA5t m\u1ED9t \u0111\u1ED1i t\u01B0\u1EE3ng tr\u01B0\u1EDBc khi ch\xE8n" : void 0,
|
|
9770
|
+
"data-testid": "geometry-insert-btn-mobile",
|
|
9771
|
+
className: "rounded bg-white/15 px-3 py-1.5 text-xs font-semibold transition hover:bg-white/25 disabled:opacity-50",
|
|
9772
|
+
children: "Ch\xE8n"
|
|
9773
|
+
}
|
|
9774
|
+
)
|
|
9775
|
+
] }),
|
|
9776
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, "aria-label": "\u0110\xF3ng", className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
9777
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
9778
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
|
|
9779
|
+
] }) })
|
|
9780
|
+
] }),
|
|
9781
|
+
generateGeometryFigure && /* @__PURE__ */ jsxRuntime.jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure }),
|
|
9782
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9783
|
+
MiniBoard2D,
|
|
9784
|
+
{
|
|
9785
|
+
ref: handleRef,
|
|
9786
|
+
store,
|
|
9787
|
+
selectedTool,
|
|
9788
|
+
showAxis,
|
|
9789
|
+
showGrid,
|
|
9790
|
+
onReady: handleReady,
|
|
9791
|
+
isDark,
|
|
9792
|
+
toast: showToast
|
|
9793
|
+
}
|
|
9794
|
+
) }) }),
|
|
9795
|
+
propsPopover && (propsPopover.kind === "point" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
9796
|
+
PropertiesPopover,
|
|
9797
|
+
{
|
|
9798
|
+
kind: "point",
|
|
9799
|
+
anchor: propsPopover.screenCoords,
|
|
9800
|
+
isDark,
|
|
9801
|
+
currentName: propsPopover.name,
|
|
9802
|
+
currentColor: propsPopover.color,
|
|
9803
|
+
currentDash: propsPopover.dash,
|
|
9804
|
+
currentWidth: propsPopover.width,
|
|
9805
|
+
currentFace: propsPopover.face,
|
|
9806
|
+
currentShowLabel: propsPopover.showLabel,
|
|
9807
|
+
getAllNames: () => handleRef.current?.getAllPointNames() ?? [],
|
|
9808
|
+
onClose: dismissPropsPopover,
|
|
9809
|
+
onMutate: (patch) => {
|
|
9810
|
+
handleRef.current?.mutateObject(propsPopover.id, patch);
|
|
9811
|
+
if (patch.remove) dismissPropsPopover();
|
|
9812
|
+
if (typeof patch.valueLabel === "boolean" || patch.attrs) {
|
|
9813
|
+
setPropsPopover((cur) => cur ? { ...cur, showValue: patch.valueLabel ?? cur.showValue } : cur);
|
|
9814
|
+
}
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
9818
|
+
PropertiesPopover,
|
|
9819
|
+
{
|
|
9820
|
+
kind: propsPopover.kind,
|
|
9821
|
+
anchor: propsPopover.screenCoords,
|
|
9822
|
+
isDark,
|
|
9823
|
+
currentName: propsPopover.name,
|
|
9824
|
+
currentColor: propsPopover.color,
|
|
9825
|
+
currentDash: propsPopover.dash,
|
|
9826
|
+
currentWidth: propsPopover.width,
|
|
9827
|
+
currentShowLabel: propsPopover.showLabel,
|
|
9828
|
+
currentShowValue: propsPopover.showValue,
|
|
9829
|
+
getAllNames: () => handleRef.current?.getAllPointNames() ?? [],
|
|
9830
|
+
onClose: dismissPropsPopover,
|
|
9831
|
+
onMutate: (patch) => {
|
|
9832
|
+
handleRef.current?.mutateObject(propsPopover.id, patch);
|
|
9833
|
+
if (patch.remove) dismissPropsPopover();
|
|
9834
|
+
if (typeof patch.valueLabel === "boolean") {
|
|
9835
|
+
setPropsPopover((cur) => cur ? { ...cur, showValue: patch.valueLabel ?? cur.showValue } : cur);
|
|
9836
|
+
}
|
|
9837
|
+
if (patch.attrs && "withLabel" in patch.attrs) {
|
|
9838
|
+
setPropsPopover((cur) => cur ? { ...cur, showLabel: !!patch.attrs?.withLabel } : cur);
|
|
9839
|
+
}
|
|
9840
|
+
}
|
|
9841
|
+
}
|
|
9842
|
+
)),
|
|
9843
|
+
multiSelection && multiSelection.ids.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
9844
|
+
MultiPropertiesPopover,
|
|
9845
|
+
{
|
|
9846
|
+
anchor: multiSelection.anchor,
|
|
9847
|
+
count: multiSelection.ids.length,
|
|
9848
|
+
isDark,
|
|
9849
|
+
onColor: applyMultiColor,
|
|
9850
|
+
onDelete: applyMultiDelete,
|
|
9851
|
+
onClose: dismissMultiPopover
|
|
9852
|
+
}
|
|
9853
|
+
),
|
|
9854
|
+
transformPopover && (transformPopover.tool === "rotate" || transformPopover.tool === "dilate" || transformPopover.tool === "regularPolygon" || transformPopover.tool === "circleCR") && /* @__PURE__ */ jsxRuntime.jsx(
|
|
9855
|
+
TransformParamPopover,
|
|
9856
|
+
{
|
|
9857
|
+
kind: transformPopover.tool,
|
|
9858
|
+
anchor: transformPopover.anchor,
|
|
9859
|
+
defaultValue: transformPopover.tool === "rotate" ? 90 : transformPopover.tool === "dilate" ? 2 : transformPopover.tool === "circleCR" ? 3 : 6,
|
|
9860
|
+
isDark,
|
|
9861
|
+
onConfirm: (v) => {
|
|
9862
|
+
handleRef.current?.confirmTransformParam(v);
|
|
9863
|
+
setTransformPopover(null);
|
|
9864
|
+
},
|
|
9865
|
+
onCancel: () => {
|
|
9866
|
+
handleRef.current?.cancelTransformParam();
|
|
9867
|
+
setTransformPopover(null);
|
|
9868
|
+
}
|
|
9869
|
+
}
|
|
9870
|
+
),
|
|
9871
|
+
!isMobile && /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "flex items-center justify-between border-t border-slate-200 bg-slate-50 px-3 py-2", children: [
|
|
9872
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-slate-500", children: "Ch\u1ECDn c\xF4ng c\u1EE5 b\xEAn tr\xE1i, click tr\xEAn b\u1EA3ng \u0111\u1EC3 d\u1EF1ng h\xECnh." }),
|
|
9873
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
9874
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9875
|
+
"button",
|
|
9876
|
+
{
|
|
9877
|
+
onClick: onClose,
|
|
9878
|
+
className: "rounded border border-slate-300 bg-white px-3 py-1 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
|
|
9879
|
+
children: "Hu\u1EF7"
|
|
9880
|
+
}
|
|
9881
|
+
),
|
|
9882
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9883
|
+
"button",
|
|
9884
|
+
{
|
|
9885
|
+
onClick: performInsert,
|
|
9886
|
+
disabled: !ready || !hasContent,
|
|
9887
|
+
title: !hasContent ? "V\u1EBD \xEDt nh\u1EA5t m\u1ED9t \u0111\u1ED1i t\u01B0\u1EE3ng tr\u01B0\u1EDBc khi ch\xE8n" : void 0,
|
|
9888
|
+
"data-testid": "geometry-insert-btn",
|
|
9889
|
+
className: "rounded bg-emerald-600 px-3 py-1 text-xs font-medium text-white transition hover:bg-emerald-700 disabled:opacity-50",
|
|
9890
|
+
children: "Ch\xE8n"
|
|
9891
|
+
}
|
|
9892
|
+
)
|
|
9893
|
+
] })
|
|
9894
|
+
] }),
|
|
9895
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToastHost, {})
|
|
9896
|
+
]
|
|
10102
9897
|
}
|
|
10103
|
-
|
|
10104
|
-
}
|
|
10105
|
-
|
|
9898
|
+
);
|
|
9899
|
+
}
|
|
9900
|
+
);
|
|
9901
|
+
GeometryEditorPanel = React19.forwardRef(
|
|
9902
|
+
function GeometryEditorPanel2(props, ref) {
|
|
9903
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ToastProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(GeometryEditorPanelInner, { ...props, ref }) });
|
|
9904
|
+
}
|
|
9905
|
+
);
|
|
10106
9906
|
}
|
|
10107
9907
|
});
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
"
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
}
|
|
10143
|
-
}]
|
|
10144
|
-
});
|
|
10145
|
-
}
|
|
10146
|
-
});
|
|
10147
|
-
var secondIntersectionModule;
|
|
10148
|
-
var init_secondIntersection2 = __esm({
|
|
10149
|
-
"src/stamps/geometry-2d/dsl/kinds/points/secondIntersection.ts"() {
|
|
10150
|
-
init_names();
|
|
10151
|
-
init_types5();
|
|
10152
|
-
init_shared3();
|
|
10153
|
-
secondIntersectionModule = defineModule({
|
|
10154
|
-
kind: "secondIntersection",
|
|
10155
|
-
role: "point",
|
|
10156
|
-
category: "points",
|
|
10157
|
-
prefix: "p",
|
|
10158
|
-
schema: zod.z.object({
|
|
10159
|
-
name: NameZ,
|
|
10160
|
-
kind: zod.z.literal("secondIntersection"),
|
|
10161
|
-
line: NameZ,
|
|
10162
|
-
circle: NameZ,
|
|
10163
|
-
other: NameZ
|
|
10164
|
-
}),
|
|
10165
|
-
collectRefs: (e) => [e.line, e.circle, e.other],
|
|
10166
|
-
refSpecs: [
|
|
10167
|
-
{ field: "line", role: "line-like" },
|
|
10168
|
-
{ field: "circle", role: "circle" },
|
|
10169
|
-
{ field: "other", role: "point" }
|
|
10170
|
-
],
|
|
10171
|
-
emit: (e, ctx) => [{
|
|
10172
|
-
role: "primary",
|
|
10173
|
-
object: emitPointObject(
|
|
10174
|
-
ctx.resolveId(e.name),
|
|
10175
|
-
e.name,
|
|
10176
|
-
{
|
|
10177
|
-
kind: "secondIntersection",
|
|
10178
|
-
line: ctx.resolveId(e.line),
|
|
10179
|
-
circle: ctx.resolveId(e.circle),
|
|
10180
|
-
other: ctx.resolveId(e.other)
|
|
10181
|
-
}
|
|
10182
|
-
)
|
|
10183
|
-
}]
|
|
10184
|
-
});
|
|
10185
|
-
}
|
|
10186
|
-
});
|
|
10187
|
-
var circleIntersectionModule;
|
|
10188
|
-
var init_circleIntersection2 = __esm({
|
|
10189
|
-
"src/stamps/geometry-2d/dsl/kinds/points/circleIntersection.ts"() {
|
|
10190
|
-
init_names();
|
|
10191
|
-
init_types5();
|
|
10192
|
-
init_shared3();
|
|
10193
|
-
circleIntersectionModule = defineModule({
|
|
10194
|
-
kind: "circleIntersection",
|
|
10195
|
-
role: "point",
|
|
10196
|
-
category: "points",
|
|
10197
|
-
prefix: "p",
|
|
10198
|
-
schema: zod.z.object({
|
|
10199
|
-
name: NameZ,
|
|
10200
|
-
kind: zod.z.literal("circleIntersection"),
|
|
10201
|
-
c1: NameZ,
|
|
10202
|
-
c2: NameZ,
|
|
10203
|
-
which: zod.z.union([zod.z.literal(0), zod.z.literal(1)])
|
|
10204
|
-
}),
|
|
10205
|
-
collectRefs: (e) => [e.c1, e.c2],
|
|
10206
|
-
refSpecs: [
|
|
10207
|
-
{ field: "c1", role: "circle" },
|
|
10208
|
-
{ field: "c2", role: "circle" }
|
|
10209
|
-
],
|
|
10210
|
-
emit: (e, ctx) => [{
|
|
10211
|
-
role: "primary",
|
|
10212
|
-
object: emitPointObject(
|
|
10213
|
-
ctx.resolveId(e.name),
|
|
10214
|
-
e.name,
|
|
10215
|
-
{
|
|
10216
|
-
kind: "circleIntersection",
|
|
10217
|
-
c1: ctx.resolveId(e.c1),
|
|
10218
|
-
c2: ctx.resolveId(e.c2),
|
|
10219
|
-
which: e.which
|
|
10220
|
-
}
|
|
10221
|
-
)
|
|
10222
|
-
}]
|
|
10223
|
-
});
|
|
10224
|
-
}
|
|
10225
|
-
});
|
|
10226
|
-
var circleSecondIntersectionModule;
|
|
10227
|
-
var init_circleSecondIntersection2 = __esm({
|
|
10228
|
-
"src/stamps/geometry-2d/dsl/kinds/points/circleSecondIntersection.ts"() {
|
|
10229
|
-
init_names();
|
|
10230
|
-
init_types5();
|
|
10231
|
-
init_shared3();
|
|
10232
|
-
circleSecondIntersectionModule = defineModule({
|
|
10233
|
-
kind: "circleSecondIntersection",
|
|
10234
|
-
role: "point",
|
|
10235
|
-
category: "points",
|
|
10236
|
-
prefix: "p",
|
|
10237
|
-
schema: zod.z.object({
|
|
10238
|
-
name: NameZ,
|
|
10239
|
-
kind: zod.z.literal("circleSecondIntersection"),
|
|
10240
|
-
c1: NameZ,
|
|
10241
|
-
c2: NameZ,
|
|
10242
|
-
exclude: NameZ
|
|
10243
|
-
}),
|
|
10244
|
-
collectRefs: (e) => [e.c1, e.c2, e.exclude],
|
|
10245
|
-
refSpecs: [
|
|
10246
|
-
{ field: "c1", role: "circle" },
|
|
10247
|
-
{ field: "c2", role: "circle" },
|
|
10248
|
-
{ field: "exclude", role: "point" }
|
|
10249
|
-
],
|
|
10250
|
-
emit: (e, ctx) => [{
|
|
10251
|
-
role: "primary",
|
|
10252
|
-
object: emitPointObject(
|
|
10253
|
-
ctx.resolveId(e.name),
|
|
10254
|
-
e.name,
|
|
10255
|
-
{
|
|
10256
|
-
kind: "circleSecondIntersection",
|
|
10257
|
-
c1: ctx.resolveId(e.c1),
|
|
10258
|
-
c2: ctx.resolveId(e.c2),
|
|
10259
|
-
exclude: ctx.resolveId(e.exclude)
|
|
10260
|
-
}
|
|
10261
|
-
)
|
|
10262
|
-
}]
|
|
10263
|
-
});
|
|
10264
|
-
}
|
|
10265
|
-
});
|
|
10266
|
-
var tangencyPointModule;
|
|
10267
|
-
var init_tangencyPoint2 = __esm({
|
|
10268
|
-
"src/stamps/geometry-2d/dsl/kinds/points/tangencyPoint.ts"() {
|
|
10269
|
-
init_names();
|
|
10270
|
-
init_types5();
|
|
10271
|
-
init_shared3();
|
|
10272
|
-
tangencyPointModule = defineModule({
|
|
10273
|
-
kind: "tangencyPoint",
|
|
10274
|
-
role: "point",
|
|
10275
|
-
category: "points",
|
|
10276
|
-
prefix: "p",
|
|
10277
|
-
schema: zod.z.object({
|
|
10278
|
-
name: NameZ,
|
|
10279
|
-
kind: zod.z.literal("tangencyPoint"),
|
|
10280
|
-
circle: NameZ,
|
|
10281
|
-
onLine: NameZ
|
|
10282
|
-
}),
|
|
10283
|
-
collectRefs: (e) => [e.circle, e.onLine],
|
|
10284
|
-
refSpecs: [
|
|
10285
|
-
{ field: "circle", role: "circle" },
|
|
10286
|
-
{ field: "onLine", role: "line-like" }
|
|
10287
|
-
],
|
|
10288
|
-
emit: (e, ctx) => [{
|
|
10289
|
-
role: "primary",
|
|
10290
|
-
object: emitPointObject(
|
|
10291
|
-
ctx.resolveId(e.name),
|
|
10292
|
-
e.name,
|
|
10293
|
-
{
|
|
10294
|
-
kind: "tangencyPoint",
|
|
10295
|
-
circle: ctx.resolveId(e.circle),
|
|
10296
|
-
onLine: ctx.resolveId(e.onLine)
|
|
10297
|
-
}
|
|
10298
|
-
)
|
|
10299
|
-
}]
|
|
10300
|
-
});
|
|
10301
|
-
}
|
|
10302
|
-
});
|
|
10303
|
-
var tangentPointExtModule;
|
|
10304
|
-
var init_tangentPointExt2 = __esm({
|
|
10305
|
-
"src/stamps/geometry-2d/dsl/kinds/points/tangentPointExt.ts"() {
|
|
10306
|
-
init_names();
|
|
10307
|
-
init_types5();
|
|
10308
|
-
init_shared3();
|
|
10309
|
-
tangentPointExtModule = defineModule({
|
|
10310
|
-
kind: "tangentPointExt",
|
|
10311
|
-
role: "point",
|
|
10312
|
-
category: "points",
|
|
10313
|
-
prefix: "p",
|
|
10314
|
-
schema: zod.z.object({
|
|
10315
|
-
name: NameZ,
|
|
10316
|
-
kind: zod.z.literal("tangentPointExt"),
|
|
10317
|
-
from: NameZ,
|
|
10318
|
-
circle: NameZ,
|
|
10319
|
-
which: zod.z.union([zod.z.literal(0), zod.z.literal(1)])
|
|
10320
|
-
}),
|
|
10321
|
-
collectRefs: (e) => [e.from, e.circle],
|
|
10322
|
-
refSpecs: [
|
|
10323
|
-
{ field: "from", role: "point" },
|
|
10324
|
-
{ field: "circle", role: "circle" }
|
|
10325
|
-
],
|
|
10326
|
-
emit: (e, ctx) => [{
|
|
10327
|
-
role: "primary",
|
|
10328
|
-
object: emitPointObject(
|
|
10329
|
-
ctx.resolveId(e.name),
|
|
10330
|
-
e.name,
|
|
10331
|
-
{
|
|
10332
|
-
kind: "tangentPointExt",
|
|
10333
|
-
from: ctx.resolveId(e.from),
|
|
10334
|
-
circle: ctx.resolveId(e.circle),
|
|
10335
|
-
which: e.which
|
|
10336
|
-
}
|
|
10337
|
-
)
|
|
10338
|
-
}]
|
|
10339
|
-
});
|
|
10340
|
-
}
|
|
10341
|
-
});
|
|
10342
|
-
var circleCRModule;
|
|
10343
|
-
var init_circleCR = __esm({
|
|
10344
|
-
"src/stamps/geometry-2d/dsl/kinds/circles/circleCR.ts"() {
|
|
10345
|
-
init_names();
|
|
10346
|
-
init_types5();
|
|
10347
|
-
init_shared3();
|
|
10348
|
-
circleCRModule = defineModule({
|
|
10349
|
-
kind: "circleCR",
|
|
10350
|
-
role: "circle",
|
|
10351
|
-
category: "circles",
|
|
10352
|
-
prefix: "c",
|
|
10353
|
-
schema: zod.z.object({
|
|
10354
|
-
name: NameZ,
|
|
10355
|
-
kind: zod.z.literal("circleCR"),
|
|
10356
|
-
center: NameZ,
|
|
10357
|
-
radius: zod.z.number().positive()
|
|
10358
|
-
}),
|
|
10359
|
-
collectRefs: (e) => [e.center],
|
|
10360
|
-
refSpecs: [{ field: "center", role: "point" }],
|
|
10361
|
-
emit: (e, ctx) => [{
|
|
10362
|
-
role: "primary",
|
|
10363
|
-
object: {
|
|
10364
|
-
id: ctx.resolveId(e.name),
|
|
10365
|
-
kind: "circle",
|
|
10366
|
-
label: e.name,
|
|
10367
|
-
...SHAPE_BASE_FIELDS,
|
|
10368
|
-
attrs: { center: ctx.resolveId(e.center), radius: e.radius }
|
|
9908
|
+
function isFieldFocused() {
|
|
9909
|
+
const ae = typeof document !== "undefined" ? document.activeElement : null;
|
|
9910
|
+
return !!(ae && (ae.tagName === "INPUT" || ae.tagName === "TEXTAREA" || ae.isContentEditable));
|
|
9911
|
+
}
|
|
9912
|
+
function useChordShortcut(args) {
|
|
9913
|
+
const { groupOrder, tools, onSelect, enabled } = args;
|
|
9914
|
+
const [chordGroup, setChordGroup] = React19.useState(null);
|
|
9915
|
+
const groupOrderRef = React19.useRef(groupOrder);
|
|
9916
|
+
const toolsRef = React19.useRef(tools);
|
|
9917
|
+
const onSelectRef = React19.useRef(onSelect);
|
|
9918
|
+
const chordGroupRef = React19.useRef(null);
|
|
9919
|
+
groupOrderRef.current = groupOrder;
|
|
9920
|
+
toolsRef.current = tools;
|
|
9921
|
+
onSelectRef.current = onSelect;
|
|
9922
|
+
const cancel = React19.useCallback(() => {
|
|
9923
|
+
chordGroupRef.current = null;
|
|
9924
|
+
setChordGroup(null);
|
|
9925
|
+
}, []);
|
|
9926
|
+
React19.useEffect(() => {
|
|
9927
|
+
if (!enabled) return;
|
|
9928
|
+
const setChord = (next) => {
|
|
9929
|
+
chordGroupRef.current = next;
|
|
9930
|
+
setChordGroup(next);
|
|
9931
|
+
};
|
|
9932
|
+
const onKey = (e) => {
|
|
9933
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
9934
|
+
if (isFieldFocused()) return;
|
|
9935
|
+
const key = e.key;
|
|
9936
|
+
const lower = key.length === 1 ? key.toLowerCase() : key;
|
|
9937
|
+
if (key === "Escape") {
|
|
9938
|
+
if (chordGroupRef.current !== null) {
|
|
9939
|
+
e.preventDefault();
|
|
9940
|
+
e.stopPropagation();
|
|
9941
|
+
setChord(null);
|
|
10369
9942
|
}
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
init_types5();
|
|
10379
|
-
init_shared3();
|
|
10380
|
-
incircleModule = defineModule({
|
|
10381
|
-
kind: "incircle",
|
|
10382
|
-
role: "circle",
|
|
10383
|
-
category: "circles",
|
|
10384
|
-
prefix: "c",
|
|
10385
|
-
schema: zod.z.object({
|
|
10386
|
-
name: NameZ,
|
|
10387
|
-
kind: zod.z.literal("incircle"),
|
|
10388
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
10389
|
-
}),
|
|
10390
|
-
collectRefs: (e) => [...e.vertices],
|
|
10391
|
-
refSpecs: [{ field: "vertices", role: "point", many: true }],
|
|
10392
|
-
emit: (e, ctx) => [{
|
|
10393
|
-
role: "primary",
|
|
10394
|
-
object: {
|
|
10395
|
-
id: ctx.resolveId(e.name),
|
|
10396
|
-
kind: "circle",
|
|
10397
|
-
label: e.name,
|
|
10398
|
-
...SHAPE_BASE_FIELDS,
|
|
10399
|
-
attrs: {
|
|
10400
|
-
kind: "incircle",
|
|
10401
|
-
vertices: [
|
|
10402
|
-
ctx.resolveId(e.vertices[0]),
|
|
10403
|
-
ctx.resolveId(e.vertices[1]),
|
|
10404
|
-
ctx.resolveId(e.vertices[2])
|
|
10405
|
-
]
|
|
10406
|
-
}
|
|
9943
|
+
return;
|
|
9944
|
+
}
|
|
9945
|
+
if (lower.length === 1 && lower >= "a" && lower <= "z") {
|
|
9946
|
+
const idx = lower.charCodeAt(0) - A_CODE2;
|
|
9947
|
+
if (idx < groupOrderRef.current.length) {
|
|
9948
|
+
e.preventDefault();
|
|
9949
|
+
e.stopPropagation();
|
|
9950
|
+
setChord(groupOrderRef.current[idx]);
|
|
10407
9951
|
}
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
category: "circles",
|
|
10422
|
-
prefix: "c",
|
|
10423
|
-
schema: zod.z.object({
|
|
10424
|
-
name: NameZ,
|
|
10425
|
-
kind: zod.z.literal("excircle"),
|
|
10426
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ]),
|
|
10427
|
-
opposite: NameZ
|
|
10428
|
-
}),
|
|
10429
|
-
collectRefs: (e) => [...e.vertices],
|
|
10430
|
-
refSpecs: [
|
|
10431
|
-
{ field: "vertices", role: "point", many: true },
|
|
10432
|
-
{ field: "opposite", role: "point" }
|
|
10433
|
-
],
|
|
10434
|
-
emit: (e, ctx) => [{
|
|
10435
|
-
role: "primary",
|
|
10436
|
-
object: {
|
|
10437
|
-
id: ctx.resolveId(e.name),
|
|
10438
|
-
kind: "circle",
|
|
10439
|
-
label: e.name,
|
|
10440
|
-
...SHAPE_BASE_FIELDS,
|
|
10441
|
-
attrs: {
|
|
10442
|
-
construction: {
|
|
10443
|
-
kind: "excircle",
|
|
10444
|
-
p1: ctx.resolveId(e.vertices[0]),
|
|
10445
|
-
p2: ctx.resolveId(e.vertices[1]),
|
|
10446
|
-
p3: ctx.resolveId(e.vertices[2]),
|
|
10447
|
-
opposite: ctx.resolveId(e.opposite)
|
|
10448
|
-
}
|
|
10449
|
-
}
|
|
9952
|
+
return;
|
|
9953
|
+
}
|
|
9954
|
+
if (key >= "1" && key <= "9") {
|
|
9955
|
+
const active = chordGroupRef.current;
|
|
9956
|
+
if (active === null) return;
|
|
9957
|
+
const n = key.charCodeAt(0) - "1".charCodeAt(0);
|
|
9958
|
+
const toolsInGroup = toolsRef.current.filter(
|
|
9959
|
+
(t) => t.group === active
|
|
9960
|
+
);
|
|
9961
|
+
e.preventDefault();
|
|
9962
|
+
e.stopPropagation();
|
|
9963
|
+
if (n < toolsInGroup.length) {
|
|
9964
|
+
onSelectRef.current(toolsInGroup[n].key);
|
|
10450
9965
|
}
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
}
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
schema: zod.z.object({
|
|
10467
|
-
name: NameZ,
|
|
10468
|
-
kind: zod.z.literal("arcMidpoint"),
|
|
10469
|
-
circle: NameZ,
|
|
10470
|
-
a: NameZ,
|
|
10471
|
-
b: NameZ,
|
|
10472
|
-
notContaining: NameZ
|
|
10473
|
-
}),
|
|
10474
|
-
collectRefs: (e) => [e.circle, e.a, e.b, e.notContaining],
|
|
10475
|
-
emit: (e, ctx) => [{
|
|
10476
|
-
role: "primary",
|
|
10477
|
-
object: emitPointObject(ctx.resolveId(e.name), e.name, {
|
|
10478
|
-
kind: "arcMidpoint",
|
|
10479
|
-
circle: ctx.resolveId(e.circle),
|
|
10480
|
-
a: ctx.resolveId(e.a),
|
|
10481
|
-
b: ctx.resolveId(e.b),
|
|
10482
|
-
notContaining: ctx.resolveId(e.notContaining)
|
|
10483
|
-
})
|
|
10484
|
-
}]
|
|
10485
|
-
});
|
|
9966
|
+
setChord(null);
|
|
9967
|
+
return;
|
|
9968
|
+
}
|
|
9969
|
+
};
|
|
9970
|
+
window.addEventListener("keydown", onKey, { capture: true });
|
|
9971
|
+
return () => {
|
|
9972
|
+
window.removeEventListener("keydown", onKey, { capture: true });
|
|
9973
|
+
};
|
|
9974
|
+
}, [enabled]);
|
|
9975
|
+
return { chordGroup, cancel };
|
|
9976
|
+
}
|
|
9977
|
+
var A_CODE2;
|
|
9978
|
+
var init_useChordShortcut = __esm({
|
|
9979
|
+
"src/stamps/shared/useChordShortcut.ts"() {
|
|
9980
|
+
A_CODE2 = "a".charCodeAt(0);
|
|
10486
9981
|
}
|
|
10487
9982
|
});
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
9983
|
+
|
|
9984
|
+
// src/stamps/shared/insertImage.ts
|
|
9985
|
+
function buildStampImageElement(api, fileId, width, height, customData, x, y) {
|
|
9986
|
+
const appState = api?.getAppState() ?? { scrollX: 0, scrollY: 0, width: 800, height: 600, zoom: { value: 1 } };
|
|
9987
|
+
const cx = x ?? appState.scrollX + (appState.width ?? 800) / 2 / (appState.zoom?.value ?? 1) - width / 2;
|
|
9988
|
+
const cy = y ?? appState.scrollY + (appState.height ?? 600) / 2 / (appState.zoom?.value ?? 1) - height / 2;
|
|
9989
|
+
return {
|
|
9990
|
+
type: "image",
|
|
9991
|
+
id: "stamp_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8),
|
|
9992
|
+
x: cx,
|
|
9993
|
+
y: cy,
|
|
9994
|
+
width,
|
|
9995
|
+
height,
|
|
9996
|
+
fileId,
|
|
9997
|
+
customData,
|
|
9998
|
+
angle: 0,
|
|
9999
|
+
strokeColor: "transparent",
|
|
10000
|
+
backgroundColor: "transparent",
|
|
10001
|
+
fillStyle: "solid",
|
|
10002
|
+
strokeWidth: 1,
|
|
10003
|
+
strokeStyle: "solid",
|
|
10004
|
+
roughness: 0,
|
|
10005
|
+
opacity: 100,
|
|
10006
|
+
groupIds: [],
|
|
10007
|
+
roundness: null,
|
|
10008
|
+
seed: Math.floor(Math.random() * 1e9),
|
|
10009
|
+
versionNonce: 0,
|
|
10010
|
+
version: 1,
|
|
10011
|
+
isDeleted: false,
|
|
10012
|
+
boundElements: null,
|
|
10013
|
+
updated: Date.now(),
|
|
10014
|
+
link: null,
|
|
10015
|
+
locked: false,
|
|
10016
|
+
status: "saved",
|
|
10017
|
+
scale: [1, 1]
|
|
10018
|
+
};
|
|
10019
|
+
}
|
|
10020
|
+
async function insertStampImage(api, opts) {
|
|
10021
|
+
const { dataURL, fileId, width, height, mimeType } = await createStampFile(opts.svgString);
|
|
10022
|
+
api.addFiles([{ id: fileId, dataURL, mimeType, created: Date.now() }]);
|
|
10023
|
+
const customData = opts.makeCustomData();
|
|
10024
|
+
const elements = api.getSceneElements();
|
|
10025
|
+
const editingId = opts.editingElementId ?? null;
|
|
10026
|
+
if (editingId) {
|
|
10027
|
+
const updated = elements.map(
|
|
10028
|
+
(e) => e.id === editingId ? { ...e, fileId, customData, width, height } : e
|
|
10029
|
+
);
|
|
10030
|
+
api.updateScene({ elements: updated, appState: clearAppStateAfterInsert() });
|
|
10031
|
+
return { fileId, width, height, elementId: editingId };
|
|
10515
10032
|
}
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10033
|
+
const newElement = buildStampImageElement(
|
|
10034
|
+
api,
|
|
10035
|
+
fileId,
|
|
10036
|
+
width,
|
|
10037
|
+
height,
|
|
10038
|
+
customData,
|
|
10039
|
+
opts.position?.x,
|
|
10040
|
+
opts.position?.y
|
|
10041
|
+
);
|
|
10042
|
+
api.updateScene({
|
|
10043
|
+
elements: [...elements, newElement],
|
|
10044
|
+
appState: clearAppStateAfterInsert()
|
|
10045
|
+
});
|
|
10046
|
+
return { fileId, width, height, elementId: newElement.id };
|
|
10047
|
+
}
|
|
10048
|
+
var clearAppStateAfterInsert;
|
|
10049
|
+
var init_insertImage = __esm({
|
|
10050
|
+
"src/stamps/shared/insertImage.ts"() {
|
|
10051
|
+
init_svgToStampFile();
|
|
10052
|
+
clearAppStateAfterInsert = () => ({
|
|
10053
|
+
selectedElementIds: {},
|
|
10054
|
+
croppingElementId: null
|
|
10538
10055
|
});
|
|
10539
10056
|
}
|
|
10540
10057
|
});
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
init_shared3();
|
|
10547
|
-
reflectLineModule = defineModule({
|
|
10548
|
-
kind: "reflectLine",
|
|
10549
|
-
role: "point",
|
|
10550
|
-
category: "points",
|
|
10551
|
-
prefix: "p",
|
|
10552
|
-
schema: zod.z.object({ name: NameZ, kind: zod.z.literal("reflectLine"), of: NameZ, through: NameZ }),
|
|
10553
|
-
collectRefs: (e) => [e.of, e.through],
|
|
10554
|
-
emit: (e, ctx) => [{
|
|
10555
|
-
role: "primary",
|
|
10556
|
-
object: emitPointObject(ctx.resolveId(e.name), e.name, {
|
|
10557
|
-
kind: "transformed",
|
|
10558
|
-
source: ctx.resolveId(e.of),
|
|
10559
|
-
transform: { kind: "reflectLine", line: ctx.resolveId(e.through) }
|
|
10560
|
-
})
|
|
10561
|
-
}]
|
|
10562
|
-
});
|
|
10058
|
+
function useStampStore(domain, editingElement, parseInitial) {
|
|
10059
|
+
const ref = React19.useRef(null);
|
|
10060
|
+
if (!ref.current) {
|
|
10061
|
+
const initial = editingElement?.customData ? parseInitial(editingElement.customData) ?? createEmptyState(domain) : createEmptyState(domain);
|
|
10062
|
+
ref.current = createStore(initial);
|
|
10563
10063
|
}
|
|
10564
|
-
|
|
10565
|
-
function withScaleOffset(base, d) {
|
|
10566
|
-
const out = { ...base };
|
|
10567
|
-
if (d.scale !== void 0) out.scale = d.scale;
|
|
10568
|
-
if (d.offset !== void 0) out.offset = d.offset;
|
|
10569
|
-
return out;
|
|
10064
|
+
return ref.current;
|
|
10570
10065
|
}
|
|
10571
|
-
var
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
init_names();
|
|
10575
|
-
init_types5();
|
|
10576
|
-
init_shared3();
|
|
10577
|
-
ScaleOffsetZ = {
|
|
10578
|
-
scale: zod.z.number().positive().optional(),
|
|
10579
|
-
offset: zod.z.number().optional()
|
|
10580
|
-
};
|
|
10581
|
-
DistanceZ = zod.z.discriminatedUnion("kind", [
|
|
10582
|
-
zod.z.object({ kind: zod.z.literal("circleRadius"), circle: NameZ, ...ScaleOffsetZ }),
|
|
10583
|
-
zod.z.object({ kind: zod.z.literal("segmentLength"), p1: NameZ, p2: NameZ, ...ScaleOffsetZ }),
|
|
10584
|
-
zod.z.object({ kind: zod.z.literal("literal"), value: zod.z.number().positive(), ...ScaleOffsetZ })
|
|
10585
|
-
]);
|
|
10586
|
-
pointAtDistanceModule = defineModule({
|
|
10587
|
-
kind: "pointAtDistance",
|
|
10588
|
-
role: "point",
|
|
10589
|
-
category: "points",
|
|
10590
|
-
prefix: "p",
|
|
10591
|
-
schema: zod.z.object({
|
|
10592
|
-
name: NameZ,
|
|
10593
|
-
kind: zod.z.literal("pointAtDistance"),
|
|
10594
|
-
from: NameZ,
|
|
10595
|
-
through: NameZ,
|
|
10596
|
-
distance: DistanceZ
|
|
10597
|
-
}),
|
|
10598
|
-
collectRefs: (e) => {
|
|
10599
|
-
const d = e.distance;
|
|
10600
|
-
const extra = d.kind === "circleRadius" ? [d.circle] : d.kind === "segmentLength" ? [d.p1, d.p2] : [];
|
|
10601
|
-
return [e.from, e.through, ...extra];
|
|
10602
|
-
},
|
|
10603
|
-
// TODO(Mức 1 defer): distance.{circle,p1,p2} là nested trong `distance` — refSpec
|
|
10604
|
-
// phẳng đọc top-level không với tới, validate riêng nếu cần. Hiện validate from/through.
|
|
10605
|
-
refSpecs: [
|
|
10606
|
-
{ field: "from", role: "point" },
|
|
10607
|
-
{ field: "through", role: "point" }
|
|
10608
|
-
],
|
|
10609
|
-
emit: (e, ctx) => {
|
|
10610
|
-
const d = e.distance;
|
|
10611
|
-
const distance = d.kind === "circleRadius" ? withScaleOffset({ kind: "circleRadius", circle: ctx.resolveId(d.circle) }, d) : d.kind === "segmentLength" ? withScaleOffset({ kind: "segmentLength", p1: ctx.resolveId(d.p1), p2: ctx.resolveId(d.p2) }, d) : withScaleOffset({ kind: "literal", value: d.value }, d);
|
|
10612
|
-
return [{
|
|
10613
|
-
role: "primary",
|
|
10614
|
-
object: emitPointObject(ctx.resolveId(e.name), e.name, {
|
|
10615
|
-
kind: "pointAtDistance",
|
|
10616
|
-
from: ctx.resolveId(e.from),
|
|
10617
|
-
through: ctx.resolveId(e.through),
|
|
10618
|
-
distance
|
|
10619
|
-
})
|
|
10620
|
-
}];
|
|
10621
|
-
}
|
|
10622
|
-
});
|
|
10623
|
-
}
|
|
10624
|
-
});
|
|
10625
|
-
var ALL_MODULES, POINT_KINDS;
|
|
10626
|
-
var init_registry4 = __esm({
|
|
10627
|
-
"src/stamps/geometry-2d/dsl/registry.ts"() {
|
|
10628
|
-
init_free2();
|
|
10629
|
-
init_midpoint2();
|
|
10630
|
-
init_onSegment2();
|
|
10631
|
-
init_onLine2();
|
|
10632
|
-
init_onCircle2();
|
|
10633
|
-
init_perpFoot2();
|
|
10634
|
-
init_circumcenter2();
|
|
10635
|
-
init_incenter2();
|
|
10636
|
-
init_centroid2();
|
|
10637
|
-
init_orthocenter2();
|
|
10638
|
-
init_intersection2();
|
|
10639
|
-
init_segment2();
|
|
10640
|
-
init_line2();
|
|
10641
|
-
init_ray2();
|
|
10642
|
-
init_perpendicular();
|
|
10643
|
-
init_parallel();
|
|
10644
|
-
init_perpBisector();
|
|
10645
|
-
init_angleBisector();
|
|
10646
|
-
init_lineThrough();
|
|
10647
|
-
init_radicalAxis();
|
|
10648
|
-
init_tangent();
|
|
10649
|
-
init_polygon3();
|
|
10650
|
-
init_circleCP();
|
|
10651
|
-
init_circle3();
|
|
10652
|
-
init_circleDiameter();
|
|
10653
|
-
init_secondIntersection2();
|
|
10654
|
-
init_circleIntersection2();
|
|
10655
|
-
init_circleSecondIntersection2();
|
|
10656
|
-
init_tangencyPoint2();
|
|
10657
|
-
init_tangentPointExt2();
|
|
10658
|
-
init_circleCR();
|
|
10659
|
-
init_incircle();
|
|
10660
|
-
init_excircle();
|
|
10661
|
-
init_arcMidpoint2();
|
|
10662
|
-
init_excenter2();
|
|
10663
|
-
init_reflectPoint();
|
|
10664
|
-
init_reflectLine();
|
|
10665
|
-
init_pointAtDistance2();
|
|
10666
|
-
ALL_MODULES = [
|
|
10667
|
-
freeModule,
|
|
10668
|
-
midpointModule,
|
|
10669
|
-
onSegmentModule,
|
|
10670
|
-
onLineModule,
|
|
10671
|
-
onCircleModule,
|
|
10672
|
-
perpFootModule,
|
|
10673
|
-
circumcenterModule,
|
|
10674
|
-
incenterModule,
|
|
10675
|
-
centroidModule,
|
|
10676
|
-
orthocenterModule,
|
|
10677
|
-
intersectionModule,
|
|
10678
|
-
// NEW Tier 4+5 points
|
|
10679
|
-
secondIntersectionModule,
|
|
10680
|
-
circleIntersectionModule,
|
|
10681
|
-
circleSecondIntersectionModule,
|
|
10682
|
-
tangencyPointModule,
|
|
10683
|
-
tangentPointExtModule,
|
|
10684
|
-
segmentModule,
|
|
10685
|
-
lineModule,
|
|
10686
|
-
rayModule,
|
|
10687
|
-
perpendicularModule,
|
|
10688
|
-
parallelModule,
|
|
10689
|
-
perpBisectorModule,
|
|
10690
|
-
angleBisectorModule,
|
|
10691
|
-
lineThroughModule,
|
|
10692
|
-
radicalAxisModule,
|
|
10693
|
-
tangentModule,
|
|
10694
|
-
polygonModule,
|
|
10695
|
-
circleCPModule,
|
|
10696
|
-
circle3Module,
|
|
10697
|
-
circleDiameterModule,
|
|
10698
|
-
// NEW Tier 4+5 circles
|
|
10699
|
-
circleCRModule,
|
|
10700
|
-
incircleModule,
|
|
10701
|
-
excircleModule,
|
|
10702
|
-
// Cụm A points
|
|
10703
|
-
arcMidpointModule,
|
|
10704
|
-
excenterModule,
|
|
10705
|
-
reflectPointModule,
|
|
10706
|
-
reflectLineModule,
|
|
10707
|
-
// Cụm B points
|
|
10708
|
-
pointAtDistanceModule
|
|
10709
|
-
];
|
|
10710
|
-
new Map(ALL_MODULES.map((m) => [m.kind, m]));
|
|
10711
|
-
POINT_KINDS = new Set(
|
|
10712
|
-
ALL_MODULES.filter((m) => m.role === "point").map((m) => m.kind)
|
|
10713
|
-
);
|
|
10714
|
-
new Set(
|
|
10715
|
-
ALL_MODULES.filter(
|
|
10716
|
-
(m) => m.role === "segment" || m.role === "line" || m.role === "ray" || m.role === "lineConstruction"
|
|
10717
|
-
).map((m) => m.kind)
|
|
10718
|
-
);
|
|
10719
|
-
new Set(
|
|
10720
|
-
ALL_MODULES.filter((m) => m.role === "circle").map((m) => m.kind)
|
|
10721
|
-
);
|
|
10722
|
-
zod.z.discriminatedUnion(
|
|
10723
|
-
"kind",
|
|
10724
|
-
ALL_MODULES.map((m) => m.schema)
|
|
10725
|
-
);
|
|
10066
|
+
var init_useStampStore = __esm({
|
|
10067
|
+
"src/stamps/shared/useStampStore.ts"() {
|
|
10068
|
+
init_scene();
|
|
10726
10069
|
}
|
|
10727
10070
|
});
|
|
10728
10071
|
|
|
@@ -11018,2182 +10361,104 @@ function serializeCircle(obj, state) {
|
|
|
11018
10361
|
};
|
|
11019
10362
|
}
|
|
11020
10363
|
if (raw.kind === "excircle" && raw.vertices && raw.opposite) {
|
|
11021
|
-
return {
|
|
11022
|
-
kind: "excircle",
|
|
11023
|
-
p1: raw.vertices[0],
|
|
11024
|
-
p2: raw.vertices[1],
|
|
11025
|
-
p3: raw.vertices[2],
|
|
11026
|
-
opposite: raw.opposite
|
|
11027
|
-
};
|
|
11028
|
-
}
|
|
11029
|
-
if (raw.kind === "circleDiameter" && raw.p1 && raw.p2) {
|
|
11030
|
-
return {
|
|
11031
|
-
kind: "diameter",
|
|
11032
|
-
p1: raw.p1,
|
|
11033
|
-
p2: raw.p2
|
|
11034
|
-
};
|
|
11035
|
-
}
|
|
11036
|
-
return void 0;
|
|
11037
|
-
})();
|
|
11038
|
-
if (!c) {
|
|
11039
|
-
if (typeof a.radius === "number") {
|
|
11040
|
-
if (!a.center) return fail("unsupported-construction", "missing center");
|
|
11041
|
-
const refs2 = resolveRefs([a.center], state);
|
|
11042
|
-
if (!refs2) return fail("unresolved-ref", `${a.center}`);
|
|
11043
|
-
return {
|
|
11044
|
-
ok: true,
|
|
11045
|
-
entity: { name: obj.label, kind: "circleCR", center: refs2[0], radius: a.radius }
|
|
11046
|
-
};
|
|
11047
|
-
}
|
|
11048
|
-
if (!a.center || !a.surfacePoint) {
|
|
11049
|
-
return fail("unsupported-construction", "missing center/surfacePoint");
|
|
11050
|
-
}
|
|
11051
|
-
const refs = resolveRefs([a.center, a.surfacePoint], state);
|
|
11052
|
-
if (!refs) return fail("unresolved-ref", `${a.center},${a.surfacePoint}`);
|
|
11053
|
-
return {
|
|
11054
|
-
ok: true,
|
|
11055
|
-
entity: { name: obj.label, kind: "circleCP", center: refs[0], surfacePoint: refs[1] }
|
|
11056
|
-
};
|
|
11057
|
-
}
|
|
11058
|
-
if (c.kind === "circumscribed") {
|
|
11059
|
-
const refs = resolveRefs([c.p1, c.p2, c.p3], state);
|
|
11060
|
-
if (!refs) return fail("unresolved-ref", `${c.p1},${c.p2},${c.p3}`);
|
|
11061
|
-
return {
|
|
11062
|
-
ok: true,
|
|
11063
|
-
entity: { name: obj.label, kind: "circle3", p1: refs[0], p2: refs[1], p3: refs[2] }
|
|
11064
|
-
};
|
|
11065
|
-
}
|
|
11066
|
-
if (c.kind === "incircle") {
|
|
11067
|
-
const refs = resolveRefs([c.p1, c.p2, c.p3], state);
|
|
11068
|
-
if (!refs) return fail("unresolved-ref", `${c.p1},${c.p2},${c.p3}`);
|
|
11069
|
-
return {
|
|
11070
|
-
ok: true,
|
|
11071
|
-
entity: { name: obj.label, kind: "incircle", vertices: [refs[0], refs[1], refs[2]] }
|
|
11072
|
-
};
|
|
11073
|
-
}
|
|
11074
|
-
if (c.kind === "excircle") {
|
|
11075
|
-
const refs = resolveRefs([c.p1, c.p2, c.p3, c.opposite], state);
|
|
11076
|
-
if (!refs) return fail("unresolved-ref", `${c.p1},${c.p2},${c.p3},${c.opposite}`);
|
|
11077
|
-
return {
|
|
11078
|
-
ok: true,
|
|
11079
|
-
entity: { name: obj.label, kind: "excircle", vertices: [refs[0], refs[1], refs[2]], opposite: refs[3] }
|
|
11080
|
-
};
|
|
11081
|
-
}
|
|
11082
|
-
if (c.kind === "diameter") {
|
|
11083
|
-
const refs = resolveRefs([c.p1, c.p2], state);
|
|
11084
|
-
if (!refs) return fail("unresolved-ref", `${c.p1},${c.p2}`);
|
|
11085
|
-
return {
|
|
11086
|
-
ok: true,
|
|
11087
|
-
entity: { name: obj.label, kind: "circleDiameter", p1: refs[0], p2: refs[1] }
|
|
11088
|
-
};
|
|
11089
|
-
}
|
|
11090
|
-
return fail("unsupported-construction");
|
|
11091
|
-
}
|
|
11092
|
-
function serializeObject(obj, state) {
|
|
11093
|
-
if (!isValidName(obj.label)) {
|
|
11094
|
-
return fail("invalid-label", obj.label);
|
|
11095
|
-
}
|
|
11096
|
-
switch (obj.kind) {
|
|
11097
|
-
case "point":
|
|
11098
|
-
return serializePoint(obj, state);
|
|
11099
|
-
case "segment":
|
|
11100
|
-
return serializeSegment(obj, state);
|
|
11101
|
-
case "ray":
|
|
11102
|
-
return serializeRay(obj, state);
|
|
11103
|
-
case "line":
|
|
11104
|
-
return serializeLine(obj, state);
|
|
11105
|
-
case "polygon":
|
|
11106
|
-
return serializePolygon(obj, state);
|
|
11107
|
-
case "circle":
|
|
11108
|
-
return serializeCircle(obj, state);
|
|
11109
|
-
case "intersection":
|
|
11110
|
-
return serializeIntersection(obj, state);
|
|
11111
|
-
default:
|
|
11112
|
-
return fail("unsupported-kind", obj.kind);
|
|
11113
|
-
}
|
|
11114
|
-
}
|
|
11115
|
-
function serializeState(state) {
|
|
11116
|
-
const points = [];
|
|
11117
|
-
const shapes = [];
|
|
11118
|
-
const unsupported = [];
|
|
11119
|
-
for (const id of state.order) {
|
|
11120
|
-
const obj = state.objects[id];
|
|
11121
|
-
if (!obj) continue;
|
|
11122
|
-
const r = serializeObject(obj, state);
|
|
11123
|
-
if (!r.ok) {
|
|
11124
|
-
unsupported.push({
|
|
11125
|
-
id: obj.id,
|
|
11126
|
-
label: obj.label,
|
|
11127
|
-
kind: obj.kind,
|
|
11128
|
-
reason: r.reason,
|
|
11129
|
-
detail: r.detail
|
|
11130
|
-
});
|
|
11131
|
-
continue;
|
|
11132
|
-
}
|
|
11133
|
-
if (POINT_KINDS.has(r.entity.kind)) {
|
|
11134
|
-
points.push(r.entity);
|
|
11135
|
-
} else {
|
|
11136
|
-
shapes.push(r.entity);
|
|
11137
|
-
}
|
|
11138
|
-
}
|
|
11139
|
-
return {
|
|
11140
|
-
dsl: { version: 1, points, shapes },
|
|
11141
|
-
unsupported
|
|
11142
|
-
};
|
|
11143
|
-
}
|
|
11144
|
-
var NAME_REGEX;
|
|
11145
|
-
var init_serialize2 = __esm({
|
|
11146
|
-
"src/stamps/geometry-2d/dsl/serialize.ts"() {
|
|
11147
|
-
init_registry4();
|
|
11148
|
-
NAME_REGEX = /^[A-Za-z][A-Za-z0-9_'₀-₉]{0,11}$/;
|
|
11149
|
-
}
|
|
11150
|
-
});
|
|
11151
|
-
function useAiFigure(generator, options = {}) {
|
|
11152
|
-
const { currentState } = options;
|
|
11153
|
-
const [prompt, setPrompt] = React19.useState("");
|
|
11154
|
-
const [isLoading, setIsLoading] = React19.useState(false);
|
|
11155
|
-
const [error, setError] = React19.useState(null);
|
|
11156
|
-
const [tokens, setTokens] = React19.useState(0);
|
|
11157
|
-
const abortRef = React19.useRef(null);
|
|
11158
|
-
const requestIdRef = React19.useRef(0);
|
|
11159
|
-
const { dsl: currentDsl, unsupported, entityCount, hasContent } = React19.useMemo(() => {
|
|
11160
|
-
if (!currentState || currentState.order.length === 0) {
|
|
11161
|
-
return {
|
|
11162
|
-
dsl: null,
|
|
11163
|
-
unsupported: [],
|
|
11164
|
-
entityCount: { points: 0, shapes: 0 },
|
|
11165
|
-
hasContent: false
|
|
11166
|
-
};
|
|
11167
|
-
}
|
|
11168
|
-
const { dsl, unsupported: unsupported2 } = serializeState(currentState);
|
|
11169
|
-
return {
|
|
11170
|
-
dsl,
|
|
11171
|
-
unsupported: unsupported2,
|
|
11172
|
-
entityCount: { points: dsl.points.length, shapes: dsl.shapes.length },
|
|
11173
|
-
hasContent: true
|
|
11174
|
-
};
|
|
11175
|
-
}, [currentState]);
|
|
11176
|
-
const hasUnsupported = unsupported.length > 0;
|
|
11177
|
-
const initialMode = hasContent && !hasUnsupported ? "refine" : "build";
|
|
11178
|
-
const [mode, setModeInternal] = React19.useState(initialMode);
|
|
11179
|
-
React19.useEffect(() => {
|
|
11180
|
-
if (!hasContent && mode === "refine") setModeInternal("build");
|
|
11181
|
-
if (hasUnsupported && mode === "refine") setModeInternal("build");
|
|
11182
|
-
}, [hasContent, hasUnsupported, mode]);
|
|
11183
|
-
const setMode = React19.useCallback((next) => {
|
|
11184
|
-
setModeInternal(next);
|
|
11185
|
-
}, []);
|
|
11186
|
-
React19.useEffect(() => () => abortRef.current?.abort(), []);
|
|
11187
|
-
const submit = React19.useCallback(async () => {
|
|
11188
|
-
const problem = prompt.trim();
|
|
11189
|
-
if (!problem) {
|
|
11190
|
-
setError("Nh\u1EADp \u0111\u1EC1 b\xE0i c\u1EA7n d\u1EF1ng h\xECnh.");
|
|
11191
|
-
return null;
|
|
11192
|
-
}
|
|
11193
|
-
if (!generator) {
|
|
11194
|
-
setError("T\xEDnh n\u0103ng d\u1EF1ng h\xECnh AI ch\u01B0a \u0111\u01B0\u1EE3c c\u1EA5u h\xECnh.");
|
|
11195
|
-
return null;
|
|
11196
|
-
}
|
|
11197
|
-
abortRef.current?.abort();
|
|
11198
|
-
const controller = new AbortController();
|
|
11199
|
-
const requestId = ++requestIdRef.current;
|
|
11200
|
-
abortRef.current = controller;
|
|
11201
|
-
setIsLoading(true);
|
|
11202
|
-
setError(null);
|
|
11203
|
-
setTokens(0);
|
|
11204
|
-
try {
|
|
11205
|
-
const generated = await generator(problem, {
|
|
11206
|
-
signal: controller.signal,
|
|
11207
|
-
onProgress: (info) => {
|
|
11208
|
-
if (requestId === requestIdRef.current) setTokens(info.tokens);
|
|
11209
|
-
},
|
|
11210
|
-
...mode === "refine" && currentDsl ? { currentDsl } : {}
|
|
11211
|
-
});
|
|
11212
|
-
if (controller.signal.aborted || requestId !== requestIdRef.current) return null;
|
|
11213
|
-
if (!generated.ok) {
|
|
11214
|
-
setError(generated.message);
|
|
11215
|
-
return null;
|
|
11216
|
-
}
|
|
11217
|
-
return generated.state;
|
|
11218
|
-
} catch (caught) {
|
|
11219
|
-
if (controller.signal.aborted || caught instanceof DOMException && caught.name === "AbortError") {
|
|
11220
|
-
return null;
|
|
11221
|
-
}
|
|
11222
|
-
if (requestId === requestIdRef.current) {
|
|
11223
|
-
setError(
|
|
11224
|
-
caught instanceof Error && caught.message ? caught.message : "Kh\xF4ng th\u1EC3 d\u1EF1ng h\xECnh b\u1EB1ng AI."
|
|
11225
|
-
);
|
|
11226
|
-
}
|
|
11227
|
-
return null;
|
|
11228
|
-
} finally {
|
|
11229
|
-
if (requestId === requestIdRef.current) {
|
|
11230
|
-
abortRef.current = null;
|
|
11231
|
-
setIsLoading(false);
|
|
11232
|
-
}
|
|
11233
|
-
}
|
|
11234
|
-
}, [generator, prompt, mode, currentDsl]);
|
|
11235
|
-
const cancel = React19.useCallback(() => {
|
|
11236
|
-
abortRef.current?.abort();
|
|
11237
|
-
}, []);
|
|
11238
|
-
return {
|
|
11239
|
-
prompt,
|
|
11240
|
-
setPrompt,
|
|
11241
|
-
isLoading,
|
|
11242
|
-
error,
|
|
11243
|
-
submit,
|
|
11244
|
-
cancel,
|
|
11245
|
-
tokens,
|
|
11246
|
-
mode,
|
|
11247
|
-
setMode,
|
|
11248
|
-
entityCount,
|
|
11249
|
-
hasUnsupported
|
|
11250
|
-
};
|
|
11251
|
-
}
|
|
11252
|
-
var init_useAiFigure = __esm({
|
|
11253
|
-
"src/stamps/geometry-2d/editor/useAiFigure.ts"() {
|
|
11254
|
-
"use client";
|
|
11255
|
-
init_serialize2();
|
|
11256
|
-
}
|
|
11257
|
-
});
|
|
11258
|
-
function toUsage(u) {
|
|
11259
|
-
return {
|
|
11260
|
-
inputTokens: u.input_tokens,
|
|
11261
|
-
outputTokens: u.output_tokens,
|
|
11262
|
-
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
11263
|
-
cacheCreationTokens: u.cache_creation_input_tokens ?? 0
|
|
11264
|
-
};
|
|
11265
|
-
}
|
|
11266
|
-
var TOOL_NAME, VISION_TOOL_NAME, AnthropicProvider;
|
|
11267
|
-
var init_anthropic = __esm({
|
|
11268
|
-
"src/stamps/geometry-2d/ai/providers/anthropic.ts"() {
|
|
11269
|
-
TOOL_NAME = "emit_figure_envelope";
|
|
11270
|
-
VISION_TOOL_NAME = "extract_problem_envelope";
|
|
11271
|
-
AnthropicProvider = class {
|
|
11272
|
-
constructor(opts) {
|
|
11273
|
-
this.opts = opts;
|
|
11274
|
-
this.name = "anthropic";
|
|
11275
|
-
this.defaultModel = "claude-opus-4-7";
|
|
11276
|
-
if (!opts.apiKey) throw new Error("AnthropicProvider: apiKey b\u1EAFt bu\u1ED9c");
|
|
11277
|
-
}
|
|
11278
|
-
async call(req) {
|
|
11279
|
-
const enableCaching = this.opts.enableCaching !== false;
|
|
11280
|
-
const systemBlock = enableCaching ? { type: "text", text: req.systemPrompt, cache_control: { type: "ephemeral" } } : { type: "text", text: req.systemPrompt };
|
|
11281
|
-
const tool = {
|
|
11282
|
-
name: TOOL_NAME,
|
|
11283
|
-
description: 'Emit envelope JSON cho ph\xE9p v\u1EBD h\xECnh ho\u1EB7c t\u1EEB ch\u1ED1i. decision="build" k\xE8m figure (DSL h\xECnh h\u1ECDc); decision="refuse" k\xE8m reason.',
|
|
11284
|
-
input_schema: req.schema
|
|
11285
|
-
};
|
|
11286
|
-
const client = new Anthropic__default.default({ apiKey: this.opts.apiKey });
|
|
11287
|
-
let resp;
|
|
11288
|
-
try {
|
|
11289
|
-
resp = await client.messages.create(
|
|
11290
|
-
{
|
|
11291
|
-
model: req.model,
|
|
11292
|
-
max_tokens: req.maxTokens,
|
|
11293
|
-
system: [systemBlock],
|
|
11294
|
-
tools: [tool],
|
|
11295
|
-
tool_choice: { type: "tool", name: TOOL_NAME },
|
|
11296
|
-
messages: [{ role: "user", content: req.userPrompt }]
|
|
11297
|
-
},
|
|
11298
|
-
req.signal ? { signal: req.signal } : void 0
|
|
11299
|
-
);
|
|
11300
|
-
} catch (e) {
|
|
11301
|
-
const err = e;
|
|
11302
|
-
return {
|
|
11303
|
-
kind: "error",
|
|
11304
|
-
message: err.message ?? "L\u1ED7i g\u1ECDi Anthropic API",
|
|
11305
|
-
...err.status !== void 0 ? { status: err.status } : {}
|
|
11306
|
-
};
|
|
11307
|
-
}
|
|
11308
|
-
const usage = toUsage(resp.usage);
|
|
11309
|
-
const toolUse = resp.content.find((c) => c.type === "tool_use");
|
|
11310
|
-
if (!toolUse || toolUse.type !== "tool_use") {
|
|
11311
|
-
return {
|
|
11312
|
-
kind: "error",
|
|
11313
|
-
message: "Claude kh\xF4ng g\u1ECDi tool. stop_reason=" + resp.stop_reason
|
|
11314
|
-
};
|
|
11315
|
-
}
|
|
11316
|
-
if (toolUse.name !== TOOL_NAME) {
|
|
11317
|
-
return {
|
|
11318
|
-
kind: "error",
|
|
11319
|
-
message: `Tool kh\xF4ng x\xE1c \u0111\u1ECBnh: "${toolUse.name}"`
|
|
11320
|
-
};
|
|
11321
|
-
}
|
|
11322
|
-
return { kind: "json", data: toolUse.input, usage };
|
|
11323
|
-
}
|
|
11324
|
-
async extractText(req) {
|
|
11325
|
-
const model = req.model ?? this.defaultModel;
|
|
11326
|
-
const enableCaching = this.opts.enableCaching !== false;
|
|
11327
|
-
const systemBlock = enableCaching ? { type: "text", text: req.systemPrompt, cache_control: { type: "ephemeral" } } : { type: "text", text: req.systemPrompt };
|
|
11328
|
-
const tool = {
|
|
11329
|
-
name: VISION_TOOL_NAME,
|
|
11330
|
-
description: "Tr\xEDch \u0111\u1EC1 b\xE0i h\xECnh h\u1ECDc t\u1EEB \u1EA3nh, ho\u1EB7c t\u1EEB ch\u1ED1i n\u1EBFu kh\xF4ng ph\u1EA3i \u0111\u1EC1 to\xE1n.",
|
|
11331
|
-
input_schema: req.schema
|
|
11332
|
-
};
|
|
11333
|
-
const imageBlocks = req.images.map((img) => ({
|
|
11334
|
-
type: "image",
|
|
11335
|
-
source: {
|
|
11336
|
-
type: "base64",
|
|
11337
|
-
media_type: img.mediaType,
|
|
11338
|
-
data: img.base64
|
|
11339
|
-
}
|
|
11340
|
-
}));
|
|
11341
|
-
const client = new Anthropic__default.default({ apiKey: this.opts.apiKey });
|
|
11342
|
-
let resp;
|
|
11343
|
-
try {
|
|
11344
|
-
resp = await client.messages.create(
|
|
11345
|
-
{
|
|
11346
|
-
model,
|
|
11347
|
-
max_tokens: req.maxTokens,
|
|
11348
|
-
system: [systemBlock],
|
|
11349
|
-
tools: [tool],
|
|
11350
|
-
tool_choice: { type: "tool", name: VISION_TOOL_NAME },
|
|
11351
|
-
messages: [
|
|
11352
|
-
{
|
|
11353
|
-
role: "user",
|
|
11354
|
-
content: [...imageBlocks, { type: "text", text: req.userPrompt }]
|
|
11355
|
-
}
|
|
11356
|
-
]
|
|
11357
|
-
},
|
|
11358
|
-
req.signal ? { signal: req.signal } : void 0
|
|
11359
|
-
);
|
|
11360
|
-
} catch (e) {
|
|
11361
|
-
const err = e;
|
|
11362
|
-
return {
|
|
11363
|
-
kind: "error",
|
|
11364
|
-
message: err.message ?? "L\u1ED7i g\u1ECDi Anthropic Vision API",
|
|
11365
|
-
...err.status !== void 0 ? { status: err.status } : {}
|
|
11366
|
-
};
|
|
11367
|
-
}
|
|
11368
|
-
const usage = toUsage(resp.usage);
|
|
11369
|
-
const toolUse = resp.content.find((c) => c.type === "tool_use");
|
|
11370
|
-
if (!toolUse || toolUse.type !== "tool_use") {
|
|
11371
|
-
return { kind: "error", message: "Claude kh\xF4ng g\u1ECDi vision tool. stop_reason=" + resp.stop_reason };
|
|
11372
|
-
}
|
|
11373
|
-
if (toolUse.name !== VISION_TOOL_NAME) {
|
|
11374
|
-
return { kind: "error", message: `Claude g\u1ECDi sai tool: ${toolUse.name}` };
|
|
11375
|
-
}
|
|
11376
|
-
return { kind: "json", data: toolUse.input, usage };
|
|
11377
|
-
}
|
|
11378
|
-
};
|
|
11379
|
-
}
|
|
11380
|
-
});
|
|
11381
|
-
|
|
11382
|
-
// src/stamps/geometry-2d/ai/providers/claude-agent-sdk.ts
|
|
11383
|
-
function memoSchemaJson(schema) {
|
|
11384
|
-
let s = SCHEMA_CACHE.get(schema);
|
|
11385
|
-
if (s) return s;
|
|
11386
|
-
s = JSON.stringify(schema, null, 2);
|
|
11387
|
-
SCHEMA_CACHE.set(schema, s);
|
|
11388
|
-
return s;
|
|
11389
|
-
}
|
|
11390
|
-
var DEFAULT_MODEL, SCHEMA_CACHE, ClaudeAgentSdkProvider;
|
|
11391
|
-
var init_claude_agent_sdk = __esm({
|
|
11392
|
-
"src/stamps/geometry-2d/ai/providers/claude-agent-sdk.ts"() {
|
|
11393
|
-
DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
11394
|
-
SCHEMA_CACHE = /* @__PURE__ */ new WeakMap();
|
|
11395
|
-
ClaudeAgentSdkProvider = class {
|
|
11396
|
-
constructor(opts = {}) {
|
|
11397
|
-
this.name = "claude-agent-sdk";
|
|
11398
|
-
this.defaultModel = opts.defaultModel ?? DEFAULT_MODEL;
|
|
11399
|
-
this.oauthToken = opts.oauthToken;
|
|
11400
|
-
this.queryImpl = opts.queryImpl ?? null;
|
|
11401
|
-
}
|
|
11402
|
-
async resolveQuery() {
|
|
11403
|
-
if (this.queryImpl) return this.queryImpl;
|
|
11404
|
-
const mod = await import('@anthropic-ai/claude-agent-sdk');
|
|
11405
|
-
return mod.query;
|
|
11406
|
-
}
|
|
11407
|
-
async call(req) {
|
|
11408
|
-
if (this.oauthToken) {
|
|
11409
|
-
process.env.CLAUDE_CODE_OAUTH_TOKEN = this.oauthToken;
|
|
11410
|
-
}
|
|
11411
|
-
let query;
|
|
11412
|
-
try {
|
|
11413
|
-
query = await this.resolveQuery();
|
|
11414
|
-
} catch (e) {
|
|
11415
|
-
return {
|
|
11416
|
-
kind: "error",
|
|
11417
|
-
message: "ClaudeAgentSdkProvider: SDK kh\xF4ng kh\u1EA3 d\u1EE5ng. " + (e.message ?? "")
|
|
11418
|
-
};
|
|
11419
|
-
}
|
|
11420
|
-
const schemaText = memoSchemaJson(req.schema);
|
|
11421
|
-
const constrainedSystem = `${req.systemPrompt}
|
|
11422
|
-
|
|
11423
|
-
QUAN TR\u1ECCNG: Output PH\u1EA2I l\xE0 valid JSON \u0111\xFAng schema sau. KH\xD4NG markdown wrapper, KH\xD4NG prose gi\u1EA3i th\xEDch, CH\u1EC8 raw JSON:
|
|
11424
|
-
|
|
11425
|
-
${schemaText}`;
|
|
11426
|
-
let assistantText = "";
|
|
11427
|
-
let usageInput = 0;
|
|
11428
|
-
let usageOutput = 0;
|
|
11429
|
-
let cacheRead = 0;
|
|
11430
|
-
let cacheCreation = 0;
|
|
11431
|
-
try {
|
|
11432
|
-
for await (const msg of query({
|
|
11433
|
-
prompt: req.userPrompt,
|
|
11434
|
-
options: {
|
|
11435
|
-
systemPrompt: constrainedSystem,
|
|
11436
|
-
allowedTools: [],
|
|
11437
|
-
model: req.model ?? this.defaultModel,
|
|
11438
|
-
...req.signal ? { abortSignal: req.signal } : {}
|
|
11439
|
-
}
|
|
11440
|
-
})) {
|
|
11441
|
-
if (msg.type === "assistant") {
|
|
11442
|
-
const message = msg.message;
|
|
11443
|
-
for (const block of message.content) {
|
|
11444
|
-
const b = block;
|
|
11445
|
-
if (b.type === "text" && typeof b.text === "string") {
|
|
11446
|
-
assistantText += b.text;
|
|
11447
|
-
if (req.onToken) {
|
|
11448
|
-
try {
|
|
11449
|
-
req.onToken(b.text);
|
|
11450
|
-
} catch {
|
|
11451
|
-
}
|
|
11452
|
-
}
|
|
11453
|
-
}
|
|
11454
|
-
}
|
|
11455
|
-
} else if (msg.type === "result") {
|
|
11456
|
-
const r = msg;
|
|
11457
|
-
if (r.subtype && r.subtype !== "success") {
|
|
11458
|
-
return {
|
|
11459
|
-
kind: "error",
|
|
11460
|
-
message: `ClaudeAgentSdkProvider: result subtype=${r.subtype}`
|
|
11461
|
-
};
|
|
11462
|
-
}
|
|
11463
|
-
if (r.usage) {
|
|
11464
|
-
usageInput = r.usage.input_tokens ?? 0;
|
|
11465
|
-
usageOutput = r.usage.output_tokens ?? 0;
|
|
11466
|
-
cacheRead = r.usage.cache_read_input_tokens ?? 0;
|
|
11467
|
-
cacheCreation = r.usage.cache_creation_input_tokens ?? 0;
|
|
11468
|
-
}
|
|
11469
|
-
}
|
|
11470
|
-
}
|
|
11471
|
-
} catch (e) {
|
|
11472
|
-
return {
|
|
11473
|
-
kind: "error",
|
|
11474
|
-
message: "ClaudeAgentSdkProvider: query() throw: " + (e.message ?? "?")
|
|
11475
|
-
};
|
|
11476
|
-
}
|
|
11477
|
-
if (!assistantText.trim()) {
|
|
11478
|
-
return {
|
|
11479
|
-
kind: "error",
|
|
11480
|
-
message: "ClaudeAgentSdkProvider: assistant tr\u1EA3 response r\u1ED7ng."
|
|
11481
|
-
};
|
|
11482
|
-
}
|
|
11483
|
-
let cleaned = assistantText.trim();
|
|
11484
|
-
cleaned = cleaned.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?\s*```\s*$/, "");
|
|
11485
|
-
let data;
|
|
11486
|
-
try {
|
|
11487
|
-
data = JSON.parse(cleaned);
|
|
11488
|
-
} catch (e) {
|
|
11489
|
-
return {
|
|
11490
|
-
kind: "error",
|
|
11491
|
-
message: "ClaudeAgentSdkProvider: output kh\xF4ng parse JSON: " + (e.message ?? "?") + ". Output preview: " + cleaned.slice(0, 200)
|
|
11492
|
-
};
|
|
11493
|
-
}
|
|
11494
|
-
return {
|
|
11495
|
-
kind: "json",
|
|
11496
|
-
data,
|
|
11497
|
-
usage: {
|
|
11498
|
-
inputTokens: usageInput,
|
|
11499
|
-
outputTokens: usageOutput,
|
|
11500
|
-
cacheReadTokens: cacheRead,
|
|
11501
|
-
cacheCreationTokens: cacheCreation
|
|
11502
|
-
}
|
|
11503
|
-
};
|
|
11504
|
-
}
|
|
11505
|
-
};
|
|
11506
|
-
}
|
|
11507
|
-
});
|
|
11508
|
-
|
|
11509
|
-
// src/stamps/geometry-2d/ai/providers/claude-cli.ts
|
|
11510
|
-
function toUsage2(u) {
|
|
11511
|
-
return {
|
|
11512
|
-
inputTokens: u?.input_tokens ?? 0,
|
|
11513
|
-
outputTokens: u?.output_tokens ?? 0,
|
|
11514
|
-
cacheReadTokens: u?.cache_read_input_tokens ?? 0,
|
|
11515
|
-
cacheCreationTokens: u?.cache_creation_input_tokens ?? 0
|
|
11516
|
-
};
|
|
11517
|
-
}
|
|
11518
|
-
var DEFAULT_BIN, DEFAULT_MODEL2, DEFAULT_MAX_BUDGET_USD, ClaudeCliProvider;
|
|
11519
|
-
var init_claude_cli = __esm({
|
|
11520
|
-
"src/stamps/geometry-2d/ai/providers/claude-cli.ts"() {
|
|
11521
|
-
DEFAULT_BIN = "claude";
|
|
11522
|
-
DEFAULT_MODEL2 = "claude-sonnet-4-6";
|
|
11523
|
-
DEFAULT_MAX_BUDGET_USD = 0.5;
|
|
11524
|
-
ClaudeCliProvider = class {
|
|
11525
|
-
constructor(opts = {}) {
|
|
11526
|
-
this.name = "claude-cli";
|
|
11527
|
-
this.bin = opts.bin ?? DEFAULT_BIN;
|
|
11528
|
-
this.defaultModel = opts.defaultModel ?? DEFAULT_MODEL2;
|
|
11529
|
-
this.maxBudgetUsd = opts.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD;
|
|
11530
|
-
this.spawnImpl = opts.spawnImpl ?? null;
|
|
11531
|
-
}
|
|
11532
|
-
async resolveSpawn() {
|
|
11533
|
-
if (this.spawnImpl) return this.spawnImpl;
|
|
11534
|
-
const mod = await import('child_process');
|
|
11535
|
-
return mod.spawn;
|
|
11536
|
-
}
|
|
11537
|
-
async call(req) {
|
|
11538
|
-
let spawn;
|
|
11539
|
-
try {
|
|
11540
|
-
spawn = await this.resolveSpawn();
|
|
11541
|
-
} catch (e) {
|
|
11542
|
-
return {
|
|
11543
|
-
kind: "error",
|
|
11544
|
-
message: "ClaudeCliProvider: node:child_process kh\xF4ng kh\u1EA3 d\u1EE5ng (ch\u1EC9 ch\u1EA1y \u0111\u01B0\u1EE3c \u1EDF Node env). " + (e.message ?? "")
|
|
11545
|
-
};
|
|
11546
|
-
}
|
|
11547
|
-
const args = [
|
|
11548
|
-
"--print",
|
|
11549
|
-
"--output-format",
|
|
11550
|
-
"json",
|
|
11551
|
-
"--json-schema",
|
|
11552
|
-
JSON.stringify(req.schema),
|
|
11553
|
-
"--append-system-prompt",
|
|
11554
|
-
req.systemPrompt,
|
|
11555
|
-
"--tools",
|
|
11556
|
-
"",
|
|
11557
|
-
"--model",
|
|
11558
|
-
req.model,
|
|
11559
|
-
"--max-budget-usd",
|
|
11560
|
-
String(this.maxBudgetUsd)
|
|
11561
|
-
];
|
|
11562
|
-
return new Promise((resolve) => {
|
|
11563
|
-
let child;
|
|
11564
|
-
try {
|
|
11565
|
-
child = spawn(this.bin, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
11566
|
-
} catch (e) {
|
|
11567
|
-
resolve({
|
|
11568
|
-
kind: "error",
|
|
11569
|
-
message: `ClaudeCliProvider: spawn '${this.bin}' th\u1EA5t b\u1EA1i. ` + (e.message ?? "Ki\u1EC3m tra claude CLI \u0111\xE3 c\xE0i ch\u01B0a.")
|
|
11570
|
-
});
|
|
11571
|
-
return;
|
|
11572
|
-
}
|
|
11573
|
-
let stdout = "";
|
|
11574
|
-
let stderr = "";
|
|
11575
|
-
let settled = false;
|
|
11576
|
-
const settle = (out) => {
|
|
11577
|
-
if (settled) return;
|
|
11578
|
-
settled = true;
|
|
11579
|
-
resolve(out);
|
|
11580
|
-
};
|
|
11581
|
-
const onAbort = () => {
|
|
11582
|
-
child.kill("SIGTERM");
|
|
11583
|
-
settle({ kind: "error", message: "ClaudeCliProvider: aborted" });
|
|
11584
|
-
};
|
|
11585
|
-
if (req.signal) {
|
|
11586
|
-
if (req.signal.aborted) {
|
|
11587
|
-
onAbort();
|
|
11588
|
-
return;
|
|
11589
|
-
}
|
|
11590
|
-
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
11591
|
-
}
|
|
11592
|
-
child.stdout?.on("data", (chunk) => {
|
|
11593
|
-
stdout += chunk.toString("utf8");
|
|
11594
|
-
});
|
|
11595
|
-
child.stderr?.on("data", (chunk) => {
|
|
11596
|
-
stderr += chunk.toString("utf8");
|
|
11597
|
-
});
|
|
11598
|
-
child.on("error", (e) => {
|
|
11599
|
-
settle({
|
|
11600
|
-
kind: "error",
|
|
11601
|
-
message: `ClaudeCliProvider: subprocess error: ${e.message}`
|
|
11602
|
-
});
|
|
11603
|
-
});
|
|
11604
|
-
child.on("close", (code) => {
|
|
11605
|
-
if (settled) return;
|
|
11606
|
-
if (code !== 0) {
|
|
11607
|
-
settle({
|
|
11608
|
-
kind: "error",
|
|
11609
|
-
message: `ClaudeCliProvider: exit code ${code}. stderr: ${stderr.trim() || "(empty)"}`,
|
|
11610
|
-
...typeof code === "number" ? { status: code } : {}
|
|
11611
|
-
});
|
|
11612
|
-
return;
|
|
11613
|
-
}
|
|
11614
|
-
let env;
|
|
11615
|
-
try {
|
|
11616
|
-
env = JSON.parse(stdout.trim());
|
|
11617
|
-
} catch (e) {
|
|
11618
|
-
settle({
|
|
11619
|
-
kind: "error",
|
|
11620
|
-
message: "ClaudeCliProvider: stdout kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: " + (e.message ?? "?")
|
|
11621
|
-
});
|
|
11622
|
-
return;
|
|
11623
|
-
}
|
|
11624
|
-
if (env.is_error) {
|
|
11625
|
-
settle({
|
|
11626
|
-
kind: "error",
|
|
11627
|
-
message: `ClaudeCliProvider: CLI b\xE1o l\u1ED7i (subtype=${env.subtype}, api_status=${env.api_error_status ?? "n/a"})`
|
|
11628
|
-
});
|
|
11629
|
-
return;
|
|
11630
|
-
}
|
|
11631
|
-
if (env.structured_output === void 0 || env.structured_output === null) {
|
|
11632
|
-
settle({
|
|
11633
|
-
kind: "error",
|
|
11634
|
-
message: `ClaudeCliProvider: thi\u1EBFu structured_output trong response. result="${(env.result ?? "").slice(0, 200)}"`
|
|
11635
|
-
});
|
|
11636
|
-
return;
|
|
11637
|
-
}
|
|
11638
|
-
const usage = toUsage2(env.usage);
|
|
11639
|
-
settle({ kind: "json", data: env.structured_output, usage });
|
|
11640
|
-
});
|
|
11641
|
-
try {
|
|
11642
|
-
child.stdin?.write(req.userPrompt);
|
|
11643
|
-
child.stdin?.end();
|
|
11644
|
-
} catch (e) {
|
|
11645
|
-
settle({
|
|
11646
|
-
kind: "error",
|
|
11647
|
-
message: "ClaudeCliProvider: ghi stdin th\u1EA5t b\u1EA1i: " + (e.message ?? "?")
|
|
11648
|
-
});
|
|
11649
|
-
}
|
|
11650
|
-
});
|
|
11651
|
-
}
|
|
11652
|
-
};
|
|
11653
|
-
}
|
|
11654
|
-
});
|
|
11655
|
-
|
|
11656
|
-
// src/stamps/geometry-2d/ai/providers/ollama.ts
|
|
11657
|
-
var DEFAULT_BASE_URL, DEFAULT_MODEL3, OllamaProvider;
|
|
11658
|
-
var init_ollama = __esm({
|
|
11659
|
-
"src/stamps/geometry-2d/ai/providers/ollama.ts"() {
|
|
11660
|
-
DEFAULT_BASE_URL = "http://localhost:11434";
|
|
11661
|
-
DEFAULT_MODEL3 = "gemma3:4b";
|
|
11662
|
-
OllamaProvider = class {
|
|
11663
|
-
constructor(opts = {}) {
|
|
11664
|
-
this.name = "ollama";
|
|
11665
|
-
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
11666
|
-
this.defaultModel = opts.defaultModel ?? DEFAULT_MODEL3;
|
|
11667
|
-
this.fetchImpl = opts.fetchImpl ?? null;
|
|
11668
|
-
}
|
|
11669
|
-
resolveFetch() {
|
|
11670
|
-
if (this.fetchImpl) return this.fetchImpl;
|
|
11671
|
-
if (typeof fetch === "undefined") {
|
|
11672
|
-
throw new Error(
|
|
11673
|
-
"OllamaProvider: global `fetch` kh\xF4ng kh\u1EA3 d\u1EE5ng. Truy\u1EC1n `fetchImpl` qua constructor ho\u1EB7c ch\u1EA1y \u1EDF Node 18+ / browser."
|
|
11674
|
-
);
|
|
11675
|
-
}
|
|
11676
|
-
return fetch;
|
|
11677
|
-
}
|
|
11678
|
-
async call(req) {
|
|
11679
|
-
const body = {
|
|
11680
|
-
model: req.model,
|
|
11681
|
-
messages: [
|
|
11682
|
-
{ role: "system", content: req.systemPrompt },
|
|
11683
|
-
{ role: "user", content: req.userPrompt }
|
|
11684
|
-
],
|
|
11685
|
-
format: req.schema,
|
|
11686
|
-
stream: true,
|
|
11687
|
-
options: { num_predict: req.maxTokens, temperature: 0.2 }
|
|
11688
|
-
};
|
|
11689
|
-
let doFetch;
|
|
11690
|
-
try {
|
|
11691
|
-
doFetch = this.resolveFetch();
|
|
11692
|
-
} catch (e) {
|
|
11693
|
-
const err = e;
|
|
11694
|
-
return { kind: "error", message: err.message ?? "fetch kh\xF4ng kh\u1EA3 d\u1EE5ng" };
|
|
11695
|
-
}
|
|
11696
|
-
let resp;
|
|
11697
|
-
try {
|
|
11698
|
-
resp = await doFetch(`${this.baseUrl}/api/chat`, {
|
|
11699
|
-
method: "POST",
|
|
11700
|
-
headers: { "content-type": "application/json" },
|
|
11701
|
-
body: JSON.stringify(body),
|
|
11702
|
-
signal: req.signal
|
|
11703
|
-
});
|
|
11704
|
-
} catch (e) {
|
|
11705
|
-
const err = e;
|
|
11706
|
-
return {
|
|
11707
|
-
kind: "error",
|
|
11708
|
-
message: err.message ?? `Kh\xF4ng k\u1EBFt n\u1ED1i \u0111\u01B0\u1EE3c Ollama \u1EDF ${this.baseUrl}`
|
|
11709
|
-
};
|
|
11710
|
-
}
|
|
11711
|
-
if (!resp.ok || !resp.body) {
|
|
11712
|
-
let detail = "";
|
|
11713
|
-
try {
|
|
11714
|
-
detail = await resp.text();
|
|
11715
|
-
} catch {
|
|
11716
|
-
}
|
|
11717
|
-
return {
|
|
11718
|
-
kind: "error",
|
|
11719
|
-
message: `Ollama HTTP ${resp.status}: ${detail || resp.statusText}`,
|
|
11720
|
-
status: resp.status
|
|
11721
|
-
};
|
|
11722
|
-
}
|
|
11723
|
-
const reader = resp.body.getReader();
|
|
11724
|
-
const decoder = new TextDecoder();
|
|
11725
|
-
let buffer = "";
|
|
11726
|
-
let content = "";
|
|
11727
|
-
let promptEvalCount = 0;
|
|
11728
|
-
let evalCount = 0;
|
|
11729
|
-
while (true) {
|
|
11730
|
-
const { value, done } = await reader.read();
|
|
11731
|
-
if (done) break;
|
|
11732
|
-
buffer += decoder.decode(value, { stream: true });
|
|
11733
|
-
let nl;
|
|
11734
|
-
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
11735
|
-
const line = buffer.slice(0, nl).trim();
|
|
11736
|
-
buffer = buffer.slice(nl + 1);
|
|
11737
|
-
if (!line) continue;
|
|
11738
|
-
try {
|
|
11739
|
-
const chunk = JSON.parse(line);
|
|
11740
|
-
if (chunk.message?.content) {
|
|
11741
|
-
content += chunk.message.content;
|
|
11742
|
-
if (req.onToken) {
|
|
11743
|
-
try {
|
|
11744
|
-
req.onToken(chunk.message.content);
|
|
11745
|
-
} catch {
|
|
11746
|
-
}
|
|
11747
|
-
}
|
|
11748
|
-
}
|
|
11749
|
-
if (chunk.done) {
|
|
11750
|
-
promptEvalCount = chunk.prompt_eval_count ?? promptEvalCount;
|
|
11751
|
-
evalCount = chunk.eval_count ?? evalCount;
|
|
11752
|
-
}
|
|
11753
|
-
} catch {
|
|
11754
|
-
}
|
|
11755
|
-
}
|
|
11756
|
-
}
|
|
11757
|
-
const trimmed = content.trim();
|
|
11758
|
-
if (!trimmed) return { kind: "error", message: "Ollama tr\u1EA3 message.content r\u1ED7ng" };
|
|
11759
|
-
let data;
|
|
11760
|
-
try {
|
|
11761
|
-
data = JSON.parse(trimmed);
|
|
11762
|
-
} catch (e) {
|
|
11763
|
-
const err = e;
|
|
11764
|
-
return {
|
|
11765
|
-
kind: "error",
|
|
11766
|
-
message: "Ollama content kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: " + (err.message ?? "?")
|
|
11767
|
-
};
|
|
11768
|
-
}
|
|
11769
|
-
return {
|
|
11770
|
-
kind: "json",
|
|
11771
|
-
data,
|
|
11772
|
-
usage: {
|
|
11773
|
-
inputTokens: promptEvalCount,
|
|
11774
|
-
outputTokens: evalCount,
|
|
11775
|
-
cacheReadTokens: 0,
|
|
11776
|
-
cacheCreationTokens: 0
|
|
11777
|
-
}
|
|
11778
|
-
};
|
|
11779
|
-
}
|
|
11780
|
-
// Vision: gửi ảnh qua images[] field trong message (Ollama multimodal API).
|
|
11781
|
-
// Model cần hỗ trợ vision (gemma3, llava, ...). Output vẫn là JSON envelope.
|
|
11782
|
-
async extractText(req) {
|
|
11783
|
-
const model = req.model ?? this.defaultModel;
|
|
11784
|
-
const body = {
|
|
11785
|
-
model,
|
|
11786
|
-
messages: [
|
|
11787
|
-
{ role: "system", content: req.systemPrompt },
|
|
11788
|
-
{
|
|
11789
|
-
role: "user",
|
|
11790
|
-
content: req.userPrompt,
|
|
11791
|
-
images: req.images.map((i) => i.base64)
|
|
11792
|
-
}
|
|
11793
|
-
],
|
|
11794
|
-
format: req.schema,
|
|
11795
|
-
stream: false,
|
|
11796
|
-
options: { num_predict: req.maxTokens, temperature: 0.2 }
|
|
11797
|
-
};
|
|
11798
|
-
let doFetch;
|
|
11799
|
-
try {
|
|
11800
|
-
doFetch = this.resolveFetch();
|
|
11801
|
-
} catch (e) {
|
|
11802
|
-
return { kind: "error", message: e.message ?? "fetch kh\xF4ng kh\u1EA3 d\u1EE5ng" };
|
|
11803
|
-
}
|
|
11804
|
-
let resp;
|
|
11805
|
-
try {
|
|
11806
|
-
resp = await doFetch(`${this.baseUrl}/api/chat`, {
|
|
11807
|
-
method: "POST",
|
|
11808
|
-
headers: { "content-type": "application/json" },
|
|
11809
|
-
body: JSON.stringify(body),
|
|
11810
|
-
signal: req.signal
|
|
11811
|
-
});
|
|
11812
|
-
} catch (e) {
|
|
11813
|
-
return {
|
|
11814
|
-
kind: "error",
|
|
11815
|
-
message: e.message ?? `Kh\xF4ng k\u1EBFt n\u1ED1i \u0111\u01B0\u1EE3c Ollama \u1EDF ${this.baseUrl}`
|
|
11816
|
-
};
|
|
11817
|
-
}
|
|
11818
|
-
if (!resp.ok) {
|
|
11819
|
-
let detail = "";
|
|
11820
|
-
try {
|
|
11821
|
-
detail = await resp.text();
|
|
11822
|
-
} catch {
|
|
11823
|
-
}
|
|
11824
|
-
return {
|
|
11825
|
-
kind: "error",
|
|
11826
|
-
message: `Ollama Vision HTTP ${resp.status}: ${detail || resp.statusText}`,
|
|
11827
|
-
status: resp.status
|
|
11828
|
-
};
|
|
11829
|
-
}
|
|
11830
|
-
let json;
|
|
11831
|
-
try {
|
|
11832
|
-
json = await resp.json();
|
|
11833
|
-
} catch (e) {
|
|
11834
|
-
return { kind: "error", message: "Ollama vision response kh\xF4ng ph\u1EA3i JSON: " + (e.message ?? "?") };
|
|
11835
|
-
}
|
|
11836
|
-
const content = json.message?.content?.trim();
|
|
11837
|
-
if (!content) return { kind: "error", message: "Ollama vision tr\u1EA3 content r\u1ED7ng" };
|
|
11838
|
-
let data;
|
|
11839
|
-
try {
|
|
11840
|
-
data = JSON.parse(content);
|
|
11841
|
-
} catch (e) {
|
|
11842
|
-
return { kind: "error", message: "Ollama vision content kh\xF4ng parse JSON: " + (e.message ?? "?") };
|
|
11843
|
-
}
|
|
11844
|
-
const usage = {
|
|
11845
|
-
inputTokens: json.prompt_eval_count ?? 0,
|
|
11846
|
-
outputTokens: json.eval_count ?? 0,
|
|
11847
|
-
cacheReadTokens: 0,
|
|
11848
|
-
cacheCreationTokens: 0
|
|
11849
|
-
};
|
|
11850
|
-
return { kind: "json", data, usage };
|
|
11851
|
-
}
|
|
11852
|
-
};
|
|
11853
|
-
}
|
|
11854
|
-
});
|
|
11855
|
-
|
|
11856
|
-
// src/stamps/geometry-2d/ai/providers/index.ts
|
|
11857
|
-
function selectProvider(opts = {}) {
|
|
11858
|
-
if (opts.provider) return opts.provider;
|
|
11859
|
-
if (opts.apiKey) {
|
|
11860
|
-
return new AnthropicProvider({
|
|
11861
|
-
apiKey: opts.apiKey,
|
|
11862
|
-
enableCaching: opts.enableCaching
|
|
11863
|
-
});
|
|
11864
|
-
}
|
|
11865
|
-
const env = opts.env ?? readEnv();
|
|
11866
|
-
const wanted = (env.WHITEBOARD_AI_PROVIDER ?? "claude-agent-sdk").toLowerCase();
|
|
11867
|
-
if (wanted === "anthropic") {
|
|
11868
|
-
const key = env.ANTHROPIC_API_KEY;
|
|
11869
|
-
if (!key) {
|
|
11870
|
-
throw new Error(
|
|
11871
|
-
"selectProvider: WHITEBOARD_AI_PROVIDER=anthropic nh\u01B0ng thi\u1EBFu env ANTHROPIC_API_KEY"
|
|
11872
|
-
);
|
|
11873
|
-
}
|
|
11874
|
-
return new AnthropicProvider({ apiKey: key, enableCaching: opts.enableCaching });
|
|
11875
|
-
}
|
|
11876
|
-
if (wanted === "claude-cli") {
|
|
11877
|
-
const budgetEnv = env.CLAUDE_CLI_MAX_BUDGET_USD;
|
|
11878
|
-
const budgetNum = budgetEnv !== void 0 ? Number(budgetEnv) : void 0;
|
|
11879
|
-
return new ClaudeCliProvider({
|
|
11880
|
-
bin: opts.claudeCliBin ?? env.CLAUDE_CLI_BIN,
|
|
11881
|
-
defaultModel: opts.claudeCliDefaultModel ?? env.CLAUDE_CLI_MODEL,
|
|
11882
|
-
maxBudgetUsd: opts.claudeCliMaxBudgetUsd ?? (budgetNum !== void 0 && Number.isFinite(budgetNum) ? budgetNum : void 0)
|
|
11883
|
-
});
|
|
11884
|
-
}
|
|
11885
|
-
if (wanted === "claude-agent-sdk") {
|
|
11886
|
-
return new ClaudeAgentSdkProvider({
|
|
11887
|
-
oauthToken: opts.claudeAgentSdkOauthToken ?? env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
11888
|
-
defaultModel: opts.claudeAgentSdkDefaultModel ?? env.CLAUDE_AGENT_SDK_MODEL
|
|
11889
|
-
});
|
|
11890
|
-
}
|
|
11891
|
-
if (wanted === "ollama") {
|
|
11892
|
-
return new OllamaProvider({
|
|
11893
|
-
baseUrl: opts.ollamaBaseUrl ?? env.OLLAMA_BASE_URL,
|
|
11894
|
-
defaultModel: opts.ollamaDefaultModel ?? env.OLLAMA_DEFAULT_MODEL
|
|
11895
|
-
});
|
|
11896
|
-
}
|
|
11897
|
-
throw new Error(
|
|
11898
|
-
`selectProvider: WHITEBOARD_AI_PROVIDER="${wanted}" kh\xF4ng h\u1EE3p l\u1EC7 (anthropic|claude-cli|claude-agent-sdk|ollama)`
|
|
11899
|
-
);
|
|
11900
|
-
}
|
|
11901
|
-
function readEnv() {
|
|
11902
|
-
if (typeof process !== "undefined" && process.env) {
|
|
11903
|
-
return process.env;
|
|
11904
|
-
}
|
|
11905
|
-
return {};
|
|
11906
|
-
}
|
|
11907
|
-
var init_providers = __esm({
|
|
11908
|
-
"src/stamps/geometry-2d/ai/providers/index.ts"() {
|
|
11909
|
-
init_anthropic();
|
|
11910
|
-
init_claude_agent_sdk();
|
|
11911
|
-
init_claude_cli();
|
|
11912
|
-
init_ollama();
|
|
11913
|
-
}
|
|
11914
|
-
});
|
|
11915
|
-
function visionEnvelopeJsonSchema() {
|
|
11916
|
-
return zodToJsonSchema.zodToJsonSchema(VisionEnvelopeZ, {
|
|
11917
|
-
$refStrategy: "none",
|
|
11918
|
-
target: "jsonSchema7"
|
|
11919
|
-
});
|
|
11920
|
-
}
|
|
11921
|
-
var VisionEnvelopeZ;
|
|
11922
|
-
var init_envelope = __esm({
|
|
11923
|
-
"src/stamps/geometry-2d/ai/vision/envelope.ts"() {
|
|
11924
|
-
VisionEnvelopeZ = zod.z.object({
|
|
11925
|
-
decision: zod.z.enum(["extract", "refuse"]),
|
|
11926
|
-
text: zod.z.string().optional(),
|
|
11927
|
-
confidence: zod.z.enum(["high", "low"]).optional(),
|
|
11928
|
-
reason: zod.z.string().optional()
|
|
11929
|
-
}).refine(
|
|
11930
|
-
(e) => e.decision === "extract" ? e.text != null && e.text.length > 0 : e.reason != null && e.reason.length > 0,
|
|
11931
|
-
{ message: "extract c\u1EA7n text kh\xF4ng r\u1ED7ng; refuse c\u1EA7n reason kh\xF4ng r\u1ED7ng" }
|
|
11932
|
-
);
|
|
11933
|
-
}
|
|
11934
|
-
});
|
|
11935
|
-
|
|
11936
|
-
// src/stamps/geometry-2d/ai/vision/prompt.ts
|
|
11937
|
-
function buildVisionSystemPrompt() {
|
|
11938
|
-
return [
|
|
11939
|
-
"B\u1EA1n l\xE0 OCR chuy\xEAn \u0111\u1ECDc \u0111\u1EC1 to\xE1n h\xECnh h\u1ECDc ti\u1EBFng Vi\u1EC7t t\u1EEB \u1EA3nh.",
|
|
11940
|
-
"",
|
|
11941
|
-
"NHI\u1EC6M V\u1EE4:",
|
|
11942
|
-
"1. \u0110\u1ECDc text trong \u1EA3nh, tr\u1EA3 v\u1EC1 ph\u1EA7n \u0110\u1EC0 B\xC0I (l\u1EDDi v\u0103n + c\xF4ng th\u1EE9c inline).",
|
|
11943
|
-
"2. GI\u1EEE NGUY\xCAN c\xE1c k\xFD hi\u1EC7u to\xE1n Unicode: \u0394 \u22A5 \u2225 \xB0 \u2299 \u03C0 \u2192 \u2264 \u2265 \u2208 \u2209 \u2229 \u222A.",
|
|
11944
|
-
"3. B\u1ECE QUA h\xECnh v\u1EBD minh ho\u1EA1 \u2014 ch\u1EC9 tr\u1EA3 ph\u1EA7n text.",
|
|
11945
|
-
'4. N\u1EBFu \u1EA3nh KH\xD4NG ph\u1EA3i \u0111\u1EC1 to\xE1n h\xECnh h\u1ECDc (vd: v\u0103n h\u1ECDc, \u1EA3nh \u0111\u1EDDi th\u01B0\u1EDDng, code, c\xF4ng th\u1EE9c kh\xF4ng li\xEAn quan): decision="refuse" v\u1EDBi reason c\u1EE5 th\u1EC3 b\u1EB1ng ti\u1EBFng Vi\u1EC7t.',
|
|
11946
|
-
"5. \u0110\xE1nh gi\xE1 confidence:",
|
|
11947
|
-
' - "high": \u2265 80% k\xFD t\u1EF1 \u0111\u1ECDc r\xF5 r\xE0ng, kh\xF4ng nghi ng\u1EDD.',
|
|
11948
|
-
' - "low": \u1EA3nh m\u1EDD, c\xF3 ch\u1EEF kh\xF4ng ch\u1EAFc ch\u1EAFn, ho\u1EB7c < 80% k\xFD t\u1EF1 confident.',
|
|
11949
|
-
"",
|
|
11950
|
-
"OUTPUT: JSON theo schema sau, kh\xF4ng markdown, kh\xF4ng gi\u1EA3i th\xEDch th\xEAm.",
|
|
11951
|
-
' { "decision": "extract", "text": "...", "confidence": "high"|"low" }',
|
|
11952
|
-
' { "decision": "refuse", "reason": "..." }',
|
|
11953
|
-
"",
|
|
11954
|
-
"V\xCD D\u1EE4 extract success:",
|
|
11955
|
-
' { "decision": "extract", "text": "Cho tam gi\xE1c ABC vu\xF4ng t\u1EA1i A. K\u1EBB \u0111\u01B0\u1EDDng cao AH \u22A5 BC. Ch\u1EE9ng minh AH\xB2 = BH \xB7 CH.", "confidence": "high" }',
|
|
11956
|
-
"",
|
|
11957
|
-
"V\xCD D\u1EE4 refuse:",
|
|
11958
|
-
' { "decision": "refuse", "reason": "\u1EA2nh kh\xF4ng ph\u1EA3i \u0111\u1EC1 to\xE1n \u2014 \u0111\xE2y l\xE0 m\u1ED9t \u0111o\u1EA1n v\u0103n v\u1EC1 Truy\u1EC7n Ki\u1EC1u." }'
|
|
11959
|
-
].join("\n");
|
|
11960
|
-
}
|
|
11961
|
-
var VISION_USER_PROMPT;
|
|
11962
|
-
var init_prompt = __esm({
|
|
11963
|
-
"src/stamps/geometry-2d/ai/vision/prompt.ts"() {
|
|
11964
|
-
VISION_USER_PROMPT = "\u0110\u1ECDc \u0111\u1EC1 b\xE0i h\xECnh h\u1ECDc trong \u1EA3nh sau.";
|
|
11965
|
-
}
|
|
11966
|
-
});
|
|
11967
|
-
|
|
11968
|
-
// src/stamps/geometry-2d/ai/vision/tesseract.ts
|
|
11969
|
-
async function runTesseractOcr(image, opts = {}) {
|
|
11970
|
-
if (opts.signal?.aborted) {
|
|
11971
|
-
const err = new Error("Tesseract OCR aborted");
|
|
11972
|
-
err.name = "AbortError";
|
|
11973
|
-
throw err;
|
|
11974
|
-
}
|
|
11975
|
-
const { createWorker } = await import('tesseract.js');
|
|
11976
|
-
const lang = opts.lang ?? DEFAULT_LANG;
|
|
11977
|
-
const worker = await createWorker(lang);
|
|
11978
|
-
try {
|
|
11979
|
-
const dataUrl = `data:${image.mediaType};base64,${image.base64}`;
|
|
11980
|
-
const { data } = await worker.recognize(dataUrl);
|
|
11981
|
-
return { text: data.text, confidence: data.confidence };
|
|
11982
|
-
} finally {
|
|
11983
|
-
await worker.terminate();
|
|
11984
|
-
}
|
|
11985
|
-
}
|
|
11986
|
-
var DEFAULT_LANG;
|
|
11987
|
-
var init_tesseract = __esm({
|
|
11988
|
-
"src/stamps/geometry-2d/ai/vision/tesseract.ts"() {
|
|
11989
|
-
DEFAULT_LANG = "vie+eng";
|
|
11990
|
-
}
|
|
11991
|
-
});
|
|
11992
|
-
|
|
11993
|
-
// src/stamps/geometry-2d/ai/vision/extractProblem.ts
|
|
11994
|
-
function pickVisionModel(providerDefault, opts, env) {
|
|
11995
|
-
return opts.visionModel ?? env.WHITEBOARD_AI_VISION_MODEL ?? providerDefault;
|
|
11996
|
-
}
|
|
11997
|
-
async function extractProblemFromImage(image, opts = {}) {
|
|
11998
|
-
const engine = opts.engine ?? "tesseract";
|
|
11999
|
-
if (engine === "tesseract") {
|
|
12000
|
-
return extractViaTesseract(image, opts);
|
|
12001
|
-
}
|
|
12002
|
-
return extractViaLlm(image, opts);
|
|
12003
|
-
}
|
|
12004
|
-
async function extractViaTesseract(image, opts) {
|
|
12005
|
-
let raw;
|
|
12006
|
-
try {
|
|
12007
|
-
raw = await runTesseractOcr(image, {
|
|
12008
|
-
...opts.tesseractLang ? { lang: opts.tesseractLang } : {},
|
|
12009
|
-
...opts.signal ? { signal: opts.signal } : {}
|
|
12010
|
-
});
|
|
12011
|
-
} catch (e) {
|
|
12012
|
-
const err = e;
|
|
12013
|
-
return {
|
|
12014
|
-
ok: false,
|
|
12015
|
-
reason: "unreadable",
|
|
12016
|
-
message: "Tesseract OCR fail: " + (err.message ?? "?")
|
|
12017
|
-
};
|
|
12018
|
-
}
|
|
12019
|
-
const text = postProcess(raw.text);
|
|
12020
|
-
if (text.length === 0) {
|
|
12021
|
-
return { ok: false, reason: "empty", message: "Tesseract kh\xF4ng tr\xEDch \u0111\u01B0\u1EE3c text." };
|
|
12022
|
-
}
|
|
12023
|
-
const tooShort = text.length < MIN_HIGH_CONFIDENCE_CHARS;
|
|
12024
|
-
const lowConf = raw.confidence < TESSERACT_HIGH_CONFIDENCE_THRESHOLD;
|
|
12025
|
-
const confidence = tooShort || lowConf ? "low" : "high";
|
|
12026
|
-
return {
|
|
12027
|
-
ok: true,
|
|
12028
|
-
text,
|
|
12029
|
-
confidence,
|
|
12030
|
-
usage: { inputTokens: 0, outputTokens: 0 }
|
|
12031
|
-
};
|
|
12032
|
-
}
|
|
12033
|
-
async function extractViaLlm(image, opts) {
|
|
12034
|
-
const provider = opts.provider ?? selectProvider(opts);
|
|
12035
|
-
if (!provider.extractText) {
|
|
12036
|
-
return {
|
|
12037
|
-
ok: false,
|
|
12038
|
-
reason: "unsupported",
|
|
12039
|
-
message: `Provider "${provider.name}" kh\xF4ng h\u1ED7 tr\u1EE3 \u0111\u1ECDc \u1EA3nh.`
|
|
12040
|
-
};
|
|
12041
|
-
}
|
|
12042
|
-
const env = opts.env ?? readEnv2();
|
|
12043
|
-
const model = pickVisionModel(provider.defaultModel, opts, env);
|
|
12044
|
-
const req = {
|
|
12045
|
-
systemPrompt: buildVisionSystemPrompt(),
|
|
12046
|
-
userPrompt: VISION_USER_PROMPT,
|
|
12047
|
-
schema: visionEnvelopeJsonSchema(),
|
|
12048
|
-
images: [image],
|
|
12049
|
-
model,
|
|
12050
|
-
maxTokens: opts.maxTokens ?? 1024,
|
|
12051
|
-
...opts.signal ? { signal: opts.signal } : {}
|
|
12052
|
-
};
|
|
12053
|
-
const out = await provider.extractText(req);
|
|
12054
|
-
if (out.kind === "error") {
|
|
12055
|
-
return { ok: false, reason: "unreadable", message: out.message };
|
|
12056
|
-
}
|
|
12057
|
-
const parsed = VisionEnvelopeZ.safeParse(out.data);
|
|
12058
|
-
if (!parsed.success) {
|
|
12059
|
-
return {
|
|
12060
|
-
ok: false,
|
|
12061
|
-
reason: "empty",
|
|
12062
|
-
message: "Kh\xF4ng parse \u0111\u01B0\u1EE3c output OCR: " + parsed.error.message
|
|
12063
|
-
};
|
|
12064
|
-
}
|
|
12065
|
-
const env_ = parsed.data;
|
|
12066
|
-
if (env_.decision === "refuse") {
|
|
12067
|
-
return {
|
|
12068
|
-
ok: false,
|
|
12069
|
-
reason: "not-math",
|
|
12070
|
-
message: env_.reason ?? "\u1EA2nh kh\xF4ng ph\u1EA3i \u0111\u1EC1 to\xE1n."
|
|
12071
|
-
};
|
|
12072
|
-
}
|
|
12073
|
-
const rawText = env_.text ?? "";
|
|
12074
|
-
const text = postProcess(rawText);
|
|
12075
|
-
if (text.length === 0) {
|
|
12076
|
-
return { ok: false, reason: "empty", message: "OCR kh\xF4ng tr\xEDch \u0111\u01B0\u1EE3c text." };
|
|
12077
|
-
}
|
|
12078
|
-
const tooShort = text.length < MIN_HIGH_CONFIDENCE_CHARS;
|
|
12079
|
-
const confidence = env_.confidence === "low" || tooShort ? "low" : "high";
|
|
12080
|
-
const usage = out.usage ?? { inputTokens: 0, outputTokens: 0 };
|
|
12081
|
-
return {
|
|
12082
|
-
ok: true,
|
|
12083
|
-
text,
|
|
12084
|
-
confidence,
|
|
12085
|
-
usage: { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens }
|
|
12086
|
-
};
|
|
12087
|
-
}
|
|
12088
|
-
function postProcess(raw) {
|
|
12089
|
-
let t = raw.trim();
|
|
12090
|
-
t = t.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
12091
|
-
t = t.replace(/\*(.+?)\*/g, "$1");
|
|
12092
|
-
t = t.replace(/_(.+?)_/g, "$1");
|
|
12093
|
-
t = t.replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1");
|
|
12094
|
-
t = t.replace(/\s+/g, " ").trim();
|
|
12095
|
-
t = t.normalize("NFC");
|
|
12096
|
-
if (t.length > MAX_TEXT_CHARS) t = t.slice(0, MAX_TEXT_CHARS);
|
|
12097
|
-
return t;
|
|
12098
|
-
}
|
|
12099
|
-
function readEnv2() {
|
|
12100
|
-
if (typeof process !== "undefined" && process.env) {
|
|
12101
|
-
return process.env;
|
|
12102
|
-
}
|
|
12103
|
-
return {};
|
|
12104
|
-
}
|
|
12105
|
-
var MIN_HIGH_CONFIDENCE_CHARS, MAX_TEXT_CHARS, TESSERACT_HIGH_CONFIDENCE_THRESHOLD;
|
|
12106
|
-
var init_extractProblem = __esm({
|
|
12107
|
-
"src/stamps/geometry-2d/ai/vision/extractProblem.ts"() {
|
|
12108
|
-
init_providers();
|
|
12109
|
-
init_envelope();
|
|
12110
|
-
init_prompt();
|
|
12111
|
-
init_tesseract();
|
|
12112
|
-
MIN_HIGH_CONFIDENCE_CHARS = 10;
|
|
12113
|
-
MAX_TEXT_CHARS = 2e3;
|
|
12114
|
-
TESSERACT_HIGH_CONFIDENCE_THRESHOLD = 70;
|
|
12115
|
-
}
|
|
12116
|
-
});
|
|
12117
|
-
|
|
12118
|
-
// src/stamps/geometry-2d/ai/handleExtractProblem.ts
|
|
12119
|
-
async function handleExtractProblem(image, opts = {}) {
|
|
12120
|
-
try {
|
|
12121
|
-
const r = await extractProblemFromImage(image, opts);
|
|
12122
|
-
if (r.ok) {
|
|
12123
|
-
if (r.confidence === "low") {
|
|
12124
|
-
return {
|
|
12125
|
-
kind: "low-confidence",
|
|
12126
|
-
text: r.text,
|
|
12127
|
-
warning: "OCR c\xF3 th\u1EC3 kh\xF4ng ch\xEDnh x\xE1c, ki\u1EC3m tra tr\u01B0\u1EDBc khi v\u1EBD.",
|
|
12128
|
-
usage: r.usage
|
|
12129
|
-
};
|
|
12130
|
-
}
|
|
12131
|
-
return { kind: "success", text: r.text, usage: r.usage };
|
|
12132
|
-
}
|
|
12133
|
-
if (r.reason === "not-math") {
|
|
12134
|
-
return { kind: "refused", reason: "not-math", message: r.message };
|
|
12135
|
-
}
|
|
12136
|
-
if (r.reason === "unsupported") {
|
|
12137
|
-
return { kind: "error", code: "unsupported", message: r.message };
|
|
12138
|
-
}
|
|
12139
|
-
if (r.reason === "unreadable") {
|
|
12140
|
-
return { kind: "error", code: "network", message: r.message };
|
|
12141
|
-
}
|
|
12142
|
-
return { kind: "error", code: "empty", message: r.message };
|
|
12143
|
-
} catch (e) {
|
|
12144
|
-
return {
|
|
12145
|
-
kind: "error",
|
|
12146
|
-
code: "unexpected",
|
|
12147
|
-
message: e instanceof Error ? e.message : String(e)
|
|
12148
|
-
};
|
|
12149
|
-
}
|
|
12150
|
-
}
|
|
12151
|
-
var init_handleExtractProblem = __esm({
|
|
12152
|
-
"src/stamps/geometry-2d/ai/handleExtractProblem.ts"() {
|
|
12153
|
-
init_extractProblem();
|
|
12154
|
-
}
|
|
12155
|
-
});
|
|
12156
|
-
|
|
12157
|
-
// src/stamps/geometry-2d/ai/vision/preprocess.ts
|
|
12158
|
-
function inferMediaType(file) {
|
|
12159
|
-
const t = file.type.toLowerCase();
|
|
12160
|
-
if (ALLOWED_TYPES.includes(t)) return t;
|
|
12161
|
-
return null;
|
|
12162
|
-
}
|
|
12163
|
-
function validateFile(file) {
|
|
12164
|
-
const mt = inferMediaType(file);
|
|
12165
|
-
if (mt == null) {
|
|
12166
|
-
return {
|
|
12167
|
-
ok: false,
|
|
12168
|
-
code: "invalid-format",
|
|
12169
|
-
message: "Ch\u1EC9 h\u1ED7 tr\u1EE3 PNG, JPEG, WEBP."
|
|
12170
|
-
};
|
|
12171
|
-
}
|
|
12172
|
-
if (file.size > MAX_RAW_BYTES) {
|
|
12173
|
-
return {
|
|
12174
|
-
ok: false,
|
|
12175
|
-
code: "too-large",
|
|
12176
|
-
message: `\u1EA2nh qu\xE1 l\u1EDBn (> ${Math.round(MAX_RAW_BYTES / 1024 / 1024)} MB). Crop ho\u1EB7c resize tr\u01B0\u1EDBc.`
|
|
12177
|
-
};
|
|
12178
|
-
}
|
|
12179
|
-
return { ok: true, mediaType: mt };
|
|
12180
|
-
}
|
|
12181
|
-
async function fileToImagePart(file) {
|
|
12182
|
-
const v = validateFile(file);
|
|
12183
|
-
if (!v.ok) throw new Error(v.message);
|
|
12184
|
-
const bitmap = await createImageBitmap(file);
|
|
12185
|
-
const { width, height } = bitmap;
|
|
12186
|
-
const maxEdge = Math.max(width, height);
|
|
12187
|
-
const scale3 = maxEdge > MAX_EDGE_PX ? MAX_EDGE_PX / maxEdge : 1;
|
|
12188
|
-
const targetW = Math.round(width * scale3);
|
|
12189
|
-
const targetH = Math.round(height * scale3);
|
|
12190
|
-
const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetW, targetH) : Object.assign(document.createElement("canvas"), { width: targetW, height: targetH });
|
|
12191
|
-
const ctx = canvas.getContext("2d");
|
|
12192
|
-
if (!ctx) throw new Error("Kh\xF4ng t\u1EA1o \u0111\u01B0\u1EE3c canvas 2D context");
|
|
12193
|
-
ctx.drawImage(bitmap, 0, 0, targetW, targetH);
|
|
12194
|
-
bitmap.close();
|
|
12195
|
-
let outputType = v.mediaType === "image/png" ? "image/png" : "image/jpeg";
|
|
12196
|
-
let finalBlob = await canvasToBlob(canvas, outputType, 0.92);
|
|
12197
|
-
if (finalBlob.size > MAX_ENCODED_BYTES) {
|
|
12198
|
-
outputType = "image/jpeg";
|
|
12199
|
-
finalBlob = await canvasToBlob(canvas, "image/jpeg", 0.7);
|
|
12200
|
-
}
|
|
12201
|
-
const base64 = await blobToBase64(finalBlob);
|
|
12202
|
-
return { mediaType: outputType, base64 };
|
|
12203
|
-
}
|
|
12204
|
-
async function canvasToBlob(canvas, type, quality) {
|
|
12205
|
-
if ("convertToBlob" in canvas) {
|
|
12206
|
-
return canvas.convertToBlob({ type, quality });
|
|
12207
|
-
}
|
|
12208
|
-
return new Promise((resolve, reject) => {
|
|
12209
|
-
canvas.toBlob(
|
|
12210
|
-
(b) => b ? resolve(b) : reject(new Error("Canvas encode fail")),
|
|
12211
|
-
type,
|
|
12212
|
-
quality
|
|
12213
|
-
);
|
|
12214
|
-
});
|
|
12215
|
-
}
|
|
12216
|
-
async function blobToBase64(blob) {
|
|
12217
|
-
const buf = await blob.arrayBuffer();
|
|
12218
|
-
let binary = "";
|
|
12219
|
-
const bytes = new Uint8Array(buf);
|
|
12220
|
-
const chunk = 32768;
|
|
12221
|
-
for (let i = 0; i < bytes.length; i += chunk) {
|
|
12222
|
-
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
12223
|
-
}
|
|
12224
|
-
return typeof btoa === "function" ? btoa(binary) : Buffer.from(binary, "binary").toString("base64");
|
|
12225
|
-
}
|
|
12226
|
-
var MAX_EDGE_PX, MAX_RAW_BYTES, MAX_ENCODED_BYTES, ALLOWED_TYPES;
|
|
12227
|
-
var init_preprocess = __esm({
|
|
12228
|
-
"src/stamps/geometry-2d/ai/vision/preprocess.ts"() {
|
|
12229
|
-
MAX_EDGE_PX = 2048;
|
|
12230
|
-
MAX_RAW_BYTES = 10 * 1024 * 1024;
|
|
12231
|
-
MAX_ENCODED_BYTES = 3 * 1024 * 1024;
|
|
12232
|
-
ALLOWED_TYPES = ["image/png", "image/jpeg", "image/webp"];
|
|
12233
|
-
}
|
|
12234
|
-
});
|
|
12235
|
-
function AiFigurePrompt({
|
|
12236
|
-
generator,
|
|
12237
|
-
onGenerated,
|
|
12238
|
-
currentState,
|
|
12239
|
-
extractProblem = handleExtractProblem
|
|
12240
|
-
}) {
|
|
12241
|
-
const {
|
|
12242
|
-
prompt,
|
|
12243
|
-
setPrompt,
|
|
12244
|
-
isLoading,
|
|
12245
|
-
error,
|
|
12246
|
-
submit,
|
|
12247
|
-
cancel,
|
|
12248
|
-
tokens,
|
|
12249
|
-
mode,
|
|
12250
|
-
setMode,
|
|
12251
|
-
entityCount,
|
|
12252
|
-
hasUnsupported
|
|
12253
|
-
} = useAiFigure(generator, { currentState });
|
|
12254
|
-
const [elapsed, setElapsed] = React19.useState(0);
|
|
12255
|
-
React19.useEffect(() => {
|
|
12256
|
-
if (!isLoading) {
|
|
12257
|
-
setElapsed(0);
|
|
12258
|
-
return;
|
|
12259
|
-
}
|
|
12260
|
-
const id = setInterval(() => setElapsed((s) => s + 1), 1e3);
|
|
12261
|
-
return () => clearInterval(id);
|
|
12262
|
-
}, [isLoading]);
|
|
12263
|
-
const [image, setImage] = React19.useState(null);
|
|
12264
|
-
const [ocrLoading, setOcrLoading] = React19.useState(false);
|
|
12265
|
-
const [ocrError, setOcrError] = React19.useState(null);
|
|
12266
|
-
const [ocrWarning, setOcrWarning] = React19.useState(null);
|
|
12267
|
-
const [isDragOver, setIsDragOver] = React19.useState(false);
|
|
12268
|
-
const fileInputRef = React19.useRef(null);
|
|
12269
|
-
const textareaRef = React19.useRef(null);
|
|
12270
|
-
const imagePreview = image ? `data:${image.mediaType};base64,${image.base64}` : null;
|
|
12271
|
-
React19.useEffect(() => {
|
|
12272
|
-
setOcrError(null);
|
|
12273
|
-
setOcrWarning(null);
|
|
12274
|
-
}, [image]);
|
|
12275
|
-
const handleFile = React19.useCallback(
|
|
12276
|
-
async (file) => {
|
|
12277
|
-
if (isLoading || ocrLoading) return;
|
|
12278
|
-
const v = validateFile(file);
|
|
12279
|
-
if (!v.ok) {
|
|
12280
|
-
setOcrError(v.message);
|
|
12281
|
-
return;
|
|
12282
|
-
}
|
|
12283
|
-
try {
|
|
12284
|
-
const part = await fileToImagePart(file);
|
|
12285
|
-
setImage(part);
|
|
12286
|
-
} catch (e) {
|
|
12287
|
-
setOcrError(e instanceof Error ? e.message : "Kh\xF4ng decode \u0111\u01B0\u1EE3c \u1EA3nh");
|
|
12288
|
-
}
|
|
12289
|
-
},
|
|
12290
|
-
[isLoading, ocrLoading]
|
|
12291
|
-
);
|
|
12292
|
-
const handleFileInput = React19.useCallback(
|
|
12293
|
-
(e) => {
|
|
12294
|
-
const file = e.target.files?.[0];
|
|
12295
|
-
if (file) void handleFile(file);
|
|
12296
|
-
e.target.value = "";
|
|
12297
|
-
},
|
|
12298
|
-
[handleFile]
|
|
12299
|
-
);
|
|
12300
|
-
const handlePaste = React19.useCallback(
|
|
12301
|
-
(e) => {
|
|
12302
|
-
const item = Array.from(e.clipboardData.items).find(
|
|
12303
|
-
(it) => it.kind === "file" && it.type.startsWith("image/")
|
|
12304
|
-
);
|
|
12305
|
-
if (!item) return;
|
|
12306
|
-
const file = item.getAsFile();
|
|
12307
|
-
if (!file) return;
|
|
12308
|
-
e.preventDefault();
|
|
12309
|
-
void handleFile(file);
|
|
12310
|
-
},
|
|
12311
|
-
[handleFile]
|
|
12312
|
-
);
|
|
12313
|
-
const handleDrop = React19.useCallback(
|
|
12314
|
-
(e) => {
|
|
12315
|
-
e.preventDefault();
|
|
12316
|
-
setIsDragOver(false);
|
|
12317
|
-
const file = Array.from(e.dataTransfer.files).find(
|
|
12318
|
-
(f) => f.type.startsWith("image/")
|
|
12319
|
-
);
|
|
12320
|
-
if (file) void handleFile(file);
|
|
12321
|
-
},
|
|
12322
|
-
[handleFile]
|
|
12323
|
-
);
|
|
12324
|
-
const runOcr = React19.useCallback(async () => {
|
|
12325
|
-
if (!image) return;
|
|
12326
|
-
setOcrLoading(true);
|
|
12327
|
-
setOcrError(null);
|
|
12328
|
-
setOcrWarning(null);
|
|
12329
|
-
try {
|
|
12330
|
-
const r = await extractProblem(image);
|
|
12331
|
-
if (r.kind === "success" || r.kind === "low-confidence") {
|
|
12332
|
-
setPrompt(r.text);
|
|
12333
|
-
if (r.kind === "low-confidence") setOcrWarning(r.warning);
|
|
12334
|
-
requestAnimationFrame(() => textareaRef.current?.focus());
|
|
12335
|
-
} else {
|
|
12336
|
-
setOcrError(r.message);
|
|
12337
|
-
}
|
|
12338
|
-
} finally {
|
|
12339
|
-
setOcrLoading(false);
|
|
12340
|
-
}
|
|
12341
|
-
}, [image, setPrompt, extractProblem]);
|
|
12342
|
-
const handleSendClick = React19.useCallback(async () => {
|
|
12343
|
-
if (image && !prompt.trim() && !ocrLoading) {
|
|
12344
|
-
await runOcr();
|
|
12345
|
-
return;
|
|
12346
|
-
}
|
|
12347
|
-
const generated = await submit();
|
|
12348
|
-
if (generated) onGenerated(generated);
|
|
12349
|
-
}, [image, prompt, ocrLoading, runOcr, submit, onGenerated]);
|
|
12350
|
-
const handleSwitchToBuild = React19.useCallback(() => {
|
|
12351
|
-
if (currentState && currentState.order.length > 0) {
|
|
12352
|
-
const ok = window.confirm(
|
|
12353
|
-
"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?"
|
|
12354
|
-
);
|
|
12355
|
-
if (!ok) return;
|
|
12356
|
-
}
|
|
12357
|
-
setMode("build");
|
|
12358
|
-
}, [currentState, setMode]);
|
|
12359
|
-
const hasContent = currentState != null && currentState.order.length > 0;
|
|
12360
|
-
const promptEmpty = !prompt.trim();
|
|
12361
|
-
const willOcr = image != null && promptEmpty;
|
|
12362
|
-
const sendDisabled = !image && promptEmpty || ocrLoading || isLoading && !willOcr;
|
|
12363
|
-
const refineChipLabel = entityCount.points + entityCount.shapes > 0 ? `Th\xEAm v\xE0o \xB7 ${entityCount.points}\u0111, ${entityCount.shapes}\u0111o\u1EA1n` : "Th\xEAm v\xE0o";
|
|
12364
|
-
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).";
|
|
12365
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-slate-200 bg-slate-50 px-3 py-3", children: [
|
|
12366
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
|
|
12367
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium tracking-wide text-slate-600", children: "D\u1EF1ng h\xECnh b\u1EB1ng AI" }),
|
|
12368
|
-
hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", role: "tablist", "aria-label": "Ch\u1EBF \u0111\u1ED9 AI", children: [
|
|
12369
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12370
|
-
"button",
|
|
12371
|
-
{
|
|
12372
|
-
type: "button",
|
|
12373
|
-
role: "tab",
|
|
12374
|
-
"aria-selected": mode === "refine",
|
|
12375
|
-
"data-testid": "geometry-ai-mode-refine",
|
|
12376
|
-
onClick: () => setMode("refine"),
|
|
12377
|
-
disabled: isLoading || hasUnsupported,
|
|
12378
|
-
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,
|
|
12379
|
-
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" : ""}`,
|
|
12380
|
-
children: refineChipLabel
|
|
12381
|
-
}
|
|
12382
|
-
),
|
|
12383
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12384
|
-
"button",
|
|
12385
|
-
{
|
|
12386
|
-
type: "button",
|
|
12387
|
-
role: "tab",
|
|
12388
|
-
"aria-selected": mode === "build",
|
|
12389
|
-
"data-testid": "geometry-ai-mode-build",
|
|
12390
|
-
onClick: handleSwitchToBuild,
|
|
12391
|
-
disabled: isLoading,
|
|
12392
|
-
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"}`,
|
|
12393
|
-
children: "D\u1EF1ng m\u1EDBi"
|
|
12394
|
-
}
|
|
12395
|
-
)
|
|
12396
|
-
] })
|
|
12397
|
-
] }),
|
|
12398
|
-
hasUnsupported && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12399
|
-
"p",
|
|
12400
|
-
{
|
|
12401
|
-
className: "mb-1.5 text-[10px] text-amber-700",
|
|
12402
|
-
"data-testid": "geometry-ai-unsupported-warning",
|
|
12403
|
-
children: "H\xECnh c\xF3 \u0111\u1ED1i t\u01B0\u1EE3ng ngo\xE0i DSL \u2014 ch\u1EC9 d\u1EF1ng m\u1EDBi \u0111\u01B0\u1EE3c"
|
|
12404
|
-
}
|
|
12405
|
-
),
|
|
12406
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
12407
|
-
"div",
|
|
12408
|
-
{
|
|
12409
|
-
onDragOver: (e) => {
|
|
12410
|
-
e.preventDefault();
|
|
12411
|
-
setIsDragOver(true);
|
|
12412
|
-
},
|
|
12413
|
-
onDragLeave: () => setIsDragOver(false),
|
|
12414
|
-
onDrop: handleDrop,
|
|
12415
|
-
onPaste: handlePaste,
|
|
12416
|
-
"aria-label": "Khu v\u1EF1c k\xE9o th\u1EA3 \u1EA3nh",
|
|
12417
|
-
role: "region",
|
|
12418
|
-
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" : ""),
|
|
12419
|
-
children: [
|
|
12420
|
-
image && imagePreview && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 px-3 pt-2.5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "group/chip relative", children: [
|
|
12421
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12422
|
-
"img",
|
|
12423
|
-
{
|
|
12424
|
-
src: imagePreview,
|
|
12425
|
-
alt: "\u1EA2nh \u0111\u1EC1 b\xE0i",
|
|
12426
|
-
className: "max-h-48 max-w-full h-auto w-auto rounded-lg border border-slate-200 shadow-sm"
|
|
12427
|
-
}
|
|
12428
|
-
),
|
|
12429
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12430
|
-
"button",
|
|
12431
|
-
{
|
|
12432
|
-
type: "button",
|
|
12433
|
-
onClick: () => setImage(null),
|
|
12434
|
-
disabled: ocrLoading || isLoading,
|
|
12435
|
-
"aria-label": "Xo\xE1 \u1EA3nh",
|
|
12436
|
-
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",
|
|
12437
|
-
children: "\xD7"
|
|
12438
|
-
}
|
|
12439
|
-
)
|
|
12440
|
-
] }) }),
|
|
12441
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12442
|
-
"textarea",
|
|
12443
|
-
{
|
|
12444
|
-
ref: textareaRef,
|
|
12445
|
-
id: "geometry-ai-prompt",
|
|
12446
|
-
"aria-label": "\u0110\u1EC1 b\xE0i cho AI",
|
|
12447
|
-
"data-testid": "geometry-ai-textarea",
|
|
12448
|
-
value: prompt,
|
|
12449
|
-
onChange: (e) => setPrompt(e.target.value),
|
|
12450
|
-
onKeyDown: (e) => {
|
|
12451
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !sendDisabled) {
|
|
12452
|
-
e.preventDefault();
|
|
12453
|
-
void handleSendClick();
|
|
12454
|
-
}
|
|
12455
|
-
},
|
|
12456
|
-
disabled: isLoading,
|
|
12457
|
-
rows: 2,
|
|
12458
|
-
placeholder,
|
|
12459
|
-
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"
|
|
12460
|
-
}
|
|
12461
|
-
),
|
|
12462
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 px-2 pb-2 pt-1", children: [
|
|
12463
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
12464
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12465
|
-
"button",
|
|
12466
|
-
{
|
|
12467
|
-
type: "button",
|
|
12468
|
-
onClick: () => fileInputRef.current?.click(),
|
|
12469
|
-
disabled: isLoading || ocrLoading,
|
|
12470
|
-
"aria-label": "\u0110\xEDnh \u1EA3nh \u0111\u1EC1 b\xE0i",
|
|
12471
|
-
title: "\u0110\xEDnh \u1EA3nh (c\u0169ng c\xF3 th\u1EC3 d\xE1n b\u1EB1ng Ctrl+V ho\u1EB7c k\xE9o th\u1EA3)",
|
|
12472
|
-
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",
|
|
12473
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(PaperclipIcon, { className: "h-[18px] w-[18px]" })
|
|
12474
|
-
}
|
|
12475
|
-
),
|
|
12476
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12477
|
-
"input",
|
|
12478
|
-
{
|
|
12479
|
-
ref: fileInputRef,
|
|
12480
|
-
type: "file",
|
|
12481
|
-
accept: "image/png,image/jpeg,image/webp",
|
|
12482
|
-
className: "sr-only",
|
|
12483
|
-
onChange: handleFileInput,
|
|
12484
|
-
disabled: isLoading || ocrLoading,
|
|
12485
|
-
"aria-label": "Ch\u1ECDn \u1EA3nh \u0111\u1EC1 b\xE0i"
|
|
12486
|
-
}
|
|
12487
|
-
)
|
|
12488
|
-
] }),
|
|
12489
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
12490
|
-
(isLoading || ocrLoading) && /* @__PURE__ */ jsxRuntime.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` }),
|
|
12491
|
-
isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
12492
|
-
"button",
|
|
12493
|
-
{
|
|
12494
|
-
type: "button",
|
|
12495
|
-
onClick: cancel,
|
|
12496
|
-
"aria-label": "Hu\u1EF7 d\u1EF1ng h\xECnh AI",
|
|
12497
|
-
"data-testid": "geometry-ai-cancel",
|
|
12498
|
-
title: `\u0110ang d\u1EF1ng\u2026 ${elapsed}s \u2014 b\u1EA5m \u0111\u1EC3 hu\u1EF7`,
|
|
12499
|
-
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",
|
|
12500
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(StopIcon, { className: "h-3.5 w-3.5" })
|
|
12501
|
-
}
|
|
12502
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
12503
|
-
"button",
|
|
12504
|
-
{
|
|
12505
|
-
type: "button",
|
|
12506
|
-
onClick: () => void handleSendClick(),
|
|
12507
|
-
disabled: sendDisabled,
|
|
12508
|
-
"aria-label": willOcr ? "\u0110\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh" : "D\u1EF1ng b\u1EB1ng AI",
|
|
12509
|
-
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)",
|
|
12510
|
-
"data-testid": willOcr ? "geometry-ai-ocr" : "geometry-ai-submit",
|
|
12511
|
-
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",
|
|
12512
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ArrowUpIcon, { className: "h-[18px] w-[18px]" })
|
|
12513
|
-
}
|
|
12514
|
-
)
|
|
12515
|
-
] })
|
|
12516
|
-
] }),
|
|
12517
|
-
isDragOver && /* @__PURE__ */ jsxRuntime.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" })
|
|
12518
|
-
]
|
|
12519
|
-
}
|
|
12520
|
-
),
|
|
12521
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1.5 px-1 text-[10px] text-slate-500", children: [
|
|
12522
|
-
"D\xE1n \u1EA3nh (Ctrl+V), k\xE9o th\u1EA3, ho\u1EB7c b\u1EA5m ",
|
|
12523
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: "\u{1F4CE}" }),
|
|
12524
|
-
" \u0111\u1EC3 \u0111\xEDnh \u1EA3nh \u0111\u1EC1."
|
|
12525
|
-
] }),
|
|
12526
|
-
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error }),
|
|
12527
|
-
ocrError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: ocrError }),
|
|
12528
|
-
ocrWarning && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 rounded bg-amber-50 px-2 py-1 text-[11px] text-amber-700", children: ocrWarning })
|
|
12529
|
-
] });
|
|
12530
|
-
}
|
|
12531
|
-
var PaperclipIcon, ArrowUpIcon, StopIcon;
|
|
12532
|
-
var init_AiFigurePrompt = __esm({
|
|
12533
|
-
"src/stamps/geometry-2d/editor/AiFigurePrompt.tsx"() {
|
|
12534
|
-
"use client";
|
|
12535
|
-
init_useAiFigure();
|
|
12536
|
-
init_handleExtractProblem();
|
|
12537
|
-
init_preprocess();
|
|
12538
|
-
PaperclipIcon = (props) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
12539
|
-
"svg",
|
|
12540
|
-
{
|
|
12541
|
-
viewBox: "0 0 24 24",
|
|
12542
|
-
fill: "none",
|
|
12543
|
-
stroke: "currentColor",
|
|
12544
|
-
strokeWidth: 1.75,
|
|
12545
|
-
strokeLinecap: "round",
|
|
12546
|
-
strokeLinejoin: "round",
|
|
12547
|
-
"aria-hidden": true,
|
|
12548
|
-
...props,
|
|
12549
|
-
children: /* @__PURE__ */ jsxRuntime.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" })
|
|
12550
|
-
}
|
|
12551
|
-
);
|
|
12552
|
-
ArrowUpIcon = (props) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
12553
|
-
"svg",
|
|
12554
|
-
{
|
|
12555
|
-
viewBox: "0 0 24 24",
|
|
12556
|
-
fill: "none",
|
|
12557
|
-
stroke: "currentColor",
|
|
12558
|
-
strokeWidth: 2.25,
|
|
12559
|
-
strokeLinecap: "round",
|
|
12560
|
-
strokeLinejoin: "round",
|
|
12561
|
-
"aria-hidden": true,
|
|
12562
|
-
...props,
|
|
12563
|
-
children: [
|
|
12564
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 19V5" }),
|
|
12565
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m5 12 7-7 7 7" })
|
|
12566
|
-
]
|
|
12567
|
-
}
|
|
12568
|
-
);
|
|
12569
|
-
StopIcon = (props) => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) });
|
|
12570
|
-
}
|
|
12571
|
-
});
|
|
12572
|
-
|
|
12573
|
-
// src/stamps/geometry-2d/draft.ts
|
|
12574
|
-
function svgIntrinsicSize(svg) {
|
|
12575
|
-
const w = svg.match(/<svg[^>]*\swidth="([\d.]+)"/);
|
|
12576
|
-
const h = svg.match(/<svg[^>]*\sheight="([\d.]+)"/);
|
|
12577
|
-
if (w && h) return { width: parseFloat(w[1]), height: parseFloat(h[1]) };
|
|
12578
|
-
const vb = svg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/);
|
|
12579
|
-
if (vb) return { width: parseFloat(vb[1]), height: parseFloat(vb[2]) };
|
|
12580
|
-
return { width: 300, height: 200 };
|
|
12581
|
-
}
|
|
12582
|
-
function draftFromViewport(svg, appState, seq) {
|
|
12583
|
-
const { width, height } = svgIntrinsicSize(svg);
|
|
12584
|
-
const zoom = appState.zoom?.value ?? 1;
|
|
12585
|
-
const vw = appState.width ?? 800;
|
|
12586
|
-
const vh = appState.height ?? 600;
|
|
12587
|
-
const cx = appState.scrollX + vw / 2 / zoom;
|
|
12588
|
-
const cy = appState.scrollY + vh / 2 / zoom;
|
|
12589
|
-
return { svg, width, height, x: cx - width / 2, y: cy - height / 2, seq };
|
|
12590
|
-
}
|
|
12591
|
-
function didStateChange(seen, jsonState) {
|
|
12592
|
-
if (seen.last === jsonState) return false;
|
|
12593
|
-
seen.last = jsonState;
|
|
12594
|
-
return true;
|
|
12595
|
-
}
|
|
12596
|
-
var init_draft = __esm({
|
|
12597
|
-
"src/stamps/geometry-2d/draft.ts"() {
|
|
12598
|
-
}
|
|
12599
|
-
});
|
|
12600
|
-
function useGeometryDraftEmit({
|
|
12601
|
-
store,
|
|
12602
|
-
handleRef,
|
|
12603
|
-
api,
|
|
12604
|
-
showAxis,
|
|
12605
|
-
showGrid,
|
|
12606
|
-
onGeometryDraft,
|
|
12607
|
-
debounceMs = 350
|
|
12608
|
-
}) {
|
|
12609
|
-
const seqRef = React19.useRef(0);
|
|
12610
|
-
const seenRef = React19.useRef({ last: null });
|
|
12611
|
-
const timerRef = React19.useRef(null);
|
|
12612
|
-
const cbRef = React19.useRef(onGeometryDraft);
|
|
12613
|
-
cbRef.current = onGeometryDraft;
|
|
12614
|
-
React19.useEffect(() => {
|
|
12615
|
-
if (!cbRef.current) return;
|
|
12616
|
-
const emit = () => {
|
|
12617
|
-
const h = handleRef.current;
|
|
12618
|
-
if (!h) return;
|
|
12619
|
-
const state = h.getState();
|
|
12620
|
-
if (Object.keys(state.objects).length === 0) {
|
|
12621
|
-
if (seenRef.current.last !== null) {
|
|
12622
|
-
seenRef.current.last = null;
|
|
12623
|
-
cbRef.current?.(null);
|
|
12624
|
-
}
|
|
12625
|
-
return;
|
|
12626
|
-
}
|
|
12627
|
-
const bbox = h.getBbox();
|
|
12628
|
-
const jsonState = serializeBoard(state, { bbox, showAxis, showGrid });
|
|
12629
|
-
if (!didStateChange(seenRef.current, jsonState)) return;
|
|
12630
|
-
void (async () => {
|
|
12631
|
-
try {
|
|
12632
|
-
const svg = await renderGeometrySvgFromState(jsonState);
|
|
12633
|
-
const appState = api?.getAppState?.() ?? { scrollX: 0, scrollY: 0, width: 800, height: 600, zoom: { value: 1 } };
|
|
12634
|
-
seqRef.current += 1;
|
|
12635
|
-
cbRef.current?.(draftFromViewport(svg, appState, seqRef.current));
|
|
12636
|
-
} catch (err) {
|
|
12637
|
-
console.warn("[geometry] draft render failed:", err);
|
|
12638
|
-
}
|
|
12639
|
-
})();
|
|
12640
|
-
};
|
|
12641
|
-
const schedule = () => {
|
|
12642
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
12643
|
-
timerRef.current = setTimeout(emit, debounceMs);
|
|
12644
|
-
};
|
|
12645
|
-
const unsub = store.subscribe(schedule);
|
|
12646
|
-
return () => {
|
|
12647
|
-
unsub();
|
|
12648
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
12649
|
-
cbRef.current?.(null);
|
|
12650
|
-
};
|
|
12651
|
-
}, [store, handleRef, api, showAxis, showGrid, debounceMs]);
|
|
12652
|
-
}
|
|
12653
|
-
var init_useGeometryDraftEmit = __esm({
|
|
12654
|
-
"src/stamps/geometry-2d/editor/useGeometryDraftEmit.ts"() {
|
|
12655
|
-
init_serialize();
|
|
12656
|
-
init_render();
|
|
12657
|
-
init_draft();
|
|
12658
|
-
}
|
|
12659
|
-
});
|
|
12660
|
-
var GeometryEditorPanelInner, GeometryEditorPanel;
|
|
12661
|
-
var init_EditorPanel = __esm({
|
|
12662
|
-
"src/stamps/geometry-2d/editor/EditorPanel.tsx"() {
|
|
12663
|
-
"use client";
|
|
12664
|
-
init_MiniBoard();
|
|
12665
|
-
init_serialize();
|
|
12666
|
-
init_render();
|
|
12667
|
-
init_PropertiesPopover();
|
|
12668
|
-
init_MultiPropertiesPopover();
|
|
12669
|
-
init_TransformParamPopover();
|
|
12670
|
-
init_snapshot();
|
|
12671
|
-
init_icons();
|
|
12672
|
-
init_scene();
|
|
12673
|
-
init_constants();
|
|
12674
|
-
init_Toast2();
|
|
12675
|
-
init_AiFigurePrompt();
|
|
12676
|
-
init_useGeometryDraftEmit();
|
|
12677
|
-
GeometryEditorPanelInner = React19.forwardRef(
|
|
12678
|
-
function GeometryEditorPanelInner2({
|
|
12679
|
-
store,
|
|
12680
|
-
onInsert,
|
|
12681
|
-
onClose,
|
|
12682
|
-
withLeftPanel = false,
|
|
12683
|
-
selectedTool,
|
|
12684
|
-
showAxis,
|
|
12685
|
-
showGrid,
|
|
12686
|
-
onHistoryChange,
|
|
12687
|
-
isDark,
|
|
12688
|
-
isMobile = false,
|
|
12689
|
-
onOpenDrawer,
|
|
12690
|
-
onUndo,
|
|
12691
|
-
onRedo,
|
|
12692
|
-
canUndo,
|
|
12693
|
-
canRedo,
|
|
12694
|
-
onSelectionChange,
|
|
12695
|
-
generateGeometryFigure,
|
|
12696
|
-
api,
|
|
12697
|
-
onGeometryDraft
|
|
12698
|
-
}, ref) {
|
|
12699
|
-
const { showToast } = useToast();
|
|
12700
|
-
const handleRef = React19.useRef(null);
|
|
12701
|
-
const [ready, setReady] = React19.useState(false);
|
|
12702
|
-
const [hasContent, setHasContent] = React19.useState(false);
|
|
12703
|
-
const [propsPopover, setPropsPopover] = React19.useState(null);
|
|
12704
|
-
const [multiSelection, setMultiSelection] = React19.useState(null);
|
|
12705
|
-
const [transformPopover, setTransformPopover] = React19.useState(null);
|
|
12706
|
-
const onSelectionChangeRef = React19.useRef(onSelectionChange);
|
|
12707
|
-
React19.useEffect(() => {
|
|
12708
|
-
onSelectionChangeRef.current = onSelectionChange;
|
|
12709
|
-
}, [onSelectionChange]);
|
|
12710
|
-
const onGeometryDraftRef = React19.useRef(onGeometryDraft);
|
|
12711
|
-
React19.useEffect(() => {
|
|
12712
|
-
onGeometryDraftRef.current = onGeometryDraft;
|
|
12713
|
-
}, [onGeometryDraft]);
|
|
12714
|
-
useEditorState({ store, onHistoryChange });
|
|
12715
|
-
useGeometryDraftEmit({ store, handleRef, api, showAxis, showGrid, onGeometryDraft });
|
|
12716
|
-
const snap = () => store.getState();
|
|
12717
|
-
const currentSceneState = React19.useSyncExternalStore((cb) => store.subscribe(cb), snap, snap);
|
|
12718
|
-
React19.useEffect(() => {
|
|
12719
|
-
const sync = () => setHasContent(Object.keys(store.getState().objects).length > 0);
|
|
12720
|
-
sync();
|
|
12721
|
-
return store.subscribe(sync);
|
|
12722
|
-
}, [store]);
|
|
12723
|
-
const handleReady = React19.useCallback(() => {
|
|
12724
|
-
const h = handleRef.current;
|
|
12725
|
-
if (!h) return;
|
|
12726
|
-
setReady(true);
|
|
12727
|
-
h.onSelect((snap2) => {
|
|
12728
|
-
setPropsPopover(snap2);
|
|
12729
|
-
setMultiSelection(null);
|
|
12730
|
-
onSelectionChangeRef.current?.(snap2.id);
|
|
12731
|
-
});
|
|
12732
|
-
h.onTransformParam((info) => setTransformPopover(info));
|
|
12733
|
-
h.onSelectionState((snap2) => {
|
|
12734
|
-
if (!snap2 || snap2.ids.length === 0) {
|
|
12735
|
-
setPropsPopover(null);
|
|
12736
|
-
setMultiSelection(null);
|
|
12737
|
-
onSelectionChangeRef.current?.(void 0);
|
|
12738
|
-
return;
|
|
12739
|
-
}
|
|
12740
|
-
if (snap2.ids.length === 1) {
|
|
12741
|
-
const id = snap2.ids[0];
|
|
12742
|
-
const single = buildObjectSnapshot(store.getState(), id, snap2.anchor);
|
|
12743
|
-
if (single) {
|
|
12744
|
-
setPropsPopover(single);
|
|
12745
|
-
setMultiSelection(null);
|
|
12746
|
-
onSelectionChangeRef.current?.(id);
|
|
12747
|
-
}
|
|
12748
|
-
return;
|
|
12749
|
-
}
|
|
12750
|
-
setMultiSelection(snap2);
|
|
12751
|
-
setPropsPopover(null);
|
|
12752
|
-
onSelectionChangeRef.current?.(void 0);
|
|
12753
|
-
});
|
|
12754
|
-
}, [store]);
|
|
12755
|
-
const dismissPropsPopover = React19.useCallback(() => {
|
|
12756
|
-
setPropsPopover(null);
|
|
12757
|
-
onSelectionChangeRef.current?.(void 0);
|
|
12758
|
-
}, []);
|
|
12759
|
-
const dismissMultiPopover = React19.useCallback(() => {
|
|
12760
|
-
setMultiSelection(null);
|
|
12761
|
-
handleRef.current?.clearSelection();
|
|
12762
|
-
onSelectionChangeRef.current?.(void 0);
|
|
12763
|
-
}, []);
|
|
12764
|
-
const applyMultiColor = React19.useCallback((color) => {
|
|
12765
|
-
const ids = multiSelection?.ids ?? [];
|
|
12766
|
-
const h = handleRef.current;
|
|
12767
|
-
if (!h) return;
|
|
12768
|
-
for (const id of ids) {
|
|
12769
|
-
h.mutateObject(id, { attrs: { strokeColor: color, color } });
|
|
12770
|
-
}
|
|
12771
|
-
}, [multiSelection]);
|
|
12772
|
-
const applyMultiDelete = React19.useCallback(() => {
|
|
12773
|
-
const ids = multiSelection?.ids ?? [];
|
|
12774
|
-
const h = handleRef.current;
|
|
12775
|
-
if (!h) return;
|
|
12776
|
-
for (const id of ids) {
|
|
12777
|
-
h.mutateObject(id, { remove: true });
|
|
12778
|
-
}
|
|
12779
|
-
h.clearSelection();
|
|
12780
|
-
setMultiSelection(null);
|
|
12781
|
-
onSelectionChangeRef.current?.(void 0);
|
|
12782
|
-
}, [multiSelection]);
|
|
12783
|
-
const performInsert = React19.useCallback(() => {
|
|
12784
|
-
if (!handleRef.current) return false;
|
|
12785
|
-
const h = handleRef.current;
|
|
12786
|
-
const state = h.getState();
|
|
12787
|
-
if (Object.keys(state.objects).length === 0) return false;
|
|
12788
|
-
const bbox = h.getBbox();
|
|
12789
|
-
const jsonState = serializeBoard(state, { bbox, showAxis, showGrid });
|
|
12790
|
-
void (async () => {
|
|
12791
|
-
try {
|
|
12792
|
-
const svgString = await renderGeometrySvgFromState(jsonState);
|
|
12793
|
-
onInsert(jsonState, svgString);
|
|
12794
|
-
onGeometryDraftRef.current?.(null);
|
|
12795
|
-
} catch (err) {
|
|
12796
|
-
console.error("Geometry insert failed:", err);
|
|
12797
|
-
}
|
|
12798
|
-
})();
|
|
12799
|
-
return true;
|
|
12800
|
-
}, [onInsert, showAxis, showGrid]);
|
|
12801
|
-
const loadAiFigure = React19.useCallback((generated) => {
|
|
12802
|
-
handleRef.current?.clearSelection();
|
|
12803
|
-
setPropsPopover(null);
|
|
12804
|
-
setMultiSelection(null);
|
|
12805
|
-
setTransformPopover(null);
|
|
12806
|
-
onSelectionChangeRef.current?.(void 0);
|
|
12807
|
-
const current = store.getState();
|
|
12808
|
-
store.dispatch({
|
|
12809
|
-
type: "LOAD",
|
|
12810
|
-
payload: { state: { ...generated, meta: current.meta } }
|
|
12811
|
-
});
|
|
12812
|
-
}, [store]);
|
|
12813
|
-
React19.useImperativeHandle(ref, () => ({
|
|
12814
|
-
insert: performInsert,
|
|
12815
|
-
hasContent: () => Object.keys(handleRef.current?.getState().objects ?? {}).length > 0,
|
|
12816
|
-
selectObject: (id) => handleRef.current?.highlight(id)
|
|
12817
|
-
}), [performInsert]);
|
|
12818
|
-
const wrapperStyle = isMobile ? { position: "fixed", inset: 0, zIndex: 40 } : {
|
|
12819
|
-
position: "absolute",
|
|
12820
|
-
top: "50%",
|
|
12821
|
-
left: withLeftPanel ? "calc(50% + 120px)" : "50%",
|
|
12822
|
-
transform: "translate(-50%, -50%)",
|
|
12823
|
-
zIndex: 40
|
|
12824
|
-
};
|
|
12825
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
12826
|
-
"div",
|
|
12827
|
-
{
|
|
12828
|
-
role: "dialog",
|
|
12829
|
-
"aria-label": "D\u1EF1ng h\xECnh h\u1ECDc",
|
|
12830
|
-
"data-testid": "geometry-editor-panel",
|
|
12831
|
-
"data-stamp-area": "true",
|
|
12832
|
-
"data-mobile-editor": isMobile ? "true" : void 0,
|
|
12833
|
-
style: wrapperStyle,
|
|
12834
|
-
className: [
|
|
12835
|
-
isDark ? "theme--dark " : "",
|
|
12836
|
-
"relative flex flex-col overflow-hidden bg-white",
|
|
12837
|
-
isMobile ? "h-full w-full" : `${STAMP_PANEL_DESKTOP} rounded-lg border border-slate-300 shadow-2xl ring-1 ring-black/5`
|
|
12838
|
-
].join(" "),
|
|
12839
|
-
children: [
|
|
12840
|
-
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex items-center gap-2 border-b border-slate-200 bg-gradient-to-r from-emerald-600 to-teal-600 px-3 py-2 text-white", children: [
|
|
12841
|
-
isMobile && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12842
|
-
"button",
|
|
12843
|
-
{
|
|
12844
|
-
type: "button",
|
|
12845
|
-
onClick: onOpenDrawer,
|
|
12846
|
-
"aria-label": "M\u1EDF ng\u0103n c\xF4ng c\u1EE5",
|
|
12847
|
-
className: "-ml-1 inline-flex h-10 w-10 items-center justify-center rounded transition hover:bg-white/15",
|
|
12848
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
12849
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "6", x2: "20", y2: "6" }),
|
|
12850
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "12", x2: "20", y2: "12" }),
|
|
12851
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "18", x2: "20", y2: "18" })
|
|
12852
|
-
] })
|
|
12853
|
-
}
|
|
12854
|
-
),
|
|
12855
|
-
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "flex flex-1 items-center gap-2 text-sm font-semibold", children: [
|
|
12856
|
-
/* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
12857
|
-
/* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "3,18 12,3 21,18" }),
|
|
12858
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "3", r: "1.5", fill: "currentColor" }),
|
|
12859
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "3", cy: "18", r: "1.5", fill: "currentColor" }),
|
|
12860
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "21", cy: "18", r: "1.5", fill: "currentColor" })
|
|
12861
|
-
] }),
|
|
12862
|
-
"D\u1EF1ng h\xECnh h\u1ECDc"
|
|
12863
|
-
] }),
|
|
12864
|
-
isMobile && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
12865
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12866
|
-
"button",
|
|
12867
|
-
{
|
|
12868
|
-
type: "button",
|
|
12869
|
-
onClick: onUndo,
|
|
12870
|
-
disabled: !canUndo,
|
|
12871
|
-
"aria-label": "Ho\xE0n t\xE1c",
|
|
12872
|
-
title: "Ho\xE0n t\xE1c (Ctrl/Cmd+Z)",
|
|
12873
|
-
"data-testid": "undo-btn-mobile",
|
|
12874
|
-
className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15 disabled:opacity-40",
|
|
12875
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(UndoIcon3, {})
|
|
12876
|
-
}
|
|
12877
|
-
),
|
|
12878
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12879
|
-
"button",
|
|
12880
|
-
{
|
|
12881
|
-
type: "button",
|
|
12882
|
-
onClick: onRedo,
|
|
12883
|
-
disabled: !canRedo,
|
|
12884
|
-
"aria-label": "L\xE0m l\u1EA1i",
|
|
12885
|
-
title: "L\xE0m l\u1EA1i (Ctrl/Cmd+Shift+Z)",
|
|
12886
|
-
"data-testid": "redo-btn-mobile",
|
|
12887
|
-
className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15 disabled:opacity-40",
|
|
12888
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(RedoIcon3, {})
|
|
12889
|
-
}
|
|
12890
|
-
),
|
|
12891
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12892
|
-
"button",
|
|
12893
|
-
{
|
|
12894
|
-
type: "button",
|
|
12895
|
-
onClick: performInsert,
|
|
12896
|
-
disabled: !ready || !hasContent,
|
|
12897
|
-
title: !hasContent ? "V\u1EBD \xEDt nh\u1EA5t m\u1ED9t \u0111\u1ED1i t\u01B0\u1EE3ng tr\u01B0\u1EDBc khi ch\xE8n" : void 0,
|
|
12898
|
-
"data-testid": "geometry-insert-btn-mobile",
|
|
12899
|
-
className: "rounded bg-white/15 px-3 py-1.5 text-xs font-semibold transition hover:bg-white/25 disabled:opacity-50",
|
|
12900
|
-
children: "Ch\xE8n"
|
|
12901
|
-
}
|
|
12902
|
-
)
|
|
12903
|
-
] }),
|
|
12904
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, "aria-label": "\u0110\xF3ng", className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
12905
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
12906
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
|
|
12907
|
-
] }) })
|
|
12908
|
-
] }),
|
|
12909
|
-
generateGeometryFigure && /* @__PURE__ */ jsxRuntime.jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure, currentState: currentSceneState }),
|
|
12910
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
12911
|
-
MiniBoard2D,
|
|
12912
|
-
{
|
|
12913
|
-
ref: handleRef,
|
|
12914
|
-
store,
|
|
12915
|
-
selectedTool,
|
|
12916
|
-
showAxis,
|
|
12917
|
-
showGrid,
|
|
12918
|
-
onReady: handleReady,
|
|
12919
|
-
isDark,
|
|
12920
|
-
toast: showToast
|
|
12921
|
-
}
|
|
12922
|
-
) }) }),
|
|
12923
|
-
propsPopover && (propsPopover.kind === "point" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
12924
|
-
PropertiesPopover,
|
|
12925
|
-
{
|
|
12926
|
-
kind: "point",
|
|
12927
|
-
anchor: propsPopover.screenCoords,
|
|
12928
|
-
isDark,
|
|
12929
|
-
currentName: propsPopover.name,
|
|
12930
|
-
currentColor: propsPopover.color,
|
|
12931
|
-
currentDash: propsPopover.dash,
|
|
12932
|
-
currentWidth: propsPopover.width,
|
|
12933
|
-
currentFace: propsPopover.face,
|
|
12934
|
-
currentShowLabel: propsPopover.showLabel,
|
|
12935
|
-
getAllNames: () => handleRef.current?.getAllPointNames() ?? [],
|
|
12936
|
-
onClose: dismissPropsPopover,
|
|
12937
|
-
onMutate: (patch) => {
|
|
12938
|
-
handleRef.current?.mutateObject(propsPopover.id, patch);
|
|
12939
|
-
if (patch.remove) dismissPropsPopover();
|
|
12940
|
-
if (typeof patch.valueLabel === "boolean" || patch.attrs) {
|
|
12941
|
-
setPropsPopover((cur) => cur ? { ...cur, showValue: patch.valueLabel ?? cur.showValue } : cur);
|
|
12942
|
-
}
|
|
12943
|
-
}
|
|
12944
|
-
}
|
|
12945
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
12946
|
-
PropertiesPopover,
|
|
12947
|
-
{
|
|
12948
|
-
kind: propsPopover.kind,
|
|
12949
|
-
anchor: propsPopover.screenCoords,
|
|
12950
|
-
isDark,
|
|
12951
|
-
currentName: propsPopover.name,
|
|
12952
|
-
currentColor: propsPopover.color,
|
|
12953
|
-
currentDash: propsPopover.dash,
|
|
12954
|
-
currentWidth: propsPopover.width,
|
|
12955
|
-
currentShowLabel: propsPopover.showLabel,
|
|
12956
|
-
currentShowValue: propsPopover.showValue,
|
|
12957
|
-
getAllNames: () => handleRef.current?.getAllPointNames() ?? [],
|
|
12958
|
-
onClose: dismissPropsPopover,
|
|
12959
|
-
onMutate: (patch) => {
|
|
12960
|
-
handleRef.current?.mutateObject(propsPopover.id, patch);
|
|
12961
|
-
if (patch.remove) dismissPropsPopover();
|
|
12962
|
-
if (typeof patch.valueLabel === "boolean") {
|
|
12963
|
-
setPropsPopover((cur) => cur ? { ...cur, showValue: patch.valueLabel ?? cur.showValue } : cur);
|
|
12964
|
-
}
|
|
12965
|
-
if (patch.attrs && "withLabel" in patch.attrs) {
|
|
12966
|
-
setPropsPopover((cur) => cur ? { ...cur, showLabel: !!patch.attrs?.withLabel } : cur);
|
|
12967
|
-
}
|
|
12968
|
-
}
|
|
12969
|
-
}
|
|
12970
|
-
)),
|
|
12971
|
-
multiSelection && multiSelection.ids.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12972
|
-
MultiPropertiesPopover,
|
|
12973
|
-
{
|
|
12974
|
-
anchor: multiSelection.anchor,
|
|
12975
|
-
count: multiSelection.ids.length,
|
|
12976
|
-
isDark,
|
|
12977
|
-
onColor: applyMultiColor,
|
|
12978
|
-
onDelete: applyMultiDelete,
|
|
12979
|
-
onClose: dismissMultiPopover
|
|
12980
|
-
}
|
|
12981
|
-
),
|
|
12982
|
-
transformPopover && (transformPopover.tool === "rotate" || transformPopover.tool === "dilate" || transformPopover.tool === "regularPolygon" || transformPopover.tool === "circleCR") && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12983
|
-
TransformParamPopover,
|
|
12984
|
-
{
|
|
12985
|
-
kind: transformPopover.tool,
|
|
12986
|
-
anchor: transformPopover.anchor,
|
|
12987
|
-
defaultValue: transformPopover.tool === "rotate" ? 90 : transformPopover.tool === "dilate" ? 2 : transformPopover.tool === "circleCR" ? 3 : 6,
|
|
12988
|
-
isDark,
|
|
12989
|
-
onConfirm: (v) => {
|
|
12990
|
-
handleRef.current?.confirmTransformParam(v);
|
|
12991
|
-
setTransformPopover(null);
|
|
12992
|
-
},
|
|
12993
|
-
onCancel: () => {
|
|
12994
|
-
handleRef.current?.cancelTransformParam();
|
|
12995
|
-
setTransformPopover(null);
|
|
12996
|
-
}
|
|
12997
|
-
}
|
|
12998
|
-
),
|
|
12999
|
-
!isMobile && /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "flex items-center justify-between border-t border-slate-200 bg-slate-50 px-3 py-2", children: [
|
|
13000
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-slate-500", children: "Ch\u1ECDn c\xF4ng c\u1EE5 b\xEAn tr\xE1i, click tr\xEAn b\u1EA3ng \u0111\u1EC3 d\u1EF1ng h\xECnh." }),
|
|
13001
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
13002
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13003
|
-
"button",
|
|
13004
|
-
{
|
|
13005
|
-
onClick: onClose,
|
|
13006
|
-
className: "rounded border border-slate-300 bg-white px-3 py-1 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
|
|
13007
|
-
children: "Hu\u1EF7"
|
|
13008
|
-
}
|
|
13009
|
-
),
|
|
13010
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13011
|
-
"button",
|
|
13012
|
-
{
|
|
13013
|
-
onClick: performInsert,
|
|
13014
|
-
disabled: !ready || !hasContent,
|
|
13015
|
-
title: !hasContent ? "V\u1EBD \xEDt nh\u1EA5t m\u1ED9t \u0111\u1ED1i t\u01B0\u1EE3ng tr\u01B0\u1EDBc khi ch\xE8n" : void 0,
|
|
13016
|
-
"data-testid": "geometry-insert-btn",
|
|
13017
|
-
className: "rounded bg-emerald-600 px-3 py-1 text-xs font-medium text-white transition hover:bg-emerald-700 disabled:opacity-50",
|
|
13018
|
-
children: "Ch\xE8n"
|
|
13019
|
-
}
|
|
13020
|
-
)
|
|
13021
|
-
] })
|
|
13022
|
-
] }),
|
|
13023
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToastHost, {})
|
|
13024
|
-
]
|
|
13025
|
-
}
|
|
13026
|
-
);
|
|
13027
|
-
}
|
|
13028
|
-
);
|
|
13029
|
-
GeometryEditorPanel = React19.forwardRef(
|
|
13030
|
-
function GeometryEditorPanel2(props, ref) {
|
|
13031
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ToastProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(GeometryEditorPanelInner, { ...props, ref }) });
|
|
13032
|
-
}
|
|
13033
|
-
);
|
|
10364
|
+
return {
|
|
10365
|
+
kind: "excircle",
|
|
10366
|
+
p1: raw.vertices[0],
|
|
10367
|
+
p2: raw.vertices[1],
|
|
10368
|
+
p3: raw.vertices[2],
|
|
10369
|
+
opposite: raw.opposite
|
|
10370
|
+
};
|
|
10371
|
+
}
|
|
10372
|
+
if (raw.kind === "circleDiameter" && raw.p1 && raw.p2) {
|
|
10373
|
+
return {
|
|
10374
|
+
kind: "diameter",
|
|
10375
|
+
p1: raw.p1,
|
|
10376
|
+
p2: raw.p2
|
|
10377
|
+
};
|
|
10378
|
+
}
|
|
10379
|
+
return void 0;
|
|
10380
|
+
})();
|
|
10381
|
+
if (!c) {
|
|
10382
|
+
if (typeof a.radius === "number") {
|
|
10383
|
+
if (!a.center) return fail("unsupported-construction", "missing center");
|
|
10384
|
+
const refs2 = resolveRefs([a.center], state);
|
|
10385
|
+
if (!refs2) return fail("unresolved-ref", `${a.center}`);
|
|
10386
|
+
return {
|
|
10387
|
+
ok: true,
|
|
10388
|
+
entity: { name: obj.label, kind: "circleCR", center: refs2[0], radius: a.radius }
|
|
10389
|
+
};
|
|
10390
|
+
}
|
|
10391
|
+
if (!a.center || !a.surfacePoint) {
|
|
10392
|
+
return fail("unsupported-construction", "missing center/surfacePoint");
|
|
10393
|
+
}
|
|
10394
|
+
const refs = resolveRefs([a.center, a.surfacePoint], state);
|
|
10395
|
+
if (!refs) return fail("unresolved-ref", `${a.center},${a.surfacePoint}`);
|
|
10396
|
+
return {
|
|
10397
|
+
ok: true,
|
|
10398
|
+
entity: { name: obj.label, kind: "circleCP", center: refs[0], surfacePoint: refs[1] }
|
|
10399
|
+
};
|
|
13034
10400
|
}
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
const { groupOrder, tools, onSelect, enabled } = args;
|
|
13042
|
-
const [chordGroup, setChordGroup] = React19.useState(null);
|
|
13043
|
-
const groupOrderRef = React19.useRef(groupOrder);
|
|
13044
|
-
const toolsRef = React19.useRef(tools);
|
|
13045
|
-
const onSelectRef = React19.useRef(onSelect);
|
|
13046
|
-
const chordGroupRef = React19.useRef(null);
|
|
13047
|
-
groupOrderRef.current = groupOrder;
|
|
13048
|
-
toolsRef.current = tools;
|
|
13049
|
-
onSelectRef.current = onSelect;
|
|
13050
|
-
const cancel = React19.useCallback(() => {
|
|
13051
|
-
chordGroupRef.current = null;
|
|
13052
|
-
setChordGroup(null);
|
|
13053
|
-
}, []);
|
|
13054
|
-
React19.useEffect(() => {
|
|
13055
|
-
if (!enabled) return;
|
|
13056
|
-
const setChord = (next) => {
|
|
13057
|
-
chordGroupRef.current = next;
|
|
13058
|
-
setChordGroup(next);
|
|
10401
|
+
if (c.kind === "circumscribed") {
|
|
10402
|
+
const refs = resolveRefs([c.p1, c.p2, c.p3], state);
|
|
10403
|
+
if (!refs) return fail("unresolved-ref", `${c.p1},${c.p2},${c.p3}`);
|
|
10404
|
+
return {
|
|
10405
|
+
ok: true,
|
|
10406
|
+
entity: { name: obj.label, kind: "circle3", p1: refs[0], p2: refs[1], p3: refs[2] }
|
|
13059
10407
|
};
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
e.preventDefault();
|
|
13068
|
-
e.stopPropagation();
|
|
13069
|
-
setChord(null);
|
|
13070
|
-
}
|
|
13071
|
-
return;
|
|
13072
|
-
}
|
|
13073
|
-
if (lower.length === 1 && lower >= "a" && lower <= "z") {
|
|
13074
|
-
const idx = lower.charCodeAt(0) - A_CODE2;
|
|
13075
|
-
if (idx < groupOrderRef.current.length) {
|
|
13076
|
-
e.preventDefault();
|
|
13077
|
-
e.stopPropagation();
|
|
13078
|
-
setChord(groupOrderRef.current[idx]);
|
|
13079
|
-
}
|
|
13080
|
-
return;
|
|
13081
|
-
}
|
|
13082
|
-
if (key >= "1" && key <= "9") {
|
|
13083
|
-
const active = chordGroupRef.current;
|
|
13084
|
-
if (active === null) return;
|
|
13085
|
-
const n = key.charCodeAt(0) - "1".charCodeAt(0);
|
|
13086
|
-
const toolsInGroup = toolsRef.current.filter(
|
|
13087
|
-
(t) => t.group === active
|
|
13088
|
-
);
|
|
13089
|
-
e.preventDefault();
|
|
13090
|
-
e.stopPropagation();
|
|
13091
|
-
if (n < toolsInGroup.length) {
|
|
13092
|
-
onSelectRef.current(toolsInGroup[n].key);
|
|
13093
|
-
}
|
|
13094
|
-
setChord(null);
|
|
13095
|
-
return;
|
|
13096
|
-
}
|
|
10408
|
+
}
|
|
10409
|
+
if (c.kind === "incircle") {
|
|
10410
|
+
const refs = resolveRefs([c.p1, c.p2, c.p3], state);
|
|
10411
|
+
if (!refs) return fail("unresolved-ref", `${c.p1},${c.p2},${c.p3}`);
|
|
10412
|
+
return {
|
|
10413
|
+
ok: true,
|
|
10414
|
+
entity: { name: obj.label, kind: "incircle", vertices: [refs[0], refs[1], refs[2]] }
|
|
13097
10415
|
};
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
|
|
10416
|
+
}
|
|
10417
|
+
if (c.kind === "excircle") {
|
|
10418
|
+
const refs = resolveRefs([c.p1, c.p2, c.p3, c.opposite], state);
|
|
10419
|
+
if (!refs) return fail("unresolved-ref", `${c.p1},${c.p2},${c.p3},${c.opposite}`);
|
|
10420
|
+
return {
|
|
10421
|
+
ok: true,
|
|
10422
|
+
entity: { name: obj.label, kind: "excircle", vertices: [refs[0], refs[1], refs[2]], opposite: refs[3] }
|
|
13101
10423
|
};
|
|
13102
|
-
}, [enabled]);
|
|
13103
|
-
return { chordGroup, cancel };
|
|
13104
|
-
}
|
|
13105
|
-
var A_CODE2;
|
|
13106
|
-
var init_useChordShortcut = __esm({
|
|
13107
|
-
"src/stamps/shared/useChordShortcut.ts"() {
|
|
13108
|
-
A_CODE2 = "a".charCodeAt(0);
|
|
13109
10424
|
}
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
return {
|
|
13118
|
-
type: "image",
|
|
13119
|
-
id: "stamp_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8),
|
|
13120
|
-
x: cx,
|
|
13121
|
-
y: cy,
|
|
13122
|
-
width,
|
|
13123
|
-
height,
|
|
13124
|
-
fileId,
|
|
13125
|
-
customData,
|
|
13126
|
-
angle: 0,
|
|
13127
|
-
strokeColor: "transparent",
|
|
13128
|
-
backgroundColor: "transparent",
|
|
13129
|
-
fillStyle: "solid",
|
|
13130
|
-
strokeWidth: 1,
|
|
13131
|
-
strokeStyle: "solid",
|
|
13132
|
-
roughness: 0,
|
|
13133
|
-
opacity: 100,
|
|
13134
|
-
groupIds: [],
|
|
13135
|
-
roundness: null,
|
|
13136
|
-
seed: Math.floor(Math.random() * 1e9),
|
|
13137
|
-
versionNonce: 0,
|
|
13138
|
-
version: 1,
|
|
13139
|
-
isDeleted: false,
|
|
13140
|
-
boundElements: null,
|
|
13141
|
-
updated: Date.now(),
|
|
13142
|
-
link: null,
|
|
13143
|
-
locked: false,
|
|
13144
|
-
status: "saved",
|
|
13145
|
-
scale: [1, 1]
|
|
13146
|
-
};
|
|
13147
|
-
}
|
|
13148
|
-
async function insertStampImage(api, opts) {
|
|
13149
|
-
const { dataURL, fileId, width, height, mimeType } = await createStampFile(opts.svgString);
|
|
13150
|
-
api.addFiles([{ id: fileId, dataURL, mimeType, created: Date.now() }]);
|
|
13151
|
-
const customData = opts.makeCustomData();
|
|
13152
|
-
const elements = api.getSceneElements();
|
|
13153
|
-
const editingId = opts.editingElementId ?? null;
|
|
13154
|
-
if (editingId) {
|
|
13155
|
-
const updated = elements.map(
|
|
13156
|
-
(e) => e.id === editingId ? { ...e, fileId, customData, width, height } : e
|
|
13157
|
-
);
|
|
13158
|
-
api.updateScene({ elements: updated, appState: clearAppStateAfterInsert() });
|
|
13159
|
-
return { fileId, width, height, elementId: editingId };
|
|
10425
|
+
if (c.kind === "diameter") {
|
|
10426
|
+
const refs = resolveRefs([c.p1, c.p2], state);
|
|
10427
|
+
if (!refs) return fail("unresolved-ref", `${c.p1},${c.p2}`);
|
|
10428
|
+
return {
|
|
10429
|
+
ok: true,
|
|
10430
|
+
entity: { name: obj.label, kind: "circleDiameter", p1: refs[0], p2: refs[1] }
|
|
10431
|
+
};
|
|
13160
10432
|
}
|
|
13161
|
-
|
|
13162
|
-
api,
|
|
13163
|
-
fileId,
|
|
13164
|
-
width,
|
|
13165
|
-
height,
|
|
13166
|
-
customData,
|
|
13167
|
-
opts.position?.x,
|
|
13168
|
-
opts.position?.y
|
|
13169
|
-
);
|
|
13170
|
-
api.updateScene({
|
|
13171
|
-
elements: [...elements, newElement],
|
|
13172
|
-
appState: clearAppStateAfterInsert()
|
|
13173
|
-
});
|
|
13174
|
-
return { fileId, width, height, elementId: newElement.id };
|
|
10433
|
+
return fail("unsupported-construction");
|
|
13175
10434
|
}
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13179
|
-
init_svgToStampFile();
|
|
13180
|
-
clearAppStateAfterInsert = () => ({
|
|
13181
|
-
selectedElementIds: {},
|
|
13182
|
-
croppingElementId: null
|
|
13183
|
-
});
|
|
10435
|
+
function serializeObject(obj, state) {
|
|
10436
|
+
if (!isValidName(obj.label)) {
|
|
10437
|
+
return fail("invalid-label", obj.label);
|
|
13184
10438
|
}
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
10439
|
+
switch (obj.kind) {
|
|
10440
|
+
case "point":
|
|
10441
|
+
return serializePoint(obj, state);
|
|
10442
|
+
case "segment":
|
|
10443
|
+
return serializeSegment(obj, state);
|
|
10444
|
+
case "ray":
|
|
10445
|
+
return serializeRay(obj, state);
|
|
10446
|
+
case "line":
|
|
10447
|
+
return serializeLine(obj, state);
|
|
10448
|
+
case "polygon":
|
|
10449
|
+
return serializePolygon(obj, state);
|
|
10450
|
+
case "circle":
|
|
10451
|
+
return serializeCircle(obj, state);
|
|
10452
|
+
case "intersection":
|
|
10453
|
+
return serializeIntersection(obj, state);
|
|
10454
|
+
default:
|
|
10455
|
+
return fail("unsupported-kind", obj.kind);
|
|
13191
10456
|
}
|
|
13192
|
-
return ref.current;
|
|
13193
10457
|
}
|
|
13194
|
-
var
|
|
13195
|
-
|
|
13196
|
-
|
|
10458
|
+
var NAME_REGEX;
|
|
10459
|
+
var init_serialize2 = __esm({
|
|
10460
|
+
"src/stamps/geometry-2d/dsl/serialize.ts"() {
|
|
10461
|
+
NAME_REGEX = /^[A-Za-z][A-Za-z0-9_'₀-₉]{0,11}$/;
|
|
13197
10462
|
}
|
|
13198
10463
|
});
|
|
13199
10464
|
|
|
@@ -13549,7 +10814,7 @@ function isLatexCustomData(data) {
|
|
|
13549
10814
|
const d = data;
|
|
13550
10815
|
return d.kind === "latex" && d.version === 1 && typeof d.src === "string";
|
|
13551
10816
|
}
|
|
13552
|
-
var
|
|
10817
|
+
var init_types5 = __esm({
|
|
13553
10818
|
"src/stamps/latex/types.ts"() {
|
|
13554
10819
|
}
|
|
13555
10820
|
});
|
|
@@ -13958,7 +11223,7 @@ var init_host2 = __esm({
|
|
|
13958
11223
|
init_EditorPopover();
|
|
13959
11224
|
init_insertImage();
|
|
13960
11225
|
init_useIsMobile();
|
|
13961
|
-
|
|
11226
|
+
init_types5();
|
|
13962
11227
|
LatexStampHost = React19.forwardRef(
|
|
13963
11228
|
function LatexStampHost2({ api, editingElement, onClose }, ref) {
|
|
13964
11229
|
const editorRef = React19.useRef(null);
|
|
@@ -14062,7 +11327,7 @@ var init_serialize3 = __esm({
|
|
|
14062
11327
|
|
|
14063
11328
|
// src/core/scene/render/types.ts
|
|
14064
11329
|
var DEFAULT_THEME_3D;
|
|
14065
|
-
var
|
|
11330
|
+
var init_types6 = __esm({
|
|
14066
11331
|
"src/core/scene/render/types.ts"() {
|
|
14067
11332
|
DEFAULT_THEME_3D = {
|
|
14068
11333
|
point: { size: 4, color: "#1e40af" },
|
|
@@ -14077,7 +11342,7 @@ var JxgRenderer3D;
|
|
|
14077
11342
|
var init_JxgRenderer3D = __esm({
|
|
14078
11343
|
"src/core/scene/render/JxgRenderer3D.ts"() {
|
|
14079
11344
|
init_registry();
|
|
14080
|
-
|
|
11345
|
+
init_types6();
|
|
14081
11346
|
JxgRenderer3D = class {
|
|
14082
11347
|
constructor(store, view, options = {}) {
|
|
14083
11348
|
this.elements = /* @__PURE__ */ new Map();
|
|
@@ -14571,7 +11836,7 @@ function buildVector(args, store) {
|
|
|
14571
11836
|
if (!from || !to || from === to) return null;
|
|
14572
11837
|
return addDerived(store, "vector3d", "v", { from, to });
|
|
14573
11838
|
}
|
|
14574
|
-
var
|
|
11839
|
+
var init_segment2 = __esm({
|
|
14575
11840
|
"src/stamps/geometry-3d/editor/tools/handlers/segment.ts"() {
|
|
14576
11841
|
init_scene();
|
|
14577
11842
|
init_ensurePoint();
|
|
@@ -14598,7 +11863,7 @@ function buildPolygon(args, store) {
|
|
|
14598
11863
|
store.dispatch({ type: "ADD", payload: { obj } });
|
|
14599
11864
|
return id;
|
|
14600
11865
|
}
|
|
14601
|
-
var
|
|
11866
|
+
var init_polygon3 = __esm({
|
|
14602
11867
|
"src/stamps/geometry-3d/editor/tools/handlers/polygon.ts"() {
|
|
14603
11868
|
init_scene();
|
|
14604
11869
|
init_ensurePoint();
|
|
@@ -14711,8 +11976,8 @@ function constraintToWorld(c, state) {
|
|
|
14711
11976
|
const radius = norm(sub(surface, center));
|
|
14712
11977
|
const x = center[0] + radius * Math.sin(c.phi) * Math.cos(c.theta);
|
|
14713
11978
|
const y = center[1] + radius * Math.sin(c.phi) * Math.sin(c.theta);
|
|
14714
|
-
const
|
|
14715
|
-
return [x, y,
|
|
11979
|
+
const z = center[2] + radius * Math.cos(c.phi);
|
|
11980
|
+
return [x, y, z];
|
|
14716
11981
|
}
|
|
14717
11982
|
}
|
|
14718
11983
|
}
|
|
@@ -15115,8 +12380,8 @@ var stubBuild, ALL_SURFACES, OBJECT_ONLY, NO_SURFACE, TOOLS2;
|
|
|
15115
12380
|
var init_spec = __esm({
|
|
15116
12381
|
"src/stamps/geometry-3d/editor/tools/spec.ts"() {
|
|
15117
12382
|
init_point3();
|
|
15118
|
-
|
|
15119
|
-
|
|
12383
|
+
init_segment2();
|
|
12384
|
+
init_polygon3();
|
|
15120
12385
|
init_plane();
|
|
15121
12386
|
init_pyramid();
|
|
15122
12387
|
init_prism();
|
|
@@ -17117,7 +14382,7 @@ function isGraph2DCustomData(data) {
|
|
|
17117
14382
|
const d = data;
|
|
17118
14383
|
return d.kind === "graph2d" && d.version === 2 && typeof d.jsonState === "string";
|
|
17119
14384
|
}
|
|
17120
|
-
var
|
|
14385
|
+
var init_types7 = __esm({
|
|
17121
14386
|
"src/stamps/graph-2d/types.ts"() {
|
|
17122
14387
|
}
|
|
17123
14388
|
});
|
|
@@ -18159,7 +15424,7 @@ var init_host4 = __esm({
|
|
|
18159
15424
|
init_insertImage();
|
|
18160
15425
|
init_useIsMobile();
|
|
18161
15426
|
init_useStampStore();
|
|
18162
|
-
|
|
15427
|
+
init_types7();
|
|
18163
15428
|
init_serialize4();
|
|
18164
15429
|
init_tools2();
|
|
18165
15430
|
init_FunctionRow();
|
|
@@ -18408,7 +15673,7 @@ var geometryStamp = {
|
|
|
18408
15673
|
|
|
18409
15674
|
// src/stamps/latex/index.tsx
|
|
18410
15675
|
init_render2();
|
|
18411
|
-
|
|
15676
|
+
init_types5();
|
|
18412
15677
|
var LatexStampHost3 = React19.lazy(
|
|
18413
15678
|
() => Promise.resolve().then(() => (init_host2(), host_exports2)).then((m) => ({ default: m.LatexStampHost }))
|
|
18414
15679
|
);
|
|
@@ -18498,7 +15763,7 @@ var geometry3dStamp = {
|
|
|
18498
15763
|
|
|
18499
15764
|
// src/stamps/graph-2d/index.tsx
|
|
18500
15765
|
init_render4();
|
|
18501
|
-
|
|
15766
|
+
init_types7();
|
|
18502
15767
|
init_serialize4();
|
|
18503
15768
|
init_svgToStampFile();
|
|
18504
15769
|
var Graph2DStampHost3 = React19.lazy(
|
|
@@ -20679,9 +17944,6 @@ function Whiteboard({
|
|
|
20679
17944
|
] });
|
|
20680
17945
|
}
|
|
20681
17946
|
|
|
20682
|
-
// src/index.ts
|
|
20683
|
-
init_handleExtractProblem();
|
|
20684
|
-
|
|
20685
17947
|
exports.ALL_STAMPS = ALL_STAMPS;
|
|
20686
17948
|
exports.DEFAULT_STAMPS = DEFAULT_STAMPS;
|
|
20687
17949
|
exports.EXPERIMENTAL_STAMPS = EXPERIMENTAL_STAMPS;
|
|
@@ -20695,7 +17957,6 @@ exports.findStampForCustomData = findStampForCustomData;
|
|
|
20695
17957
|
exports.geometry3dStamp = geometry3dStamp;
|
|
20696
17958
|
exports.geometryStamp = geometryStamp;
|
|
20697
17959
|
exports.graph2dStamp = graph2dStamp;
|
|
20698
|
-
exports.handleExtractProblem = handleExtractProblem;
|
|
20699
17960
|
exports.insertPdfPages = insertPdfPages;
|
|
20700
17961
|
exports.insertRasterizedPagesIntoScene = insertRasterizedPagesIntoScene;
|
|
20701
17962
|
exports.isStampElement = isStampElement;
|