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