@xom11/whiteboard 0.24.2 → 0.25.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/README.md +84 -11
- package/dist/ai.d.mts +422 -566
- package/dist/ai.d.ts +422 -566
- package/dist/ai.js +1527 -407
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +1008 -512
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +4 -4
- package/dist/{chunk-BKSXPNPQ.mjs → chunk-AYSFWUPK.mjs} +4 -3
- package/dist/chunk-AYSFWUPK.mjs.map +1 -0
- package/dist/chunk-B4NJJZFR.mjs +18 -0
- package/dist/chunk-B4NJJZFR.mjs.map +1 -0
- package/dist/{chunk-LVNCYP4J.mjs → chunk-CJBLJUWG.mjs} +5 -5
- package/dist/{chunk-LVNCYP4J.mjs.map → chunk-CJBLJUWG.mjs.map} +1 -1
- package/dist/{chunk-7WQXXEVR.mjs → chunk-ESVPQWHX.mjs} +5 -5
- package/dist/{chunk-7WQXXEVR.mjs.map → chunk-ESVPQWHX.mjs.map} +1 -1
- package/dist/{chunk-KRC2XOIG.mjs → chunk-I24QOHPU.mjs} +3 -3
- package/dist/{chunk-KRC2XOIG.mjs.map → chunk-I24QOHPU.mjs.map} +1 -1
- package/dist/{chunk-ZBJBQKJ2.mjs → chunk-IHUFOV7L.mjs} +4 -19
- package/dist/chunk-IHUFOV7L.mjs.map +1 -0
- package/dist/{chunk-AZIARTGX.mjs → chunk-M42TGYT6.mjs} +3 -3
- package/dist/{chunk-AZIARTGX.mjs.map → chunk-M42TGYT6.mjs.map} +1 -1
- package/dist/{chunk-45CGKJ7S.mjs → chunk-NDEZJKNY.mjs} +4 -4
- package/dist/{chunk-45CGKJ7S.mjs.map → chunk-NDEZJKNY.mjs.map} +1 -1
- package/dist/{chunk-BEZSQKPY.mjs → chunk-ONBCUWVI.mjs} +5 -4
- package/dist/chunk-ONBCUWVI.mjs.map +1 -0
- package/dist/{chunk-WM2VDYQA.mjs → chunk-REIJZDVZ.mjs} +4 -3
- package/dist/chunk-REIJZDVZ.mjs.map +1 -0
- package/dist/{chunk-2WF6KIGF.mjs → chunk-TB4CL25L.mjs} +9 -8
- package/dist/chunk-TB4CL25L.mjs.map +1 -0
- package/dist/chunk-VNCCIV6O.mjs +938 -0
- package/dist/chunk-VNCCIV6O.mjs.map +1 -0
- package/dist/{chunk-CGZZO4BX.mjs → chunk-VRHWDZ66.mjs} +5 -5
- package/dist/{chunk-CGZZO4BX.mjs.map → chunk-VRHWDZ66.mjs.map} +1 -1
- package/dist/{chunk-4DS3MKID.mjs → chunk-YSJOVBCD.mjs} +4 -4
- package/dist/{chunk-4DS3MKID.mjs.map → chunk-YSJOVBCD.mjs.map} +1 -1
- package/dist/geometry-2d.d.mts +2 -2
- package/dist/geometry-2d.d.ts +2 -2
- package/dist/geometry-2d.js +1383 -23
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +6 -5
- package/dist/geometry-3d.d.mts +2 -2
- package/dist/geometry-3d.d.ts +2 -2
- package/dist/geometry-3d.js +2 -2
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +5 -4
- package/dist/graph-2d.d.mts +2 -2
- package/dist/graph-2d.d.ts +2 -2
- package/dist/graph-2d.js +2 -2
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +8 -7
- package/dist/{host-ZIQ77W33.mjs → host-A64ITWVX.mjs} +7 -6
- package/dist/host-A64ITWVX.mjs.map +1 -0
- package/dist/{host-EPZCNFLH.mjs → host-L7FMFZUW.mjs} +226 -29
- package/dist/host-L7FMFZUW.mjs.map +1 -0
- package/dist/{host-LKCMYEAV.mjs → host-QK53UYMD.mjs} +11 -10
- package/dist/host-QK53UYMD.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1414 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -17
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +2 -2
- package/dist/latex.d.ts +2 -2
- package/dist/render-3WTY7NZB.mjs +9 -0
- package/dist/{render-SA4JTOW3.mjs.map → render-3WTY7NZB.mjs.map} +1 -1
- package/dist/serialize-SRJVKYUG.mjs +8 -0
- package/dist/{serialize-JAVOU22E.mjs.map → serialize-SRJVKYUG.mjs.map} +1 -1
- package/dist/{types-vtvyKGAA.d.mts → types-DWRyCa2m.d.mts} +187 -2
- package/dist/{types-Crbefnfe.d.ts → types-DWRyCa2m.d.ts} +187 -2
- package/package.json +1 -1
- package/dist/chunk-2WF6KIGF.mjs.map +0 -1
- package/dist/chunk-BEZSQKPY.mjs.map +0 -1
- package/dist/chunk-BKSXPNPQ.mjs.map +0 -1
- package/dist/chunk-WM2VDYQA.mjs.map +0 -1
- package/dist/chunk-ZBJBQKJ2.mjs.map +0 -1
- package/dist/host-EPZCNFLH.mjs.map +0 -1
- package/dist/host-LKCMYEAV.mjs.map +0 -1
- package/dist/host-ZIQ77W33.mjs.map +0 -1
- package/dist/render-SA4JTOW3.mjs +0 -8
- package/dist/serialize-JAVOU22E.mjs +0 -7
- package/dist/types-DxlMPh-6.d.mts +0 -49
- package/dist/types-DxlMPh-6.d.ts +0 -49
package/dist/ai.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var zod = require('zod');
|
|
4
|
-
var Anthropic = require('@anthropic-ai/sdk');
|
|
5
4
|
var zodToJsonSchema = require('zod-to-json-schema');
|
|
5
|
+
var Anthropic = require('@anthropic-ai/sdk');
|
|
6
6
|
|
|
7
7
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
8
|
|
|
@@ -10,143 +10,628 @@ var Anthropic__default = /*#__PURE__*/_interopDefault(Anthropic);
|
|
|
10
10
|
|
|
11
11
|
// src/stamps/geometry-2d/dsl/schema.ts
|
|
12
12
|
var NameZ = zod.z.string().regex(/^[A-Za-z][A-Za-z0-9_'₀-₉]{0,11}$/);
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
// src/stamps/geometry-2d/dsl/kinds/_types.ts
|
|
15
|
+
function defineModule(m) {
|
|
16
|
+
return m;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/stamps/geometry-2d/dsl/kinds/_shared.ts
|
|
20
|
+
var POINT_BASE_FIELDS = {
|
|
21
|
+
visible: true,
|
|
22
|
+
locked: false,
|
|
23
|
+
layer: "default",
|
|
24
|
+
schemaVersion: 1
|
|
25
|
+
};
|
|
26
|
+
function emitPointObject(id, name, constraint) {
|
|
27
|
+
return {
|
|
28
|
+
id,
|
|
29
|
+
kind: "point",
|
|
30
|
+
label: name,
|
|
31
|
+
...POINT_BASE_FIELDS,
|
|
32
|
+
attrs: { constraint }
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function resolveTriangleVertices(ctx, vertices) {
|
|
36
|
+
return [ctx.resolveId(vertices[0]), ctx.resolveId(vertices[1]), ctx.resolveId(vertices[2])];
|
|
37
|
+
}
|
|
38
|
+
var SHAPE_BASE_FIELDS = {
|
|
39
|
+
visible: true,
|
|
40
|
+
locked: false,
|
|
41
|
+
layer: "default",
|
|
42
|
+
schemaVersion: 1
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/stamps/geometry-2d/dsl/kinds/points/free.ts
|
|
46
|
+
var freeModule = defineModule({
|
|
47
|
+
kind: "free",
|
|
48
|
+
role: "point",
|
|
49
|
+
category: "points",
|
|
50
|
+
prefix: "p",
|
|
51
|
+
schema: zod.z.object({
|
|
15
52
|
name: NameZ,
|
|
16
53
|
kind: zod.z.literal("free"),
|
|
17
54
|
x: zod.z.number().finite(),
|
|
18
55
|
y: zod.z.number().finite()
|
|
19
56
|
}),
|
|
20
|
-
|
|
57
|
+
collectRefs: () => [],
|
|
58
|
+
emit: (e, ctx) => [{
|
|
59
|
+
role: "primary",
|
|
60
|
+
object: emitPointObject(ctx.resolveId(e.name), e.name, { kind: "free", x: e.x, y: e.y })
|
|
61
|
+
}]
|
|
62
|
+
});
|
|
63
|
+
var midpointModule = defineModule({
|
|
64
|
+
kind: "midpoint",
|
|
65
|
+
role: "point",
|
|
66
|
+
category: "points",
|
|
67
|
+
prefix: "p",
|
|
68
|
+
schema: zod.z.object({
|
|
21
69
|
name: NameZ,
|
|
22
70
|
kind: zod.z.literal("midpoint"),
|
|
23
71
|
p1: NameZ,
|
|
24
72
|
p2: NameZ
|
|
25
73
|
}),
|
|
26
|
-
|
|
74
|
+
collectRefs: (e) => [e.p1, e.p2],
|
|
75
|
+
emit: (e, ctx) => [{
|
|
76
|
+
role: "primary",
|
|
77
|
+
object: emitPointObject(
|
|
78
|
+
ctx.resolveId(e.name),
|
|
79
|
+
e.name,
|
|
80
|
+
{ kind: "midpoint", p1: ctx.resolveId(e.p1), p2: ctx.resolveId(e.p2) }
|
|
81
|
+
)
|
|
82
|
+
}]
|
|
83
|
+
});
|
|
84
|
+
var onSegmentModule = defineModule({
|
|
85
|
+
kind: "onSegment",
|
|
86
|
+
role: "point",
|
|
87
|
+
category: "points",
|
|
88
|
+
prefix: "p",
|
|
89
|
+
schema: zod.z.object({
|
|
27
90
|
name: NameZ,
|
|
28
91
|
kind: zod.z.literal("onSegment"),
|
|
29
92
|
segmentId: NameZ,
|
|
30
93
|
t: zod.z.number().min(0).max(1)
|
|
31
94
|
}),
|
|
32
|
-
|
|
95
|
+
collectRefs: (e) => [e.segmentId],
|
|
96
|
+
emit: (e, ctx) => [{
|
|
97
|
+
role: "primary",
|
|
98
|
+
object: emitPointObject(
|
|
99
|
+
ctx.resolveId(e.name),
|
|
100
|
+
e.name,
|
|
101
|
+
{ kind: "onSegment", segmentId: ctx.resolveId(e.segmentId), t: e.t }
|
|
102
|
+
)
|
|
103
|
+
}]
|
|
104
|
+
});
|
|
105
|
+
var onLineModule = defineModule({
|
|
106
|
+
kind: "onLine",
|
|
107
|
+
role: "point",
|
|
108
|
+
category: "points",
|
|
109
|
+
prefix: "p",
|
|
110
|
+
schema: zod.z.object({
|
|
33
111
|
name: NameZ,
|
|
34
112
|
kind: zod.z.literal("onLine"),
|
|
35
113
|
lineId: NameZ,
|
|
36
114
|
t: zod.z.number().finite()
|
|
37
115
|
}),
|
|
38
|
-
|
|
116
|
+
collectRefs: (e) => [e.lineId],
|
|
117
|
+
emit: (e, ctx) => [{
|
|
118
|
+
role: "primary",
|
|
119
|
+
object: emitPointObject(
|
|
120
|
+
ctx.resolveId(e.name),
|
|
121
|
+
e.name,
|
|
122
|
+
{ kind: "onLine", lineId: ctx.resolveId(e.lineId), t: e.t }
|
|
123
|
+
)
|
|
124
|
+
}]
|
|
125
|
+
});
|
|
126
|
+
var onCircleModule = defineModule({
|
|
127
|
+
kind: "onCircle",
|
|
128
|
+
role: "point",
|
|
129
|
+
category: "points",
|
|
130
|
+
prefix: "p",
|
|
131
|
+
schema: zod.z.object({
|
|
39
132
|
name: NameZ,
|
|
40
133
|
kind: zod.z.literal("onCircle"),
|
|
41
134
|
circleId: NameZ,
|
|
42
135
|
theta: zod.z.number().finite()
|
|
43
136
|
}),
|
|
44
|
-
|
|
137
|
+
collectRefs: (e) => [e.circleId],
|
|
138
|
+
emit: (e, ctx) => [{
|
|
139
|
+
role: "primary",
|
|
140
|
+
object: emitPointObject(
|
|
141
|
+
ctx.resolveId(e.name),
|
|
142
|
+
e.name,
|
|
143
|
+
{ kind: "onCircle", circleId: ctx.resolveId(e.circleId), theta: e.theta }
|
|
144
|
+
)
|
|
145
|
+
}]
|
|
146
|
+
});
|
|
147
|
+
var perpFootModule = defineModule({
|
|
148
|
+
kind: "perpFoot",
|
|
149
|
+
role: "point",
|
|
150
|
+
category: "points",
|
|
151
|
+
prefix: "p",
|
|
152
|
+
schema: zod.z.object({
|
|
45
153
|
name: NameZ,
|
|
46
154
|
kind: zod.z.literal("perpFoot"),
|
|
47
155
|
from: NameZ,
|
|
48
156
|
onLine: NameZ
|
|
49
157
|
}),
|
|
50
|
-
|
|
158
|
+
collectRefs: (e) => [e.from, e.onLine],
|
|
159
|
+
emit: (e, ctx) => [{
|
|
160
|
+
role: "primary",
|
|
161
|
+
object: emitPointObject(
|
|
162
|
+
ctx.resolveId(e.name),
|
|
163
|
+
e.name,
|
|
164
|
+
{ kind: "perpFoot", from: ctx.resolveId(e.from), onLine: ctx.resolveId(e.onLine) }
|
|
165
|
+
)
|
|
166
|
+
}]
|
|
167
|
+
});
|
|
168
|
+
var circumcenterModule = defineModule({
|
|
169
|
+
kind: "circumcenter",
|
|
170
|
+
role: "point",
|
|
171
|
+
category: "points",
|
|
172
|
+
prefix: "p",
|
|
173
|
+
schema: zod.z.object({
|
|
51
174
|
name: NameZ,
|
|
52
175
|
kind: zod.z.literal("circumcenter"),
|
|
53
176
|
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
54
177
|
}),
|
|
55
|
-
|
|
178
|
+
collectRefs: (e) => [...e.vertices],
|
|
179
|
+
emit: (e, ctx) => [{
|
|
180
|
+
role: "primary",
|
|
181
|
+
object: emitPointObject(
|
|
182
|
+
ctx.resolveId(e.name),
|
|
183
|
+
e.name,
|
|
184
|
+
{ kind: "circumcenter", vertices: resolveTriangleVertices(ctx, e.vertices) }
|
|
185
|
+
)
|
|
186
|
+
}]
|
|
187
|
+
});
|
|
188
|
+
var incenterModule = defineModule({
|
|
189
|
+
kind: "incenter",
|
|
190
|
+
role: "point",
|
|
191
|
+
category: "points",
|
|
192
|
+
prefix: "p",
|
|
193
|
+
schema: zod.z.object({
|
|
56
194
|
name: NameZ,
|
|
57
195
|
kind: zod.z.literal("incenter"),
|
|
58
196
|
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
59
197
|
}),
|
|
60
|
-
|
|
198
|
+
collectRefs: (e) => [...e.vertices],
|
|
199
|
+
emit: (e, ctx) => [{
|
|
200
|
+
role: "primary",
|
|
201
|
+
object: emitPointObject(
|
|
202
|
+
ctx.resolveId(e.name),
|
|
203
|
+
e.name,
|
|
204
|
+
{ kind: "incenter", vertices: resolveTriangleVertices(ctx, e.vertices) }
|
|
205
|
+
)
|
|
206
|
+
}]
|
|
207
|
+
});
|
|
208
|
+
var centroidModule = defineModule({
|
|
209
|
+
kind: "centroid",
|
|
210
|
+
role: "point",
|
|
211
|
+
category: "points",
|
|
212
|
+
prefix: "p",
|
|
213
|
+
schema: zod.z.object({
|
|
61
214
|
name: NameZ,
|
|
62
215
|
kind: zod.z.literal("centroid"),
|
|
63
216
|
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
64
217
|
}),
|
|
65
|
-
|
|
218
|
+
collectRefs: (e) => [...e.vertices],
|
|
219
|
+
emit: (e, ctx) => [{
|
|
220
|
+
role: "primary",
|
|
221
|
+
object: emitPointObject(
|
|
222
|
+
ctx.resolveId(e.name),
|
|
223
|
+
e.name,
|
|
224
|
+
{ kind: "centroid", vertices: resolveTriangleVertices(ctx, e.vertices) }
|
|
225
|
+
)
|
|
226
|
+
}]
|
|
227
|
+
});
|
|
228
|
+
var orthocenterModule = defineModule({
|
|
229
|
+
kind: "orthocenter",
|
|
230
|
+
role: "point",
|
|
231
|
+
category: "points",
|
|
232
|
+
prefix: "p",
|
|
233
|
+
schema: zod.z.object({
|
|
66
234
|
name: NameZ,
|
|
67
235
|
kind: zod.z.literal("orthocenter"),
|
|
68
236
|
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
69
237
|
}),
|
|
70
|
-
|
|
238
|
+
collectRefs: (e) => [...e.vertices],
|
|
239
|
+
emit: (e, ctx) => [{
|
|
240
|
+
role: "primary",
|
|
241
|
+
object: emitPointObject(
|
|
242
|
+
ctx.resolveId(e.name),
|
|
243
|
+
e.name,
|
|
244
|
+
{ kind: "orthocenter", vertices: resolveTriangleVertices(ctx, e.vertices) }
|
|
245
|
+
)
|
|
246
|
+
}]
|
|
247
|
+
});
|
|
248
|
+
var intersectionModule = defineModule({
|
|
249
|
+
kind: "intersection",
|
|
250
|
+
role: "point",
|
|
251
|
+
category: "points",
|
|
252
|
+
prefix: "i",
|
|
253
|
+
schema: zod.z.object({
|
|
71
254
|
name: NameZ,
|
|
72
255
|
kind: zod.z.literal("intersection"),
|
|
73
256
|
ref1: NameZ,
|
|
74
257
|
ref2: NameZ,
|
|
75
258
|
branch: zod.z.union([zod.z.literal(0), zod.z.literal(1)]).optional()
|
|
76
|
-
})
|
|
77
|
-
]
|
|
78
|
-
|
|
79
|
-
|
|
259
|
+
}),
|
|
260
|
+
collectRefs: (e) => [e.ref1, e.ref2],
|
|
261
|
+
emit: (e, ctx) => {
|
|
262
|
+
const r1IsCircle = ctx.hintOf(e.ref1) === "circle";
|
|
263
|
+
const r2IsCircle = ctx.hintOf(e.ref2) === "circle";
|
|
264
|
+
let intersectKind;
|
|
265
|
+
if (r1IsCircle && r2IsCircle) intersectKind = "circleCircle";
|
|
266
|
+
else if (r1IsCircle || r2IsCircle) intersectKind = "lineCircle";
|
|
267
|
+
else intersectKind = "lineLine";
|
|
268
|
+
const attrs = {
|
|
269
|
+
kind: intersectKind,
|
|
270
|
+
ref1: ctx.resolveId(e.ref1),
|
|
271
|
+
ref2: ctx.resolveId(e.ref2)
|
|
272
|
+
};
|
|
273
|
+
if (intersectKind !== "lineLine") {
|
|
274
|
+
attrs.branch = e.branch ?? 0;
|
|
275
|
+
}
|
|
276
|
+
return [{
|
|
277
|
+
role: "primary",
|
|
278
|
+
object: {
|
|
279
|
+
id: ctx.resolveId(e.name),
|
|
280
|
+
kind: "intersection",
|
|
281
|
+
label: e.name,
|
|
282
|
+
...POINT_BASE_FIELDS,
|
|
283
|
+
attrs
|
|
284
|
+
}
|
|
285
|
+
}];
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
var segmentModule = defineModule({
|
|
289
|
+
kind: "segment",
|
|
290
|
+
role: "segment",
|
|
291
|
+
category: "lines",
|
|
292
|
+
prefix: "s",
|
|
293
|
+
schema: zod.z.object({
|
|
80
294
|
name: NameZ,
|
|
81
295
|
kind: zod.z.literal("segment"),
|
|
82
296
|
p1: NameZ,
|
|
83
297
|
p2: NameZ
|
|
84
298
|
}),
|
|
85
|
-
|
|
299
|
+
collectRefs: (e) => [e.p1, e.p2],
|
|
300
|
+
emit: (e, ctx) => [{
|
|
301
|
+
role: "primary",
|
|
302
|
+
object: {
|
|
303
|
+
id: ctx.resolveId(e.name),
|
|
304
|
+
kind: "segment",
|
|
305
|
+
label: e.name,
|
|
306
|
+
...SHAPE_BASE_FIELDS,
|
|
307
|
+
attrs: { p1: ctx.resolveId(e.p1), p2: ctx.resolveId(e.p2) }
|
|
308
|
+
}
|
|
309
|
+
}]
|
|
310
|
+
});
|
|
311
|
+
var lineModule = defineModule({
|
|
312
|
+
kind: "line",
|
|
313
|
+
role: "line",
|
|
314
|
+
category: "lines",
|
|
315
|
+
prefix: "l",
|
|
316
|
+
schema: zod.z.object({
|
|
86
317
|
name: NameZ,
|
|
87
318
|
kind: zod.z.literal("line"),
|
|
88
319
|
p1: NameZ,
|
|
89
320
|
p2: NameZ
|
|
90
321
|
}),
|
|
91
|
-
|
|
322
|
+
collectRefs: (e) => [e.p1, e.p2],
|
|
323
|
+
emit: (e, ctx) => [{
|
|
324
|
+
role: "primary",
|
|
325
|
+
object: {
|
|
326
|
+
id: ctx.resolveId(e.name),
|
|
327
|
+
kind: "line",
|
|
328
|
+
label: e.name,
|
|
329
|
+
...SHAPE_BASE_FIELDS,
|
|
330
|
+
attrs: { p1: ctx.resolveId(e.p1), p2: ctx.resolveId(e.p2) }
|
|
331
|
+
}
|
|
332
|
+
}]
|
|
333
|
+
});
|
|
334
|
+
var rayModule = defineModule({
|
|
335
|
+
kind: "ray",
|
|
336
|
+
role: "ray",
|
|
337
|
+
category: "lines",
|
|
338
|
+
prefix: "r",
|
|
339
|
+
schema: zod.z.object({
|
|
92
340
|
name: NameZ,
|
|
93
341
|
kind: zod.z.literal("ray"),
|
|
94
342
|
origin: NameZ,
|
|
95
343
|
through: NameZ
|
|
96
344
|
}),
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
345
|
+
collectRefs: (e) => [e.origin, e.through],
|
|
346
|
+
emit: (e, ctx) => [{
|
|
347
|
+
role: "primary",
|
|
348
|
+
object: {
|
|
349
|
+
id: ctx.resolveId(e.name),
|
|
350
|
+
kind: "ray",
|
|
351
|
+
label: e.name,
|
|
352
|
+
...SHAPE_BASE_FIELDS,
|
|
353
|
+
attrs: { origin: ctx.resolveId(e.origin), through: ctx.resolveId(e.through) }
|
|
354
|
+
}
|
|
355
|
+
}]
|
|
356
|
+
});
|
|
357
|
+
var perpendicularModule = defineModule({
|
|
358
|
+
kind: "perpendicular",
|
|
359
|
+
role: "lineConstruction",
|
|
360
|
+
category: "lines",
|
|
361
|
+
prefix: "l",
|
|
362
|
+
schema: zod.z.object({
|
|
104
363
|
name: NameZ,
|
|
105
364
|
kind: zod.z.literal("perpendicular"),
|
|
106
365
|
throughPoint: NameZ,
|
|
107
366
|
toLine: NameZ
|
|
108
367
|
}),
|
|
109
|
-
|
|
368
|
+
collectRefs: (e) => [e.throughPoint, e.toLine],
|
|
369
|
+
emit: (e, ctx) => [{
|
|
370
|
+
role: "primary",
|
|
371
|
+
object: {
|
|
372
|
+
id: ctx.resolveId(e.name),
|
|
373
|
+
kind: "line",
|
|
374
|
+
label: e.name,
|
|
375
|
+
...SHAPE_BASE_FIELDS,
|
|
376
|
+
attrs: {
|
|
377
|
+
construction: {
|
|
378
|
+
kind: "perpendicular",
|
|
379
|
+
throughPoint: ctx.resolveId(e.throughPoint),
|
|
380
|
+
toLine: ctx.resolveId(e.toLine)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}]
|
|
385
|
+
});
|
|
386
|
+
var parallelModule = defineModule({
|
|
387
|
+
kind: "parallel",
|
|
388
|
+
role: "lineConstruction",
|
|
389
|
+
category: "lines",
|
|
390
|
+
prefix: "l",
|
|
391
|
+
schema: zod.z.object({
|
|
110
392
|
name: NameZ,
|
|
111
393
|
kind: zod.z.literal("parallel"),
|
|
112
394
|
throughPoint: NameZ,
|
|
113
395
|
toLine: NameZ
|
|
114
396
|
}),
|
|
115
|
-
|
|
397
|
+
collectRefs: (e) => [e.throughPoint, e.toLine],
|
|
398
|
+
emit: (e, ctx) => [{
|
|
399
|
+
role: "primary",
|
|
400
|
+
object: {
|
|
401
|
+
id: ctx.resolveId(e.name),
|
|
402
|
+
kind: "line",
|
|
403
|
+
label: e.name,
|
|
404
|
+
...SHAPE_BASE_FIELDS,
|
|
405
|
+
attrs: {
|
|
406
|
+
construction: {
|
|
407
|
+
kind: "parallel",
|
|
408
|
+
throughPoint: ctx.resolveId(e.throughPoint),
|
|
409
|
+
toLine: ctx.resolveId(e.toLine)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}]
|
|
414
|
+
});
|
|
415
|
+
var perpBisectorModule = defineModule({
|
|
416
|
+
kind: "perpBisector",
|
|
417
|
+
role: "lineConstruction",
|
|
418
|
+
category: "lines",
|
|
419
|
+
prefix: "l",
|
|
420
|
+
schema: zod.z.object({
|
|
116
421
|
name: NameZ,
|
|
117
422
|
kind: zod.z.literal("perpBisector"),
|
|
118
423
|
p1: NameZ,
|
|
119
424
|
p2: NameZ
|
|
120
425
|
}),
|
|
121
|
-
|
|
426
|
+
collectRefs: (e) => [e.p1, e.p2],
|
|
427
|
+
emit: (e, ctx) => [{
|
|
428
|
+
role: "primary",
|
|
429
|
+
object: {
|
|
430
|
+
id: ctx.resolveId(e.name),
|
|
431
|
+
kind: "line",
|
|
432
|
+
label: e.name,
|
|
433
|
+
...SHAPE_BASE_FIELDS,
|
|
434
|
+
attrs: {
|
|
435
|
+
construction: {
|
|
436
|
+
kind: "perpBisector",
|
|
437
|
+
p1: ctx.resolveId(e.p1),
|
|
438
|
+
p2: ctx.resolveId(e.p2)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}]
|
|
443
|
+
});
|
|
444
|
+
var angleBisectorModule = defineModule({
|
|
445
|
+
kind: "angleBisector",
|
|
446
|
+
role: "lineConstruction",
|
|
447
|
+
category: "lines",
|
|
448
|
+
prefix: "l",
|
|
449
|
+
schema: zod.z.object({
|
|
122
450
|
name: NameZ,
|
|
123
451
|
kind: zod.z.literal("angleBisector"),
|
|
124
452
|
p1: NameZ,
|
|
125
453
|
vertex: NameZ,
|
|
126
454
|
p2: NameZ
|
|
127
455
|
}),
|
|
128
|
-
|
|
456
|
+
collectRefs: (e) => [e.p1, e.vertex, e.p2],
|
|
457
|
+
emit: (e, ctx) => [{
|
|
458
|
+
role: "primary",
|
|
459
|
+
object: {
|
|
460
|
+
id: ctx.resolveId(e.name),
|
|
461
|
+
kind: "line",
|
|
462
|
+
label: e.name,
|
|
463
|
+
...SHAPE_BASE_FIELDS,
|
|
464
|
+
attrs: {
|
|
465
|
+
construction: {
|
|
466
|
+
kind: "angleBisector",
|
|
467
|
+
p1: ctx.resolveId(e.p1),
|
|
468
|
+
vertex: ctx.resolveId(e.vertex),
|
|
469
|
+
p2: ctx.resolveId(e.p2)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}]
|
|
474
|
+
});
|
|
475
|
+
var tangentModule = defineModule({
|
|
476
|
+
kind: "tangent",
|
|
477
|
+
role: "lineConstruction",
|
|
478
|
+
category: "lines",
|
|
479
|
+
prefix: "l",
|
|
480
|
+
schema: zod.z.object({
|
|
129
481
|
name: NameZ,
|
|
130
482
|
kind: zod.z.literal("tangent"),
|
|
131
483
|
throughPoint: NameZ,
|
|
132
484
|
toCircle: NameZ,
|
|
133
485
|
branch: zod.z.union([zod.z.literal(0), zod.z.literal(1), zod.z.literal("on")]).optional()
|
|
134
486
|
}),
|
|
135
|
-
|
|
136
|
-
|
|
487
|
+
collectRefs: (e) => [e.throughPoint, e.toCircle],
|
|
488
|
+
emit: (e, ctx) => {
|
|
489
|
+
const construction = {
|
|
490
|
+
kind: "tangent",
|
|
491
|
+
throughPoint: ctx.resolveId(e.throughPoint),
|
|
492
|
+
toCircle: ctx.resolveId(e.toCircle)
|
|
493
|
+
};
|
|
494
|
+
if (e.branch !== void 0) construction.branch = e.branch;
|
|
495
|
+
return [{
|
|
496
|
+
role: "primary",
|
|
497
|
+
object: {
|
|
498
|
+
id: ctx.resolveId(e.name),
|
|
499
|
+
kind: "line",
|
|
500
|
+
label: e.name,
|
|
501
|
+
...SHAPE_BASE_FIELDS,
|
|
502
|
+
attrs: { construction }
|
|
503
|
+
}
|
|
504
|
+
}];
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
var polygonModule = defineModule({
|
|
508
|
+
kind: "polygon",
|
|
509
|
+
role: "polygon",
|
|
510
|
+
category: "polygons",
|
|
511
|
+
prefix: "poly",
|
|
512
|
+
schema: zod.z.object({
|
|
513
|
+
name: NameZ,
|
|
514
|
+
kind: zod.z.literal("polygon"),
|
|
515
|
+
vertices: zod.z.array(NameZ).min(3)
|
|
516
|
+
}),
|
|
517
|
+
collectRefs: (e) => [...e.vertices],
|
|
518
|
+
emit: (e, ctx) => [{
|
|
519
|
+
role: "primary",
|
|
520
|
+
object: {
|
|
521
|
+
id: ctx.resolveId(e.name),
|
|
522
|
+
kind: "polygon",
|
|
523
|
+
label: e.name,
|
|
524
|
+
...SHAPE_BASE_FIELDS,
|
|
525
|
+
attrs: { vertices: e.vertices.map((v) => ctx.resolveId(v)) }
|
|
526
|
+
}
|
|
527
|
+
}]
|
|
528
|
+
});
|
|
529
|
+
var circleCPModule = defineModule({
|
|
530
|
+
kind: "circleCP",
|
|
531
|
+
role: "circle",
|
|
532
|
+
category: "circles",
|
|
533
|
+
prefix: "c",
|
|
534
|
+
schema: zod.z.object({
|
|
137
535
|
name: NameZ,
|
|
138
536
|
kind: zod.z.literal("circleCP"),
|
|
139
537
|
center: NameZ,
|
|
140
538
|
surfacePoint: NameZ
|
|
141
539
|
}),
|
|
142
|
-
|
|
540
|
+
collectRefs: (e) => [e.center, e.surfacePoint],
|
|
541
|
+
emit: (e, ctx) => [{
|
|
542
|
+
role: "primary",
|
|
543
|
+
object: {
|
|
544
|
+
id: ctx.resolveId(e.name),
|
|
545
|
+
kind: "circle",
|
|
546
|
+
label: e.name,
|
|
547
|
+
...SHAPE_BASE_FIELDS,
|
|
548
|
+
attrs: { center: ctx.resolveId(e.center), surfacePoint: ctx.resolveId(e.surfacePoint) }
|
|
549
|
+
}
|
|
550
|
+
}]
|
|
551
|
+
});
|
|
552
|
+
var circle3Module = defineModule({
|
|
553
|
+
kind: "circle3",
|
|
554
|
+
role: "circle",
|
|
555
|
+
category: "circles",
|
|
556
|
+
prefix: "c",
|
|
557
|
+
schema: zod.z.object({
|
|
143
558
|
name: NameZ,
|
|
144
559
|
kind: zod.z.literal("circle3"),
|
|
145
560
|
p1: NameZ,
|
|
146
561
|
p2: NameZ,
|
|
147
562
|
p3: NameZ
|
|
148
|
-
})
|
|
149
|
-
]
|
|
563
|
+
}),
|
|
564
|
+
collectRefs: (e) => [e.p1, e.p2, e.p3],
|
|
565
|
+
emit: (e, ctx) => [{
|
|
566
|
+
role: "primary",
|
|
567
|
+
object: {
|
|
568
|
+
id: ctx.resolveId(e.name),
|
|
569
|
+
kind: "circle",
|
|
570
|
+
label: e.name,
|
|
571
|
+
...SHAPE_BASE_FIELDS,
|
|
572
|
+
attrs: {
|
|
573
|
+
construction: {
|
|
574
|
+
kind: "circumscribed",
|
|
575
|
+
p1: ctx.resolveId(e.p1),
|
|
576
|
+
p2: ctx.resolveId(e.p2),
|
|
577
|
+
p3: ctx.resolveId(e.p3)
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}]
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// src/stamps/geometry-2d/dsl/registry.ts
|
|
585
|
+
var ALL_MODULES = [
|
|
586
|
+
freeModule,
|
|
587
|
+
midpointModule,
|
|
588
|
+
onSegmentModule,
|
|
589
|
+
onLineModule,
|
|
590
|
+
onCircleModule,
|
|
591
|
+
perpFootModule,
|
|
592
|
+
circumcenterModule,
|
|
593
|
+
incenterModule,
|
|
594
|
+
centroidModule,
|
|
595
|
+
orthocenterModule,
|
|
596
|
+
intersectionModule,
|
|
597
|
+
segmentModule,
|
|
598
|
+
lineModule,
|
|
599
|
+
rayModule,
|
|
600
|
+
perpendicularModule,
|
|
601
|
+
parallelModule,
|
|
602
|
+
perpBisectorModule,
|
|
603
|
+
angleBisectorModule,
|
|
604
|
+
tangentModule,
|
|
605
|
+
polygonModule,
|
|
606
|
+
circleCPModule,
|
|
607
|
+
circle3Module
|
|
608
|
+
];
|
|
609
|
+
var KIND_REGISTRY = new Map(ALL_MODULES.map((m) => [m.kind, m]));
|
|
610
|
+
var POINT_KINDS = new Set(
|
|
611
|
+
ALL_MODULES.filter((m) => m.role === "point").map((m) => m.kind)
|
|
612
|
+
);
|
|
613
|
+
var LINE_LIKE_SHAPE_KINDS = new Set(
|
|
614
|
+
ALL_MODULES.filter(
|
|
615
|
+
(m) => m.role === "segment" || m.role === "line" || m.role === "ray" || m.role === "lineConstruction"
|
|
616
|
+
).map((m) => m.kind)
|
|
617
|
+
);
|
|
618
|
+
var CIRCLE_KINDS = new Set(
|
|
619
|
+
ALL_MODULES.filter((m) => m.role === "circle").map((m) => m.kind)
|
|
620
|
+
);
|
|
621
|
+
zod.z.discriminatedUnion(
|
|
622
|
+
"kind",
|
|
623
|
+
ALL_MODULES.map((m) => m.schema)
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
// src/stamps/geometry-2d/dsl/schema.ts
|
|
627
|
+
function asTuple(arr) {
|
|
628
|
+
if (arr.length < 2) throw new Error("schema: need at least 2 variants for discriminatedUnion");
|
|
629
|
+
return arr;
|
|
630
|
+
}
|
|
631
|
+
var pointSchemas = Array.from(KIND_REGISTRY.values()).filter((m) => POINT_KINDS.has(m.kind)).map((m) => m.schema);
|
|
632
|
+
var shapeSchemas = Array.from(KIND_REGISTRY.values()).filter((m) => !POINT_KINDS.has(m.kind)).map((m) => m.schema);
|
|
633
|
+
var DslPoint = zod.z.discriminatedUnion("kind", asTuple(pointSchemas));
|
|
634
|
+
var DslShape = zod.z.discriminatedUnion("kind", asTuple(shapeSchemas));
|
|
150
635
|
var DslInput = zod.z.object({
|
|
151
636
|
version: zod.z.literal(1),
|
|
152
637
|
points: zod.z.array(DslPoint),
|
|
@@ -215,17 +700,6 @@ function buildSymbols(dsl) {
|
|
|
215
700
|
function isPointLike(sym) {
|
|
216
701
|
return !!sym && sym.role === "point";
|
|
217
702
|
}
|
|
218
|
-
var LINE_LIKE_SHAPE_KINDS = /* @__PURE__ */ new Set([
|
|
219
|
-
"line",
|
|
220
|
-
"segment",
|
|
221
|
-
"ray",
|
|
222
|
-
"perpendicular",
|
|
223
|
-
"parallel",
|
|
224
|
-
"perpBisector",
|
|
225
|
-
"angleBisector",
|
|
226
|
-
"tangent"
|
|
227
|
-
]);
|
|
228
|
-
var CIRCLE_KINDS = /* @__PURE__ */ new Set(["circleCP", "circle3"]);
|
|
229
703
|
function isLineLike(sym) {
|
|
230
704
|
if (!sym || sym.role !== "shape") return false;
|
|
231
705
|
return LINE_LIKE_SHAPE_KINDS.has(sym.entity.kind);
|
|
@@ -340,50 +814,9 @@ function validateRefs(dsl, symbols) {
|
|
|
340
814
|
return { errors };
|
|
341
815
|
}
|
|
342
816
|
function collectRefs(entity) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return [];
|
|
347
|
-
case "midpoint":
|
|
348
|
-
return [entity.p1, entity.p2];
|
|
349
|
-
case "onSegment":
|
|
350
|
-
return [entity.segmentId];
|
|
351
|
-
case "onLine":
|
|
352
|
-
return [entity.lineId];
|
|
353
|
-
case "onCircle":
|
|
354
|
-
return [entity.circleId];
|
|
355
|
-
case "perpFoot":
|
|
356
|
-
return [entity.from, entity.onLine];
|
|
357
|
-
case "circumcenter":
|
|
358
|
-
case "incenter":
|
|
359
|
-
case "centroid":
|
|
360
|
-
case "orthocenter":
|
|
361
|
-
return [...entity.vertices];
|
|
362
|
-
case "intersection":
|
|
363
|
-
return [entity.ref1, entity.ref2];
|
|
364
|
-
case "segment":
|
|
365
|
-
case "line":
|
|
366
|
-
return [entity.p1, entity.p2];
|
|
367
|
-
case "ray":
|
|
368
|
-
return [entity.origin, entity.through];
|
|
369
|
-
case "polygon":
|
|
370
|
-
return [...entity.vertices];
|
|
371
|
-
case "perpendicular":
|
|
372
|
-
case "parallel":
|
|
373
|
-
return [entity.throughPoint, entity.toLine];
|
|
374
|
-
case "perpBisector":
|
|
375
|
-
return [entity.p1, entity.p2];
|
|
376
|
-
case "angleBisector":
|
|
377
|
-
return [entity.p1, entity.vertex, entity.p2];
|
|
378
|
-
case "tangent":
|
|
379
|
-
return [entity.throughPoint, entity.toCircle];
|
|
380
|
-
case "circleCP":
|
|
381
|
-
return [entity.center, entity.surfacePoint];
|
|
382
|
-
case "circle3":
|
|
383
|
-
return [entity.p1, entity.p2, entity.p3];
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return [];
|
|
817
|
+
const mod = KIND_REGISTRY.get(entity.kind);
|
|
818
|
+
if (!mod) throw new Error(`collectRefs: no registry entry for kind "${entity.kind}"`);
|
|
819
|
+
return mod.collectRefs(entity);
|
|
387
820
|
}
|
|
388
821
|
|
|
389
822
|
// src/stamps/geometry-2d/dsl/transpile/cycles.ts
|
|
@@ -441,229 +874,45 @@ function detectCycles(symbols) {
|
|
|
441
874
|
}
|
|
442
875
|
|
|
443
876
|
// src/stamps/geometry-2d/dsl/transpile/ids.ts
|
|
444
|
-
function prefixFor(sym) {
|
|
445
|
-
if (sym.role === "point") {
|
|
446
|
-
const p = sym.entity;
|
|
447
|
-
return p.kind === "intersection" ? "i" : "p";
|
|
448
|
-
}
|
|
449
|
-
const s = sym.entity;
|
|
450
|
-
switch (s.kind) {
|
|
451
|
-
case "segment":
|
|
452
|
-
return "s";
|
|
453
|
-
case "ray":
|
|
454
|
-
return "r";
|
|
455
|
-
case "polygon":
|
|
456
|
-
return "poly";
|
|
457
|
-
case "circleCP":
|
|
458
|
-
case "circle3":
|
|
459
|
-
return "c";
|
|
460
|
-
// line + 5 line-constructions all share 'l'
|
|
461
|
-
case "line":
|
|
462
|
-
case "perpendicular":
|
|
463
|
-
case "parallel":
|
|
464
|
-
case "perpBisector":
|
|
465
|
-
case "angleBisector":
|
|
466
|
-
case "tangent":
|
|
467
|
-
return "l";
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
877
|
function assignIds(symbols) {
|
|
471
|
-
const counters =
|
|
878
|
+
const counters = /* @__PURE__ */ new Map();
|
|
472
879
|
const ids = /* @__PURE__ */ new Map();
|
|
473
880
|
for (const [name, sym] of symbols.entries()) {
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
881
|
+
const mod = KIND_REGISTRY.get(sym.entity.kind);
|
|
882
|
+
if (!mod) throw new Error(`assignIds: no registry entry for kind "${sym.entity.kind}"`);
|
|
883
|
+
const prefix = mod.prefix;
|
|
884
|
+
counters.set(prefix, (counters.get(prefix) ?? 0) + 1);
|
|
885
|
+
ids.set(name, `${prefix}${counters.get(prefix)}`);
|
|
477
886
|
}
|
|
478
887
|
return ids;
|
|
479
888
|
}
|
|
480
889
|
|
|
481
|
-
// src/stamps/geometry-2d/dsl/transpile/emitPoint.ts
|
|
482
|
-
function resolveId(ids, name) {
|
|
483
|
-
const id = ids.get(name);
|
|
484
|
-
if (!id) throw new Error(`emitPoint: id not assigned for "${name}"`);
|
|
485
|
-
return id;
|
|
486
|
-
}
|
|
487
|
-
function emitPoint(p, ids, kindHints) {
|
|
488
|
-
const baseId = resolveId(ids, p.name);
|
|
489
|
-
const baseFields = {
|
|
490
|
-
label: p.name,
|
|
491
|
-
visible: true,
|
|
492
|
-
locked: false,
|
|
493
|
-
layer: "default",
|
|
494
|
-
schemaVersion: 1
|
|
495
|
-
};
|
|
496
|
-
if (p.kind === "intersection") {
|
|
497
|
-
const r1Hint = kindHints.get(p.ref1);
|
|
498
|
-
const r2Hint = kindHints.get(p.ref2);
|
|
499
|
-
const r1IsCircle = r1Hint === "circle";
|
|
500
|
-
const r2IsCircle = r2Hint === "circle";
|
|
501
|
-
let intersectKind;
|
|
502
|
-
if (r1IsCircle && r2IsCircle) intersectKind = "circleCircle";
|
|
503
|
-
else if (r1IsCircle || r2IsCircle) intersectKind = "lineCircle";
|
|
504
|
-
else intersectKind = "lineLine";
|
|
505
|
-
const attrs = {
|
|
506
|
-
kind: intersectKind,
|
|
507
|
-
ref1: resolveId(ids, p.ref1),
|
|
508
|
-
ref2: resolveId(ids, p.ref2)
|
|
509
|
-
};
|
|
510
|
-
if (intersectKind !== "lineLine") {
|
|
511
|
-
attrs.branch = p.branch ?? 0;
|
|
512
|
-
}
|
|
513
|
-
return {
|
|
514
|
-
id: baseId,
|
|
515
|
-
kind: "intersection",
|
|
516
|
-
...baseFields,
|
|
517
|
-
attrs
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
let constraint;
|
|
521
|
-
switch (p.kind) {
|
|
522
|
-
case "free":
|
|
523
|
-
constraint = { kind: "free", x: p.x, y: p.y };
|
|
524
|
-
break;
|
|
525
|
-
case "midpoint":
|
|
526
|
-
constraint = { kind: "midpoint", p1: resolveId(ids, p.p1), p2: resolveId(ids, p.p2) };
|
|
527
|
-
break;
|
|
528
|
-
case "onSegment":
|
|
529
|
-
constraint = { kind: "onSegment", segmentId: resolveId(ids, p.segmentId), t: p.t };
|
|
530
|
-
break;
|
|
531
|
-
case "onLine":
|
|
532
|
-
constraint = { kind: "onLine", lineId: resolveId(ids, p.lineId), t: p.t };
|
|
533
|
-
break;
|
|
534
|
-
case "onCircle":
|
|
535
|
-
constraint = { kind: "onCircle", circleId: resolveId(ids, p.circleId), theta: p.theta };
|
|
536
|
-
break;
|
|
537
|
-
case "perpFoot":
|
|
538
|
-
constraint = { kind: "perpFoot", from: resolveId(ids, p.from), onLine: resolveId(ids, p.onLine) };
|
|
539
|
-
break;
|
|
540
|
-
case "circumcenter":
|
|
541
|
-
case "incenter":
|
|
542
|
-
case "centroid":
|
|
543
|
-
case "orthocenter":
|
|
544
|
-
constraint = {
|
|
545
|
-
kind: p.kind,
|
|
546
|
-
vertices: [resolveId(ids, p.vertices[0]), resolveId(ids, p.vertices[1]), resolveId(ids, p.vertices[2])]
|
|
547
|
-
};
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
550
|
-
return {
|
|
551
|
-
id: baseId,
|
|
552
|
-
kind: "point",
|
|
553
|
-
...baseFields,
|
|
554
|
-
attrs: { constraint }
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// src/stamps/geometry-2d/dsl/transpile/emitShape.ts
|
|
559
|
-
function r(ids, name) {
|
|
560
|
-
const id = ids.get(name);
|
|
561
|
-
if (!id) throw new Error(`emitShape: id not assigned for "${name}"`);
|
|
562
|
-
return id;
|
|
563
|
-
}
|
|
564
|
-
function emitShape(s, ids) {
|
|
565
|
-
const id = r(ids, s.name);
|
|
566
|
-
const base = {
|
|
567
|
-
label: s.name,
|
|
568
|
-
visible: true,
|
|
569
|
-
locked: false,
|
|
570
|
-
layer: "default",
|
|
571
|
-
schemaVersion: 1
|
|
572
|
-
};
|
|
573
|
-
switch (s.kind) {
|
|
574
|
-
case "segment":
|
|
575
|
-
return { id, kind: "segment", ...base, attrs: { p1: r(ids, s.p1), p2: r(ids, s.p2) } };
|
|
576
|
-
case "line":
|
|
577
|
-
return { id, kind: "line", ...base, attrs: { p1: r(ids, s.p1), p2: r(ids, s.p2) } };
|
|
578
|
-
case "ray":
|
|
579
|
-
return { id, kind: "ray", ...base, attrs: { origin: r(ids, s.origin), through: r(ids, s.through) } };
|
|
580
|
-
case "polygon":
|
|
581
|
-
return { id, kind: "polygon", ...base, attrs: { vertices: s.vertices.map((v) => r(ids, v)) } };
|
|
582
|
-
case "perpendicular":
|
|
583
|
-
case "parallel":
|
|
584
|
-
return {
|
|
585
|
-
id,
|
|
586
|
-
kind: "line",
|
|
587
|
-
...base,
|
|
588
|
-
attrs: { construction: { kind: s.kind, throughPoint: r(ids, s.throughPoint), toLine: r(ids, s.toLine) } }
|
|
589
|
-
};
|
|
590
|
-
case "perpBisector":
|
|
591
|
-
return {
|
|
592
|
-
id,
|
|
593
|
-
kind: "line",
|
|
594
|
-
...base,
|
|
595
|
-
attrs: { construction: { kind: "perpBisector", p1: r(ids, s.p1), p2: r(ids, s.p2) } }
|
|
596
|
-
};
|
|
597
|
-
case "angleBisector":
|
|
598
|
-
return {
|
|
599
|
-
id,
|
|
600
|
-
kind: "line",
|
|
601
|
-
...base,
|
|
602
|
-
attrs: { construction: { kind: "angleBisector", p1: r(ids, s.p1), vertex: r(ids, s.vertex), p2: r(ids, s.p2) } }
|
|
603
|
-
};
|
|
604
|
-
case "tangent": {
|
|
605
|
-
const construction = {
|
|
606
|
-
kind: "tangent",
|
|
607
|
-
throughPoint: r(ids, s.throughPoint),
|
|
608
|
-
toCircle: r(ids, s.toCircle)
|
|
609
|
-
};
|
|
610
|
-
if (s.branch !== void 0) construction.branch = s.branch;
|
|
611
|
-
return { id, kind: "line", ...base, attrs: { construction } };
|
|
612
|
-
}
|
|
613
|
-
case "circleCP":
|
|
614
|
-
return {
|
|
615
|
-
id,
|
|
616
|
-
kind: "circle",
|
|
617
|
-
...base,
|
|
618
|
-
attrs: { center: r(ids, s.center), surfacePoint: r(ids, s.surfacePoint) }
|
|
619
|
-
};
|
|
620
|
-
case "circle3":
|
|
621
|
-
return {
|
|
622
|
-
id,
|
|
623
|
-
kind: "circle",
|
|
624
|
-
...base,
|
|
625
|
-
attrs: { construction: { kind: "circumscribed", p1: r(ids, s.p1), p2: r(ids, s.p2), p3: r(ids, s.p3) } }
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
890
|
// src/stamps/geometry-2d/dsl/transpile.ts
|
|
631
891
|
function hintOf(entity) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
// not used as ref target in MVP
|
|
655
|
-
case "perpendicular":
|
|
656
|
-
case "parallel":
|
|
657
|
-
case "perpBisector":
|
|
658
|
-
case "angleBisector":
|
|
659
|
-
case "tangent":
|
|
660
|
-
return "lineConstruction";
|
|
661
|
-
case "circleCP":
|
|
662
|
-
case "circle3":
|
|
663
|
-
return "circle";
|
|
892
|
+
const mod = KIND_REGISTRY.get(entity.kind);
|
|
893
|
+
if (!mod) throw new Error(`hintOf: no registry entry for kind "${entity.kind}"`);
|
|
894
|
+
return mod.role === "polygon" ? "point" : mod.role;
|
|
895
|
+
}
|
|
896
|
+
function buildEmitContext(ids, kindHints) {
|
|
897
|
+
const auxCounters = /* @__PURE__ */ new Map();
|
|
898
|
+
return {
|
|
899
|
+
resolveId(name) {
|
|
900
|
+
const id = ids.get(name);
|
|
901
|
+
if (!id) throw new Error(`emit: id not assigned for "${name}"`);
|
|
902
|
+
return id;
|
|
903
|
+
},
|
|
904
|
+
hintOf(name) {
|
|
905
|
+
const hint = kindHints.get(name);
|
|
906
|
+
if (!hint) throw new Error(`emit: hint not assigned for "${name}"`);
|
|
907
|
+
return hint;
|
|
908
|
+
},
|
|
909
|
+
mintAuxId(parentName, suffix) {
|
|
910
|
+
const key = `${parentName}.${suffix}`;
|
|
911
|
+
auxCounters.set(key, (auxCounters.get(key) ?? 0) + 1);
|
|
912
|
+
const seq = auxCounters.get(key);
|
|
913
|
+
return `aux_${parentName}_${suffix}${seq}`;
|
|
664
914
|
}
|
|
665
|
-
}
|
|
666
|
-
return "point";
|
|
915
|
+
};
|
|
667
916
|
}
|
|
668
917
|
function transpile(dslRaw) {
|
|
669
918
|
const parsed = DslInput.safeParse(dslRaw);
|
|
@@ -686,16 +935,18 @@ function transpile(dslRaw) {
|
|
|
686
935
|
}
|
|
687
936
|
const objects = {};
|
|
688
937
|
const order = [];
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
938
|
+
const ctx = buildEmitContext(ids, kindHints);
|
|
939
|
+
const emitEntity = (entity) => {
|
|
940
|
+
const mod = KIND_REGISTRY.get(entity.kind);
|
|
941
|
+
if (!mod) throw new Error(`emit: no registry entry for kind "${entity.kind}"`);
|
|
942
|
+
const emitted = mod.emit(entity, ctx);
|
|
943
|
+
for (const ent of emitted) {
|
|
944
|
+
objects[ent.object.id] = ent.object;
|
|
945
|
+
order.push(ent.object.id);
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
for (const p of dsl.points) emitEntity(p);
|
|
949
|
+
for (const s of dsl.shapes) emitEntity(s);
|
|
699
950
|
const empty = createEmptyState("2d");
|
|
700
951
|
const state = {
|
|
701
952
|
objects,
|
|
@@ -705,20 +956,29 @@ function transpile(dslRaw) {
|
|
|
705
956
|
};
|
|
706
957
|
return { ok: true, state };
|
|
707
958
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
return
|
|
959
|
+
var FigureEnvelopeZ = zod.z.object({
|
|
960
|
+
decision: zod.z.enum(["build", "refuse"]),
|
|
961
|
+
// figure: DslInput khi build; bỏ qua khi refuse.
|
|
962
|
+
figure: DslInput.optional(),
|
|
963
|
+
// reason: lý do từ chối (Việt) khi refuse; bỏ qua khi build.
|
|
964
|
+
reason: zod.z.string().optional()
|
|
965
|
+
}).refine(
|
|
966
|
+
(e) => e.decision === "build" ? e.figure != null : e.reason != null && e.reason.length > 0,
|
|
967
|
+
{
|
|
968
|
+
message: "decision=build c\u1EA7n `figure`; decision=refuse c\u1EA7n `reason` kh\xF4ng r\u1ED7ng"
|
|
969
|
+
}
|
|
970
|
+
);
|
|
971
|
+
function envelopeJsonSchema() {
|
|
972
|
+
return zodToJsonSchema.zodToJsonSchema(FigureEnvelopeZ, {
|
|
973
|
+
target: "jsonSchema7",
|
|
974
|
+
$refStrategy: "none"
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
function envelopeBuildDsl(env) {
|
|
978
|
+
if (env.decision !== "build" || env.figure == null) {
|
|
979
|
+
throw new Error("envelopeBuildDsl: envelope kh\xF4ng ph\u1EA3i decision=build ho\u1EB7c thi\u1EBFu figure");
|
|
980
|
+
}
|
|
981
|
+
return env.figure;
|
|
722
982
|
}
|
|
723
983
|
|
|
724
984
|
// src/stamps/geometry-2d/dsl/fixtures/triangle-equilateral.ts
|
|
@@ -886,62 +1146,267 @@ var fixture9 = {
|
|
|
886
1146
|
}
|
|
887
1147
|
};
|
|
888
1148
|
|
|
1149
|
+
// src/stamps/geometry-2d/dsl/fixtures/triangle-angle-bisector.ts
|
|
1150
|
+
var fixture10 = {
|
|
1151
|
+
problem: "Tam gi\xE1c ABC, AD l\xE0 ph\xE2n gi\xE1c g\xF3c A (D thu\u1ED9c BC).",
|
|
1152
|
+
dsl: {
|
|
1153
|
+
version: 1,
|
|
1154
|
+
points: [
|
|
1155
|
+
{ name: "A", kind: "free", x: 0, y: 3 },
|
|
1156
|
+
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
1157
|
+
{ name: "C", kind: "free", x: 3, y: 0 },
|
|
1158
|
+
// D = giao của đường phân giác góc A với BC. KHÔNG dùng segment AD
|
|
1159
|
+
// vì AD lại tham chiếu D → cycle.
|
|
1160
|
+
{ name: "D", kind: "intersection", ref1: "bisA", ref2: "BC" }
|
|
1161
|
+
],
|
|
1162
|
+
shapes: [
|
|
1163
|
+
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] },
|
|
1164
|
+
{ name: "BC", kind: "segment", p1: "B", p2: "C" },
|
|
1165
|
+
// Đường phân giác (line construction), chứ không phải segment.
|
|
1166
|
+
{ name: "bisA", kind: "angleBisector", p1: "B", vertex: "A", p2: "C" },
|
|
1167
|
+
// Sau khi đã có D, mới dựng được segment AD.
|
|
1168
|
+
{ name: "AD", kind: "segment", p1: "A", p2: "D" }
|
|
1169
|
+
]
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
// src/stamps/geometry-2d/dsl/fixtures/triangle-median-altitude.ts
|
|
1174
|
+
var fixture11 = {
|
|
1175
|
+
problem: "Tam gi\xE1c ABC, M l\xE0 trung \u0111i\u1EC3m BC v\xE0 AH l\xE0 \u0111\u01B0\u1EDDng cao xu\u1ED1ng BC.",
|
|
1176
|
+
dsl: {
|
|
1177
|
+
version: 1,
|
|
1178
|
+
points: [
|
|
1179
|
+
{ name: "A", kind: "free", x: 1, y: 3 },
|
|
1180
|
+
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
1181
|
+
{ name: "C", kind: "free", x: 3, y: 0 },
|
|
1182
|
+
// M = trung điểm B và C. KHÔNG dùng perpFoot ở đây — đó là H.
|
|
1183
|
+
{ name: "M", kind: "midpoint", p1: "B", p2: "C" },
|
|
1184
|
+
// H = chân đường vuông góc từ A xuống cạnh BC.
|
|
1185
|
+
{ name: "H", kind: "perpFoot", from: "A", onLine: "BC" }
|
|
1186
|
+
],
|
|
1187
|
+
shapes: [
|
|
1188
|
+
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] },
|
|
1189
|
+
{ name: "BC", kind: "segment", p1: "B", p2: "C" },
|
|
1190
|
+
{ name: "AM", kind: "segment", p1: "A", p2: "M" },
|
|
1191
|
+
{ name: "AH", kind: "segment", p1: "A", p2: "H" }
|
|
1192
|
+
]
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
// src/stamps/geometry-2d/dsl/fixtures/trapezoid.ts
|
|
1197
|
+
var fixture12 = {
|
|
1198
|
+
problem: "H\xECnh thang ABCD c\xF3 AB song song CD.",
|
|
1199
|
+
dsl: {
|
|
1200
|
+
version: 1,
|
|
1201
|
+
points: [
|
|
1202
|
+
{ name: "A", kind: "free", x: 0, y: 0 },
|
|
1203
|
+
{ name: "B", kind: "free", x: 4, y: 0 },
|
|
1204
|
+
{ name: "C", kind: "free", x: 5, y: 3 },
|
|
1205
|
+
{ name: "D", kind: "free", x: -1, y: 3 }
|
|
1206
|
+
],
|
|
1207
|
+
shapes: [
|
|
1208
|
+
{ name: "ABCD", kind: "polygon", vertices: ["A", "B", "C", "D"] }
|
|
1209
|
+
]
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
// src/stamps/geometry-2d/dsl/fixtures/rhombus.ts
|
|
1214
|
+
var fixture13 = {
|
|
1215
|
+
problem: "H\xECnh thoi ABCD, hai \u0111\u01B0\u1EDDng ch\xE9o AC, BD c\u1EAFt nhau t\u1EA1i O.",
|
|
1216
|
+
dsl: {
|
|
1217
|
+
version: 1,
|
|
1218
|
+
points: [
|
|
1219
|
+
{ name: "A", kind: "free", x: 0, y: 2 },
|
|
1220
|
+
{ name: "B", kind: "free", x: 3, y: 0 },
|
|
1221
|
+
{ name: "C", kind: "free", x: 0, y: -2 },
|
|
1222
|
+
{ name: "D", kind: "free", x: -3, y: 0 },
|
|
1223
|
+
{ name: "O", kind: "intersection", ref1: "AC", ref2: "BD" }
|
|
1224
|
+
],
|
|
1225
|
+
shapes: [
|
|
1226
|
+
{ name: "ABCD", kind: "polygon", vertices: ["A", "B", "C", "D"] },
|
|
1227
|
+
{ name: "AC", kind: "segment", p1: "A", p2: "C" },
|
|
1228
|
+
{ name: "BD", kind: "segment", p1: "B", p2: "D" }
|
|
1229
|
+
]
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
// src/stamps/geometry-2d/dsl/fixtures/right-triangle-altitude.ts
|
|
1234
|
+
var fixture14 = {
|
|
1235
|
+
problem: "Tam gi\xE1c ABC vu\xF4ng t\u1EA1i A, AH l\xE0 \u0111\u01B0\u1EDDng cao xu\u1ED1ng c\u1EA1nh huy\u1EC1n BC.",
|
|
1236
|
+
dsl: {
|
|
1237
|
+
version: 1,
|
|
1238
|
+
points: [
|
|
1239
|
+
{ name: "A", kind: "free", x: 0, y: 0 },
|
|
1240
|
+
{ name: "B", kind: "free", x: 0, y: 3 },
|
|
1241
|
+
{ name: "C", kind: "free", x: 4, y: 0 },
|
|
1242
|
+
{ name: "H", kind: "perpFoot", from: "A", onLine: "BC" }
|
|
1243
|
+
],
|
|
1244
|
+
shapes: [
|
|
1245
|
+
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] },
|
|
1246
|
+
{ name: "BC", kind: "segment", p1: "B", p2: "C" },
|
|
1247
|
+
{ name: "AH", kind: "segment", p1: "A", p2: "H" }
|
|
1248
|
+
]
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
// src/stamps/geometry-2d/dsl/fixtures/tangent-from-point.ts
|
|
1253
|
+
var fixture15 = {
|
|
1254
|
+
problem: "T\u1EEB \u0111i\u1EC3m M ngo\xE0i \u0111\u01B0\u1EDDng tr\xF2n (O), k\u1EBB hai ti\u1EBFp tuy\u1EBFn t\u1EDBi (O).",
|
|
1255
|
+
dsl: {
|
|
1256
|
+
version: 1,
|
|
1257
|
+
points: [
|
|
1258
|
+
{ name: "O", kind: "free", x: 0, y: 0 },
|
|
1259
|
+
// P là điểm trên đường tròn dùng để định bán kính.
|
|
1260
|
+
{ name: "P", kind: "free", x: 2, y: 0 },
|
|
1261
|
+
{ name: "M", kind: "free", x: 5, y: 0 }
|
|
1262
|
+
],
|
|
1263
|
+
shapes: [
|
|
1264
|
+
{ name: "k", kind: "circleCP", center: "O", surfacePoint: "P" },
|
|
1265
|
+
{ name: "t1", kind: "tangent", throughPoint: "M", toCircle: "k", branch: 0 },
|
|
1266
|
+
{ name: "t2", kind: "tangent", throughPoint: "M", toCircle: "k", branch: 1 }
|
|
1267
|
+
]
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
// src/stamps/geometry-2d/dsl/fixtures/internal-external-bisector.ts
|
|
1272
|
+
var fixture16 = {
|
|
1273
|
+
problem: "Tam gi\xE1c ABC, v\u1EBD tia ph\xE2n gi\xE1c trong v\xE0 ph\xE2n gi\xE1c ngo\xE0i t\u1EA1i \u0111\u1EC9nh A.",
|
|
1274
|
+
dsl: {
|
|
1275
|
+
version: 1,
|
|
1276
|
+
points: [
|
|
1277
|
+
{ name: "A", kind: "free", x: 0, y: 3 },
|
|
1278
|
+
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
1279
|
+
{ name: "C", kind: "free", x: 3, y: 0 }
|
|
1280
|
+
],
|
|
1281
|
+
shapes: [
|
|
1282
|
+
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] },
|
|
1283
|
+
{ name: "bisIn", kind: "angleBisector", p1: "B", vertex: "A", p2: "C" },
|
|
1284
|
+
// Phân giác ngoài = vuông góc với phân giác trong tại đỉnh A.
|
|
1285
|
+
{ name: "bisExt", kind: "perpendicular", throughPoint: "A", toLine: "bisIn" }
|
|
1286
|
+
]
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
|
|
889
1290
|
// src/stamps/geometry-2d/ai/prompt.ts
|
|
890
|
-
var FIXTURES = [
|
|
1291
|
+
var FIXTURES = [
|
|
1292
|
+
fixture,
|
|
1293
|
+
fixture2,
|
|
1294
|
+
fixture3,
|
|
1295
|
+
fixture4,
|
|
1296
|
+
fixture5,
|
|
1297
|
+
fixture6,
|
|
1298
|
+
fixture7,
|
|
1299
|
+
fixture10,
|
|
1300
|
+
fixture11,
|
|
1301
|
+
fixture14,
|
|
1302
|
+
fixture16,
|
|
1303
|
+
fixture15,
|
|
1304
|
+
fixture8,
|
|
1305
|
+
fixture13,
|
|
1306
|
+
fixture12,
|
|
1307
|
+
fixture9
|
|
1308
|
+
];
|
|
891
1309
|
function buildSystemPrompt() {
|
|
892
1310
|
const examples = FIXTURES.map(
|
|
893
1311
|
(f, i) => `### V\xED d\u1EE5 ${i + 1}
|
|
894
1312
|
**\u0110\u1EC1:** ${f.problem}
|
|
895
|
-
**
|
|
896
|
-
|
|
897
|
-
${JSON.stringify(f.dsl, null, 2)}
|
|
898
|
-
\`\`\``
|
|
1313
|
+
**Output:**
|
|
1314
|
+
${JSON.stringify({ decision: "build", figure: f.dsl }, null, 2)}`
|
|
899
1315
|
).join("\n\n");
|
|
900
1316
|
return `B\u1EA1n l\xE0 tr\u1EE3 l\xFD v\u1EBD h\xECnh h\u1ECDc 2D cho h\u1ECDc sinh THCS v\xE0 l\u1EDBp 10 Vi\u1EC7t Nam.
|
|
901
1317
|
|
|
902
1318
|
## Nhi\u1EC7m v\u1EE5
|
|
903
|
-
\u0110\u1ECDc \u0111\u1EC1 b\xE0i ti\u1EBFng Vi\u1EC7t \u2192 emit
|
|
1319
|
+
\u0110\u1ECDc \u0111\u1EC1 b\xE0i ti\u1EBFng Vi\u1EC7t \u2192 emit JSON envelope m\xF4 t\u1EA3 h\xECnh. H\u1EC7 th\u1ED1ng s\u1EBD render h\xECnh t\u1EEB DSL.
|
|
1320
|
+
|
|
1321
|
+
## Output format (CH\u1EC8 JSON, kh\xF4ng markdown, kh\xF4ng text kh\xE1c)
|
|
1322
|
+
{ "decision": "build", "figure": { /* DSL */ } }
|
|
1323
|
+
ho\u1EB7c
|
|
1324
|
+
{ "decision": "refuse", "reason": "l\xFD do ti\u1EBFng Vi\u1EC7t" }
|
|
904
1325
|
|
|
905
1326
|
## Quy t\u1EAFc
|
|
906
|
-
1.
|
|
907
|
-
2. \
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1327
|
+
1. V\u1EBD \u0111\u01B0\u1EE3c \u2192 decision="build" + figure \u0111\u1EA7y \u0111\u1EE7 DSL.
|
|
1328
|
+
2. \u0110\u1EC1 ngo\xE0i ph\u1EA1m vi \u2192 decision="refuse" + reason ti\u1EBFng Vi\u1EC7t c\u1EE5 th\u1EC3. Bao g\u1ED3m:
|
|
1329
|
+
- T\xEDnh to\xE1n \u0111\u1EA1i s\u1ED1 / l\u01B0\u1EE3ng gi\xE1c / gi\u1EA3i ph\u01B0\u01A1ng tr\xECnh (kh\xF4ng y\xEAu c\u1EA7u v\u1EBD).
|
|
1330
|
+
- H\xECnh 3D, l\u1EADp th\u1EC3, kh\xF4ng gian.
|
|
1331
|
+
- Ph\xE9p bi\u1EBFn h\xECnh affine / t\u1ECBnh ti\u1EBFn / v\u1ECB t\u1EF1 / quay (l\u1EDBp 11+).
|
|
1332
|
+
- \u0110\u1EC1 m\xF4 t\u1EA3 kh\xF4ng \u0111\u1EE7 th\xF4ng tin \u0111\u1EC3 d\u1EF1ng (vd "\u0111i\u1EC3m M b\u1EA5t k\u1EF3 tr\xEAn m\u1EB7t ph\u1EB3ng").
|
|
1333
|
+
3. **\u01AFu ti\xEAn derived points** (midpoint, perpFoot, intersection, circumcenter, \u2026) thay v\xEC t\u1EF1 compute to\u1EA1 \u0111\u1ED9. Ch\u1EC9 d\xF9ng \`free\` cho \u0111i\u1EC3m g\u1ED1c t\u1EF1 do (A, B, C c\u1EE7a tam gi\xE1c \u2014 \u0111\u1EB7t coord -5..5).
|
|
1334
|
+
4. **\u0110\u01B0\u1EDDng cao**: d\xF9ng \`perpFoot\` cho ch\xE2n, KH\xD4NG free anchor.
|
|
1335
|
+
5. **Ph\xE2n gi\xE1c g\xF3c A \u0111\u1EBFn BC**: emit line \`angleBisector\` (B, A, C) + segment BC + point D = \`intersection\` c\u1EE7a 2 line \u0111\xF3. KH\xD4NG emit \`onSegment\` v\u1EDBi segmentId ch\xEDnh l\xE0 segment \u0111ang d\u1EF1ng (cycle).
|
|
1336
|
+
6. **\u0110\u01B0\u1EDDng tr\xF2n ngo\u1EA1i ti\u1EBFp**: d\xF9ng \`circle3\` (3 \u0111i\u1EC3m), kh\xF4ng ph\u1EA3i \`polygon\`.
|
|
1337
|
+
7. **\u0110\u01B0\u1EDDng tr\xF2n n\u1ED9i ti\u1EBFp / ti\u1EBFp x\xFAc**: t\xE2m b\u1EB1ng \`incenter\`, \u0111i\u1EC3m ti\u1EBFp x\xFAc b\u1EB1ng \`perpFoot\` t\u1EEB t\xE2m xu\u1ED1ng c\u1EA1nh t\u01B0\u01A1ng \u1EE9ng, r\u1ED3i \`circleCP\`.
|
|
1338
|
+
8. **\u0110\u01B0\u1EDDng tr\xF2n (O; R) b\xE1n k\xEDnh s\u1ED1**: emit free helper tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n r\u1ED3i d\xF9ng \`circleCP\` (DSL kh\xF4ng h\u1ED7 tr\u1EE3 radius numeric tr\u1EF1c ti\u1EBFp).
|
|
1339
|
+
9. **\u0110\u1EC1 gh\xE9p nhi\u1EC1u y\xEAu c\u1EA7u** (vd "trung \u0111i\u1EC3m M v\xE0 \u0111\u01B0\u1EDDng cao AH"): m\u1ED7i y\xEAu c\u1EA7u l\xE0 1 derived point ri\xEAng v\u1EDBi kind \u0111\xFAng. KH\xD4NG \u0111\u1EB7t chung 1 point tho\u1EA3 nhi\u1EC1u r\xE0ng bu\u1ED9c. V\u1EBD th\xEAm segment cho t\u1EEBng y\xEAu c\u1EA7u (AM, AH, ...) khi \u0111\u1EC1 m\xF4 t\u1EA3 "d\u1EF1ng" ho\u1EB7c "v\u1EBD".
|
|
1340
|
+
|
|
1341
|
+
## Anti-pattern (B\u1EAET BU\u1ED8C tr\xE1nh)
|
|
1342
|
+
- **Cycle / forward-ref**: KH\xD4NG \u0111\u01B0\u1EE3c tham chi\u1EBFu ch\xE9o. N\u1EBFu point D \u2208 segment AD \u2192 cycle (AD c\u1EA7n D, D c\u1EA7n AD). \u0110\xFAng pattern: D = intersection c\u1EE7a line n\xE0o \u0111\xF3 v\u1EDBi BC, sau \u0111\xF3 AD = segment(A, D).
|
|
1343
|
+
- **M\u1ECDi name b\u1ECB reference ph\u1EA3i \u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a tr\u01B0\u1EDBc** (DSL topological sort: free \u2192 derived \u2192 shape).
|
|
1344
|
+
- **\u0110a gi\xE1c polygon KH\xD4NG thay th\u1EBF cho \u0111\u01B0\u1EDDng tr\xF2n**: n\u1EBFu \u0111\u1EC1 n\xF3i "\u0111\u01B0\u1EDDng tr\xF2n qua 3 \u0111i\u1EC3m" m\xE0 b\u1EA1n emit polygon th\xEC sai.
|
|
913
1345
|
|
|
914
1346
|
## Primitives s\u1EB5n c\xF3
|
|
915
1347
|
**Points:** free, midpoint, onSegment, onLine, onCircle, perpFoot, circumcenter, incenter, centroid, orthocenter, intersection
|
|
916
1348
|
**Shapes:** segment, line, ray, polygon, perpendicular, parallel, perpBisector, angleBisector, tangent, circleCP, circle3
|
|
917
1349
|
|
|
918
|
-
##
|
|
1350
|
+
## ${FIXTURES.length} v\xED d\u1EE5
|
|
919
1351
|
${examples}
|
|
920
1352
|
|
|
921
|
-
|
|
922
|
-
G\u1ECDi \`refuse\` v\u1EDBi \`reason\` ti\u1EBFng Vi\u1EC7t gi\u1EA3i th\xEDch c\u1EE5 th\u1EC3 (vd: "\u0110\u1EC1 thu\u1ED9c l\u1EDBp 11, ngo\xE0i ph\u1EA1m vi MVP" ho\u1EB7c "\u0110\u1EC1 kh\xF4ng r\xF5 v\u1ECB tr\xED \u0111i\u1EC3m M").`;
|
|
1353
|
+
Tr\u1EA3 v\u1EC1 CH\u1EC8 1 JSON object \u0111\xFAng schema. Kh\xF4ng c\xF3 l\u1EDDi d\u1EABn, kh\xF4ng markdown fence.`;
|
|
923
1354
|
}
|
|
924
|
-
var
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1355
|
+
var TOOL_NAME = "emit_figure_envelope";
|
|
1356
|
+
var AnthropicProvider = class {
|
|
1357
|
+
constructor(opts) {
|
|
1358
|
+
this.opts = opts;
|
|
1359
|
+
this.name = "anthropic";
|
|
1360
|
+
this.defaultModel = "claude-opus-4-7";
|
|
1361
|
+
if (!opts.apiKey) throw new Error("AnthropicProvider: apiKey b\u1EAFt bu\u1ED9c");
|
|
1362
|
+
}
|
|
1363
|
+
async call(req) {
|
|
1364
|
+
const enableCaching = this.opts.enableCaching !== false;
|
|
1365
|
+
const systemBlock = enableCaching ? { type: "text", text: req.systemPrompt, cache_control: { type: "ephemeral" } } : { type: "text", text: req.systemPrompt };
|
|
1366
|
+
const tool = {
|
|
1367
|
+
name: TOOL_NAME,
|
|
1368
|
+
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.',
|
|
1369
|
+
input_schema: req.schema
|
|
1370
|
+
};
|
|
1371
|
+
const client = new Anthropic__default.default({ apiKey: this.opts.apiKey });
|
|
1372
|
+
let resp;
|
|
1373
|
+
try {
|
|
1374
|
+
resp = await client.messages.create(
|
|
1375
|
+
{
|
|
1376
|
+
model: req.model,
|
|
1377
|
+
max_tokens: req.maxTokens,
|
|
1378
|
+
system: [systemBlock],
|
|
1379
|
+
tools: [tool],
|
|
1380
|
+
tool_choice: { type: "tool", name: TOOL_NAME },
|
|
1381
|
+
messages: [{ role: "user", content: req.userPrompt }]
|
|
1382
|
+
},
|
|
1383
|
+
req.signal ? { signal: req.signal } : void 0
|
|
1384
|
+
);
|
|
1385
|
+
} catch (e) {
|
|
1386
|
+
const err = e;
|
|
1387
|
+
return {
|
|
1388
|
+
kind: "error",
|
|
1389
|
+
message: err.message ?? "L\u1ED7i g\u1ECDi Anthropic API",
|
|
1390
|
+
...err.status !== void 0 ? { status: err.status } : {}
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
const usage = toUsage(resp.usage);
|
|
1394
|
+
const toolUse = resp.content.find((c) => c.type === "tool_use");
|
|
1395
|
+
if (!toolUse || toolUse.type !== "tool_use") {
|
|
1396
|
+
return {
|
|
1397
|
+
kind: "error",
|
|
1398
|
+
message: "Claude kh\xF4ng g\u1ECDi tool. stop_reason=" + resp.stop_reason
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
if (toolUse.name !== TOOL_NAME) {
|
|
1402
|
+
return {
|
|
1403
|
+
kind: "error",
|
|
1404
|
+
message: `Tool kh\xF4ng x\xE1c \u0111\u1ECBnh: "${toolUse.name}"`
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
return { kind: "json", data: toolUse.input, usage };
|
|
1408
|
+
}
|
|
939
1409
|
};
|
|
940
|
-
var TOOLS = [BUILD_FIGURE_TOOL, REFUSE_TOOL];
|
|
941
|
-
|
|
942
|
-
// src/stamps/geometry-2d/ai/buildFigure.ts
|
|
943
|
-
var DEFAULT_MODEL = "claude-opus-4-7";
|
|
944
|
-
var DEFAULT_MAX_TOKENS = 8192;
|
|
945
1410
|
function toUsage(u) {
|
|
946
1411
|
return {
|
|
947
1412
|
inputTokens: u.input_tokens,
|
|
@@ -950,87 +1415,742 @@ function toUsage(u) {
|
|
|
950
1415
|
cacheCreationTokens: u.cache_creation_input_tokens ?? 0
|
|
951
1416
|
};
|
|
952
1417
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1418
|
+
|
|
1419
|
+
// src/stamps/geometry-2d/ai/providers/ollama.ts
|
|
1420
|
+
var DEFAULT_BASE_URL = "http://localhost:11434";
|
|
1421
|
+
var DEFAULT_MODEL = "gemma3:4b";
|
|
1422
|
+
var OllamaProvider = class {
|
|
1423
|
+
constructor(opts = {}) {
|
|
1424
|
+
this.name = "ollama";
|
|
1425
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1426
|
+
this.defaultModel = opts.defaultModel ?? DEFAULT_MODEL;
|
|
1427
|
+
this.fetchImpl = opts.fetchImpl ?? null;
|
|
1428
|
+
}
|
|
1429
|
+
resolveFetch() {
|
|
1430
|
+
if (this.fetchImpl) return this.fetchImpl;
|
|
1431
|
+
if (typeof fetch === "undefined") {
|
|
1432
|
+
throw new Error(
|
|
1433
|
+
"OllamaProvider: global `fetch` kh\xF4ng kh\u1EA3 d\u1EE5ng. Truy\u1EC1n `fetchImpl` qua constructor ho\u1EB7c ch\u1EA1y \u1EDF Node 18+ / browser."
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
return fetch;
|
|
1437
|
+
}
|
|
1438
|
+
async call(req) {
|
|
1439
|
+
const body = {
|
|
1440
|
+
model: req.model,
|
|
1441
|
+
messages: [
|
|
1442
|
+
{ role: "system", content: req.systemPrompt },
|
|
1443
|
+
{ role: "user", content: req.userPrompt }
|
|
1444
|
+
],
|
|
1445
|
+
// Ollama v0.5+ structured outputs: model bị constrain emit JSON đúng schema.
|
|
1446
|
+
format: req.schema,
|
|
1447
|
+
stream: false,
|
|
1448
|
+
options: {
|
|
1449
|
+
// num_predict ≈ max_tokens
|
|
1450
|
+
num_predict: req.maxTokens,
|
|
1451
|
+
// temperature thấp cho output deterministic hơn (DSL cần consistent).
|
|
1452
|
+
temperature: 0.2
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
let resp;
|
|
1456
|
+
let doFetch;
|
|
1457
|
+
try {
|
|
1458
|
+
doFetch = this.resolveFetch();
|
|
1459
|
+
} catch (e) {
|
|
1460
|
+
const err = e;
|
|
1461
|
+
return { kind: "error", message: err.message ?? "fetch kh\xF4ng kh\u1EA3 d\u1EE5ng" };
|
|
1462
|
+
}
|
|
1463
|
+
try {
|
|
1464
|
+
resp = await doFetch(`${this.baseUrl}/api/chat`, {
|
|
1465
|
+
method: "POST",
|
|
1466
|
+
headers: { "content-type": "application/json" },
|
|
1467
|
+
body: JSON.stringify(body),
|
|
1468
|
+
signal: req.signal
|
|
1469
|
+
});
|
|
1470
|
+
} catch (e) {
|
|
1471
|
+
const err = e;
|
|
1472
|
+
return {
|
|
1473
|
+
kind: "error",
|
|
1474
|
+
message: err.message ?? `Kh\xF4ng k\u1EBFt n\u1ED1i \u0111\u01B0\u1EE3c Ollama \u1EDF ${this.baseUrl}`
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
if (!resp.ok) {
|
|
1478
|
+
let detail = "";
|
|
1479
|
+
try {
|
|
1480
|
+
detail = await resp.text();
|
|
1481
|
+
} catch {
|
|
1482
|
+
}
|
|
1483
|
+
return {
|
|
1484
|
+
kind: "error",
|
|
1485
|
+
message: `Ollama HTTP ${resp.status}: ${detail || resp.statusText}`,
|
|
1486
|
+
status: resp.status
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
let json;
|
|
1490
|
+
try {
|
|
1491
|
+
json = await resp.json();
|
|
1492
|
+
} catch (e) {
|
|
1493
|
+
const err = e;
|
|
1494
|
+
return { kind: "error", message: "Ollama response kh\xF4ng ph\u1EA3i JSON: " + (err.message ?? "?") };
|
|
1495
|
+
}
|
|
1496
|
+
const content = json.message?.content?.trim();
|
|
1497
|
+
if (!content) {
|
|
1498
|
+
return { kind: "error", message: "Ollama tr\u1EA3 message.content r\u1ED7ng" };
|
|
1499
|
+
}
|
|
1500
|
+
let data;
|
|
1501
|
+
try {
|
|
1502
|
+
data = JSON.parse(content);
|
|
1503
|
+
} catch (e) {
|
|
1504
|
+
const err = e;
|
|
1505
|
+
return {
|
|
1506
|
+
kind: "error",
|
|
1507
|
+
message: "Ollama content kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: " + (err.message ?? "?")
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
const usage = {
|
|
1511
|
+
inputTokens: json.prompt_eval_count ?? 0,
|
|
1512
|
+
outputTokens: json.eval_count ?? 0,
|
|
1513
|
+
cacheReadTokens: 0,
|
|
1514
|
+
cacheCreationTokens: 0
|
|
1515
|
+
};
|
|
1516
|
+
return { kind: "json", data, usage };
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
|
|
1520
|
+
// src/stamps/geometry-2d/ai/providers/index.ts
|
|
1521
|
+
function selectProvider(opts = {}) {
|
|
1522
|
+
if (opts.provider) return opts.provider;
|
|
1523
|
+
if (opts.apiKey) {
|
|
1524
|
+
return new AnthropicProvider({
|
|
1525
|
+
apiKey: opts.apiKey,
|
|
1526
|
+
enableCaching: opts.enableCaching
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
const env = opts.env ?? readEnv();
|
|
1530
|
+
const wanted = (env.WHITEBOARD_AI_PROVIDER ?? "ollama").toLowerCase();
|
|
1531
|
+
if (wanted === "anthropic") {
|
|
1532
|
+
const key = env.ANTHROPIC_API_KEY;
|
|
1533
|
+
if (!key) {
|
|
1534
|
+
throw new Error(
|
|
1535
|
+
"selectProvider: WHITEBOARD_AI_PROVIDER=anthropic nh\u01B0ng thi\u1EBFu env ANTHROPIC_API_KEY"
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
return new AnthropicProvider({ apiKey: key, enableCaching: opts.enableCaching });
|
|
1539
|
+
}
|
|
1540
|
+
if (wanted === "ollama") {
|
|
1541
|
+
return new OllamaProvider({
|
|
1542
|
+
baseUrl: opts.ollamaBaseUrl ?? env.OLLAMA_BASE_URL,
|
|
1543
|
+
defaultModel: opts.ollamaDefaultModel ?? env.OLLAMA_DEFAULT_MODEL
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
throw new Error(`selectProvider: WHITEBOARD_AI_PROVIDER="${wanted}" kh\xF4ng h\u1EE3p l\u1EC7 (anthropic|ollama)`);
|
|
1547
|
+
}
|
|
1548
|
+
function readEnv() {
|
|
1549
|
+
if (typeof process !== "undefined" && process.env) {
|
|
1550
|
+
return process.env;
|
|
956
1551
|
}
|
|
1552
|
+
return {};
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/stamps/geometry-2d/ai/buildFigure.ts
|
|
1556
|
+
var DEFAULT_MAX_TOKENS = 8192;
|
|
1557
|
+
async function generateFigure(problem, opts = {}) {
|
|
957
1558
|
if (!problem || !problem.trim()) {
|
|
958
1559
|
return { ok: false, reason: "api_error", message: "\u0110\u1EC1 b\xE0i r\u1ED7ng" };
|
|
959
1560
|
}
|
|
960
|
-
|
|
961
|
-
const enableCaching = opts.enableCaching !== false;
|
|
962
|
-
const systemBlock = enableCaching ? { type: "text", text: systemText, cache_control: { type: "ephemeral" } } : { type: "text", text: systemText };
|
|
963
|
-
let response;
|
|
1561
|
+
let provider;
|
|
964
1562
|
try {
|
|
965
|
-
|
|
966
|
-
apiKey: opts.apiKey,
|
|
967
|
-
model: opts.model ?? DEFAULT_MODEL,
|
|
968
|
-
maxTokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
969
|
-
system: [systemBlock],
|
|
970
|
-
tools: TOOLS,
|
|
971
|
-
toolChoice: { type: "any" },
|
|
972
|
-
messages: [{ role: "user", content: problem }],
|
|
973
|
-
signal: opts.signal
|
|
974
|
-
});
|
|
1563
|
+
provider = selectProvider(opts);
|
|
975
1564
|
} catch (e) {
|
|
976
1565
|
const err = e;
|
|
1566
|
+
return { ok: false, reason: "api_error", message: err.message ?? "Kh\xF4ng ch\u1ECDn \u0111\u01B0\u1EE3c provider" };
|
|
1567
|
+
}
|
|
1568
|
+
const systemPrompt = buildSystemPrompt();
|
|
1569
|
+
const schema = envelopeJsonSchema();
|
|
1570
|
+
const out = await provider.call({
|
|
1571
|
+
systemPrompt,
|
|
1572
|
+
userPrompt: problem,
|
|
1573
|
+
schema,
|
|
1574
|
+
model: opts.model ?? provider.defaultModel,
|
|
1575
|
+
maxTokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
1576
|
+
signal: opts.signal
|
|
1577
|
+
});
|
|
1578
|
+
if (out.kind === "error") {
|
|
977
1579
|
return {
|
|
978
1580
|
ok: false,
|
|
979
1581
|
reason: "api_error",
|
|
980
|
-
message:
|
|
981
|
-
...
|
|
1582
|
+
message: out.message,
|
|
1583
|
+
...out.status !== void 0 ? { status: out.status } : {},
|
|
1584
|
+
provider: provider.name
|
|
982
1585
|
};
|
|
983
1586
|
}
|
|
984
|
-
const usage =
|
|
985
|
-
const
|
|
986
|
-
if (!
|
|
987
|
-
const text = response.content.find((c) => c.type === "text");
|
|
988
|
-
const textStr = text?.type === "text" ? text.text : "(empty)";
|
|
1587
|
+
const usage = toUsage2(out.usage);
|
|
1588
|
+
const parsed = FigureEnvelopeZ.safeParse(out.data);
|
|
1589
|
+
if (!parsed.success) {
|
|
989
1590
|
return {
|
|
990
1591
|
ok: false,
|
|
991
1592
|
reason: "parse_error",
|
|
992
|
-
message: "
|
|
993
|
-
raw:
|
|
994
|
-
usage
|
|
1593
|
+
message: "Envelope kh\xF4ng kh\u1EDBp schema: " + parsed.error.issues.map((i) => i.message).join("; "),
|
|
1594
|
+
raw: out.data,
|
|
1595
|
+
usage,
|
|
1596
|
+
provider: provider.name
|
|
995
1597
|
};
|
|
996
1598
|
}
|
|
997
|
-
|
|
998
|
-
|
|
1599
|
+
const env = parsed.data;
|
|
1600
|
+
if (env.decision === "refuse") {
|
|
999
1601
|
return {
|
|
1000
1602
|
ok: false,
|
|
1001
1603
|
reason: "refused",
|
|
1002
|
-
message:
|
|
1003
|
-
usage
|
|
1604
|
+
message: env.reason ?? "AI t\u1EEB ch\u1ED1i kh\xF4ng n\xEAu l\xFD do",
|
|
1605
|
+
usage,
|
|
1606
|
+
provider: provider.name
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
const dsl = envelopeBuildDsl(env);
|
|
1610
|
+
const tResult = transpile(dsl);
|
|
1611
|
+
if (!tResult.ok) {
|
|
1612
|
+
return {
|
|
1613
|
+
ok: false,
|
|
1614
|
+
reason: "transpile_error",
|
|
1615
|
+
message: "DSL t\u1EEB AI kh\xF4ng h\u1EE3p l\u1EC7",
|
|
1616
|
+
errors: tResult.errors,
|
|
1617
|
+
dsl,
|
|
1618
|
+
usage,
|
|
1619
|
+
provider: provider.name
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
return {
|
|
1623
|
+
ok: true,
|
|
1624
|
+
state: tResult.state,
|
|
1625
|
+
dsl,
|
|
1626
|
+
usage,
|
|
1627
|
+
provider: provider.name
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
function toUsage2(u) {
|
|
1631
|
+
return {
|
|
1632
|
+
inputTokens: u?.inputTokens ?? 0,
|
|
1633
|
+
outputTokens: u?.outputTokens ?? 0,
|
|
1634
|
+
cacheReadTokens: u?.cacheReadTokens ?? 0,
|
|
1635
|
+
cacheCreationTokens: u?.cacheCreationTokens ?? 0
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// src/stamps/geometry-2d/ai/handleGenerateFigure.ts
|
|
1640
|
+
var DEFAULT_MAX_ATTEMPTS = 2;
|
|
1641
|
+
async function handleGenerateFigure(input, opts = {}) {
|
|
1642
|
+
const { onResult, maxAttempts: rawMax, ...generateOpts } = opts;
|
|
1643
|
+
const maxAttempts = clampAttempts(rawMax ?? DEFAULT_MAX_ATTEMPTS);
|
|
1644
|
+
let lastResult = null;
|
|
1645
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1646
|
+
const result = await generateFigure(input.problem, generateOpts);
|
|
1647
|
+
lastResult = result;
|
|
1648
|
+
if (onResult) {
|
|
1649
|
+
try {
|
|
1650
|
+
onResult(result, attempt);
|
|
1651
|
+
} catch {
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
if (result.ok) {
|
|
1655
|
+
return { ok: true, state: result.state };
|
|
1656
|
+
}
|
|
1657
|
+
if (result.reason === "transpile_error" && attempt < maxAttempts) {
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1660
|
+
break;
|
|
1661
|
+
}
|
|
1662
|
+
return mapErrorToUi(lastResult);
|
|
1663
|
+
}
|
|
1664
|
+
function clampAttempts(n) {
|
|
1665
|
+
if (!Number.isFinite(n)) return DEFAULT_MAX_ATTEMPTS;
|
|
1666
|
+
return Math.max(1, Math.min(5, Math.floor(n)));
|
|
1667
|
+
}
|
|
1668
|
+
function mapErrorToUi(result) {
|
|
1669
|
+
if (result.ok) return { ok: true, state: result.state };
|
|
1670
|
+
switch (result.reason) {
|
|
1671
|
+
case "refused":
|
|
1672
|
+
return { ok: false, message: result.message };
|
|
1673
|
+
case "parse_error":
|
|
1674
|
+
return {
|
|
1675
|
+
ok: false,
|
|
1676
|
+
message: "AI tr\u1EA3 v\u1EC1 d\u1EEF li\u1EC7u kh\xF4ng h\u1EE3p l\u1EC7. Vui l\xF2ng th\u1EED l\u1EA1i ho\u1EB7c di\u1EC5n \u0111\u1EA1t l\u1EA1i \u0111\u1EC1 b\xE0i."
|
|
1677
|
+
};
|
|
1678
|
+
case "transpile_error":
|
|
1679
|
+
return {
|
|
1680
|
+
ok: false,
|
|
1681
|
+
message: "AI t\u1EA1o h\xECnh kh\xF4ng h\u1EE3p l\u1EC7 (\u0111\xE3 th\u1EED l\u1EA1i). Vui l\xF2ng t\xE1ch th\xE0nh 1 y\xEAu c\u1EA7u/l\u1EA7n ho\u1EB7c di\u1EC5n \u0111\u1EA1t kh\xE1c."
|
|
1682
|
+
};
|
|
1683
|
+
case "api_error":
|
|
1684
|
+
default:
|
|
1685
|
+
return { ok: false, message: result.message };
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
var FigureRefineEnvelopeZ = zod.z.object({
|
|
1689
|
+
decision: zod.z.enum(["add", "replace", "refuse"]),
|
|
1690
|
+
figure: DslInput.optional(),
|
|
1691
|
+
reason: zod.z.string().optional()
|
|
1692
|
+
}).refine(
|
|
1693
|
+
(e) => e.decision === "refuse" ? e.reason != null && e.reason.length > 0 : e.figure != null,
|
|
1694
|
+
{
|
|
1695
|
+
message: "decision=add/replace c\u1EA7n `figure`; decision=refuse c\u1EA7n `reason` kh\xF4ng r\u1ED7ng"
|
|
1696
|
+
}
|
|
1697
|
+
);
|
|
1698
|
+
function refineEnvelopeJsonSchema() {
|
|
1699
|
+
return zodToJsonSchema.zodToJsonSchema(FigureRefineEnvelopeZ, {
|
|
1700
|
+
target: "jsonSchema7",
|
|
1701
|
+
$refStrategy: "none"
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// src/stamps/geometry-2d/ai/refineFixtures.ts
|
|
1706
|
+
var triangleABC = {
|
|
1707
|
+
version: 1,
|
|
1708
|
+
points: [
|
|
1709
|
+
{ name: "A", kind: "free", x: 0, y: 3 },
|
|
1710
|
+
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
1711
|
+
{ name: "C", kind: "free", x: 3, y: 0 }
|
|
1712
|
+
],
|
|
1713
|
+
shapes: [{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] }]
|
|
1714
|
+
};
|
|
1715
|
+
var rightTriangleAtA = {
|
|
1716
|
+
version: 1,
|
|
1717
|
+
points: [
|
|
1718
|
+
{ name: "A", kind: "free", x: 0, y: 0 },
|
|
1719
|
+
{ name: "B", kind: "free", x: 4, y: 0 },
|
|
1720
|
+
{ name: "C", kind: "free", x: 0, y: 3 }
|
|
1721
|
+
],
|
|
1722
|
+
shapes: [{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] }]
|
|
1723
|
+
};
|
|
1724
|
+
var parallelogramABCD = {
|
|
1725
|
+
version: 1,
|
|
1726
|
+
points: [
|
|
1727
|
+
{ name: "A", kind: "free", x: -2, y: 0 },
|
|
1728
|
+
{ name: "B", kind: "free", x: 3, y: 0 },
|
|
1729
|
+
{ name: "C", kind: "free", x: 4, y: 2 },
|
|
1730
|
+
{ name: "D", kind: "free", x: -1, y: 2 }
|
|
1731
|
+
],
|
|
1732
|
+
shapes: [{ name: "ABCD", kind: "polygon", vertices: ["A", "B", "C", "D"] }]
|
|
1733
|
+
};
|
|
1734
|
+
var circleOnA = {
|
|
1735
|
+
version: 1,
|
|
1736
|
+
points: [
|
|
1737
|
+
{ name: "O", kind: "free", x: 0, y: 0 },
|
|
1738
|
+
{ name: "A", kind: "free", x: 3, y: 0 }
|
|
1739
|
+
],
|
|
1740
|
+
shapes: [{ name: "omega", kind: "circleCP", center: "O", surfacePoint: "A" }]
|
|
1741
|
+
};
|
|
1742
|
+
var REFINE_FIXTURES = [
|
|
1743
|
+
{
|
|
1744
|
+
name: "triangle-add-midpoint",
|
|
1745
|
+
currentDsl: triangleABC,
|
|
1746
|
+
instruction: "Th\xEAm trung \u0111i\u1EC3m M c\u1EE7a BC",
|
|
1747
|
+
expectedEnvelope: {
|
|
1748
|
+
decision: "add",
|
|
1749
|
+
figure: {
|
|
1750
|
+
version: 1,
|
|
1751
|
+
points: [{ name: "M", kind: "midpoint", p1: "B", p2: "C" }],
|
|
1752
|
+
shapes: [{ name: "AM", kind: "segment", p1: "A", p2: "M" }]
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
name: "triangle-add-altitude",
|
|
1758
|
+
currentDsl: triangleABC,
|
|
1759
|
+
instruction: "D\u1EF1ng \u0111\u01B0\u1EDDng cao AH xu\u1ED1ng BC",
|
|
1760
|
+
expectedEnvelope: {
|
|
1761
|
+
decision: "add",
|
|
1762
|
+
figure: {
|
|
1763
|
+
version: 1,
|
|
1764
|
+
points: [{ name: "H", kind: "perpFoot", from: "A", onLine: "BC_line" }],
|
|
1765
|
+
shapes: [
|
|
1766
|
+
{ name: "BC_line", kind: "line", p1: "B", p2: "C" },
|
|
1767
|
+
{ name: "AH", kind: "segment", p1: "A", p2: "H" }
|
|
1768
|
+
]
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
},
|
|
1772
|
+
{
|
|
1773
|
+
name: "triangle-add-circumcircle",
|
|
1774
|
+
currentDsl: triangleABC,
|
|
1775
|
+
instruction: "V\u1EBD \u0111\u01B0\u1EDDng tr\xF2n ngo\u1EA1i ti\u1EBFp tam gi\xE1c ABC",
|
|
1776
|
+
expectedEnvelope: {
|
|
1777
|
+
decision: "add",
|
|
1778
|
+
figure: {
|
|
1779
|
+
version: 1,
|
|
1780
|
+
points: [{ name: "O", kind: "circumcenter", vertices: ["A", "B", "C"] }],
|
|
1781
|
+
shapes: [{ name: "omega", kind: "circle3", p1: "A", p2: "B", p3: "C" }]
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
name: "right-triangle-add-centroid",
|
|
1787
|
+
currentDsl: rightTriangleAtA,
|
|
1788
|
+
instruction: "Th\xEAm tr\u1ECDng t\xE2m G c\u1EE7a tam gi\xE1c",
|
|
1789
|
+
expectedEnvelope: {
|
|
1790
|
+
decision: "add",
|
|
1791
|
+
figure: {
|
|
1792
|
+
version: 1,
|
|
1793
|
+
points: [{ name: "G", kind: "centroid", vertices: ["A", "B", "C"] }],
|
|
1794
|
+
shapes: []
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
name: "parallelogram-add-diagonals",
|
|
1800
|
+
currentDsl: parallelogramABCD,
|
|
1801
|
+
instruction: "V\u1EBD hai \u0111\u01B0\u1EDDng ch\xE9o AC, BD v\xE0 giao \u0111i\u1EC3m O",
|
|
1802
|
+
expectedEnvelope: {
|
|
1803
|
+
decision: "add",
|
|
1804
|
+
figure: {
|
|
1805
|
+
version: 1,
|
|
1806
|
+
points: [{ name: "O", kind: "intersection", ref1: "AC", ref2: "BD" }],
|
|
1807
|
+
shapes: [
|
|
1808
|
+
{ name: "AC", kind: "segment", p1: "A", p2: "C" },
|
|
1809
|
+
{ name: "BD", kind: "segment", p1: "B", p2: "D" }
|
|
1810
|
+
]
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
name: "circle-add-tangent",
|
|
1816
|
+
currentDsl: circleOnA,
|
|
1817
|
+
instruction: "K\u1EBB ti\u1EBFp tuy\u1EBFn t\u1EA1i A c\u1EE7a \u0111\u01B0\u1EDDng tr\xF2n",
|
|
1818
|
+
expectedEnvelope: {
|
|
1819
|
+
decision: "add",
|
|
1820
|
+
figure: {
|
|
1821
|
+
version: 1,
|
|
1822
|
+
points: [],
|
|
1823
|
+
shapes: [{ name: "t", kind: "tangent", throughPoint: "A", toCircle: "omega" }]
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
name: "triangle-replace-equilateral",
|
|
1829
|
+
currentDsl: triangleABC,
|
|
1830
|
+
instruction: "B\u1ECF tam gi\xE1c n\xE0y, v\u1EBD tam gi\xE1c \u0111\u1EC1u ABC thay v\xE0o",
|
|
1831
|
+
expectedEnvelope: {
|
|
1832
|
+
decision: "replace",
|
|
1833
|
+
figure: {
|
|
1834
|
+
version: 1,
|
|
1835
|
+
points: [
|
|
1836
|
+
{ name: "A", kind: "free", x: 0, y: 2 },
|
|
1837
|
+
{ name: "B", kind: "free", x: -1.732, y: -1 },
|
|
1838
|
+
{ name: "C", kind: "free", x: 1.732, y: -1 }
|
|
1839
|
+
],
|
|
1840
|
+
shapes: [{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] }]
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
},
|
|
1844
|
+
{
|
|
1845
|
+
name: "triangle-replace-rhombus",
|
|
1846
|
+
currentDsl: triangleABC,
|
|
1847
|
+
instruction: "\u0110\u1ED5i sang h\xECnh thoi ABCD",
|
|
1848
|
+
expectedEnvelope: {
|
|
1849
|
+
decision: "replace",
|
|
1850
|
+
figure: {
|
|
1851
|
+
version: 1,
|
|
1852
|
+
points: [
|
|
1853
|
+
{ name: "A", kind: "free", x: -2, y: 0 },
|
|
1854
|
+
{ name: "B", kind: "free", x: 0, y: 1.5 },
|
|
1855
|
+
{ name: "C", kind: "free", x: 2, y: 0 },
|
|
1856
|
+
{ name: "D", kind: "free", x: 0, y: -1.5 }
|
|
1857
|
+
],
|
|
1858
|
+
shapes: [{ name: "ABCD", kind: "polygon", vertices: ["A", "B", "C", "D"] }]
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
name: "refuse-calculation",
|
|
1864
|
+
currentDsl: triangleABC,
|
|
1865
|
+
instruction: "T\xEDnh di\u1EC7n t\xEDch tam gi\xE1c ABC",
|
|
1866
|
+
expectedEnvelope: {
|
|
1867
|
+
decision: "refuse",
|
|
1868
|
+
reason: "Y\xEAu c\u1EA7u t\xEDnh to\xE1n, kh\xF4ng ph\u1EA3i v\u1EBD h\xECnh."
|
|
1869
|
+
}
|
|
1870
|
+
},
|
|
1871
|
+
{
|
|
1872
|
+
name: "refuse-3d",
|
|
1873
|
+
currentDsl: triangleABC,
|
|
1874
|
+
instruction: "V\u1EBD h\xECnh ch\xF3p SABC v\u1EDBi S n\u1EB1m tr\xEAn tam gi\xE1c",
|
|
1875
|
+
expectedEnvelope: {
|
|
1876
|
+
decision: "refuse",
|
|
1877
|
+
reason: "H\xECnh 3D ngo\xE0i ph\u1EA1m vi geometry-2d."
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
];
|
|
1881
|
+
var REFINE_PROMPT_FIXTURES = REFINE_FIXTURES.slice(0, 8);
|
|
1882
|
+
|
|
1883
|
+
// src/stamps/geometry-2d/ai/refinePrompt.ts
|
|
1884
|
+
function namesOf(dsl) {
|
|
1885
|
+
return {
|
|
1886
|
+
points: dsl.points.map((p) => p.name),
|
|
1887
|
+
shapes: dsl.shapes.map((s) => s.name)
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
function buildRefineSystemPrompt(currentDsl) {
|
|
1891
|
+
const names = namesOf(currentDsl);
|
|
1892
|
+
const examples = REFINE_PROMPT_FIXTURES.map((f, i) => {
|
|
1893
|
+
const env = f.expectedEnvelope;
|
|
1894
|
+
return `### V\xED d\u1EE5 ${i + 1}
|
|
1895
|
+
**H\xECnh hi\u1EC7n t\u1EA1i:**
|
|
1896
|
+
${JSON.stringify(f.currentDsl, null, 2)}
|
|
1897
|
+
**Y\xEAu c\u1EA7u ch\u1EC9nh s\u1EEDa:** ${f.instruction}
|
|
1898
|
+
**Output:**
|
|
1899
|
+
${JSON.stringify(env, null, 2)}`;
|
|
1900
|
+
}).join("\n\n");
|
|
1901
|
+
return `B\u1EA1n l\xE0 tr\u1EE3 l\xFD v\u1EBD h\xECnh h\u1ECDc 2D. H\u1ECDc sinh \u0111\xE3 c\xF3 H\xCCNH HI\u1EC6N T\u1EA0I v\xE0 mu\u1ED1n TH\xCAM/S\u1EECA.
|
|
1902
|
+
|
|
1903
|
+
## H\xECnh hi\u1EC7n t\u1EA1i (DSL JSON)
|
|
1904
|
+
${JSON.stringify(currentDsl, null, 2)}
|
|
1905
|
+
|
|
1906
|
+
## T\xEAn \u0111\xE3 d\xF9ng (KH\xD4NG \u0111\u01B0\u1EE3c redefine)
|
|
1907
|
+
points: ${names.points.join(", ") || "(ch\u01B0a c\xF3)"}
|
|
1908
|
+
shapes: ${names.shapes.join(", ") || "(ch\u01B0a c\xF3)"}
|
|
1909
|
+
|
|
1910
|
+
## Nhi\u1EC7m v\u1EE5
|
|
1911
|
+
\u0110\u1ECDc Y\xCAU C\u1EA6U CH\u1EC8NH S\u1EECA \u2192 emit JSON envelope \u0111\xFAng 1 trong 3 d\u1EA1ng:
|
|
1912
|
+
|
|
1913
|
+
{ "decision": "add", "figure": <DSL ch\u1EC9 ch\u1EE9a entity M\u1EDAI> }
|
|
1914
|
+
{ "decision": "replace", "figure": <DSL ho\xE0n ch\u1EC9nh thay th\u1EBF h\xECnh c\u0169> }
|
|
1915
|
+
{ "decision": "refuse", "reason": "l\xFD do ti\u1EBFng Vi\u1EC7t" }
|
|
1916
|
+
|
|
1917
|
+
## Khi n\xE0o d\xF9ng decision n\xE0o?
|
|
1918
|
+
- **"add"**: user mu\u1ED1n TH\xCAM primitive v\xE0o h\xECnh hi\u1EC7n t\u1EA1i (vd: "th\xEAm trung \u0111i\u1EC3m M c\u1EE7a BC", "d\u1EF1ng \u0111\u01B0\u1EDDng cao AH").
|
|
1919
|
+
\u2192 figure ch\u1EC9 ch\u1EE9a point/shape M\u1EDAI. Ref t\xEAn c\u0169 (A, B, C, \u2026) l\xE0 OK. KH\xD4NG redefine t\xEAn c\u0169.
|
|
1920
|
+
- **"replace"**: user mu\u1ED1n v\u1EBD L\u1EA0I ho\u1EB7c \u0111\u1ED5i sang h\xECnh kh\xE1c h\u1EB3n (vd: "v\u1EBD tam gi\xE1c \u0111\u1EC1u thay v\xE0o", "b\u1ECF tam gi\xE1c, d\u1EF1ng h\xECnh thoi").
|
|
1921
|
+
\u2192 figure \u0111\u1EA7y \u0111\u1EE7 nh\u01B0 prompt m\u1EDBi (gi\u1ED1ng mode build).
|
|
1922
|
+
- **"refuse"**: y\xEAu c\u1EA7u ngo\xE0i ph\u1EA1m vi (3D, l\u01B0\u1EE3ng gi\xE1c, bi\u1EBFn h\xECnh l\u1EDBp 11+, t\xEDnh to\xE1n \u0111\u1EA1i s\u1ED1).
|
|
1923
|
+
|
|
1924
|
+
## Quy t\u1EAFc decision=add
|
|
1925
|
+
1. M\u1ECDi name M\u1EDAI KH\xD4NG \u0111\u01B0\u1EE3c tr\xF9ng v\u1EDBi t\xEAn \u0111\xE3 d\xF9ng \u1EDF tr\xEAn. Tr\xF9ng \u2192 \u0111\u1EB7t kh\xE1c (M', M1, \u2026).
|
|
1926
|
+
2. \u01AFU TI\xCAN derived points: midpoint, perpFoot, intersection, circumcenter, incenter, centroid, orthocenter.
|
|
1927
|
+
3. Ref t\u1EDBi t\xEAn c\u0169 (A, B, C) l\xE0 OK \u2014 AI bi\u1EBFt c\xE1c t\xEAn \u0111\xF3 t\u1ED3n t\u1EA1i.
|
|
1928
|
+
4. KH\xD4NG copy l\u1EA1i entity c\u0169 v\xE0o figure delta (delta ch\u1EC9 ch\u1EE9a c\xE1i M\u1EDAI).
|
|
1929
|
+
|
|
1930
|
+
## Anti-pattern (B\u1EAET BU\u1ED8C tr\xE1nh)
|
|
1931
|
+
- KH\xD4NG redefine t\xEAn \u0111\xE3 d\xF9ng (A, B, C \u0111\xE3 c\xF3 \u2192 KH\xD4NG \u0111\u1EB7t l\u1EA1i).
|
|
1932
|
+
- KH\xD4NG ref t\u1EDBi t\xEAn ch\u01B0a c\xF3 ngo\xE0i "T\xEAn \u0111\xE3 d\xF9ng" + t\xEAn v\u1EEBa \u0111\u1ECBnh ngh\u0129a trong delta.
|
|
1933
|
+
- KH\xD4NG emit add v\u1EDBi figure ch\u1EE9a c\u1EA3 entity c\u0169 (\u0111\xF3 l\xE0 replace).
|
|
1934
|
+
|
|
1935
|
+
## Primitives s\u1EB5n c\xF3
|
|
1936
|
+
**Points:** free, midpoint, onSegment, onLine, onCircle, perpFoot, circumcenter, incenter, centroid, orthocenter, intersection
|
|
1937
|
+
**Shapes:** segment, line, ray, polygon, perpendicular, parallel, perpBisector, angleBisector, tangent, circleCP, circle3
|
|
1938
|
+
|
|
1939
|
+
## ${REFINE_PROMPT_FIXTURES.length} v\xED d\u1EE5
|
|
1940
|
+
|
|
1941
|
+
${examples}
|
|
1942
|
+
|
|
1943
|
+
Tr\u1EA3 v\u1EC1 CH\u1EC8 1 JSON object \u0111\xFAng schema. Kh\xF4ng c\xF3 l\u1EDDi d\u1EABn, kh\xF4ng markdown fence.`;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// src/stamps/geometry-2d/ai/buildFigureDelta.ts
|
|
1947
|
+
var DEFAULT_MAX_TOKENS2 = 8192;
|
|
1948
|
+
async function generateFigureDelta(input, opts = {}) {
|
|
1949
|
+
const { problem, currentDsl } = input;
|
|
1950
|
+
if (!problem || !problem.trim()) {
|
|
1951
|
+
return { ok: false, reason: "api_error", message: "\u0110\u1EC1 b\xE0i r\u1ED7ng" };
|
|
1952
|
+
}
|
|
1953
|
+
let provider;
|
|
1954
|
+
try {
|
|
1955
|
+
provider = selectProvider(opts);
|
|
1956
|
+
} catch (e) {
|
|
1957
|
+
const err = e;
|
|
1958
|
+
return { ok: false, reason: "api_error", message: err.message ?? "Kh\xF4ng ch\u1ECDn \u0111\u01B0\u1EE3c provider" };
|
|
1959
|
+
}
|
|
1960
|
+
const systemPrompt = buildRefineSystemPrompt(currentDsl);
|
|
1961
|
+
const schema = refineEnvelopeJsonSchema();
|
|
1962
|
+
const out = await provider.call({
|
|
1963
|
+
systemPrompt,
|
|
1964
|
+
userPrompt: problem,
|
|
1965
|
+
schema,
|
|
1966
|
+
model: opts.model ?? provider.defaultModel,
|
|
1967
|
+
maxTokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS2,
|
|
1968
|
+
signal: opts.signal
|
|
1969
|
+
});
|
|
1970
|
+
if (out.kind === "error") {
|
|
1971
|
+
return {
|
|
1972
|
+
ok: false,
|
|
1973
|
+
reason: "api_error",
|
|
1974
|
+
message: out.message,
|
|
1975
|
+
...out.status !== void 0 ? { status: out.status } : {},
|
|
1976
|
+
provider: provider.name
|
|
1004
1977
|
};
|
|
1005
1978
|
}
|
|
1006
|
-
|
|
1979
|
+
const usage = toUsage3(out.usage);
|
|
1980
|
+
const parsed = FigureRefineEnvelopeZ.safeParse(out.data);
|
|
1981
|
+
if (!parsed.success) {
|
|
1007
1982
|
return {
|
|
1008
1983
|
ok: false,
|
|
1009
1984
|
reason: "parse_error",
|
|
1010
|
-
message:
|
|
1011
|
-
raw:
|
|
1012
|
-
usage
|
|
1985
|
+
message: "Envelope kh\xF4ng kh\u1EDBp schema: " + parsed.error.issues.map((i) => i.message).join("; "),
|
|
1986
|
+
raw: out.data,
|
|
1987
|
+
usage,
|
|
1988
|
+
provider: provider.name
|
|
1013
1989
|
};
|
|
1014
1990
|
}
|
|
1015
|
-
const
|
|
1016
|
-
if (
|
|
1991
|
+
const env = parsed.data;
|
|
1992
|
+
if (env.decision === "refuse") {
|
|
1017
1993
|
return {
|
|
1018
1994
|
ok: false,
|
|
1019
|
-
reason: "
|
|
1020
|
-
message: "
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1995
|
+
reason: "refused",
|
|
1996
|
+
message: env.reason ?? "AI t\u1EEB ch\u1ED1i kh\xF4ng n\xEAu l\xFD do",
|
|
1997
|
+
usage,
|
|
1998
|
+
provider: provider.name
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
if (env.decision === "replace") {
|
|
2002
|
+
const figure = env.figure;
|
|
2003
|
+
const tResult2 = transpile(figure);
|
|
2004
|
+
if (!tResult2.ok) {
|
|
2005
|
+
return liftTranspileError(tResult2.errors, figure, usage, provider.name);
|
|
2006
|
+
}
|
|
2007
|
+
return {
|
|
2008
|
+
ok: true,
|
|
2009
|
+
state: tResult2.state,
|
|
2010
|
+
mergedDsl: figure,
|
|
2011
|
+
mode: "replace",
|
|
2012
|
+
usage,
|
|
2013
|
+
provider: provider.name
|
|
1024
2014
|
};
|
|
1025
2015
|
}
|
|
2016
|
+
const delta = env.figure;
|
|
2017
|
+
const merged = {
|
|
2018
|
+
version: 1,
|
|
2019
|
+
points: [...currentDsl.points, ...delta.points],
|
|
2020
|
+
shapes: [...currentDsl.shapes, ...delta.shapes]
|
|
2021
|
+
};
|
|
2022
|
+
const tResult = transpile(merged);
|
|
2023
|
+
if (!tResult.ok) {
|
|
2024
|
+
return liftTranspileError(tResult.errors, merged, usage, provider.name);
|
|
2025
|
+
}
|
|
1026
2026
|
return {
|
|
1027
2027
|
ok: true,
|
|
1028
2028
|
state: tResult.state,
|
|
1029
|
-
|
|
1030
|
-
|
|
2029
|
+
mergedDsl: merged,
|
|
2030
|
+
mode: "add",
|
|
2031
|
+
usage,
|
|
2032
|
+
provider: provider.name
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
function liftTranspileError(errors, dsl, usage, providerName) {
|
|
2036
|
+
const dupes = errors.filter((e) => e.code === "DUPLICATE_NAME");
|
|
2037
|
+
if (dupes.length > 0) {
|
|
2038
|
+
const collisions = Array.from(new Set(dupes.flatMap((e) => e.path ?? []).filter(Boolean)));
|
|
2039
|
+
return {
|
|
2040
|
+
ok: false,
|
|
2041
|
+
reason: "name_collision",
|
|
2042
|
+
message: "AI t\u1EA1o entity tr\xF9ng t\xEAn v\u1EDBi h\xECnh hi\u1EC7n t\u1EA1i: " + (collisions.length > 0 ? collisions.join(", ") : "kh\xF4ng x\xE1c \u0111\u1ECBnh"),
|
|
2043
|
+
collisions,
|
|
2044
|
+
errors,
|
|
2045
|
+
dsl,
|
|
2046
|
+
usage,
|
|
2047
|
+
provider: providerName
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
const unresolved = errors.filter((e) => e.code === "UNKNOWN_REF");
|
|
2051
|
+
if (unresolved.length > 0) {
|
|
2052
|
+
const refs = Array.from(new Set(unresolved.flatMap((e) => e.path ?? []).filter(Boolean)));
|
|
2053
|
+
return {
|
|
2054
|
+
ok: false,
|
|
2055
|
+
reason: "unresolved_ref",
|
|
2056
|
+
message: "AI tham chi\u1EBFu t\xEAn kh\xF4ng c\xF3: " + (refs.length > 0 ? refs.join(", ") : "kh\xF4ng x\xE1c \u0111\u1ECBnh"),
|
|
2057
|
+
refs,
|
|
2058
|
+
errors,
|
|
2059
|
+
dsl,
|
|
2060
|
+
usage,
|
|
2061
|
+
provider: providerName
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
return {
|
|
2065
|
+
ok: false,
|
|
2066
|
+
reason: "transpile_error",
|
|
2067
|
+
message: "DSL t\u1EEB AI kh\xF4ng h\u1EE3p l\u1EC7",
|
|
2068
|
+
errors,
|
|
2069
|
+
dsl,
|
|
2070
|
+
usage,
|
|
2071
|
+
provider: providerName
|
|
1031
2072
|
};
|
|
1032
2073
|
}
|
|
2074
|
+
function toUsage3(u) {
|
|
2075
|
+
return {
|
|
2076
|
+
inputTokens: u?.inputTokens ?? 0,
|
|
2077
|
+
outputTokens: u?.outputTokens ?? 0,
|
|
2078
|
+
cacheReadTokens: u?.cacheReadTokens ?? 0,
|
|
2079
|
+
cacheCreationTokens: u?.cacheCreationTokens ?? 0
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// src/stamps/geometry-2d/ai/handleGenerateFigureDelta.ts
|
|
2084
|
+
var DEFAULT_MAX_ATTEMPTS2 = 2;
|
|
2085
|
+
async function handleGenerateFigureDelta(input, opts = {}) {
|
|
2086
|
+
const { onResult, maxAttempts: rawMax, ...generateOpts } = opts;
|
|
2087
|
+
const maxAttempts = clampAttempts2(rawMax ?? DEFAULT_MAX_ATTEMPTS2);
|
|
2088
|
+
let lastResult = null;
|
|
2089
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2090
|
+
const result = await generateFigureDelta(input, generateOpts);
|
|
2091
|
+
lastResult = result;
|
|
2092
|
+
if (onResult) {
|
|
2093
|
+
try {
|
|
2094
|
+
onResult(result, attempt);
|
|
2095
|
+
} catch {
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
if (result.ok) {
|
|
2099
|
+
return { ok: true, state: result.state };
|
|
2100
|
+
}
|
|
2101
|
+
if (result.reason === "transpile_error" && attempt < maxAttempts) {
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
2104
|
+
break;
|
|
2105
|
+
}
|
|
2106
|
+
return mapErrorToUi2(lastResult);
|
|
2107
|
+
}
|
|
2108
|
+
function clampAttempts2(n) {
|
|
2109
|
+
if (!Number.isFinite(n)) return DEFAULT_MAX_ATTEMPTS2;
|
|
2110
|
+
return Math.max(1, Math.min(5, Math.floor(n)));
|
|
2111
|
+
}
|
|
2112
|
+
function mapErrorToUi2(result) {
|
|
2113
|
+
if (result.ok) return { ok: true, state: result.state };
|
|
2114
|
+
switch (result.reason) {
|
|
2115
|
+
case "refused":
|
|
2116
|
+
return { ok: false, message: result.message };
|
|
2117
|
+
case "parse_error":
|
|
2118
|
+
return {
|
|
2119
|
+
ok: false,
|
|
2120
|
+
message: "AI tr\u1EA3 v\u1EC1 d\u1EEF li\u1EC7u kh\xF4ng h\u1EE3p l\u1EC7. Vui l\xF2ng th\u1EED l\u1EA1i ho\u1EB7c di\u1EC5n \u0111\u1EA1t l\u1EA1i."
|
|
2121
|
+
};
|
|
2122
|
+
case "transpile_error":
|
|
2123
|
+
return {
|
|
2124
|
+
ok: false,
|
|
2125
|
+
message: "AI t\u1EA1o h\xECnh kh\xF4ng h\u1EE3p l\u1EC7 (\u0111\xE3 th\u1EED l\u1EA1i). Vui l\xF2ng t\xE1ch th\xE0nh 1 y\xEAu c\u1EA7u/l\u1EA7n ho\u1EB7c di\u1EC5n \u0111\u1EA1t kh\xE1c."
|
|
2126
|
+
};
|
|
2127
|
+
case "name_collision":
|
|
2128
|
+
return {
|
|
2129
|
+
ok: false,
|
|
2130
|
+
message: `AI t\u1EA1o \u0111i\u1EC3m tr\xF9ng t\xEAn v\u1EDBi h\xECnh hi\u1EC7n t\u1EA1i (${result.collisions.join(", ")}). Vui l\xF2ng di\u1EC5n \u0111\u1EA1t l\u1EA1i.`
|
|
2131
|
+
};
|
|
2132
|
+
case "unresolved_ref":
|
|
2133
|
+
return {
|
|
2134
|
+
ok: false,
|
|
2135
|
+
message: `AI tham chi\u1EBFu sai t\xEAn \u0111\u1ED1i t\u01B0\u1EE3ng (${result.refs.join(", ")}). Vui l\xF2ng di\u1EC5n \u0111\u1EA1t l\u1EA1i.`
|
|
2136
|
+
};
|
|
2137
|
+
case "api_error":
|
|
2138
|
+
default:
|
|
2139
|
+
return { ok: false, message: result.message };
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
1033
2142
|
|
|
2143
|
+
exports.AnthropicProvider = AnthropicProvider;
|
|
2144
|
+
exports.FigureEnvelopeZ = FigureEnvelopeZ;
|
|
2145
|
+
exports.FigureRefineEnvelopeZ = FigureRefineEnvelopeZ;
|
|
2146
|
+
exports.OllamaProvider = OllamaProvider;
|
|
2147
|
+
exports.envelopeBuildDsl = envelopeBuildDsl;
|
|
2148
|
+
exports.envelopeJsonSchema = envelopeJsonSchema;
|
|
1034
2149
|
exports.generateFigure = generateFigure;
|
|
2150
|
+
exports.generateFigureDelta = generateFigureDelta;
|
|
2151
|
+
exports.handleGenerateFigure = handleGenerateFigure;
|
|
2152
|
+
exports.handleGenerateFigureDelta = handleGenerateFigureDelta;
|
|
2153
|
+
exports.refineEnvelopeJsonSchema = refineEnvelopeJsonSchema;
|
|
2154
|
+
exports.selectProvider = selectProvider;
|
|
1035
2155
|
//# sourceMappingURL=ai.js.map
|
|
1036
2156
|
//# sourceMappingURL=ai.js.map
|