@xom11/whiteboard 0.29.0 → 0.31.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 +259 -33
- package/dist/ai.d.ts +259 -33
- package/dist/ai.js +5424 -470
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +4971 -351
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +5 -5
- package/dist/{chunk-V3YJ6JFL.mjs → chunk-44JY2AKC.mjs} +3 -3
- package/dist/{chunk-V3YJ6JFL.mjs.map → chunk-44JY2AKC.mjs.map} +1 -1
- package/dist/{chunk-GEC2D2EQ.mjs → chunk-BMYC2ILT.mjs} +4 -4
- package/dist/{chunk-GEC2D2EQ.mjs.map → chunk-BMYC2ILT.mjs.map} +1 -1
- package/dist/{chunk-PPKHCRRE.mjs → chunk-C76SOFXF.mjs} +3 -3
- package/dist/{chunk-PPKHCRRE.mjs.map → chunk-C76SOFXF.mjs.map} +1 -1
- package/dist/{chunk-IHUFOV7L.mjs → chunk-CH6SFONH.mjs} +15 -3
- package/dist/chunk-CH6SFONH.mjs.map +1 -0
- package/dist/{chunk-E6EDOPGT.mjs → chunk-DWIEVCGK.mjs} +254 -16
- package/dist/chunk-DWIEVCGK.mjs.map +1 -0
- package/dist/{chunk-SZDAS7LK.mjs → chunk-IE2GGHNF.mjs} +131 -81
- package/dist/chunk-IE2GGHNF.mjs.map +1 -0
- package/dist/{chunk-ZTQBUKLJ.mjs → chunk-JJ4FPCBE.mjs} +142 -22
- package/dist/chunk-JJ4FPCBE.mjs.map +1 -0
- package/dist/{chunk-QRUAEXLR.mjs → chunk-K5BS2H56.mjs} +5 -5
- package/dist/{chunk-QRUAEXLR.mjs.map → chunk-K5BS2H56.mjs.map} +1 -1
- package/dist/{chunk-BNBOIDO5.mjs → chunk-K7VJU7LQ.mjs} +3 -3
- package/dist/{chunk-BNBOIDO5.mjs.map → chunk-K7VJU7LQ.mjs.map} +1 -1
- package/dist/{chunk-H22OZYTW.mjs → chunk-KOXOC2FI.mjs} +48 -39
- package/dist/chunk-KOXOC2FI.mjs.map +1 -0
- package/dist/{chunk-CXHNVYMD.mjs → chunk-KWDBVLST.mjs} +5 -5
- package/dist/{chunk-CXHNVYMD.mjs.map → chunk-KWDBVLST.mjs.map} +1 -1
- package/dist/{chunk-OQIQNKPQ.mjs → chunk-LTLLQUMN.mjs} +4 -4
- package/dist/{chunk-OQIQNKPQ.mjs.map → chunk-LTLLQUMN.mjs.map} +1 -1
- package/dist/chunk-QK6OVDLC.mjs +103 -0
- package/dist/chunk-QK6OVDLC.mjs.map +1 -0
- package/dist/{chunk-QGNU34T7.mjs → chunk-QLQ4MJNO.mjs} +10 -4
- package/dist/chunk-QLQ4MJNO.mjs.map +1 -0
- package/dist/{chunk-BU5KLO3P.mjs → chunk-T3N4BSJV.mjs} +4 -4
- package/dist/{chunk-BU5KLO3P.mjs.map → chunk-T3N4BSJV.mjs.map} +1 -1
- package/dist/{chunk-5JM35CXV.mjs → chunk-TMRFSOM7.mjs} +4 -4
- package/dist/{chunk-5JM35CXV.mjs.map → chunk-TMRFSOM7.mjs.map} +1 -1
- package/dist/geometry-2d.d.mts +1 -1
- package/dist/geometry-2d.d.ts +1 -1
- package/dist/geometry-2d.js +841 -204
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +5 -5
- package/dist/geometry-3d.d.mts +1 -1
- package/dist/geometry-3d.d.ts +1 -1
- package/dist/geometry-3d.js +172 -22
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +4 -4
- package/dist/graph-2d.d.mts +1 -1
- package/dist/graph-2d.d.ts +1 -1
- package/dist/graph-2d.js +307 -100
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +7 -7
- package/dist/handleExtractProblem-BrDY9ifM.d.mts +58 -0
- package/dist/handleExtractProblem-BrDY9ifM.d.ts +58 -0
- package/dist/{host-HOSJHQ5H.mjs → host-4FIUNIDQ.mjs} +13 -12
- package/dist/host-4FIUNIDQ.mjs.map +1 -0
- package/dist/{host-2ISGVO7O.mjs → host-4ZB4XD4S.mjs} +9 -8
- package/dist/host-4ZB4XD4S.mjs.map +1 -0
- package/dist/{host-ZQCDAT6O.mjs → host-H2IGOKJU.mjs} +3 -3
- package/dist/{host-ZQCDAT6O.mjs.map → host-H2IGOKJU.mjs.map} +1 -1
- package/dist/{host-HKMZSCIT.mjs → host-KMWP7KBT.mjs} +286 -74
- package/dist/host-KMWP7KBT.mjs.map +1 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +849 -206
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +22 -21
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +1 -1
- package/dist/latex.d.ts +1 -1
- package/dist/latex.js +8 -2
- package/dist/latex.js.map +1 -1
- package/dist/latex.mjs +1 -1
- package/dist/render-NMS7OAV6.mjs +10 -0
- package/dist/{render-ZX2O2IK7.mjs.map → render-NMS7OAV6.mjs.map} +1 -1
- package/dist/serialize-PGHQZEPV.mjs +9 -0
- package/dist/{serialize-N4G6RFBB.mjs.map → serialize-PGHQZEPV.mjs.map} +1 -1
- package/dist/{types-C3FjpoUi.d.ts → types-tePd94vW.d.mts} +8 -0
- package/dist/{types-C3FjpoUi.d.mts → types-tePd94vW.d.ts} +8 -0
- package/package.json +2 -1
- package/dist/chunk-E6EDOPGT.mjs.map +0 -1
- package/dist/chunk-H22OZYTW.mjs.map +0 -1
- package/dist/chunk-IHUFOV7L.mjs.map +0 -1
- package/dist/chunk-QGNU34T7.mjs.map +0 -1
- package/dist/chunk-SZDAS7LK.mjs.map +0 -1
- package/dist/chunk-ZTQBUKLJ.mjs.map +0 -1
- package/dist/host-2ISGVO7O.mjs.map +0 -1
- package/dist/host-HKMZSCIT.mjs.map +0 -1
- package/dist/host-HOSJHQ5H.mjs.map +0 -1
- package/dist/render-ZX2O2IK7.mjs +0 -10
- package/dist/serialize-N4G6RFBB.mjs +0 -9
package/dist/index.js
CHANGED
|
@@ -942,8 +942,12 @@ function constraintRefs2D(c) {
|
|
|
942
942
|
return [c.line, c.circle, c.other];
|
|
943
943
|
case "tangencyPoint":
|
|
944
944
|
return [c.circle, c.onLine];
|
|
945
|
-
case "arcMidpoint":
|
|
946
|
-
|
|
945
|
+
case "arcMidpoint": {
|
|
946
|
+
const containment = c.notContaining ?? c.containing;
|
|
947
|
+
return containment ? [c.circle, c.a, c.b, containment] : [c.circle, c.a, c.b];
|
|
948
|
+
}
|
|
949
|
+
case "mixtilinearPoint":
|
|
950
|
+
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
947
951
|
case "pointAtDistance": {
|
|
948
952
|
const d = c.distance;
|
|
949
953
|
const extra = d.kind === "circleRadius" ? [d.circle] : d.kind === "segmentLength" ? [d.p1, d.p2] : [];
|
|
@@ -1230,7 +1234,7 @@ function dist(p, q) {
|
|
|
1230
1234
|
function sideOf(a, b, p) {
|
|
1231
1235
|
return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
|
|
1232
1236
|
}
|
|
1233
|
-
function arcMidpoint(center, radius, a, b,
|
|
1237
|
+
function arcMidpoint(center, radius, a, b, reference, sameSide = false) {
|
|
1234
1238
|
const mcx = (a[0] + b[0]) / 2;
|
|
1235
1239
|
const mcy = (a[1] + b[1]) / 2;
|
|
1236
1240
|
let ux = mcx - center[0];
|
|
@@ -1245,12 +1249,18 @@ function arcMidpoint(center, radius, a, b, notContaining) {
|
|
|
1245
1249
|
uy /= len;
|
|
1246
1250
|
const cand1 = [center[0] + radius * ux, center[1] + radius * uy];
|
|
1247
1251
|
const cand2 = [center[0] - radius * ux, center[1] - radius * uy];
|
|
1252
|
+
if (!reference) return cand1[1] >= cand2[1] ? cand1 : cand2;
|
|
1253
|
+
const notContaining = reference;
|
|
1248
1254
|
const sN = sideOf(a, b, notContaining);
|
|
1249
1255
|
if (Math.abs(sN) < 1e-9) {
|
|
1250
|
-
|
|
1256
|
+
const far = dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
|
|
1257
|
+
const near = far === cand1 ? cand2 : cand1;
|
|
1258
|
+
return sameSide ? near : far;
|
|
1251
1259
|
}
|
|
1252
1260
|
const s1 = sideOf(a, b, cand1);
|
|
1253
|
-
|
|
1261
|
+
const opp = s1 * sN < 0 ? cand1 : cand2;
|
|
1262
|
+
const same = opp === cand1 ? cand2 : cand1;
|
|
1263
|
+
return sameSide ? same : opp;
|
|
1254
1264
|
}
|
|
1255
1265
|
function excenter(vertices, oppositeIndex) {
|
|
1256
1266
|
const [A, B, C] = vertices;
|
|
@@ -1266,6 +1276,35 @@ function excenter(vertices, oppositeIndex) {
|
|
|
1266
1276
|
(w[0] * A[1] + w[1] * B[1] + w[2] * C[1]) / sum
|
|
1267
1277
|
];
|
|
1268
1278
|
}
|
|
1279
|
+
function circumcenterXY(a, b, c) {
|
|
1280
|
+
const ax = a[0], ay = a[1], bx = b[0], by = b[1], cx = c[0], cy = c[1];
|
|
1281
|
+
const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
|
|
1282
|
+
if (Math.abs(d) < 1e-12) return [(ax + bx + cx) / 3, (ay + by + cy) / 3];
|
|
1283
|
+
const ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d;
|
|
1284
|
+
const uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d;
|
|
1285
|
+
return [ux, uy];
|
|
1286
|
+
}
|
|
1287
|
+
function mixtilinearPoint(a, b, c, which) {
|
|
1288
|
+
const O = circumcenterXY(a, b, c);
|
|
1289
|
+
const R = Math.hypot(O[0] - a[0], O[1] - a[1]);
|
|
1290
|
+
const ux1 = (b[0] - a[0]) / (Math.hypot(b[0] - a[0], b[1] - a[1]) || 1);
|
|
1291
|
+
const uy1 = (b[1] - a[1]) / (Math.hypot(b[0] - a[0], b[1] - a[1]) || 1);
|
|
1292
|
+
const ux2 = (c[0] - a[0]) / (Math.hypot(c[0] - a[0], c[1] - a[1]) || 1);
|
|
1293
|
+
const uy2 = (c[1] - a[1]) / (Math.hypot(c[0] - a[0], c[1] - a[1]) || 1);
|
|
1294
|
+
let bx = ux1 + ux2, by = uy1 + uy2;
|
|
1295
|
+
const bl = Math.hypot(bx, by) || 1;
|
|
1296
|
+
bx /= bl;
|
|
1297
|
+
by /= bl;
|
|
1298
|
+
const cosA = ux1 * ux2 + uy1 * uy2;
|
|
1299
|
+
const sinHalf = Math.sqrt(Math.max(0, (1 - cosA) / 2));
|
|
1300
|
+
const cos2Half = Math.max(1e-9, (1 + cosA) / 2);
|
|
1301
|
+
const dotAO = bx * (O[0] - a[0]) + by * (O[1] - a[1]);
|
|
1302
|
+
const d = 2 * (dotAO - R * sinHalf) / cos2Half;
|
|
1303
|
+
const K = [a[0] + d * bx, a[1] + d * by];
|
|
1304
|
+
if (which === "center") return K;
|
|
1305
|
+
const kl = Math.hypot(K[0] - O[0], K[1] - O[1]) || 1;
|
|
1306
|
+
return [O[0] + R * (K[0] - O[0]) / kl, O[1] + R * (K[1] - O[1]) / kl];
|
|
1307
|
+
}
|
|
1269
1308
|
function pointAtDistanceCoord(from, through, d) {
|
|
1270
1309
|
const dx = through[0] - from[0];
|
|
1271
1310
|
const dy = through[1] - from[1];
|
|
@@ -1293,29 +1332,38 @@ var init_arcMidpoint = __esm({
|
|
|
1293
1332
|
arcMidpointConstraint = definePointConstraint({
|
|
1294
1333
|
kind: "arcMidpoint",
|
|
1295
1334
|
validate: (c) => {
|
|
1296
|
-
if (!c.circle || !c.a || !c.b
|
|
1297
|
-
throw new Error("point.arcMidpoint: circle, a, b
|
|
1335
|
+
if (!c.circle || !c.a || !c.b) {
|
|
1336
|
+
throw new Error("point.arcMidpoint: circle, a, b b\u1EAFt bu\u1ED9c");
|
|
1337
|
+
}
|
|
1338
|
+
if (c.notContaining && c.containing) {
|
|
1339
|
+
throw new Error("point.arcMidpoint: kh\xF4ng th\u1EC3 v\u1EEBa notContaining v\u1EEBa containing");
|
|
1298
1340
|
}
|
|
1299
1341
|
},
|
|
1300
1342
|
describe: (obj, state, c) => {
|
|
1301
1343
|
const al = state?.objects[c.a]?.label ?? c.a;
|
|
1302
1344
|
const bl = state?.objects[c.b]?.label ?? c.b;
|
|
1303
|
-
const
|
|
1304
|
-
return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl}
|
|
1345
|
+
const ref = c.containing ?? c.notContaining;
|
|
1346
|
+
if (!ref) return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl}`;
|
|
1347
|
+
const rl = state?.objects[ref]?.label ?? ref;
|
|
1348
|
+
const rel = c.containing ? "ch\u1EE9a" : "kh\xF4ng ch\u1EE9a";
|
|
1349
|
+
return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (${rel} ${rl})`;
|
|
1305
1350
|
},
|
|
1306
1351
|
render: (obj, ctx, c, opts) => {
|
|
1307
1352
|
const board = ctx.jxg;
|
|
1308
1353
|
const circle = ctx.resolveRef(c.circle);
|
|
1309
1354
|
const A = ctx.resolveRef(c.a);
|
|
1310
1355
|
const B = ctx.resolveRef(c.b);
|
|
1311
|
-
const
|
|
1356
|
+
const refName = c.containing ?? c.notContaining;
|
|
1357
|
+
const ref = refName ? ctx.resolveRef(refName) : void 0;
|
|
1358
|
+
const sameSide = !!c.containing;
|
|
1312
1359
|
const O = circle?.center ?? circle?.midpoint ?? circle;
|
|
1313
1360
|
const am = () => arcMidpoint(
|
|
1314
1361
|
[O.X(), O.Y()],
|
|
1315
1362
|
circle.Radius(),
|
|
1316
1363
|
[A.X(), A.Y()],
|
|
1317
1364
|
[B.X(), B.Y()],
|
|
1318
|
-
[
|
|
1365
|
+
ref ? [ref.X(), ref.Y()] : void 0,
|
|
1366
|
+
sameSide
|
|
1319
1367
|
);
|
|
1320
1368
|
return board.create("point", [() => am()[0], () => am()[1]], opts);
|
|
1321
1369
|
}
|
|
@@ -1365,6 +1413,59 @@ var init_excenter = __esm({
|
|
|
1365
1413
|
}
|
|
1366
1414
|
});
|
|
1367
1415
|
|
|
1416
|
+
// src/core/scene/kinds/point-constraints/mixtilinearPoint.ts
|
|
1417
|
+
var mixtilinearPointConstraint;
|
|
1418
|
+
var init_mixtilinearPoint = __esm({
|
|
1419
|
+
"src/core/scene/kinds/point-constraints/mixtilinearPoint.ts"() {
|
|
1420
|
+
init_pointConstructions();
|
|
1421
|
+
init_types2();
|
|
1422
|
+
mixtilinearPointConstraint = definePointConstraint({
|
|
1423
|
+
kind: "mixtilinearPoint",
|
|
1424
|
+
validate: (c) => {
|
|
1425
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3 || !c.vertices.every(Boolean)) {
|
|
1426
|
+
throw new Error("point.mixtilinearPoint: vertices ph\u1EA3i l\xE0 tuple 3 id non-empty");
|
|
1427
|
+
}
|
|
1428
|
+
if (c.which !== "center" && c.which !== "touch") {
|
|
1429
|
+
throw new Error("point.mixtilinearPoint: which ph\u1EA3i 'center' | 'touch'");
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
describe: (obj, state, c) => {
|
|
1433
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1434
|
+
return c.which === "center" ? `${obj.label} = t\xE2m \u0111\u01B0\u1EDDng tr\xF2n mixtilinear \u0394${labels}` : `${obj.label} = ti\u1EBFp \u0111i\u1EC3m mixtilinear \u0394${labels} v\u1EDBi (O)`;
|
|
1435
|
+
},
|
|
1436
|
+
render: (obj, ctx, c, opts) => {
|
|
1437
|
+
const board = ctx.jxg;
|
|
1438
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1439
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1440
|
+
const d = ctx.resolveRef(c.vertices[2]);
|
|
1441
|
+
const mp = () => mixtilinearPoint(
|
|
1442
|
+
[a.X(), a.Y()],
|
|
1443
|
+
[b.X(), b.Y()],
|
|
1444
|
+
[d.X(), d.Y()],
|
|
1445
|
+
c.which
|
|
1446
|
+
);
|
|
1447
|
+
return board.create("point", [() => mp()[0], () => mp()[1]], opts);
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
// src/core/scene/kinds/_label.ts
|
|
1454
|
+
function labelOpts(labelOffset, dflt) {
|
|
1455
|
+
const offset = labelOffset ?? dflt;
|
|
1456
|
+
return { label: { fixed: false, ...offset ? { offset } : {} } };
|
|
1457
|
+
}
|
|
1458
|
+
function readLabelOffset(label) {
|
|
1459
|
+
const off = label.evalVisProp?.("offset") ?? label.visProp?.offset;
|
|
1460
|
+
const rel = label.relativeCoords?.scrCoords;
|
|
1461
|
+
if (!off || !rel || off.length < 2 || rel.length < 3) return null;
|
|
1462
|
+
return [Math.round(off[0] + rel[1]), Math.round(off[1] - rel[2])];
|
|
1463
|
+
}
|
|
1464
|
+
var init_label = __esm({
|
|
1465
|
+
"src/core/scene/kinds/_label.ts"() {
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1368
1469
|
// src/core/scene/kinds/point-constraints/shared.ts
|
|
1369
1470
|
function buildJxgTransforms(board, ctx, t) {
|
|
1370
1471
|
switch (t.kind) {
|
|
@@ -1416,11 +1517,13 @@ function buildPointOpts(obj) {
|
|
|
1416
1517
|
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
1417
1518
|
fillColor: obj.attrs.color ?? "#1e40af",
|
|
1418
1519
|
face: obj.attrs.face ?? "o",
|
|
1419
|
-
size: obj.attrs.size ?? 4
|
|
1520
|
+
size: obj.attrs.size ?? 4,
|
|
1521
|
+
...labelOpts(obj.attrs.labelOffset, [10, 10])
|
|
1420
1522
|
};
|
|
1421
1523
|
}
|
|
1422
1524
|
var init_shared = __esm({
|
|
1423
1525
|
"src/core/scene/kinds/point-constraints/shared.ts"() {
|
|
1526
|
+
init_label();
|
|
1424
1527
|
}
|
|
1425
1528
|
});
|
|
1426
1529
|
|
|
@@ -1729,6 +1832,7 @@ var init_registry2 = __esm({
|
|
|
1729
1832
|
init_centroid();
|
|
1730
1833
|
init_arcMidpoint();
|
|
1731
1834
|
init_excenter();
|
|
1835
|
+
init_mixtilinearPoint();
|
|
1732
1836
|
init_pointAtDistance();
|
|
1733
1837
|
init_circleIntersection();
|
|
1734
1838
|
init_circleSecondIntersection();
|
|
@@ -1754,6 +1858,7 @@ var init_registry2 = __esm({
|
|
|
1754
1858
|
centroidConstraint,
|
|
1755
1859
|
arcMidpointConstraint,
|
|
1756
1860
|
excenterConstraint,
|
|
1861
|
+
mixtilinearPointConstraint,
|
|
1757
1862
|
pointAtDistanceConstraint,
|
|
1758
1863
|
circleIntersectionConstraint,
|
|
1759
1864
|
circleSecondIntersectionConstraint,
|
|
@@ -1778,6 +1883,7 @@ var init_point = __esm({
|
|
|
1778
1883
|
init_d_constraint2();
|
|
1779
1884
|
init_registry2();
|
|
1780
1885
|
init_shared();
|
|
1886
|
+
init_label();
|
|
1781
1887
|
def3 = {
|
|
1782
1888
|
type: "point",
|
|
1783
1889
|
schemaVersion: 1,
|
|
@@ -1845,7 +1951,8 @@ var init_point = __esm({
|
|
|
1845
1951
|
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
1846
1952
|
fillColor: obj.attrs.color ?? "#1e40af",
|
|
1847
1953
|
face: obj.attrs.face ?? "o",
|
|
1848
|
-
size: obj.attrs.size ?? 4
|
|
1954
|
+
size: obj.attrs.size ?? 4,
|
|
1955
|
+
...labelOpts(obj.attrs.labelOffset, [10, 10])
|
|
1849
1956
|
});
|
|
1850
1957
|
} catch {
|
|
1851
1958
|
}
|
|
@@ -1865,6 +1972,7 @@ var init_segment = __esm({
|
|
|
1865
1972
|
"src/core/scene/kinds/segment.ts"() {
|
|
1866
1973
|
init_registry();
|
|
1867
1974
|
init_labelOf();
|
|
1975
|
+
init_label();
|
|
1868
1976
|
def4 = {
|
|
1869
1977
|
type: "segment",
|
|
1870
1978
|
schemaVersion: 1,
|
|
@@ -1896,7 +2004,8 @@ var init_segment = __esm({
|
|
|
1896
2004
|
strokeWidth: obj.attrs.width ?? 2,
|
|
1897
2005
|
dash: obj.attrs.dash ?? 0,
|
|
1898
2006
|
visible: obj.visible,
|
|
1899
|
-
fixed: obj.locked
|
|
2007
|
+
fixed: obj.locked,
|
|
2008
|
+
...labelOpts(obj.attrs.labelOffset)
|
|
1900
2009
|
});
|
|
1901
2010
|
}
|
|
1902
2011
|
};
|
|
@@ -1933,6 +2042,7 @@ var init_line = __esm({
|
|
|
1933
2042
|
"src/core/scene/kinds/line.ts"() {
|
|
1934
2043
|
init_registry();
|
|
1935
2044
|
init_labelOf();
|
|
2045
|
+
init_label();
|
|
1936
2046
|
init_pointConstructions();
|
|
1937
2047
|
def5 = {
|
|
1938
2048
|
type: "line",
|
|
@@ -1975,7 +2085,8 @@ var init_line = __esm({
|
|
|
1975
2085
|
strokeWidth: obj.attrs.width ?? 2,
|
|
1976
2086
|
dash: obj.attrs.dash ?? 0,
|
|
1977
2087
|
visible: obj.visible,
|
|
1978
|
-
fixed: obj.locked
|
|
2088
|
+
fixed: obj.locked,
|
|
2089
|
+
...labelOpts(obj.attrs.labelOffset)
|
|
1979
2090
|
};
|
|
1980
2091
|
const c = obj.attrs.construction;
|
|
1981
2092
|
if (!c) {
|
|
@@ -2231,6 +2342,7 @@ var init_circle = __esm({
|
|
|
2231
2342
|
"src/core/scene/kinds/circle.ts"() {
|
|
2232
2343
|
init_registry();
|
|
2233
2344
|
init_labelOf();
|
|
2345
|
+
init_label();
|
|
2234
2346
|
init_pointConstructions();
|
|
2235
2347
|
def8 = {
|
|
2236
2348
|
type: "circle",
|
|
@@ -2292,15 +2404,17 @@ var init_circle = __esm({
|
|
|
2292
2404
|
const board = ctx.jxg;
|
|
2293
2405
|
const isCenterLabel = (l) => /^[A-Z]['′]?\d*$/u.test(l);
|
|
2294
2406
|
const isCenter = isCenterLabel(obj.label);
|
|
2407
|
+
const isRenamedCircle = /_c$/.test(obj.label);
|
|
2295
2408
|
const baseOpts = {
|
|
2296
2409
|
name: obj.label,
|
|
2297
|
-
withLabel: isCenter ? obj.attrs.showLabel ?? false : true,
|
|
2410
|
+
withLabel: isCenter ? obj.attrs.showLabel ?? false : isRenamedCircle ? false : true,
|
|
2298
2411
|
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
2299
2412
|
strokeWidth: obj.attrs.width ?? 2,
|
|
2300
2413
|
dash: obj.attrs.dash ?? 0,
|
|
2301
2414
|
fillColor: "none",
|
|
2302
2415
|
visible: obj.visible,
|
|
2303
|
-
fixed: obj.locked
|
|
2416
|
+
fixed: obj.locked,
|
|
2417
|
+
...labelOpts(obj.attrs.labelOffset)
|
|
2304
2418
|
};
|
|
2305
2419
|
const c = asConstruction(obj.attrs);
|
|
2306
2420
|
if (c?.kind === "circumscribed") {
|
|
@@ -2785,8 +2899,11 @@ var init_intersection = __esm({
|
|
|
2785
2899
|
const opts = {
|
|
2786
2900
|
name: obj.label,
|
|
2787
2901
|
withLabel: true,
|
|
2788
|
-
|
|
2789
|
-
|
|
2902
|
+
// Cùng màu xanh với mọi điểm khác (buildPointOpts dùng '#1e40af').
|
|
2903
|
+
// Trước đây điểm giao tô đỏ '#dc2626' → tách biệt thị giác nhưng gây
|
|
2904
|
+
// khó hiểu ("vì sao điểm này khác màu?"). Giữ chung một màu.
|
|
2905
|
+
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
2906
|
+
fillColor: obj.attrs.color ?? "#1e40af",
|
|
2790
2907
|
visible: obj.visible,
|
|
2791
2908
|
fixed: obj.locked
|
|
2792
2909
|
};
|
|
@@ -2795,6 +2912,38 @@ var init_intersection = __esm({
|
|
|
2795
2912
|
}
|
|
2796
2913
|
const branch = obj.attrs.branch ?? 0;
|
|
2797
2914
|
return board.create("intersection", [a, b, branch], opts);
|
|
2915
|
+
},
|
|
2916
|
+
/**
|
|
2917
|
+
* Cập nhật TẠI CHỖ các thuộc tính "trang trí" (tên/màu/ẩn-hiện/khoá) qua
|
|
2918
|
+
* setAttribute — giữ nguyên JxgObj identity nên các object phụ thuộc điểm
|
|
2919
|
+
* giao (đường thẳng qua nó, …) KHÔNG bị stale parent ref. Mô phỏng update
|
|
2920
|
+
* hook của point.ts.
|
|
2921
|
+
*
|
|
2922
|
+
* Nếu định nghĩa hình học đổi (kind/ref1/ref2/branch) thì throw → renderer
|
|
2923
|
+
* fallback remove + create để JSXGraph dựng lại phép giao đúng.
|
|
2924
|
+
*/
|
|
2925
|
+
update: (obj, prev, ctx, existing) => {
|
|
2926
|
+
const a = obj.attrs;
|
|
2927
|
+
const p = prev.attrs;
|
|
2928
|
+
const branchA = a.branch;
|
|
2929
|
+
const branchP = p.branch;
|
|
2930
|
+
if (a.kind !== p.kind || a.ref1 !== p.ref1 || a.ref2 !== p.ref2 || branchA !== branchP) {
|
|
2931
|
+
throw new Error("intersection: \u0111\u1ECBnh ngh\u0129a h\xECnh h\u1ECDc \u0111\u1ED5i \u2014 recreate");
|
|
2932
|
+
}
|
|
2933
|
+
const el = existing;
|
|
2934
|
+
if (typeof el.setAttribute === "function") {
|
|
2935
|
+
try {
|
|
2936
|
+
el.setAttribute({
|
|
2937
|
+
name: obj.label,
|
|
2938
|
+
withLabel: true,
|
|
2939
|
+
strokeColor: a.color ?? "#1e40af",
|
|
2940
|
+
fillColor: a.color ?? "#1e40af",
|
|
2941
|
+
visible: obj.visible,
|
|
2942
|
+
fixed: obj.locked
|
|
2943
|
+
});
|
|
2944
|
+
} catch {
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2798
2947
|
}
|
|
2799
2948
|
};
|
|
2800
2949
|
registerKind(def12);
|
|
@@ -3481,6 +3630,7 @@ var init_JxgRenderer = __esm({
|
|
|
3481
3630
|
init_registry();
|
|
3482
3631
|
init_types2d();
|
|
3483
3632
|
init_parser();
|
|
3633
|
+
init_label();
|
|
3484
3634
|
JxgRenderer = class {
|
|
3485
3635
|
constructor(store, board, options = {}) {
|
|
3486
3636
|
this.elements = /* @__PURE__ */ new Map();
|
|
@@ -3549,6 +3699,7 @@ var init_JxgRenderer = __esm({
|
|
|
3549
3699
|
this.elements.set(obj.id, el);
|
|
3550
3700
|
this.attachFreePointDragSync(obj, el);
|
|
3551
3701
|
this.attachGliderDragSync(obj, el);
|
|
3702
|
+
this.attachLabelDragSync(obj, el);
|
|
3552
3703
|
} catch (err) {
|
|
3553
3704
|
console.warn(`[scene/render/2d] kh\xF4ng render \u0111\u01B0\u1EE3c ${obj.kind} id="${obj.id}":`, err);
|
|
3554
3705
|
}
|
|
@@ -3663,6 +3814,54 @@ var init_JxgRenderer = __esm({
|
|
|
3663
3814
|
});
|
|
3664
3815
|
});
|
|
3665
3816
|
}
|
|
3817
|
+
/**
|
|
3818
|
+
* Cho phép kéo NHÃN của bất kỳ object nào có label (point/line/segment/
|
|
3819
|
+
* circle...). Khi user kéo nhãn, JSXGraph cộng dồn vào label.relativeCoords
|
|
3820
|
+
* (drag-delta screen px) nhưng KHÔNG đổi attribute offset → vị trí cuối =
|
|
3821
|
+
* offset + relativeCoords. Ta gộp lại thành offset thuần (readLabelOffset),
|
|
3822
|
+
* zero relativeCoords để khỏi double-count khi update-hook/recreate áp lại
|
|
3823
|
+
* offset, rồi dispatch UPDATE_ATTRS { labelOffset }. Right-click → reset.
|
|
3824
|
+
*/
|
|
3825
|
+
attachLabelDragSync(obj, el) {
|
|
3826
|
+
const label = el?.label;
|
|
3827
|
+
if (!label || typeof label.on !== "function") return;
|
|
3828
|
+
const sceneId = obj.id;
|
|
3829
|
+
label.on("up", () => {
|
|
3830
|
+
if (this.disposed) return;
|
|
3831
|
+
const off = readLabelOffset(label);
|
|
3832
|
+
if (!off) return;
|
|
3833
|
+
const cur = this.store.getState().objects[sceneId];
|
|
3834
|
+
if (!cur) return;
|
|
3835
|
+
const prev = cur.attrs.labelOffset;
|
|
3836
|
+
if (prev && prev[0] === off[0] && prev[1] === off[1]) return;
|
|
3837
|
+
try {
|
|
3838
|
+
label.setAttribute({ offset: off });
|
|
3839
|
+
if (label.relativeCoords?.scrCoords) {
|
|
3840
|
+
label.relativeCoords.scrCoords[1] = 0;
|
|
3841
|
+
label.relativeCoords.scrCoords[2] = 0;
|
|
3842
|
+
}
|
|
3843
|
+
} catch {
|
|
3844
|
+
}
|
|
3845
|
+
this.store.dispatch({
|
|
3846
|
+
type: "UPDATE_ATTRS",
|
|
3847
|
+
payload: { id: sceneId, patch: { labelOffset: off } }
|
|
3848
|
+
});
|
|
3849
|
+
});
|
|
3850
|
+
const node = label.rendNode;
|
|
3851
|
+
if (node?.addEventListener) {
|
|
3852
|
+
node.addEventListener("contextmenu", (ev) => {
|
|
3853
|
+
if (this.disposed) return;
|
|
3854
|
+
ev.preventDefault();
|
|
3855
|
+
const c = this.store.getState().objects[sceneId];
|
|
3856
|
+
if (!c) return;
|
|
3857
|
+
if (c.attrs.labelOffset === void 0) return;
|
|
3858
|
+
this.store.dispatch({
|
|
3859
|
+
type: "UPDATE_ATTRS",
|
|
3860
|
+
payload: { id: sceneId, patch: { labelOffset: void 0 } }
|
|
3861
|
+
});
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3666
3865
|
remove(id) {
|
|
3667
3866
|
this.removeHalo(id);
|
|
3668
3867
|
this.selectedIds.delete(id);
|
|
@@ -3799,88 +3998,89 @@ var init_JxgRenderer = __esm({
|
|
|
3799
3998
|
layer: 4,
|
|
3800
3999
|
needsRegularUpdate: true
|
|
3801
4000
|
};
|
|
4001
|
+
const elementClass = el.elementClass;
|
|
4002
|
+
const elType = el.elType;
|
|
4003
|
+
const isPointLike = elementClass === 1 || elType === "point" || elType === "glider" || elType === "intersection";
|
|
4004
|
+
const isPolygonLike = Array.isArray(el.vertices) && el.vertices.length >= 3;
|
|
4005
|
+
const isCircleLike = elementClass === 3 || elType === "circle" || elType === "circumcircle" || elType === "incircle";
|
|
4006
|
+
const isSegment = elType === "segment";
|
|
4007
|
+
const isLineLike = elementClass === 2 || elType === "line" || elType === "arrow" || elType === "ray" || elType === "vector" || elType === "tangent" || elType === "normal" || elType === "parallel" || elType === "perpendicular" || elType === "bisector";
|
|
3802
4008
|
const halos = [];
|
|
3803
4009
|
try {
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
});
|
|
3831
|
-
halos.push(halo);
|
|
3832
|
-
}
|
|
3833
|
-
break;
|
|
3834
|
-
}
|
|
3835
|
-
case "line":
|
|
3836
|
-
case "arrow":
|
|
3837
|
-
case "ray":
|
|
3838
|
-
case "vector":
|
|
3839
|
-
case "tangent":
|
|
3840
|
-
case "normal":
|
|
3841
|
-
case "parallel":
|
|
3842
|
-
case "perpendicular":
|
|
3843
|
-
case "bisector": {
|
|
3844
|
-
if (el.point1 && el.point2) {
|
|
3845
|
-
const halo = board.create("line", [el.point1, el.point2], {
|
|
3846
|
-
...haloBase,
|
|
3847
|
-
strokeWidth: 9
|
|
3848
|
-
});
|
|
3849
|
-
halos.push(halo);
|
|
3850
|
-
}
|
|
3851
|
-
break;
|
|
3852
|
-
}
|
|
3853
|
-
case "circle": {
|
|
3854
|
-
if (el.center && typeof el.Radius === "function") {
|
|
3855
|
-
const halo = board.create("circle", [el.center, () => el.Radius?.() ?? 0], {
|
|
3856
|
-
...haloBase,
|
|
3857
|
-
strokeWidth: 9,
|
|
3858
|
-
fillOpacity: 0
|
|
3859
|
-
});
|
|
3860
|
-
halos.push(halo);
|
|
3861
|
-
}
|
|
3862
|
-
break;
|
|
3863
|
-
}
|
|
3864
|
-
case "polygon": {
|
|
3865
|
-
if (Array.isArray(el.vertices) && el.vertices.length >= 3) {
|
|
3866
|
-
const last = el.vertices.length - 1;
|
|
3867
|
-
const verts = el.vertices[last] === el.vertices[0] ? el.vertices.slice(0, last) : el.vertices.slice();
|
|
3868
|
-
const halo = board.create("polygon", verts, {
|
|
3869
|
-
...haloBase,
|
|
3870
|
-
fillOpacity: 0.2,
|
|
3871
|
-
borders: {
|
|
3872
|
-
strokeColor: SEL_STROKE,
|
|
3873
|
-
strokeWidth: 7,
|
|
3874
|
-
strokeOpacity: 0.55,
|
|
3875
|
-
highlight: false
|
|
3876
|
-
}
|
|
3877
|
-
});
|
|
3878
|
-
halos.push(halo);
|
|
4010
|
+
if (isPointLike) {
|
|
4011
|
+
const baseSize = el.getAttribute?.("size") ?? 4;
|
|
4012
|
+
const halo = board.create("point", [
|
|
4013
|
+
() => el.X?.() ?? 0,
|
|
4014
|
+
() => el.Y?.() ?? 0
|
|
4015
|
+
], {
|
|
4016
|
+
...haloBase,
|
|
4017
|
+
size: baseSize + 6,
|
|
4018
|
+
face: "o",
|
|
4019
|
+
strokeWidth: 2,
|
|
4020
|
+
strokeOpacity: 0.75,
|
|
4021
|
+
fillOpacity: 0.25
|
|
4022
|
+
});
|
|
4023
|
+
halos.push(halo);
|
|
4024
|
+
} else if (isPolygonLike) {
|
|
4025
|
+
const verts = el.vertices;
|
|
4026
|
+
const last = verts.length - 1;
|
|
4027
|
+
const trimmed = verts[last] === verts[0] ? verts.slice(0, last) : verts.slice();
|
|
4028
|
+
const halo = board.create("polygon", trimmed, {
|
|
4029
|
+
...haloBase,
|
|
4030
|
+
fillOpacity: 0.2,
|
|
4031
|
+
borders: {
|
|
4032
|
+
strokeColor: SEL_STROKE,
|
|
4033
|
+
strokeWidth: 7,
|
|
4034
|
+
strokeOpacity: 0.55,
|
|
4035
|
+
highlight: false
|
|
3879
4036
|
}
|
|
3880
|
-
|
|
4037
|
+
});
|
|
4038
|
+
halos.push(halo);
|
|
4039
|
+
} else if (isCircleLike && el.center && typeof el.Radius === "function") {
|
|
4040
|
+
const halo = board.create("circle", [el.center, () => el.Radius?.() ?? 0], {
|
|
4041
|
+
...haloBase,
|
|
4042
|
+
strokeWidth: 9,
|
|
4043
|
+
fillOpacity: 0
|
|
4044
|
+
});
|
|
4045
|
+
halos.push(halo);
|
|
4046
|
+
} else if (isSegment && el.point1 && el.point2) {
|
|
4047
|
+
const halo = board.create("segment", [el.point1, el.point2], {
|
|
4048
|
+
...haloBase,
|
|
4049
|
+
strokeWidth: 9,
|
|
4050
|
+
straightFirst: false,
|
|
4051
|
+
straightLast: false
|
|
4052
|
+
});
|
|
4053
|
+
halos.push(halo);
|
|
4054
|
+
} else if (isLineLike && el.point1 && el.point2) {
|
|
4055
|
+
const halo = board.create("line", [el.point1, el.point2], {
|
|
4056
|
+
...haloBase,
|
|
4057
|
+
strokeWidth: 9
|
|
4058
|
+
});
|
|
4059
|
+
halos.push(halo);
|
|
4060
|
+
} else if (el.center && (el.radiuspoint ?? el.radiusPoint) && (el.anglepoint ?? el.anglePoint) && (elType === "arc" || elType === "semicircle" || elType === "circumcirclearc" || elType === "sector" || elType === "angle")) {
|
|
4061
|
+
const rp = el.radiuspoint ?? el.radiusPoint;
|
|
4062
|
+
const ap = el.anglepoint ?? el.anglePoint;
|
|
4063
|
+
if (elType === "sector") {
|
|
4064
|
+
halos.push(board.create("sector", [el.center, rp, ap], {
|
|
4065
|
+
...haloBase,
|
|
4066
|
+
strokeWidth: 7,
|
|
4067
|
+
fillOpacity: 0.2
|
|
4068
|
+
}));
|
|
4069
|
+
} else if (elType === "angle") {
|
|
4070
|
+
halos.push(board.create("angle", [rp, el.center, ap], {
|
|
4071
|
+
...haloBase,
|
|
4072
|
+
strokeWidth: 7,
|
|
4073
|
+
fillOpacity: 0.2,
|
|
4074
|
+
radius: el.getAttribute?.("radius") ?? 1
|
|
4075
|
+
}));
|
|
4076
|
+
} else {
|
|
4077
|
+
halos.push(board.create("arc", [el.center, rp, ap], {
|
|
4078
|
+
...haloBase,
|
|
4079
|
+
strokeWidth: 9,
|
|
4080
|
+
fillColor: "none",
|
|
4081
|
+
fillOpacity: 0
|
|
4082
|
+
}));
|
|
3881
4083
|
}
|
|
3882
|
-
default:
|
|
3883
|
-
break;
|
|
3884
4084
|
}
|
|
3885
4085
|
} catch (err) {
|
|
3886
4086
|
console.warn("[scene/render/2d] halo create fail:", err);
|
|
@@ -4040,6 +4240,49 @@ var init_autoFitBbox = __esm({
|
|
|
4040
4240
|
}
|
|
4041
4241
|
});
|
|
4042
4242
|
|
|
4243
|
+
// src/stamps/geometry-2d/autoFitBoard.ts
|
|
4244
|
+
function isDefaultBbox(bbox) {
|
|
4245
|
+
const d = DEFAULT_VIEW_2D.bbox;
|
|
4246
|
+
return bbox.length === 4 && bbox[0] === d[0] && bbox[1] === d[1] && bbox[2] === d[2] && bbox[3] === d[3];
|
|
4247
|
+
}
|
|
4248
|
+
function fittedBboxFromBoard(board, aspect) {
|
|
4249
|
+
const objs = board?.objectsList;
|
|
4250
|
+
if (!Array.isArray(objs)) return null;
|
|
4251
|
+
const points = [];
|
|
4252
|
+
const circles = [];
|
|
4253
|
+
for (const raw of objs) {
|
|
4254
|
+
const o = raw;
|
|
4255
|
+
if (o?.elementClass === 1 && typeof o.X === "function" && typeof o.Y === "function") {
|
|
4256
|
+
const x = o.X(), y = o.Y();
|
|
4257
|
+
if (Number.isFinite(x) && Number.isFinite(y)) points.push([x, y]);
|
|
4258
|
+
} else if (o?.elementClass === 3 && o.center?.X && typeof o.Radius === "function") {
|
|
4259
|
+
const cx = o.center.X(), cy = o.center.Y(), r = o.Radius();
|
|
4260
|
+
if (Number.isFinite(cx) && Number.isFinite(cy) && Number.isFinite(r)) {
|
|
4261
|
+
circles.push({ cx, cy, r });
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
const safeAspect = Number.isFinite(aspect) && aspect > 0 ? aspect : 1;
|
|
4266
|
+
return computeAutoFitBbox(points, circles, safeAspect);
|
|
4267
|
+
}
|
|
4268
|
+
function autoFitBoardToContent(board, aspect) {
|
|
4269
|
+
const bbox = fittedBboxFromBoard(board, aspect);
|
|
4270
|
+
if (!bbox) return;
|
|
4271
|
+
const b = board;
|
|
4272
|
+
try {
|
|
4273
|
+
b.setBoundingBox?.(bbox);
|
|
4274
|
+
if (typeof b.update === "function") b.update();
|
|
4275
|
+
if (typeof b.fullUpdate === "function") b.fullUpdate();
|
|
4276
|
+
} catch {
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
var init_autoFitBoard = __esm({
|
|
4280
|
+
"src/stamps/geometry-2d/autoFitBoard.ts"() {
|
|
4281
|
+
init_types();
|
|
4282
|
+
init_autoFitBbox();
|
|
4283
|
+
}
|
|
4284
|
+
});
|
|
4285
|
+
|
|
4043
4286
|
// src/stamps/geometry-2d/labelLayout.ts
|
|
4044
4287
|
function radialLabelOffsets(points, R = 14) {
|
|
4045
4288
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -4074,8 +4317,9 @@ function containerDimsForBbox(bbox) {
|
|
|
4074
4317
|
if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
|
|
4075
4318
|
return { width: FALLBACK_W, height: FALLBACK_H };
|
|
4076
4319
|
}
|
|
4077
|
-
|
|
4078
|
-
let
|
|
4320
|
+
const pxPerUnit = DEFAULT_VIEW_PX / Math.max(w, h);
|
|
4321
|
+
let width = w * pxPerUnit;
|
|
4322
|
+
let height = h * pxPerUnit;
|
|
4079
4323
|
const maxAxis = Math.max(width, height);
|
|
4080
4324
|
if (maxAxis > MAX_DIM) {
|
|
4081
4325
|
const ratio = MAX_DIM / maxAxis;
|
|
@@ -4090,31 +4334,6 @@ function containerDimsForBbox(bbox) {
|
|
|
4090
4334
|
}
|
|
4091
4335
|
return { width: Math.round(width), height: Math.round(height) };
|
|
4092
4336
|
}
|
|
4093
|
-
function autoFitBboxFromBoard(board, aspect) {
|
|
4094
|
-
const objs = board?.objectsList;
|
|
4095
|
-
if (!Array.isArray(objs)) return;
|
|
4096
|
-
const points = [];
|
|
4097
|
-
const circles = [];
|
|
4098
|
-
for (const o of objs) {
|
|
4099
|
-
if (o?.elementClass === 1 && typeof o.X === "function" && typeof o.Y === "function") {
|
|
4100
|
-
const x = o.X(), y = o.Y();
|
|
4101
|
-
if (Number.isFinite(x) && Number.isFinite(y)) points.push([x, y]);
|
|
4102
|
-
} else if (o?.elementClass === 3 && o.center?.X && typeof o.Radius === "function") {
|
|
4103
|
-
const cx = o.center.X(), cy = o.center.Y(), r = o.Radius();
|
|
4104
|
-
if (Number.isFinite(cx) && Number.isFinite(cy) && Number.isFinite(r)) {
|
|
4105
|
-
circles.push({ cx, cy, r });
|
|
4106
|
-
}
|
|
4107
|
-
}
|
|
4108
|
-
}
|
|
4109
|
-
const bbox = computeAutoFitBbox(points, circles, aspect);
|
|
4110
|
-
if (!bbox) return;
|
|
4111
|
-
try {
|
|
4112
|
-
board.setBoundingBox(bbox);
|
|
4113
|
-
if (typeof board.update === "function") board.update();
|
|
4114
|
-
if (typeof board.fullUpdate === "function") board.fullUpdate();
|
|
4115
|
-
} catch {
|
|
4116
|
-
}
|
|
4117
|
-
}
|
|
4118
4337
|
function applyRadialLabelOffsets(board) {
|
|
4119
4338
|
const objs = board?.objectsList;
|
|
4120
4339
|
if (!Array.isArray(objs)) return;
|
|
@@ -4140,10 +4359,6 @@ function applyRadialLabelOffsets(board) {
|
|
|
4140
4359
|
} catch {
|
|
4141
4360
|
}
|
|
4142
4361
|
}
|
|
4143
|
-
function isDefaultBbox(bbox) {
|
|
4144
|
-
const d = DEFAULT_VIEW_2D.bbox;
|
|
4145
|
-
return bbox.length === 4 && bbox[0] === d[0] && bbox[1] === d[1] && bbox[2] === d[2] && bbox[3] === d[3];
|
|
4146
|
-
}
|
|
4147
4362
|
async function renderGeometrySvgFromState(jsonState) {
|
|
4148
4363
|
const state = deserializeBoard(jsonState);
|
|
4149
4364
|
const view = state.meta.domain === "2d" ? state.meta.view : DEFAULT_VIEW_2D;
|
|
@@ -4178,7 +4393,7 @@ async function renderGeometrySvgFromState(jsonState) {
|
|
|
4178
4393
|
const store = createStore(state);
|
|
4179
4394
|
const renderer = new JxgRenderer(store, board);
|
|
4180
4395
|
if (shouldAutoFit) {
|
|
4181
|
-
|
|
4396
|
+
autoFitBoardToContent(board, dims.width / dims.height);
|
|
4182
4397
|
applyRadialLabelOffsets(board);
|
|
4183
4398
|
}
|
|
4184
4399
|
return renderer;
|
|
@@ -4186,7 +4401,7 @@ async function renderGeometrySvgFromState(jsonState) {
|
|
|
4186
4401
|
});
|
|
4187
4402
|
return svgString;
|
|
4188
4403
|
}
|
|
4189
|
-
var
|
|
4404
|
+
var DEFAULT_VIEW_PX, MIN_DIM, MAX_DIM, FALLBACK_W, FALLBACK_H;
|
|
4190
4405
|
var init_render = __esm({
|
|
4191
4406
|
"src/stamps/geometry-2d/render.ts"() {
|
|
4192
4407
|
init_serialize();
|
|
@@ -4195,9 +4410,9 @@ var init_render = __esm({
|
|
|
4195
4410
|
init_types();
|
|
4196
4411
|
init_JxgRenderer();
|
|
4197
4412
|
init_jxgOffscreenRender();
|
|
4198
|
-
|
|
4413
|
+
init_autoFitBoard();
|
|
4199
4414
|
init_labelLayout();
|
|
4200
|
-
|
|
4415
|
+
DEFAULT_VIEW_PX = 500;
|
|
4201
4416
|
MIN_DIM = 100;
|
|
4202
4417
|
MAX_DIM = 1200;
|
|
4203
4418
|
FALLBACK_W = 400;
|
|
@@ -5757,41 +5972,48 @@ var init_icons = __esm({
|
|
|
5757
5972
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4", cy: "4", r: "1.7", fill: C_POINT })
|
|
5758
5973
|
] }),
|
|
5759
5974
|
// ===== Nâng cao / kind chưa có icon =====
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
5975
|
+
// Hệ màu đồng bộ: XANH = input click sẵn, ĐỎ = output suy ra. Toạ độ tính để
|
|
5976
|
+
// điểm nằm ĐÚNG trên đường tròn/cung/tiếp tuyến (không trôi nổi).
|
|
5977
|
+
excenter: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
5978
|
+
/* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "12,3 15,15 9,15", stroke: "currentColor", strokeWidth: "1.4", fill: "none" }),
|
|
5979
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "15", x2: "8", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
|
|
5980
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "15", y1: "15", x2: "16", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
|
|
5981
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "18.9", r: "3.8", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
5982
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "3", r: "1.6", fill: C_POINT }),
|
|
5983
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "15", r: "1.6", fill: C_POINT }),
|
|
5984
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15", cy: "15", r: "1.6", fill: C_POINT }),
|
|
5985
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "18.9", r: "2.2", fill: C_CONSTRUCT })
|
|
5764
5986
|
] }),
|
|
5765
|
-
tangencyPoint: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5766
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
5767
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16.5", y1: "3", x2: "16.5", y2: "
|
|
5768
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "16.5", cy: "12", r: "2.
|
|
5987
|
+
tangencyPoint: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
5988
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9.5", cy: "12", r: "7", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
5989
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16.5", y1: "3.5", x2: "16.5", y2: "20.5", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
5990
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "16.5", cy: "12", r: "2.3", fill: C_CONSTRUCT })
|
|
5769
5991
|
] }),
|
|
5770
|
-
secondIntersection: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5771
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "12", r: "6.5", stroke: "currentColor", strokeWidth: "1.
|
|
5772
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "2", y1: "8", x2: "
|
|
5773
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
5774
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
5992
|
+
secondIntersection: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
5993
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "12", r: "6.5", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
5994
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "2.4", y1: "8.2", x2: "20.4", y2: "11.4", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
5995
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5.37", cy: "8.75", r: "1.8", fill: C_POINT }),
|
|
5996
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "17.4", cy: "10.87", r: "2.4", fill: C_CONSTRUCT })
|
|
5775
5997
|
] }),
|
|
5776
5998
|
arcMidpoint: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
5777
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 17 A
|
|
5778
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4", cy: "17", r: "1.
|
|
5779
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "20", cy: "17", r: "1.
|
|
5780
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "
|
|
5999
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 17 A 8.5 8.5 0 0 1 20 17", stroke: "currentColor", strokeWidth: "1.6", fill: "none" }),
|
|
6000
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4", cy: "17", r: "1.8", fill: C_POINT }),
|
|
6001
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "20", cy: "17", r: "1.8", fill: C_POINT }),
|
|
6002
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "11.4", r: "2.3", fill: C_CONSTRUCT })
|
|
5781
6003
|
] }),
|
|
5782
6004
|
circleIntersection: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5783
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.
|
|
5784
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.
|
|
5785
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "
|
|
5786
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "
|
|
6005
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6006
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6007
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "6.8", r: "2", fill: C_CONSTRUCT }),
|
|
6008
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "17.2", r: "2", fill: C_CONSTRUCT })
|
|
5787
6009
|
] }),
|
|
5788
6010
|
tangentPointExt: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
5789
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
5790
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
5791
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3
|
|
5792
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
5793
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
5794
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
6011
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "13", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6012
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "12", x2: "11.8", y2: "18.6", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
6013
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "12", x2: "11.8", y2: "5.4", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
6014
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "3", cy: "12", r: "2", fill: C_POINT }),
|
|
6015
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9.4", cy: "16.8", r: "1.8", fill: C_CONSTRUCT }),
|
|
6016
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9.4", cy: "7.2", r: "1.8", fill: C_CONSTRUCT })
|
|
5795
6017
|
] }),
|
|
5796
6018
|
circleCR: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5797
6019
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "7.5", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
@@ -5802,9 +6024,11 @@ var init_icons = __esm({
|
|
|
5802
6024
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 19 L12 4 L21 19 Z", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }),
|
|
5803
6025
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "14", r: "4.2", fill: "none", stroke: C_CONSTRUCT, strokeWidth: "1.3" })
|
|
5804
6026
|
] }),
|
|
5805
|
-
excircle: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5806
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
5807
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
6027
|
+
excircle: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
6028
|
+
/* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "12,3 15,15 9,15", stroke: "currentColor", strokeWidth: "1.4", fill: "none" }),
|
|
6029
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "15", x2: "8", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
|
|
6030
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "15", y1: "15", x2: "16", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
|
|
6031
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "18.9", r: "3.8", stroke: C_CONSTRUCT, strokeWidth: "1.5", fill: C_CONSTRUCT, fillOpacity: "0.12" })
|
|
5808
6032
|
] }),
|
|
5809
6033
|
pointOn: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5810
6034
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "7.5", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
@@ -7814,11 +8038,11 @@ function buildObjectSnapshot(state, id, anchorScreen) {
|
|
|
7814
8038
|
const obj = state.objects[id];
|
|
7815
8039
|
if (!obj) return null;
|
|
7816
8040
|
const k = obj.kind;
|
|
7817
|
-
if (k !== "point" && k !== "line" && k !== "circle" && k !== "segment" && k !== "ray" && k !== "vector") {
|
|
8041
|
+
if (k !== "point" && k !== "line" && k !== "circle" && k !== "segment" && k !== "ray" && k !== "vector" && k !== "intersection") {
|
|
7818
8042
|
return null;
|
|
7819
8043
|
}
|
|
7820
8044
|
const a = obj.attrs;
|
|
7821
|
-
const jKind = k === "point" ? "point" : k === "circle" ? "circle" : "line";
|
|
8045
|
+
const jKind = k === "point" || k === "intersection" ? "point" : k === "circle" ? "circle" : "line";
|
|
7822
8046
|
return {
|
|
7823
8047
|
id,
|
|
7824
8048
|
kind: jKind,
|
|
@@ -8226,6 +8450,7 @@ var init_MiniBoard = __esm({
|
|
|
8226
8450
|
init_safeJsx();
|
|
8227
8451
|
init_attachJxgWheelZoom();
|
|
8228
8452
|
init_initJxgBoard();
|
|
8453
|
+
init_autoFitBoard();
|
|
8229
8454
|
init_snapshot();
|
|
8230
8455
|
init_previewActions();
|
|
8231
8456
|
init_buildHandle();
|
|
@@ -8465,6 +8690,11 @@ var init_MiniBoard = __esm({
|
|
|
8465
8690
|
rendererRef.current = new JxgRenderer(store, board, {
|
|
8466
8691
|
theme: paletteFor(isDarkRef.current)
|
|
8467
8692
|
});
|
|
8693
|
+
if (isDefaultBbox(initialView.bbox)) {
|
|
8694
|
+
const el = containerRef.current;
|
|
8695
|
+
const aspect = el && el.clientHeight > 0 ? el.clientWidth / el.clientHeight : 1;
|
|
8696
|
+
safeJsx("MiniBoard.autoFit", () => autoFitBoardToContent(board, aspect));
|
|
8697
|
+
}
|
|
8468
8698
|
if (containerRef.current) {
|
|
8469
8699
|
wheelCleanup = attachJxgWheelZoom(containerRef.current, board, "MiniBoard.2d");
|
|
8470
8700
|
}
|
|
@@ -9262,6 +9492,7 @@ function useAiFigure(generator) {
|
|
|
9262
9492
|
const [prompt, setPrompt] = React19.useState("");
|
|
9263
9493
|
const [isLoading, setIsLoading] = React19.useState(false);
|
|
9264
9494
|
const [error, setError] = React19.useState(null);
|
|
9495
|
+
const [notice, setNotice] = React19.useState(null);
|
|
9265
9496
|
const [tokens, setTokens] = React19.useState(0);
|
|
9266
9497
|
const abortRef = React19.useRef(null);
|
|
9267
9498
|
const requestIdRef = React19.useRef(0);
|
|
@@ -9282,6 +9513,7 @@ function useAiFigure(generator) {
|
|
|
9282
9513
|
abortRef.current = controller;
|
|
9283
9514
|
setIsLoading(true);
|
|
9284
9515
|
setError(null);
|
|
9516
|
+
setNotice(null);
|
|
9285
9517
|
setTokens(0);
|
|
9286
9518
|
try {
|
|
9287
9519
|
const generated = await generator(problem, {
|
|
@@ -9295,6 +9527,7 @@ function useAiFigure(generator) {
|
|
|
9295
9527
|
setError(generated.message);
|
|
9296
9528
|
return null;
|
|
9297
9529
|
}
|
|
9530
|
+
if (generated.partial) setNotice(generated.partial.message);
|
|
9298
9531
|
return generated.state;
|
|
9299
9532
|
} catch (caught) {
|
|
9300
9533
|
if (controller.signal.aborted || caught instanceof DOMException && caught.name === "AbortError") {
|
|
@@ -9321,6 +9554,7 @@ function useAiFigure(generator) {
|
|
|
9321
9554
|
setPrompt,
|
|
9322
9555
|
isLoading,
|
|
9323
9556
|
error,
|
|
9557
|
+
notice,
|
|
9324
9558
|
submit,
|
|
9325
9559
|
cancel,
|
|
9326
9560
|
tokens
|
|
@@ -9331,12 +9565,207 @@ var init_useAiFigure = __esm({
|
|
|
9331
9565
|
"use client";
|
|
9332
9566
|
}
|
|
9333
9567
|
});
|
|
9568
|
+
|
|
9569
|
+
// src/stamps/geometry-2d/ai/vision/tesseract.ts
|
|
9570
|
+
async function runTesseractOcr(image, opts = {}) {
|
|
9571
|
+
if (opts.signal?.aborted) {
|
|
9572
|
+
const err = new Error("Tesseract OCR aborted");
|
|
9573
|
+
err.name = "AbortError";
|
|
9574
|
+
throw err;
|
|
9575
|
+
}
|
|
9576
|
+
const { createWorker } = await import('tesseract.js');
|
|
9577
|
+
const lang = opts.lang ?? DEFAULT_LANG;
|
|
9578
|
+
const worker = await createWorker(lang);
|
|
9579
|
+
try {
|
|
9580
|
+
const dataUrl = `data:${image.mediaType};base64,${image.base64}`;
|
|
9581
|
+
const { data } = await worker.recognize(dataUrl);
|
|
9582
|
+
return { text: data.text, confidence: data.confidence };
|
|
9583
|
+
} finally {
|
|
9584
|
+
await worker.terminate();
|
|
9585
|
+
}
|
|
9586
|
+
}
|
|
9587
|
+
var DEFAULT_LANG;
|
|
9588
|
+
var init_tesseract = __esm({
|
|
9589
|
+
"src/stamps/geometry-2d/ai/vision/tesseract.ts"() {
|
|
9590
|
+
DEFAULT_LANG = "vie+eng";
|
|
9591
|
+
}
|
|
9592
|
+
});
|
|
9593
|
+
|
|
9594
|
+
// src/stamps/geometry-2d/ai/vision/extractProblem.ts
|
|
9595
|
+
async function extractProblemFromImage(image, opts = {}) {
|
|
9596
|
+
let raw;
|
|
9597
|
+
try {
|
|
9598
|
+
raw = await runTesseractOcr(image, {
|
|
9599
|
+
...opts.tesseractLang ? { lang: opts.tesseractLang } : {},
|
|
9600
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
9601
|
+
});
|
|
9602
|
+
} catch (e) {
|
|
9603
|
+
const err = e;
|
|
9604
|
+
return {
|
|
9605
|
+
ok: false,
|
|
9606
|
+
reason: "unreadable",
|
|
9607
|
+
message: "Tesseract OCR fail: " + (err.message ?? "?")
|
|
9608
|
+
};
|
|
9609
|
+
}
|
|
9610
|
+
const text = postProcess(raw.text);
|
|
9611
|
+
if (text.length === 0) {
|
|
9612
|
+
return { ok: false, reason: "empty", message: "Tesseract kh\xF4ng tr\xEDch \u0111\u01B0\u1EE3c text." };
|
|
9613
|
+
}
|
|
9614
|
+
const tooShort = text.length < MIN_HIGH_CONFIDENCE_CHARS;
|
|
9615
|
+
const lowConf = raw.confidence < TESSERACT_HIGH_CONFIDENCE_THRESHOLD;
|
|
9616
|
+
const confidence = tooShort || lowConf ? "low" : "high";
|
|
9617
|
+
return {
|
|
9618
|
+
ok: true,
|
|
9619
|
+
text,
|
|
9620
|
+
confidence,
|
|
9621
|
+
usage: { inputTokens: 0, outputTokens: 0 }
|
|
9622
|
+
};
|
|
9623
|
+
}
|
|
9624
|
+
function postProcess(raw) {
|
|
9625
|
+
let t = raw.trim();
|
|
9626
|
+
t = t.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
9627
|
+
t = t.replace(/\*(.+?)\*/g, "$1");
|
|
9628
|
+
t = t.replace(/_(.+?)_/g, "$1");
|
|
9629
|
+
t = t.replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1");
|
|
9630
|
+
t = t.replace(/\s+/g, " ").trim();
|
|
9631
|
+
t = t.normalize("NFC");
|
|
9632
|
+
if (t.length > MAX_TEXT_CHARS) t = t.slice(0, MAX_TEXT_CHARS);
|
|
9633
|
+
return t;
|
|
9634
|
+
}
|
|
9635
|
+
var MIN_HIGH_CONFIDENCE_CHARS, MAX_TEXT_CHARS, TESSERACT_HIGH_CONFIDENCE_THRESHOLD;
|
|
9636
|
+
var init_extractProblem = __esm({
|
|
9637
|
+
"src/stamps/geometry-2d/ai/vision/extractProblem.ts"() {
|
|
9638
|
+
init_tesseract();
|
|
9639
|
+
MIN_HIGH_CONFIDENCE_CHARS = 10;
|
|
9640
|
+
MAX_TEXT_CHARS = 2e3;
|
|
9641
|
+
TESSERACT_HIGH_CONFIDENCE_THRESHOLD = 70;
|
|
9642
|
+
}
|
|
9643
|
+
});
|
|
9644
|
+
|
|
9645
|
+
// src/stamps/geometry-2d/ai/handleExtractProblem.ts
|
|
9646
|
+
async function handleExtractProblem(image, opts = {}) {
|
|
9647
|
+
try {
|
|
9648
|
+
const r = await extractProblemFromImage(image, opts);
|
|
9649
|
+
if (r.ok) {
|
|
9650
|
+
if (r.confidence === "low") {
|
|
9651
|
+
return {
|
|
9652
|
+
kind: "low-confidence",
|
|
9653
|
+
text: r.text,
|
|
9654
|
+
warning: "OCR c\xF3 th\u1EC3 kh\xF4ng ch\xEDnh x\xE1c, ki\u1EC3m tra tr\u01B0\u1EDBc khi v\u1EBD.",
|
|
9655
|
+
usage: r.usage
|
|
9656
|
+
};
|
|
9657
|
+
}
|
|
9658
|
+
return { kind: "success", text: r.text, usage: r.usage };
|
|
9659
|
+
}
|
|
9660
|
+
if (r.reason === "not-math") {
|
|
9661
|
+
return { kind: "refused", reason: "not-math", message: r.message };
|
|
9662
|
+
}
|
|
9663
|
+
if (r.reason === "unsupported") {
|
|
9664
|
+
return { kind: "error", code: "unsupported", message: r.message };
|
|
9665
|
+
}
|
|
9666
|
+
if (r.reason === "unreadable") {
|
|
9667
|
+
return { kind: "error", code: "network", message: r.message };
|
|
9668
|
+
}
|
|
9669
|
+
return { kind: "error", code: "empty", message: r.message };
|
|
9670
|
+
} catch (e) {
|
|
9671
|
+
return {
|
|
9672
|
+
kind: "error",
|
|
9673
|
+
code: "unexpected",
|
|
9674
|
+
message: e instanceof Error ? e.message : String(e)
|
|
9675
|
+
};
|
|
9676
|
+
}
|
|
9677
|
+
}
|
|
9678
|
+
var init_handleExtractProblem = __esm({
|
|
9679
|
+
"src/stamps/geometry-2d/ai/handleExtractProblem.ts"() {
|
|
9680
|
+
init_extractProblem();
|
|
9681
|
+
}
|
|
9682
|
+
});
|
|
9683
|
+
|
|
9684
|
+
// src/stamps/geometry-2d/ai/vision/preprocess.ts
|
|
9685
|
+
function inferMediaType(file) {
|
|
9686
|
+
const t = file.type.toLowerCase();
|
|
9687
|
+
if (ALLOWED_TYPES.includes(t)) return t;
|
|
9688
|
+
return null;
|
|
9689
|
+
}
|
|
9690
|
+
function validateFile(file) {
|
|
9691
|
+
const mt = inferMediaType(file);
|
|
9692
|
+
if (mt == null) {
|
|
9693
|
+
return {
|
|
9694
|
+
ok: false,
|
|
9695
|
+
code: "invalid-format",
|
|
9696
|
+
message: "Ch\u1EC9 h\u1ED7 tr\u1EE3 PNG, JPEG, WEBP."
|
|
9697
|
+
};
|
|
9698
|
+
}
|
|
9699
|
+
if (file.size > MAX_RAW_BYTES) {
|
|
9700
|
+
return {
|
|
9701
|
+
ok: false,
|
|
9702
|
+
code: "too-large",
|
|
9703
|
+
message: `\u1EA2nh qu\xE1 l\u1EDBn (> ${Math.round(MAX_RAW_BYTES / 1024 / 1024)} MB). Crop ho\u1EB7c resize tr\u01B0\u1EDBc.`
|
|
9704
|
+
};
|
|
9705
|
+
}
|
|
9706
|
+
return { ok: true, mediaType: mt };
|
|
9707
|
+
}
|
|
9708
|
+
async function fileToImagePart(file) {
|
|
9709
|
+
const v = validateFile(file);
|
|
9710
|
+
if (!v.ok) throw new Error(v.message);
|
|
9711
|
+
const bitmap = await createImageBitmap(file);
|
|
9712
|
+
const { width, height } = bitmap;
|
|
9713
|
+
const maxEdge = Math.max(width, height);
|
|
9714
|
+
const scale3 = maxEdge > MAX_EDGE_PX ? MAX_EDGE_PX / maxEdge : 1;
|
|
9715
|
+
const targetW = Math.round(width * scale3);
|
|
9716
|
+
const targetH = Math.round(height * scale3);
|
|
9717
|
+
const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetW, targetH) : Object.assign(document.createElement("canvas"), { width: targetW, height: targetH });
|
|
9718
|
+
const ctx = canvas.getContext("2d");
|
|
9719
|
+
if (!ctx) throw new Error("Kh\xF4ng t\u1EA1o \u0111\u01B0\u1EE3c canvas 2D context");
|
|
9720
|
+
ctx.drawImage(bitmap, 0, 0, targetW, targetH);
|
|
9721
|
+
bitmap.close();
|
|
9722
|
+
let outputType = v.mediaType === "image/png" ? "image/png" : "image/jpeg";
|
|
9723
|
+
let finalBlob = await canvasToBlob(canvas, outputType, 0.92);
|
|
9724
|
+
if (finalBlob.size > MAX_ENCODED_BYTES) {
|
|
9725
|
+
outputType = "image/jpeg";
|
|
9726
|
+
finalBlob = await canvasToBlob(canvas, "image/jpeg", 0.7);
|
|
9727
|
+
}
|
|
9728
|
+
const base64 = await blobToBase64(finalBlob);
|
|
9729
|
+
return { mediaType: outputType, base64 };
|
|
9730
|
+
}
|
|
9731
|
+
async function canvasToBlob(canvas, type, quality) {
|
|
9732
|
+
if ("convertToBlob" in canvas) {
|
|
9733
|
+
return canvas.convertToBlob({ type, quality });
|
|
9734
|
+
}
|
|
9735
|
+
return new Promise((resolve, reject) => {
|
|
9736
|
+
canvas.toBlob(
|
|
9737
|
+
(b) => b ? resolve(b) : reject(new Error("Canvas encode fail")),
|
|
9738
|
+
type,
|
|
9739
|
+
quality
|
|
9740
|
+
);
|
|
9741
|
+
});
|
|
9742
|
+
}
|
|
9743
|
+
async function blobToBase64(blob) {
|
|
9744
|
+
const buf = await blob.arrayBuffer();
|
|
9745
|
+
let binary = "";
|
|
9746
|
+
const bytes = new Uint8Array(buf);
|
|
9747
|
+
const chunk = 32768;
|
|
9748
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
9749
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
9750
|
+
}
|
|
9751
|
+
return typeof btoa === "function" ? btoa(binary) : Buffer.from(binary, "binary").toString("base64");
|
|
9752
|
+
}
|
|
9753
|
+
var MAX_EDGE_PX, MAX_RAW_BYTES, MAX_ENCODED_BYTES, ALLOWED_TYPES;
|
|
9754
|
+
var init_preprocess = __esm({
|
|
9755
|
+
"src/stamps/geometry-2d/ai/vision/preprocess.ts"() {
|
|
9756
|
+
MAX_EDGE_PX = 2048;
|
|
9757
|
+
MAX_RAW_BYTES = 10 * 1024 * 1024;
|
|
9758
|
+
MAX_ENCODED_BYTES = 3 * 1024 * 1024;
|
|
9759
|
+
ALLOWED_TYPES = ["image/png", "image/jpeg", "image/webp"];
|
|
9760
|
+
}
|
|
9761
|
+
});
|
|
9334
9762
|
function AiFigurePrompt({ generator, onGenerated }) {
|
|
9335
9763
|
const {
|
|
9336
9764
|
prompt,
|
|
9337
9765
|
setPrompt,
|
|
9338
9766
|
isLoading,
|
|
9339
9767
|
error,
|
|
9768
|
+
notice,
|
|
9340
9769
|
submit,
|
|
9341
9770
|
cancel,
|
|
9342
9771
|
tokens
|
|
@@ -9350,20 +9779,136 @@ function AiFigurePrompt({ generator, onGenerated }) {
|
|
|
9350
9779
|
const id = setInterval(() => setElapsed((s) => s + 1), 1e3);
|
|
9351
9780
|
return () => clearInterval(id);
|
|
9352
9781
|
}, [isLoading]);
|
|
9782
|
+
const [noticeDismissed, setNoticeDismissed] = React19.useState(false);
|
|
9783
|
+
React19.useEffect(() => {
|
|
9784
|
+
setNoticeDismissed(false);
|
|
9785
|
+
}, [notice]);
|
|
9786
|
+
const [image, setImage] = React19.useState(null);
|
|
9787
|
+
const [ocrLoading, setOcrLoading] = React19.useState(false);
|
|
9788
|
+
const [ocrError, setOcrError] = React19.useState(null);
|
|
9789
|
+
const [ocrWarning, setOcrWarning] = React19.useState(null);
|
|
9790
|
+
const [isDragOver, setIsDragOver] = React19.useState(false);
|
|
9791
|
+
const fileInputRef = React19.useRef(null);
|
|
9353
9792
|
const textareaRef = React19.useRef(null);
|
|
9793
|
+
const imagePreview = image ? `data:${image.mediaType};base64,${image.base64}` : null;
|
|
9794
|
+
React19.useEffect(() => {
|
|
9795
|
+
setOcrError(null);
|
|
9796
|
+
setOcrWarning(null);
|
|
9797
|
+
}, [image]);
|
|
9798
|
+
const handleFile = React19.useCallback(
|
|
9799
|
+
async (file) => {
|
|
9800
|
+
if (isLoading || ocrLoading) return;
|
|
9801
|
+
const v = validateFile(file);
|
|
9802
|
+
if (!v.ok) {
|
|
9803
|
+
setOcrError(v.message);
|
|
9804
|
+
return;
|
|
9805
|
+
}
|
|
9806
|
+
try {
|
|
9807
|
+
const part = await fileToImagePart(file);
|
|
9808
|
+
setImage(part);
|
|
9809
|
+
} catch (e) {
|
|
9810
|
+
setOcrError(e instanceof Error ? e.message : "Kh\xF4ng decode \u0111\u01B0\u1EE3c \u1EA3nh");
|
|
9811
|
+
}
|
|
9812
|
+
},
|
|
9813
|
+
[isLoading, ocrLoading]
|
|
9814
|
+
);
|
|
9815
|
+
const handleFileInput = React19.useCallback(
|
|
9816
|
+
(e) => {
|
|
9817
|
+
const file = e.target.files?.[0];
|
|
9818
|
+
if (file) void handleFile(file);
|
|
9819
|
+
e.target.value = "";
|
|
9820
|
+
},
|
|
9821
|
+
[handleFile]
|
|
9822
|
+
);
|
|
9823
|
+
const handlePaste = React19.useCallback(
|
|
9824
|
+
(e) => {
|
|
9825
|
+
const item = Array.from(e.clipboardData.items).find(
|
|
9826
|
+
(it) => it.kind === "file" && it.type.startsWith("image/")
|
|
9827
|
+
);
|
|
9828
|
+
if (!item) return;
|
|
9829
|
+
const file = item.getAsFile();
|
|
9830
|
+
if (!file) return;
|
|
9831
|
+
e.preventDefault();
|
|
9832
|
+
void handleFile(file);
|
|
9833
|
+
},
|
|
9834
|
+
[handleFile]
|
|
9835
|
+
);
|
|
9836
|
+
const handleDrop = React19.useCallback(
|
|
9837
|
+
(e) => {
|
|
9838
|
+
e.preventDefault();
|
|
9839
|
+
setIsDragOver(false);
|
|
9840
|
+
const file = Array.from(e.dataTransfer.files).find(
|
|
9841
|
+
(f) => f.type.startsWith("image/")
|
|
9842
|
+
);
|
|
9843
|
+
if (file) void handleFile(file);
|
|
9844
|
+
},
|
|
9845
|
+
[handleFile]
|
|
9846
|
+
);
|
|
9847
|
+
const runOcr = React19.useCallback(async () => {
|
|
9848
|
+
if (!image) return;
|
|
9849
|
+
setOcrLoading(true);
|
|
9850
|
+
setOcrError(null);
|
|
9851
|
+
setOcrWarning(null);
|
|
9852
|
+
try {
|
|
9853
|
+
const r = await handleExtractProblem(image);
|
|
9854
|
+
if (r.kind === "success" || r.kind === "low-confidence") {
|
|
9855
|
+
setPrompt(r.text);
|
|
9856
|
+
if (r.kind === "low-confidence") setOcrWarning(r.warning);
|
|
9857
|
+
requestAnimationFrame(() => textareaRef.current?.focus());
|
|
9858
|
+
} else {
|
|
9859
|
+
setOcrError(r.message);
|
|
9860
|
+
}
|
|
9861
|
+
} finally {
|
|
9862
|
+
setOcrLoading(false);
|
|
9863
|
+
}
|
|
9864
|
+
}, [image, setPrompt]);
|
|
9354
9865
|
const handleSendClick = React19.useCallback(async () => {
|
|
9866
|
+
if (image && !prompt.trim() && !ocrLoading) {
|
|
9867
|
+
await runOcr();
|
|
9868
|
+
return;
|
|
9869
|
+
}
|
|
9355
9870
|
const generated = await submit();
|
|
9356
9871
|
if (generated) onGenerated(generated);
|
|
9357
|
-
}, [submit, onGenerated]);
|
|
9872
|
+
}, [image, prompt, ocrLoading, runOcr, submit, onGenerated]);
|
|
9358
9873
|
const promptEmpty = !prompt.trim();
|
|
9359
|
-
const
|
|
9874
|
+
const willOcr = image != null && promptEmpty;
|
|
9875
|
+
const sendDisabled = !image && promptEmpty || ocrLoading || isLoading && !willOcr;
|
|
9876
|
+
const placeholder = willOcr ? "B\u1EA5m g\u1EEDi \u0111\u1EC3 \u0111\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh \u2014 ho\u1EB7c t\u1EF1 g\xF5 \u1EDF \u0111\xE2y\u2026" : "M\xF4 t\u1EA3 \u0111\u1EC1 b\xE0i c\u1EA7n d\u1EF1ng \u2014 ho\u1EB7c d\xE1n/\u0111\xEDnh \u1EA3nh \u0111\u1EC1 (Ctrl+V).";
|
|
9360
9877
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-slate-200 bg-slate-50 px-3 py-3", children: [
|
|
9361
9878
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 flex items-center justify-between gap-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium tracking-wide text-slate-600", children: "D\u1EF1ng h\xECnh b\u1EB1ng AI" }) }),
|
|
9362
9879
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9363
9880
|
"div",
|
|
9364
9881
|
{
|
|
9365
|
-
|
|
9882
|
+
onDragOver: (e) => {
|
|
9883
|
+
e.preventDefault();
|
|
9884
|
+
setIsDragOver(true);
|
|
9885
|
+
},
|
|
9886
|
+
onDragLeave: () => setIsDragOver(false),
|
|
9887
|
+
onDrop: handleDrop,
|
|
9888
|
+
onPaste: handlePaste,
|
|
9889
|
+
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" : ""),
|
|
9366
9890
|
children: [
|
|
9891
|
+
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: [
|
|
9892
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9893
|
+
"img",
|
|
9894
|
+
{
|
|
9895
|
+
src: imagePreview,
|
|
9896
|
+
alt: "\u1EA2nh \u0111\u1EC1 b\xE0i",
|
|
9897
|
+
className: "max-h-48 max-w-full h-auto w-auto rounded-lg border border-slate-200 shadow-sm"
|
|
9898
|
+
}
|
|
9899
|
+
),
|
|
9900
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9901
|
+
"button",
|
|
9902
|
+
{
|
|
9903
|
+
type: "button",
|
|
9904
|
+
onClick: () => setImage(null),
|
|
9905
|
+
disabled: ocrLoading || isLoading,
|
|
9906
|
+
"aria-label": "Xo\xE1 \u1EA3nh",
|
|
9907
|
+
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",
|
|
9908
|
+
children: "\xD7"
|
|
9909
|
+
}
|
|
9910
|
+
)
|
|
9911
|
+
] }) }),
|
|
9367
9912
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9368
9913
|
"textarea",
|
|
9369
9914
|
{
|
|
@@ -9381,48 +9926,111 @@ function AiFigurePrompt({ generator, onGenerated }) {
|
|
|
9381
9926
|
},
|
|
9382
9927
|
disabled: isLoading,
|
|
9383
9928
|
rows: 2,
|
|
9384
|
-
placeholder
|
|
9929
|
+
placeholder,
|
|
9385
9930
|
className: "block w-full resize-none rounded-2xl bg-transparent px-3.5 pt-2.5 pb-1 text-sm leading-relaxed text-slate-800 placeholder:text-slate-400 outline-none disabled:opacity-60 field-sizing-content max-h-44"
|
|
9386
9931
|
}
|
|
9387
9932
|
),
|
|
9388
|
-
/* @__PURE__ */ jsxRuntime.
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9933
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 px-2 pb-2 pt-1", children: [
|
|
9934
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
9935
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9936
|
+
"button",
|
|
9937
|
+
{
|
|
9938
|
+
type: "button",
|
|
9939
|
+
onClick: () => fileInputRef.current?.click(),
|
|
9940
|
+
disabled: isLoading || ocrLoading,
|
|
9941
|
+
"aria-label": "\u0110\xEDnh \u1EA3nh \u0111\u1EC1 b\xE0i",
|
|
9942
|
+
title: "\u0110\xEDnh \u1EA3nh (c\u0169ng c\xF3 th\u1EC3 d\xE1n b\u1EB1ng Ctrl+V ho\u1EB7c k\xE9o th\u1EA3)",
|
|
9943
|
+
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",
|
|
9944
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(PaperclipIcon, { className: "h-[18px] w-[18px]" })
|
|
9945
|
+
}
|
|
9946
|
+
),
|
|
9947
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9948
|
+
"input",
|
|
9949
|
+
{
|
|
9950
|
+
ref: fileInputRef,
|
|
9951
|
+
type: "file",
|
|
9952
|
+
accept: "image/png,image/jpeg,image/webp",
|
|
9953
|
+
className: "sr-only",
|
|
9954
|
+
onChange: handleFileInput,
|
|
9955
|
+
disabled: isLoading || ocrLoading,
|
|
9956
|
+
"aria-label": "Ch\u1ECDn \u1EA3nh \u0111\u1EC1 b\xE0i"
|
|
9957
|
+
}
|
|
9958
|
+
)
|
|
9959
|
+
] }),
|
|
9960
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
9961
|
+
(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` }),
|
|
9962
|
+
isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
9963
|
+
"button",
|
|
9964
|
+
{
|
|
9965
|
+
type: "button",
|
|
9966
|
+
onClick: cancel,
|
|
9967
|
+
"aria-label": "Hu\u1EF7 d\u1EF1ng h\xECnh AI",
|
|
9968
|
+
"data-testid": "geometry-ai-cancel",
|
|
9969
|
+
title: `\u0110ang d\u1EF1ng\u2026 ${elapsed}s \u2014 b\u1EA5m \u0111\u1EC3 hu\u1EF7`,
|
|
9970
|
+
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",
|
|
9971
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(StopIcon, { className: "h-3.5 w-3.5" })
|
|
9972
|
+
}
|
|
9973
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
9974
|
+
"button",
|
|
9975
|
+
{
|
|
9976
|
+
type: "button",
|
|
9977
|
+
onClick: () => void handleSendClick(),
|
|
9978
|
+
disabled: sendDisabled,
|
|
9979
|
+
"aria-label": willOcr ? "\u0110\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh" : "D\u1EF1ng b\u1EB1ng AI",
|
|
9980
|
+
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)",
|
|
9981
|
+
"data-testid": willOcr ? "geometry-ai-ocr" : "geometry-ai-submit",
|
|
9982
|
+
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",
|
|
9983
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ArrowUpIcon, { className: "h-[18px] w-[18px]" })
|
|
9984
|
+
}
|
|
9985
|
+
)
|
|
9986
|
+
] })
|
|
9987
|
+
] }),
|
|
9988
|
+
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" })
|
|
9415
9989
|
]
|
|
9416
9990
|
}
|
|
9417
9991
|
),
|
|
9418
|
-
|
|
9992
|
+
ocrWarning && /* @__PURE__ */ jsxRuntime.jsx(
|
|
9993
|
+
"p",
|
|
9994
|
+
{
|
|
9995
|
+
className: "mt-1 px-1 text-xs text-amber-700",
|
|
9996
|
+
"data-testid": "geometry-ai-ocr-warning",
|
|
9997
|
+
children: ocrWarning
|
|
9998
|
+
}
|
|
9999
|
+
),
|
|
10000
|
+
ocrError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: ocrError }),
|
|
10001
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error }),
|
|
10002
|
+
notice && !noticeDismissed && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10003
|
+
"div",
|
|
10004
|
+
{
|
|
10005
|
+
role: "status",
|
|
10006
|
+
"data-testid": "geometry-ai-partial-notice",
|
|
10007
|
+
className: "relative mt-2 whitespace-pre-wrap rounded-lg border border-amber-200 bg-amber-50 py-2 pl-3 pr-8 text-xs leading-relaxed text-amber-800",
|
|
10008
|
+
children: [
|
|
10009
|
+
notice,
|
|
10010
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10011
|
+
"button",
|
|
10012
|
+
{
|
|
10013
|
+
type: "button",
|
|
10014
|
+
onClick: () => setNoticeDismissed(true),
|
|
10015
|
+
"aria-label": "\u0110\xF3ng th\xF4ng b\xE1o",
|
|
10016
|
+
title: "\u0110\xF3ng th\xF4ng b\xE1o",
|
|
10017
|
+
"data-testid": "geometry-ai-partial-dismiss",
|
|
10018
|
+
className: "absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded text-amber-500 transition hover:bg-amber-100 hover:text-amber-800",
|
|
10019
|
+
children: "\xD7"
|
|
10020
|
+
}
|
|
10021
|
+
)
|
|
10022
|
+
]
|
|
10023
|
+
}
|
|
10024
|
+
)
|
|
9419
10025
|
] });
|
|
9420
10026
|
}
|
|
9421
|
-
var ArrowUpIcon, StopIcon;
|
|
10027
|
+
var ArrowUpIcon, StopIcon, PaperclipIcon;
|
|
9422
10028
|
var init_AiFigurePrompt = __esm({
|
|
9423
10029
|
"src/stamps/geometry-2d/editor/AiFigurePrompt.tsx"() {
|
|
9424
10030
|
"use client";
|
|
9425
10031
|
init_useAiFigure();
|
|
10032
|
+
init_handleExtractProblem();
|
|
10033
|
+
init_preprocess();
|
|
9426
10034
|
ArrowUpIcon = (props) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9427
10035
|
"svg",
|
|
9428
10036
|
{
|
|
@@ -9441,6 +10049,20 @@ var init_AiFigurePrompt = __esm({
|
|
|
9441
10049
|
}
|
|
9442
10050
|
);
|
|
9443
10051
|
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" }) });
|
|
10052
|
+
PaperclipIcon = (props) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
10053
|
+
"svg",
|
|
10054
|
+
{
|
|
10055
|
+
viewBox: "0 0 24 24",
|
|
10056
|
+
fill: "none",
|
|
10057
|
+
stroke: "currentColor",
|
|
10058
|
+
strokeWidth: 1.75,
|
|
10059
|
+
strokeLinecap: "round",
|
|
10060
|
+
strokeLinejoin: "round",
|
|
10061
|
+
"aria-hidden": true,
|
|
10062
|
+
...props,
|
|
10063
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" })
|
|
10064
|
+
}
|
|
10065
|
+
);
|
|
9444
10066
|
}
|
|
9445
10067
|
});
|
|
9446
10068
|
|
|
@@ -10024,11 +10646,17 @@ async function insertStampImage(api, opts) {
|
|
|
10024
10646
|
const elements = api.getSceneElements();
|
|
10025
10647
|
const editingId = opts.editingElementId ?? null;
|
|
10026
10648
|
if (editingId) {
|
|
10649
|
+
const old = opts.preserveExistingSize ? elements.find((e) => e.id === editingId) : void 0;
|
|
10650
|
+
const oldLongest = old ? Math.max(old.width ?? 0, old.height ?? 0) : 0;
|
|
10651
|
+
const newLongest = Math.max(width, height);
|
|
10652
|
+
const scale3 = oldLongest > 0 && newLongest > 0 ? oldLongest / newLongest : 1;
|
|
10653
|
+
const w = width * scale3;
|
|
10654
|
+
const h = height * scale3;
|
|
10027
10655
|
const updated = elements.map(
|
|
10028
|
-
(e) => e.id === editingId ? { ...e, fileId, customData, width, height } : e
|
|
10656
|
+
(e) => e.id === editingId ? { ...e, fileId, customData, width: w, height: h } : e
|
|
10029
10657
|
);
|
|
10030
10658
|
api.updateScene({ elements: updated, appState: clearAppStateAfterInsert() });
|
|
10031
|
-
return { fileId, width, height, elementId: editingId };
|
|
10659
|
+
return { fileId, width: w, height: h, elementId: editingId };
|
|
10032
10660
|
}
|
|
10033
10661
|
const newElement = buildStampImageElement(
|
|
10034
10662
|
api,
|
|
@@ -10182,8 +10810,11 @@ function serializePoint(obj, state) {
|
|
|
10182
10810
|
};
|
|
10183
10811
|
}
|
|
10184
10812
|
case "arcMidpoint": {
|
|
10185
|
-
const
|
|
10186
|
-
|
|
10813
|
+
const containment = c.containing ?? c.notContaining;
|
|
10814
|
+
const baseRefs = [c.circle, c.a, c.b];
|
|
10815
|
+
if (containment) baseRefs.push(containment);
|
|
10816
|
+
const refs = resolveRefs(baseRefs, state);
|
|
10817
|
+
if (!refs) return fail("unresolved-ref", baseRefs.join(","));
|
|
10187
10818
|
return {
|
|
10188
10819
|
ok: true,
|
|
10189
10820
|
entity: {
|
|
@@ -10192,7 +10823,7 @@ function serializePoint(obj, state) {
|
|
|
10192
10823
|
circle: refs[0],
|
|
10193
10824
|
a: refs[1],
|
|
10194
10825
|
b: refs[2],
|
|
10195
|
-
notContaining: refs[3]
|
|
10826
|
+
...c.containing ? { containing: refs[3] } : c.notContaining ? { notContaining: refs[3] } : {}
|
|
10196
10827
|
}
|
|
10197
10828
|
};
|
|
10198
10829
|
}
|
|
@@ -10236,6 +10867,7 @@ function serializePoint(obj, state) {
|
|
|
10236
10867
|
case "onPerpendicular":
|
|
10237
10868
|
case "onPerpBisector":
|
|
10238
10869
|
case "onCircleAroundPoint":
|
|
10870
|
+
case "mixtilinearPoint":
|
|
10239
10871
|
return fail("unsupported-constraint", c.kind);
|
|
10240
10872
|
default: {
|
|
10241
10873
|
return fail("unsupported-constraint");
|
|
@@ -10538,9 +11170,11 @@ function describeEntity(e) {
|
|
|
10538
11170
|
return `${e.name} = \u0111\u01B0\u1EDDng tr\xF2n b\xE0ng ti\u1EBFp ${e.vertices.join("")} \u0111\u1ED1i di\u1EC7n ${e.opposite}`;
|
|
10539
11171
|
// Cụm A
|
|
10540
11172
|
case "arcMidpoint":
|
|
10541
|
-
return `${e.name} = trung \u0111i\u1EC3m cung ${e.a}${e.b} (kh\xF4ng ch\u1EE9a ${e.notContaining}) tr\xEAn ${e.circle}`;
|
|
11173
|
+
return `${e.name} = trung \u0111i\u1EC3m cung ${e.a}${e.b}${e.containing || e.notContaining ? ` (${e.containing ? "ch\u1EE9a" : "kh\xF4ng ch\u1EE9a"} ${e.containing ?? e.notContaining})` : ""} tr\xEAn ${e.circle}`;
|
|
10542
11174
|
case "excenter":
|
|
10543
11175
|
return `${e.name} = t\xE2m b\xE0ng ti\u1EBFp ${e.vertices.join("")} \u0111\u1ED1i di\u1EC7n ${e.opposite}`;
|
|
11176
|
+
case "mixtilinearPoint":
|
|
11177
|
+
return `${e.name} = ${e.which === "center" ? "t\xE2m" : "ti\u1EBFp \u0111i\u1EC3m"} mixtilinear ${e.vertices.join("")}`;
|
|
10544
11178
|
case "reflectPoint":
|
|
10545
11179
|
return `${e.name} = \u0111\u1ED1i x\u1EE9ng ${e.of} qua \u0111i\u1EC3m ${e.through}`;
|
|
10546
11180
|
case "reflectLine":
|
|
@@ -10551,6 +11185,8 @@ function describeEntity(e) {
|
|
|
10551
11185
|
const distStr = d.kind === "circleRadius" ? `r(${d.circle})` : d.kind === "segmentLength" ? `|${d.p1}${d.p2}|` : `${d.value}`;
|
|
10552
11186
|
return `${e.name} = \u0111i\u1EC3m tr\xEAn tia ${e.from}${e.through} c\xE1ch ${e.through} m\u1ED9t kho\u1EA3ng ${distStr}`;
|
|
10553
11187
|
}
|
|
11188
|
+
case "onPerpBisector":
|
|
11189
|
+
return `${e.name} = \u0111i\u1EC3m tr\xEAn trung tr\u1EF1c ${e.p1}${e.p2}`;
|
|
10554
11190
|
default: {
|
|
10555
11191
|
return "";
|
|
10556
11192
|
}
|
|
@@ -10673,7 +11309,8 @@ var init_host = __esm({
|
|
|
10673
11309
|
version: 1,
|
|
10674
11310
|
jsonState
|
|
10675
11311
|
}),
|
|
10676
|
-
editingElementId: editingElement?.id ?? null
|
|
11312
|
+
editingElementId: editingElement?.id ?? null,
|
|
11313
|
+
preserveExistingSize: true
|
|
10677
11314
|
});
|
|
10678
11315
|
} catch (err) {
|
|
10679
11316
|
console.error("Geometry insert failed:", err);
|
|
@@ -14216,7 +14853,8 @@ var init_host3 = __esm({
|
|
|
14216
14853
|
version: 2,
|
|
14217
14854
|
jsonState
|
|
14218
14855
|
}),
|
|
14219
|
-
editingElementId: editingElement?.id ?? null
|
|
14856
|
+
editingElementId: editingElement?.id ?? null,
|
|
14857
|
+
preserveExistingSize: true
|
|
14220
14858
|
});
|
|
14221
14859
|
onClose();
|
|
14222
14860
|
},
|
|
@@ -15519,7 +16157,8 @@ var init_host4 = __esm({
|
|
|
15519
16157
|
version: 2,
|
|
15520
16158
|
jsonState
|
|
15521
16159
|
}),
|
|
15522
|
-
editingElementId: editingElement?.id ?? null
|
|
16160
|
+
editingElementId: editingElement?.id ?? null,
|
|
16161
|
+
preserveExistingSize: true
|
|
15523
16162
|
});
|
|
15524
16163
|
} catch (err) {
|
|
15525
16164
|
console.error("Graph2D insert failed:", err);
|
|
@@ -17944,6 +18583,9 @@ function Whiteboard({
|
|
|
17944
18583
|
] });
|
|
17945
18584
|
}
|
|
17946
18585
|
|
|
18586
|
+
// src/index.ts
|
|
18587
|
+
init_handleExtractProblem();
|
|
18588
|
+
|
|
17947
18589
|
exports.ALL_STAMPS = ALL_STAMPS;
|
|
17948
18590
|
exports.DEFAULT_STAMPS = DEFAULT_STAMPS;
|
|
17949
18591
|
exports.EXPERIMENTAL_STAMPS = EXPERIMENTAL_STAMPS;
|
|
@@ -17957,6 +18599,7 @@ exports.findStampForCustomData = findStampForCustomData;
|
|
|
17957
18599
|
exports.geometry3dStamp = geometry3dStamp;
|
|
17958
18600
|
exports.geometryStamp = geometryStamp;
|
|
17959
18601
|
exports.graph2dStamp = graph2dStamp;
|
|
18602
|
+
exports.handleExtractProblem = handleExtractProblem;
|
|
17960
18603
|
exports.insertPdfPages = insertPdfPages;
|
|
17961
18604
|
exports.insertRasterizedPagesIntoScene = insertRasterizedPagesIntoScene;
|
|
17962
18605
|
exports.isStampElement = isStampElement;
|