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