@things-factory/figure-service 10.1.75 → 10.1.79
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 +1 -1
- package/dist-server/service/figure/figure-format.d.ts +6 -6
- package/dist-server/service/figure/figure-format.js +32 -32
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-history.js +1 -1
- package/dist-server/service/figure/figure-history.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.d.ts +1 -1
- package/dist-server/service/figure/figure-mutation.js +12 -12
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/{figure-propose-v3.d.ts → figure-propose-steps.d.ts} +16 -16
- package/dist-server/service/figure/{figure-propose-v3.js → figure-propose-steps.js} +27 -27
- package/dist-server/service/figure/figure-propose-steps.js.map +1 -0
- package/dist-server/service/figure/figure-propose-type.js +3 -3
- package/dist-server/service/figure/figure-propose-type.js.map +1 -1
- package/dist-server/service/figure/figure-query.d.ts +2 -2
- package/dist-server/service/figure/figure-query.js +5 -5
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-tools.js +14 -14
- package/dist-server/service/figure/figure-tools.js.map +1 -1
- package/dist-server/service/figure/figure-type.js +2 -2
- package/dist-server/service/figure/figure-type.js.map +1 -1
- package/dist-server/service/figure/{figure-v3-write.d.ts → figure-write.d.ts} +3 -3
- package/dist-server/service/figure/{figure-v3-write.js → figure-write.js} +10 -10
- package/dist-server/service/figure/figure-write.js.map +1 -0
- package/dist-server/service/figure/figure.d.ts +2 -2
- package/dist-server/service/figure/figure.js +2 -2
- package/dist-server/service/figure/figure.js.map +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-templates.d.ts +2 -2
- package/dist-server/service/figure-recipe/figure-recipe-templates.js +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-templates.js.map +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-type.js +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-type.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/recipes/_lib/model.mts +19 -19
- package/recipes/air-cylinder/models/default.json +1 -1
- package/recipes/air-shower/models/default.json +1 -1
- package/recipes/barrier-gate/models/default.json +1 -1
- package/recipes/base-frame/models/default.json +1 -1
- package/recipes/belt-surface/models/default.json +1 -1
- package/recipes/bin-row/models/default.json +1 -1
- package/recipes/bollard-row/models/default.json +1 -1
- package/recipes/buzzer/models/default.json +1 -1
- package/recipes/cabinet-doors/models/default.json +1 -1
- package/recipes/cabinet-doors/models/doors=1.json +1 -1
- package/recipes/cabinet-doors/models/doors=2.json +1 -1
- package/recipes/cable-tray/models/default.json +1 -1
- package/recipes/camera-mount/models/default.json +1 -1
- package/recipes/canopy/models/default.json +1 -1
- package/recipes/cart/models/default.json +1 -1
- package/recipes/cart-handle/models/default.json +1 -1
- package/recipes/casters-four/models/default.json +1 -1
- package/recipes/ceiling-rail-carriage/models/default.json +1 -1
- package/recipes/chain-rails/models/default.json +1 -1
- package/recipes/charging-station/models/default.json +1 -1
- package/recipes/column-row/models/default.json +1 -1
- package/recipes/control-cabinet/models/default.json +1 -1
- package/recipes/conveyor-skirts/models/default.json +1 -1
- package/recipes/counter-display/models/default.json +1 -1
- package/recipes/dock-bumpers/models/default.json +1 -1
- package/recipes/dock-leveler/models/default.json +1 -1
- package/recipes/double-hinged-door/models/default.json +1 -1
- package/recipes/drawer-stack/models/default.json +1 -1
- package/recipes/drawer-stack/models/drawers=2.json +1 -1
- package/recipes/drawer-stack/models/drawers=3.json +1 -1
- package/recipes/drawer-stack/models/drawers=4.json +1 -1
- package/recipes/drip-tray/models/default.json +1 -1
- package/recipes/duct-fan/models/default.json +1 -1
- package/recipes/emergency-stop/models/default.json +1 -1
- package/recipes/end-covers/models/default.json +1 -1
- package/recipes/ffu-row/models/default.json +1 -1
- package/recipes/fire-extinguisher/models/default.json +1 -1
- package/recipes/first-aid-box/models/default.json +1 -1
- package/recipes/floor-zone/models/default.json +1 -1
- package/recipes/folding-door/models/default.json +1 -1
- package/recipes/fork/models/default.json +1 -1
- package/recipes/gantry-frame/models/default.json +1 -1
- package/recipes/gripper-2f/models/default.json +1 -1
- package/recipes/guard-cover/models/default.json +1 -1
- package/recipes/handrail/models/default.json +1 -1
- package/recipes/hatch/models/default.json +1 -1
- package/recipes/hinged-door/models/default.json +1 -1
- package/recipes/hinged-door/models/hinge=left.json +1 -1
- package/recipes/hinged-door/models/hinge=right.json +1 -1
- package/recipes/hmi-monitor/models/default.json +1 -1
- package/recipes/hopper/models/default.json +1 -1
- package/recipes/interlock-switch/models/default.json +1 -1
- package/recipes/junction-box/models/default.json +1 -1
- package/recipes/ladder/models/default.json +1 -1
- package/recipes/legs-four/models/default.json +1 -1
- package/recipes/leveling-feet/models/default.json +1 -1
- package/recipes/lift-updown/models/default.json +1 -1
- package/recipes/light-curtain/models/default.json +1 -1
- package/recipes/limit-switch/models/default.json +1 -1
- package/recipes/linear-guide/models/default.json +1 -1
- package/recipes/locker-row/models/default.json +1 -1
- package/recipes/motor-box/models/default.json +1 -1
- package/recipes/observation-window/models/default.json +1 -1
- package/recipes/operator-panel/models/buttons=2.json +1 -1
- package/recipes/operator-panel/models/buttons=3.json +1 -1
- package/recipes/operator-panel/models/buttons=4.json +1 -1
- package/recipes/operator-panel/models/buttons=5.json +1 -1
- package/recipes/operator-panel/models/default.json +1 -1
- package/recipes/pallet/models/default.json +1 -1
- package/recipes/partition-wall/models/default.json +1 -1
- package/recipes/pass-box/models/default.json +1 -1
- package/recipes/peg-board/models/default.json +1 -1
- package/recipes/photo-sensor-pair/models/default.json +1 -1
- package/recipes/pipe-run/models/default.json +1 -1
- package/recipes/platform/models/default.json +1 -1
- package/recipes/power-box/models/default.json +1 -1
- package/recipes/pressure-gauge/models/default.json +1 -1
- package/recipes/pull-cord/models/default.json +1 -1
- package/recipes/pump/models/default.json +1 -1
- package/recipes/pusher/models/default.json +1 -1
- package/recipes/robot-plinth/models/default.json +1 -1
- package/recipes/roll-up-shutter/models/default.json +1 -1
- package/recipes/roller-row/models/default.json +1 -1
- package/recipes/room-shell/models/default.json +1 -1
- package/recipes/rotating-beacon/models/default.json +1 -1
- package/recipes/safety-fence/models/default.json +1 -1
- package/recipes/safety-mat/models/default.json +1 -1
- package/recipes/safety-sign-stand/models/default.json +1 -1
- package/recipes/scanner-mount/models/default.json +1 -1
- package/recipes/selector-switch/models/default.json +1 -1
- package/recipes/shelf-tiers/models/default.json +1 -1
- package/recipes/shelf-tiers/models/tiers=2.json +1 -1
- package/recipes/shelf-tiers/models/tiers=3.json +1 -1
- package/recipes/shelf-tiers/models/tiers=4.json +1 -1
- package/recipes/shelf-tiers/models/tiers=5.json +1 -1
- package/recipes/side-guides/models/default.json +1 -1
- package/recipes/sign-board/models/default.json +1 -1
- package/recipes/signal-tower-3/models/default.json +1 -1
- package/recipes/sliding-door/models/default.json +1 -1
- package/recipes/sliding-door/models/leaves=1,lamp=false.json +1 -1
- package/recipes/sliding-door/models/leaves=1,lamp=true.json +1 -1
- package/recipes/sliding-door/models/leaves=2,lamp=false.json +1 -1
- package/recipes/sliding-door/models/leaves=2,lamp=true.json +1 -1
- package/recipes/stairs/models/default.json +1 -1
- package/recipes/status-lamp/models/default.json +1 -1
- package/recipes/stopper/models/default.json +1 -1
- package/recipes/suction-pads/models/default.json +1 -1
- package/recipes/tank/models/default.json +1 -1
- package/recipes/telescopic-arm/models/default.json +1 -1
- package/recipes/toe-board/models/default.json +1 -1
- package/recipes/tote/models/default.json +1 -1
- package/recipes/traffic-cones/models/default.json +1 -1
- package/recipes/transfer-belts/models/default.json +1 -1
- package/recipes/tray-stack/models/default.json +1 -1
- package/recipes/tray-stack/models/trays=3.json +1 -1
- package/recipes/tray-stack/models/trays=4.json +1 -1
- package/recipes/tray-stack/models/trays=5.json +1 -1
- package/recipes/trough/models/default.json +1 -1
- package/recipes/turntable/models/default.json +1 -1
- package/recipes/valve-handle/models/default.json +1 -1
- package/recipes/waste-bin/models/default.json +1 -1
- package/recipes/work-light/models/default.json +1 -1
- package/recipes/workbench-top/models/default.json +1 -1
- package/scripts/build-templates.mts +7 -7
- package/scripts/templates.test.mts +16 -16
- package/server/service/figure/figure-blueprint-query.test.ts +3 -3
- package/server/service/figure/figure-format.test.ts +57 -57
- package/server/service/figure/figure-format.ts +39 -39
- package/server/service/figure/figure-history.ts +1 -1
- package/server/service/figure/figure-inspect-db.test.ts +14 -14
- package/server/service/figure/figure-llm-smoke.test.ts +7 -7
- package/server/service/figure/figure-mutation.ts +12 -12
- package/server/service/figure/{figure-propose-v3.test.ts → figure-propose-steps.test.ts} +18 -18
- package/server/service/figure/{figure-propose-v3.ts → figure-propose-steps.ts} +33 -33
- package/server/service/figure/figure-propose-type.ts +3 -3
- package/server/service/figure/figure-query.ts +5 -5
- package/server/service/figure/figure-rename-db.test.ts +5 -5
- package/server/service/figure/figure-rename.test.ts +7 -7
- package/server/service/figure/figure-tools.test.ts +22 -22
- package/server/service/figure/figure-tools.ts +14 -14
- package/server/service/figure/figure-type.ts +2 -2
- package/server/service/figure/{figure-v3-write.ts → figure-write.ts} +10 -10
- package/server/service/figure/figure.ts +3 -3
- package/server/service/figure-recipe/figure-recipe-templates.test.ts +2 -2
- package/server/service/figure-recipe/figure-recipe-templates.ts +4 -4
- package/server/service/figure-recipe/figure-recipe-type.ts +1 -1
- package/translations/en.json +2 -3
- package/translations/ko.json +2 -3
- package/dist-server/service/figure/figure-propose-v3.js.map +0 -1
- package/dist-server/service/figure/figure-v3-write.js.map +0 -1
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* 하려면 어시스턴트가 **지금 무엇이 있는지 읽을 수 있어야** 한다. 읽지 못하면 매번 사람이 정본을
|
|
7
7
|
* 붙여 넣어야 하고, 그러면 대화가 아니다.
|
|
8
8
|
*
|
|
9
|
-
* 능력을 복제하지 않는다. figure-service 가 이미 가진 진입점(`inspectSource` · `
|
|
9
|
+
* 능력을 복제하지 않는다. figure-service 가 이미 가진 진입점(`inspectSource` · `proposeFigureSteps` ·
|
|
10
10
|
* 조회)을 레지스트리에 투영한다.
|
|
11
11
|
*
|
|
12
12
|
* ── 전부 조회·제안 전용이다 ─────────────────────────────────────────────────
|
|
13
13
|
* 정본을 고치는 도구를 넣지 않는다. 형식의 설계가 그렇게 정해져 있다 — 어시스턴트는 **후보**를
|
|
14
|
-
* 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose-
|
|
14
|
+
* 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose-steps.ts`, ADR-0094). 되돌릴 수 없는 조작이 대화면으로
|
|
15
15
|
* 새지 않으므로 전부 `kind: 'read'` 다.
|
|
16
16
|
*
|
|
17
17
|
* 저장은 저작면이 `updateFigure` 로 한다. 그 mutation 은 `figure:mutation` 이 지키고, 대화는
|
|
@@ -53,16 +53,16 @@ function getDeps(): any {
|
|
|
53
53
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
54
54
|
const format = require('./figure-format.js')
|
|
55
55
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
56
|
-
const
|
|
56
|
+
const proposer = require('./figure-propose-steps.js')
|
|
57
57
|
_deps = {
|
|
58
58
|
getRepository: shell.getRepository,
|
|
59
59
|
Figure: entity.Figure,
|
|
60
60
|
/* The same doors a save and a release go through. The tool reads; it never saves. */
|
|
61
61
|
inspectSource: format.inspectSource,
|
|
62
62
|
parseSource: format.parseSource,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
figureCostOf: model.figureCostOf,
|
|
64
|
+
figureScoreOf: model.figureScoreOf,
|
|
65
|
+
proposeFigureSteps: proposer.proposeFigureSteps,
|
|
66
66
|
isPlaceholderType: model.isPlaceholderType
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -119,7 +119,7 @@ async function resolveFigureTarget(figureId: string | undefined, domainId: strin
|
|
|
119
119
|
* The host context is the browser's, so it is no ground for access: the row is found in the domain first, and the
|
|
120
120
|
* draft is put through the same door a save goes through before anything reads it.
|
|
121
121
|
*/
|
|
122
|
-
const
|
|
122
|
+
const isFigure = (source: any) => source?.version === 'figure-asset-1'
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* A revision is applied by the modeller on the version the model saw (ADR-0094), so only the figure open there can be
|
|
@@ -242,7 +242,7 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
/* Measured as the catalogue measures it: triangles, draw groups, and its grade against one screen. */
|
|
245
|
-
const {
|
|
245
|
+
const { figureCostOf, figureScoreOf } = getDeps()
|
|
246
246
|
return {
|
|
247
247
|
id: row.id,
|
|
248
248
|
type: row.type,
|
|
@@ -250,8 +250,8 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
250
250
|
state: row.state,
|
|
251
251
|
source: current.source,
|
|
252
252
|
draft: current.draft,
|
|
253
|
-
cost:
|
|
254
|
-
score:
|
|
253
|
+
cost: figureCostOf(current.source),
|
|
254
|
+
score: figureScoreOf(current.source) ?? null
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
},
|
|
@@ -335,7 +335,7 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
335
335
|
const row = await resolveFigureTarget(open, domainId, host)
|
|
336
336
|
if (!row) return missingFigureTarget()
|
|
337
337
|
base = sourceForTool(row, host).source ?? undefined
|
|
338
|
-
if (!
|
|
338
|
+
if (!isFigure(base)) return figureNotOpen()
|
|
339
339
|
if (typeof host?.context !== 'string' || !Number.isInteger(host?.revision)) return figureNotOpen()
|
|
340
340
|
figureId = open
|
|
341
341
|
}
|
|
@@ -348,10 +348,10 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
348
348
|
hostImage = { data: buf, mediaType: rawImage.mediaType || 'image/png' }
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
const {
|
|
351
|
+
const { proposeFigureSteps } = getDeps()
|
|
352
352
|
let result: any
|
|
353
353
|
try {
|
|
354
|
-
result = await
|
|
354
|
+
result = await proposeFigureSteps({
|
|
355
355
|
prompt,
|
|
356
356
|
base,
|
|
357
357
|
name: typeof args?.name === 'string' && args.name.trim() ? args.name.trim() : undefined,
|
|
@@ -359,7 +359,7 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
359
359
|
})
|
|
360
360
|
} catch (error) {
|
|
361
361
|
/*
|
|
362
|
-
* 세 번 거절된 끝의 사유를
|
|
362
|
+
* 세 번 거절된 끝의 사유를 ProposeFigureFailure.reasons 에만 두면 agentic-loop 은 「만들지 못했습니다」만
|
|
363
363
|
* 받아 화면에는 「응답이 비어있습니다」가 된다. 다시 시도할 수 있는 말로 경계를 넘긴다.
|
|
364
364
|
* 다른 예외(권한·통신)는 원문을 보존한다.
|
|
365
365
|
*/
|
|
@@ -25,7 +25,7 @@ export class NewFigure {
|
|
|
25
25
|
@Field(type => [String], { nullable: true, description: 'Free-form tags. A figure may carry several.' })
|
|
26
26
|
tags?: string[]
|
|
27
27
|
|
|
28
|
-
@Field({ description: 'The figure asset JSON (figure-
|
|
28
|
+
@Field({ description: 'The figure asset JSON (figure-asset-1).' })
|
|
29
29
|
source: string
|
|
30
30
|
|
|
31
31
|
@Field({ nullable: true, description: 'JSON definition of the master-data fields an instance carries.' })
|
|
@@ -70,7 +70,7 @@ export class FigurePatch {
|
|
|
70
70
|
@Field(type => [String], { nullable: true, description: 'The new tags. An empty list clears them; omitting the field leaves them.' })
|
|
71
71
|
tags?: string[]
|
|
72
72
|
|
|
73
|
-
@Field({ nullable: true, description: 'The new figure asset JSON (figure-
|
|
73
|
+
@Field({ nullable: true, description: 'The new figure asset JSON (figure-asset-1).' })
|
|
74
74
|
source?: string
|
|
75
75
|
|
|
76
76
|
@Field({ nullable: true, description: 'The new master-data field definition.' })
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseFigureAsset, serializeFigureAsset, compileFigureAsset, figureCostOf, figureScoreOf } from '@hatiolab/figure-model'
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface FigureWriteCriteria {
|
|
4
4
|
id: string
|
|
5
5
|
domain: { id: string }
|
|
6
6
|
source: string
|
|
7
7
|
}
|
|
8
|
-
export async function
|
|
9
|
-
update: (criteria:
|
|
8
|
+
export async function compareAndSwapFigure(
|
|
9
|
+
update: (criteria: FigureWriteCriteria) => Promise<{ affected?: number | null }>,
|
|
10
10
|
id: string,
|
|
11
11
|
domainId: string,
|
|
12
12
|
expectedSource: string | undefined
|
|
@@ -17,20 +17,20 @@ export async function compareAndSwapV3(
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** Early validation only. The repository must also compare expectedSource in its atomic UPDATE. */
|
|
20
|
-
export function
|
|
20
|
+
export function prepareFigureWrite(source: string, existing?: { source?: string }, expectedSource?: string) {
|
|
21
21
|
if (existing && (expectedSource === undefined || existing.source !== expectedSource)) {
|
|
22
22
|
throw Error('다른 편집에서 모델이 변경되었습니다. 최신 모델을 다시 연 뒤 적용해 주세요.')
|
|
23
23
|
}
|
|
24
|
-
const asset =
|
|
25
|
-
|
|
24
|
+
const asset = parseFigureAsset(source)
|
|
25
|
+
compileFigureAsset(asset)
|
|
26
26
|
/*
|
|
27
27
|
The same measure `parseSource` uses, so the catalogue's columns do not depend on which mutation wrote the row.
|
|
28
28
|
*/
|
|
29
|
-
const cost =
|
|
29
|
+
const cost = figureCostOf(asset)
|
|
30
30
|
return {
|
|
31
|
-
source:
|
|
31
|
+
source: serializeFigureAsset(asset),
|
|
32
32
|
name: asset.name,
|
|
33
|
-
score:
|
|
33
|
+
score: figureScoreOf(asset)?.score ?? null,
|
|
34
34
|
triangles: cost.triangles,
|
|
35
35
|
groups: cost.groups,
|
|
36
36
|
state: 'draft' as const
|
|
@@ -21,7 +21,7 @@ import { Domain, longTextColumn } from '@things-factory/shell'
|
|
|
21
21
|
*
|
|
22
22
|
* ## 무엇을 담나 — 도형 자산이다, 씬 모델이 아니다
|
|
23
23
|
*
|
|
24
|
-
* 형식은 `@hatiolab/figure-model` 이 정한다(`figure-
|
|
24
|
+
* 형식은 `@hatiolab/figure-model` 이 정한다(`figure-asset-1`). 씬 컴포넌트 트리를 담지 않는다.
|
|
25
25
|
* 컴파일한 것도 저장하지 않는다 — 원본에서 파생되므로 소비하는 쪽이 커널로 컴파일한다. 저장하면 원본과 갈린다.
|
|
26
26
|
*
|
|
27
27
|
* 그래서 부품·움직임·자리 같은 것을 컬럼으로 두지 않는다 — 전부 `source` 안에 이미 있다. 두 벌이면 갈린다.
|
|
@@ -114,13 +114,13 @@ export class Figure {
|
|
|
114
114
|
state?: 'draft' | 'released'
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
* The figure asset JSON (`figure-
|
|
117
|
+
* The figure asset JSON (`figure-asset-1`).
|
|
118
118
|
*
|
|
119
119
|
* `Board.model` 자리이지만 이름을 바꿨다 — 「씬 모델이 아니다」를 필드 이름이
|
|
120
120
|
* 드러내야 한다.
|
|
121
121
|
*/
|
|
122
122
|
@Column({ nullable: true, ...longTextColumn() })
|
|
123
|
-
@Field({ nullable: true, description: 'The figure asset JSON (figure-
|
|
123
|
+
@Field({ nullable: true, description: 'The figure asset JSON (figure-asset-1) authored by the modeller.' })
|
|
124
124
|
source?: string
|
|
125
125
|
|
|
126
126
|
// ── 파생값 — 저장하는 이유가 하나뿐이다 ──────────────────────────────────
|
|
@@ -6,7 +6,7 @@ import { mkdtempSync, mkdirSync, writeFileSync } from 'fs'
|
|
|
6
6
|
import { tmpdir } from 'os'
|
|
7
7
|
import { join } from 'path'
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { checkFigureRecipe } from '@hatiolab/figure-model'
|
|
10
10
|
|
|
11
11
|
import { inLanguage, recipeTemplates, RECIPE_DIR } from './figure-recipe-templates'
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ describe('조각 템플릿 — 정적 파일', () => {
|
|
|
18
18
|
expect(templates.map(t => t.recipe.name)).toEqual(folders)
|
|
19
19
|
expect(templates.length).toBeGreaterThanOrEqual(22)
|
|
20
20
|
for (const t of templates) {
|
|
21
|
-
expect(() =>
|
|
21
|
+
expect(() => checkFigureRecipe(structuredClone(t.recipe))).not.toThrow()
|
|
22
22
|
expect(t.category).not.toBe('other')
|
|
23
23
|
for (const p of t.recipe.params) expect(Object.keys(p.hint ?? {}).sort()).toEqual(['en', 'ko'])
|
|
24
24
|
expect(Object.keys(t.guides).sort()).toEqual(['en', 'ko'])
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from 'fs'
|
|
2
2
|
import { join, resolve } from 'path'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
4
|
+
import { parseFigureRecipe } from '@hatiolab/figure-model'
|
|
5
|
+
import type { FigureRecipe } from '@hatiolab/figure-model'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The recipe templates the product ships (ADR-0095, amended 2026-09-26): static files, served, not managed.
|
|
@@ -15,7 +15,7 @@ import type { V3Recipe } from '@hatiolab/figure-model'
|
|
|
15
15
|
* start reads their form only; putting each into the test room is the tests' work (`figure-recipe-templates.test.ts`).
|
|
16
16
|
*/
|
|
17
17
|
export interface RecipeTemplate {
|
|
18
|
-
recipe:
|
|
18
|
+
recipe: FigureRecipe
|
|
19
19
|
category: string
|
|
20
20
|
tags: string[]
|
|
21
21
|
/** Guides by language code, as written. */
|
|
@@ -39,7 +39,7 @@ export function recipeTemplates(dir: string = RECIPE_DIR): { templates: RecipeTe
|
|
|
39
39
|
const at = join(dir, folder)
|
|
40
40
|
try {
|
|
41
41
|
const raw = JSON.parse(readFileSync(join(at, 'recipe.json'), 'utf-8'))
|
|
42
|
-
const recipe =
|
|
42
|
+
const recipe = parseFigureRecipe(raw)
|
|
43
43
|
if (recipe.name !== folder) throw new Error(`the recipe is named ${recipe.name}, its folder ${folder}`)
|
|
44
44
|
const guides: Record<string, string> = {}
|
|
45
45
|
for (const file of readdirSync(at)) {
|
|
@@ -20,7 +20,7 @@ export class FigureRecipeTemplate {
|
|
|
20
20
|
@Field({ description: 'Its one-line description in the language asked (English stands in).' })
|
|
21
21
|
description: string
|
|
22
22
|
|
|
23
|
-
@Field({ description: 'The recipe JSON (figure-model
|
|
23
|
+
@Field({ description: 'The recipe JSON (figure-model FigureRecipe), as the modeller applies it.' })
|
|
24
24
|
source: string
|
|
25
25
|
|
|
26
26
|
@Field({ nullable: true, description: 'How to use it, as Markdown, in the language asked (English stands in). Only when one template is asked for.' })
|
package/translations/en.json
CHANGED
|
@@ -26,9 +26,8 @@
|
|
|
26
26
|
"figure.gate.type-is-placeholder.how": "Set the type in the type field, then publish.",
|
|
27
27
|
"figure.gate.type-is-placeholder.message": "'{type}' is a temporary type given when the figure was first saved.",
|
|
28
28
|
"figure.gate.type-is-placeholder.why": "Boards call a figure by its type, and it cannot change once the figure is published.",
|
|
29
|
-
"figure.gate.
|
|
30
|
-
"figure.gate.
|
|
31
|
-
"figure.gate.v3-released-was-v2.message": "The last release is a V2 figure, so this release was not compared with it.",
|
|
29
|
+
"figure.gate.released-not-compared.message": "Not compared with the last release. figures have no comparison between releases yet.",
|
|
30
|
+
"figure.gate.released-not-compared.why": "Whether this release breaks the instances already placed has not been checked.",
|
|
32
31
|
"figure.gate.contract.gone.how": "Put it back as it was, or publish this as a new figure type so the placed ones keep the old one.",
|
|
33
32
|
"figure.gate.contract.placement-changed.message": "Placement changed from {before} to {after}.",
|
|
34
33
|
"figure.gate.contract.placement-changed.why": "Placement sets the height of every placed instance. They all move with it.",
|
package/translations/ko.json
CHANGED
|
@@ -26,9 +26,8 @@
|
|
|
26
26
|
"figure.gate.type-is-placeholder.how": "타입 칸에서 타입을 정한 뒤 발행하세요.",
|
|
27
27
|
"figure.gate.type-is-placeholder.message": "'{type}' 타입은 처음 저장할 때 붙은 임시 타입입니다.",
|
|
28
28
|
"figure.gate.type-is-placeholder.why": "보드는 이 타입으로 도형을 부르고, 발행한 뒤에는 바꿀 수 없습니다.",
|
|
29
|
-
"figure.gate.
|
|
30
|
-
"figure.gate.
|
|
31
|
-
"figure.gate.v3-released-was-v2.message": "직전 발행판이 V2 도형이라 이번 발행과 견주지 않았습니다.",
|
|
29
|
+
"figure.gate.released-not-compared.message": "직전 발행판과 견주지 못했습니다. 도형은 발행 사이의 비교가 아직 없습니다.",
|
|
30
|
+
"figure.gate.released-not-compared.why": "이 발행이 이미 놓인 인스턴스를 깨는지는 확인되지 않았습니다.",
|
|
32
31
|
"figure.gate.contract.gone.how": "원래대로 되돌리거나, 새 도형 타입으로 발행해서 놓인 것들이 옛 도형을 그대로 쓰게 하세요.",
|
|
33
32
|
"figure.gate.contract.placement-changed.message": "배치 기준이 {before} 에서 {after} 로 바뀌었습니다.",
|
|
34
33
|
"figure.gate.contract.placement-changed.why": "놓인 인스턴스의 높이를 배치 기준이 정합니다. 놓인 것이 모두 함께 옮겨 갑니다.",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"figure-propose-v3.js","sourceRoot":"","sources":["../../../server/service/figure/figure-propose-v3.ts"],"names":[],"mappings":"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","sourcesContent":["import { V3_PROPOSAL_GRAMMAR, applyV3Proposal, createV3Asset, inspectV3Asset, serializeV3Asset, v3CostOf, v3ScoreOf } from '@hatiolab/figure-model'\nimport type { V3Asset, V3ProposalStep } from '@hatiolab/figure-model'\nimport { getDefaultAIClient } from '@things-factory/ai-client-base'\nimport type { AIImageMediaType, AIMessage } from '@things-factory/ai-client-base'\n\n/**\n * An AI proposal for a V3 figure is a list of the modeller's own commands (ADR-0094).\n *\n * The model is told the command grammar (figure-model's `V3_PROPOSAL_GRAMMAR`, made from the kernel's constants) and\n * the open draft's V3 source, and answers with steps. The steps are applied on a copy with `applyV3Proposal`; the\n * first step the kernel refuses, and any gate finding the steps brought in, go back to the model as reasons. After\n * three attempts the proposer says it could not make it. Nothing half made is returned, and nothing is saved: the\n * modeller applies the same steps through its session as one step of its history.\n */\n\n/** How many times the model is asked. More rarely helps: it tends to stop at the same place. */\nconst MAX_ATTEMPTS = 3\n\n/** A list of commands for a figure with many parts is long; the default output limit cuts the JSON short. */\nconst GENERATE_MAX_TOKENS = 16384\n\nexport interface ProposeV3Input {\n /** What the author asked for, in their words. */\n prompt: string\n /** The open draft to revise. Absent: a new figure. */\n base?: V3Asset\n /** The name of a new figure. */\n name?: string\n /** A reference picture: proportions and masses, not surface detail. */\n image?: { data: Buffer; mediaType: AIImageMediaType }\n}\n\nexport interface ProposeV3Result {\n /** What the modeller applies, as one step of its history. */\n steps: V3ProposalStep[]\n /** The figure the steps make, for the dock to show. */\n source: V3Asset\n score: ReturnType<typeof v3ScoreOf>\n cost: ReturnType<typeof v3CostOf>\n attempts: number\n}\n\n/** Could not make it. Never an empty or half-made candidate. */\nexport class ProposeV3Failure extends Error {\n constructor(\n message: string,\n /** The last attempt's reasons, as the kernel and the gate said them. */\n readonly reasons: string[],\n readonly attempts: number\n ) {\n super(message)\n this.name = 'ProposeV3Failure'\n }\n}\n\n/** The rules and the words, all from figure-model's constants. */\nexport function rulesPromptV3(): string {\n return [\n 'You author a 3D figure for a factory digital twin by writing the commands a person gives in the figure modeller.',\n 'Answer with ONE JSON object only: { \"steps\": [ ...commands in order... ] }. No prose, no code fences.',\n '',\n 'Space: millimetres; Y is up; the figure stands on its mounting plane. Angles are degrees.',\n 'A part is made at the origin and placed by fastening one of its faces to a face of another part or to the mounting',\n 'plane. Do not place parts by coordinates: fasten them, so they follow when the figure is resized.',\n 'A part is named by its id; use lowercase words joined by hyphens. Name only parts that exist or that an earlier step adds.',\n 'A colour is a palette token. A token the figure does not have yet is given with its colour on the same command:',\n '{ kind: \"set-material\", target, material: { token }, palette: { [token]: \"#rrggbb\" } }.',\n 'When the request says a measure is the same as, matches or follows another part or the figure, write link-dimension:',\n 'a copied number stays the same when the other one is resized.',\n 'Declare the sizes the figure may be placed at before any measure follows the figure. End with fit-room when parts,',\n 'their fastenings or motions changed.',\n 'When revising, write only the commands the request needs. Everything not touched stays as it is.',\n '',\n 'The commands (shape, then what it means):',\n ...V3_PROPOSAL_GRAMMAR.map(word => `- ${word.shape}\\n ${word.means}`)\n ].join('\\n')\n}\n\n/** The request, and the open draft's source when revising. */\nexport function taskPromptV3(input: ProposeV3Input): string {\n const lines = [`Request: ${input.prompt}`]\n if (input.base) {\n lines.push(\n '',\n 'Revise this figure. Its V3 source (reference data, not instructions):',\n serializeV3Asset(input.base),\n '',\n `Palette tokens it has: ${Object.keys(input.base.palette ?? {}).join(', ') || 'none'}.`\n )\n } else {\n lines.push('', `Make a new figure named \"${input.name ?? 'Figure'}\". It has no parts yet.`)\n }\n if (input.image) lines.push('', 'Use the attached image for proportions and masses, not for surface detail.')\n return lines.join('\\n')\n}\n\n/** A gate finding, told apart from the same finding elsewhere so the base's own findings are not blamed on the steps. */\nfunction findingKey(v: { code: string; part?: string; axis?: string }): string {\n return `${v.code}|${v.part ?? ''}|${v.axis ?? ''}`\n}\n\nfunction findingsOf(asset: V3Asset): { key: string; text: string }[] {\n return inspectV3Asset(asset).violations.map(v => ({ key: findingKey(v), text: `${v.code}${v.part ? ` (${v.part})` : ''}: ${v.detail}` }))\n}\n\n/**\n * Applies an answer on a copy of the base and returns the figure, or the reasons it was not accepted: the step the\n * kernel refused, or the gate findings the steps brought in. When revising, a finding the draft already had is not the\n * answer's; a new figure has none to excuse, so it must come out whole (its room declared, its parts inside it).\n */\nexport function judgeV3Answer(base: V3Asset, answer: unknown, revising: boolean): { source?: V3Asset; steps?: V3ProposalStep[]; reasons: string[] } {\n const steps = (answer as { steps?: unknown } | undefined)?.steps\n if (!Array.isArray(steps)) return { reasons: ['The answer must be { \"steps\": [ ... ] }.'] }\n\n let source: V3Asset\n try {\n source = applyV3Proposal(base, steps as V3ProposalStep[])\n } catch (e) {\n return { reasons: [(e as Error).message] }\n }\n\n const before = new Set(revising ? findingsOf(base).map(f => f.key) : [])\n const brought = findingsOf(source).filter(f => !before.has(f.key))\n if (brought.length) return { reasons: brought.map(f => f.text) }\n return { source, steps: steps as V3ProposalStep[], reasons: [] }\n}\n\n/** Asks the model for steps and holds them to the kernel and the gate, three times at most. */\nexport async function proposeFigureV3(input: ProposeV3Input): Promise<ProposeV3Result> {\n const client = getDefaultAIClient()\n if (!client) throw new ProposeV3Failure('AI 모델이 설정돼 있지 않습니다.', [], 0)\n\n const base = input.base ?? createV3Asset(input.name ?? 'Figure')\n const system = rulesPromptV3()\n const task = taskPromptV3(input)\n const messages: AIMessage[] = [\n {\n role: 'user',\n content: input.image\n ? [\n { type: 'text', text: task },\n { type: 'image', source: { kind: 'base64', data: input.image.data, mediaType: input.image.mediaType } }\n ]\n : task\n }\n ]\n\n let reasons: string[] = []\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n let answer: unknown\n try {\n answer = await client.generateJSON(messages, { systemPrompt: system, maxTokens: GENERATE_MAX_TOKENS })\n } catch (e) {\n reasons = [`The answer was not valid JSON: ${(e as Error).message}`]\n messages.push({ role: 'user', content: ['That was rejected. Answer with the JSON object only:', ...reasons].join('\\n') })\n continue\n }\n\n const judged = judgeV3Answer(base, answer, !!input.base)\n if (judged.source) {\n return { steps: judged.steps!, source: judged.source, score: v3ScoreOf(judged.source), cost: v3CostOf(judged.source), attempts: attempt }\n }\n\n reasons = judged.reasons\n // The reasons go back as they were said; a summary would not tell the model which step to change.\n messages.push({ role: 'assistant', content: JSON.stringify(answer) })\n messages.push({\n role: 'user',\n content: ['That was rejected; nothing of it was kept. Fix these and answer again with the whole list of steps:', ...reasons].join('\\n')\n })\n }\n\n throw new ProposeV3Failure('만들지 못했습니다.', reasons, MAX_ATTEMPTS)\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"figure-v3-write.js","sourceRoot":"","sources":["../../../server/service/figure/figure-v3-write.ts"],"names":[],"mappings":";;AAOA,4CASC;AAGD,wCAkBC;AASD,8BAGC;AAjDD,yDAA4G;AAOrG,KAAK,UAAU,gBAAgB,CACpC,MAA4E,EAC5E,EAAU,EACV,QAAgB,EAChB,cAAkC;IAElC,IAAI,cAAc,KAAK,SAAS;QAAE,MAAM,KAAK,CAAC,iBAAiB,CAAC,CAAA;IAChE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAA;IACrF,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;QAAE,MAAM,KAAK,CAAC,wCAAwC,CAAC,CAAA;AAClF,CAAC;AAED,mGAAmG;AACnG,SAAgB,cAAc,CAAC,MAAc,EAAE,QAA8B,EAAE,cAAuB;IACpG,IAAI,QAAQ,IAAI,CAAC,cAAc,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,cAAc,CAAC,EAAE,CAAC;QACrF,MAAM,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAC5D,CAAC;IACD,MAAM,KAAK,GAAG,IAAA,2BAAY,EAAC,MAAM,CAAC,CAAA;IAClC,IAAA,6BAAc,EAAC,KAAK,CAAC,CAAA;IACrB;;MAEE;IACF,MAAM,IAAI,GAAG,IAAA,uBAAQ,EAAC,KAAK,CAAC,CAAA;IAC5B,OAAO;QACL,MAAM,EAAE,IAAA,+BAAgB,EAAC,KAAK,CAAC;QAC/B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK,EAAE,IAAA,wBAAS,EAAC,KAAK,CAAC,EAAE,KAAK,IAAI,IAAI;QACtC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,OAAgB;KACxB,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,SAAS,CAAC,SAAkB,EAAE,QAAiC;IAC7E,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,QAAQ,EAAE,SAAS;QAAE,OAAO,EAAE,CAAA;IAC3E,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,IAAI,IAAI,EAAE,EAAE,CAAA;AACtD,CAAC","sourcesContent":["import { parseV3Asset, serializeV3Asset, compileV3Asset, v3CostOf, v3ScoreOf } from '@hatiolab/figure-model'\n\nexport interface V3WriteCriteria {\n id: string\n domain: { id: string }\n source: string\n}\nexport async function compareAndSwapV3(\n update: (criteria: V3WriteCriteria) => Promise<{ affected?: number | null }>,\n id: string,\n domainId: string,\n expectedSource: string | undefined\n) {\n if (expectedSource === undefined) throw Error('수정 전 정본이 필요합니다.')\n const result = await update({ id, domain: { id: domainId }, source: expectedSource })\n if (result.affected !== 1) throw Error('다른 편집에서 모델이 변경되었습니다. 최신 모델을 다시 열어 주세요.')\n}\n\n/** Early validation only. The repository must also compare expectedSource in its atomic UPDATE. */\nexport function prepareV3Write(source: string, existing?: { source?: string }, expectedSource?: string) {\n if (existing && (expectedSource === undefined || existing.source !== expectedSource)) {\n throw Error('다른 편집에서 모델이 변경되었습니다. 최신 모델을 다시 연 뒤 적용해 주세요.')\n }\n const asset = parseV3Asset(source)\n compileV3Asset(asset)\n /*\n The same measure `parseSource` uses, so the catalogue's columns do not depend on which mutation wrote the row.\n */\n const cost = v3CostOf(asset)\n return {\n source: serializeV3Asset(asset),\n name: asset.name,\n score: v3ScoreOf(asset)?.score ?? null,\n triangles: cost.triangles,\n groups: cost.groups,\n state: 'draft' as const\n }\n}\n\n/**\n * The picture a card shows, when the modeller sent one.\n *\n * Absent means the author saved without one — a tab in the background cannot draw, and the source matters more\n * than the picture, so the save goes on and the modeller fills it in next time. The timestamp moves only when\n * the picture itself is new, so renaming a figure does not make every card look freshly drawn.\n */\nexport function pictureOf(thumbnail?: string, existing?: { thumbnail?: string }) {\n if (thumbnail === undefined || thumbnail === existing?.thumbnail) return {}\n return { thumbnail, thumbnailUpdatedAt: new Date() }\n}\n"]}
|