@things-factory/figure-service 10.1.6 → 10.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/service/figure/figure-gate.d.ts +18 -0
- package/dist-server/service/figure/figure-gate.js +52 -1
- package/dist-server/service/figure/figure-gate.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.js +8 -0
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/figure-propose-type.d.ts +30 -0
- package/dist-server/service/figure/figure-propose-type.js +112 -1
- package/dist-server/service/figure/figure-propose-type.js.map +1 -1
- package/dist-server/service/figure/figure-propose.d.ts +25 -1
- package/dist-server/service/figure/figure-propose.js +102 -4
- package/dist-server/service/figure/figure-propose.js.map +1 -1
- package/dist-server/service/figure/figure-quality.d.ts +29 -0
- package/dist-server/service/figure/figure-quality.js +62 -0
- package/dist-server/service/figure/figure-quality.js.map +1 -0
- package/dist-server/service/figure/figure-query.js +8 -1
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-tools.js +55 -9
- package/dist-server/service/figure/figure-tools.js.map +1 -1
- package/dist-server/service/figure/figure-type.d.ts +13 -0
- package/dist-server/service/figure/figure-type.js +51 -1
- package/dist-server/service/figure/figure-type.js.map +1 -1
- package/dist-server/service/figure/figure.d.ts +17 -1
- package/dist-server/service/figure/figure.js +6 -1
- package/dist-server/service/figure/figure.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/service/figure/catalog-db.test.ts +162 -0
- package/server/service/figure/figure-ai-flow.test.ts +95 -0
- package/server/service/figure/figure-gate.test.ts +32 -0
- package/server/service/figure/figure-gate.ts +71 -2
- package/server/service/figure/figure-llm-smoke.test.ts +91 -0
- package/server/service/figure/figure-mutation.ts +8 -0
- package/server/service/figure/figure-propose-type.ts +80 -0
- package/server/service/figure/figure-propose.test.ts +77 -0
- package/server/service/figure/figure-propose.ts +137 -4
- package/server/service/figure/figure-quality.test.ts +43 -0
- package/server/service/figure/figure-quality.ts +98 -0
- package/server/service/figure/figure-query.ts +8 -1
- package/server/service/figure/figure-tools.test.ts +62 -1
- package/server/service/figure/figure-tools.ts +60 -8
- package/server/service/figure/figure-type.ts +41 -0
- package/server/service/figure/figure.ts +22 -2
|
@@ -55,7 +55,8 @@ function getDeps(): any {
|
|
|
55
55
|
proposeFigure: propose.proposeFigure,
|
|
56
56
|
compile: model.compile,
|
|
57
57
|
costOf: model.costOf,
|
|
58
|
-
scoreOf: model.scoreOf
|
|
58
|
+
scoreOf: model.scoreOf,
|
|
59
|
+
validate: model.validate
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
return _deps
|
|
@@ -92,6 +93,51 @@ async function figureInDomain(figureId: string, domainId: string): Promise<any>
|
|
|
92
93
|
return found
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* 모델러가 아직 저장하지 않은 source를 도구가 읽는 유일한 길.
|
|
98
|
+
*
|
|
99
|
+
* host context는 브라우저가 보낸 값이므로 권한 근거가 될 수 없다. 먼저 `row`를 현재 도메인에서
|
|
100
|
+
* 찾은 뒤에만 보며, Figure의 불변 type과 형식 검증을 다시 확인한다. 검증되지 않은 큰 객체를 LLM
|
|
101
|
+
* 프롬프트로 옮기면 비용만 태우고, DB에 있던 Figure와 다른 것을 고친다고 잘못 말할 수 있다.
|
|
102
|
+
*/
|
|
103
|
+
function sourceForTool(row: any, host: any): { source: any; draft: boolean } {
|
|
104
|
+
const draft = host?.draftSource
|
|
105
|
+
if (!draft) {
|
|
106
|
+
return { source: row.source ? JSON.parse(row.source) : null, draft: false }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (typeof draft !== 'object' || Array.isArray(draft)) {
|
|
110
|
+
throw new Error('the unsaved draft is not a FigureSource object')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const serialized = JSON.stringify(draft)
|
|
114
|
+
if (serialized.length > 100_000) {
|
|
115
|
+
throw new Error('the unsaved draft is too large to safely review')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { validate } = getDeps()
|
|
119
|
+
const checked = validate(draft)
|
|
120
|
+
if (checked.errors.length > 0) {
|
|
121
|
+
throw new Error(`the unsaved draft is invalid: ${checked.errors.map((one: any) => one.message).join(' · ')}`)
|
|
122
|
+
}
|
|
123
|
+
if (draft.type !== row.type) {
|
|
124
|
+
throw new Error(`the unsaved draft type '${draft.type}' does not match Figure '${row.type}'`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { source: draft, draft: true }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 후보를 냈던 바로 그 Figure의 세션 반응만 후보 생성기에 넘긴다.
|
|
132
|
+
*
|
|
133
|
+
* 도크의 host context는 브라우저 값이므로 이 함수는 접근 권한을 판단하지 않는다. 이미 현재
|
|
134
|
+
* 도메인에서 Figure를 찾은 뒤, `proposeFigure`가 다시 길이·형식을 정리할 참고 데이터만 건넨다.
|
|
135
|
+
*/
|
|
136
|
+
function feedbackForTool(host: any, figureId?: string): unknown[] | undefined {
|
|
137
|
+
if (!figureId || host?.figureId !== figureId || !Array.isArray(host?.proposalFeedback)) return undefined
|
|
138
|
+
return host.proposalFeedback.slice(-5)
|
|
139
|
+
}
|
|
140
|
+
|
|
95
141
|
/**
|
|
96
142
|
* 무엇을 고치라는 것인지 대화면 문맥에서 해소한다.
|
|
97
143
|
*
|
|
@@ -163,21 +209,22 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
163
209
|
const figureId = figureIdFrom(args, ctx?.state?.host)
|
|
164
210
|
const row = await figureInDomain(figureId, domainId)
|
|
165
211
|
|
|
166
|
-
|
|
212
|
+
const current = sourceForTool(row, ctx?.state?.host)
|
|
213
|
+
if (!current.source) {
|
|
167
214
|
// 저장된 정본이 없는 상태가 실제로 있다(새로 만들고 저장 안 한 것). 지어내지 않는다.
|
|
168
215
|
return { id: row.id, type: row.type, name: row.name, source: null, cost: null }
|
|
169
216
|
}
|
|
170
217
|
|
|
171
218
|
const { compile, costOf, scoreOf } = getDeps()
|
|
172
|
-
const
|
|
173
|
-
const blueprint = compile(source)
|
|
219
|
+
const blueprint = compile(current.source)
|
|
174
220
|
|
|
175
221
|
return {
|
|
176
222
|
id: row.id,
|
|
177
223
|
type: row.type,
|
|
178
224
|
name: row.name,
|
|
179
225
|
state: row.state,
|
|
180
|
-
source,
|
|
226
|
+
source: current.source,
|
|
227
|
+
draft: current.draft,
|
|
181
228
|
cost: costOf(blueprint, 100),
|
|
182
229
|
score: scoreOf(blueprint)
|
|
183
230
|
}
|
|
@@ -196,8 +243,9 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
196
243
|
const figureId = figureIdFrom(args, ctx?.state?.host)
|
|
197
244
|
const row = await figureInDomain(figureId, domainId)
|
|
198
245
|
|
|
246
|
+
const current = sourceForTool(row, ctx?.state?.host)
|
|
199
247
|
const { inspectSource } = getDeps()
|
|
200
|
-
return inspectSource(
|
|
248
|
+
return { ...inspectSource(current.source ? JSON.stringify(current.source) : null), draft: current.draft }
|
|
201
249
|
}
|
|
202
250
|
},
|
|
203
251
|
{
|
|
@@ -244,7 +292,7 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
244
292
|
const open = typeof args?.figureId === 'string' ? args.figureId : ctx?.state?.host?.figureId
|
|
245
293
|
if (typeof open === 'string' && open.trim()) {
|
|
246
294
|
const row = await figureInDomain(open.trim(), domainId)
|
|
247
|
-
|
|
295
|
+
base = sourceForTool(row, ctx?.state?.host).source ?? undefined
|
|
248
296
|
}
|
|
249
297
|
}
|
|
250
298
|
|
|
@@ -258,11 +306,14 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
258
306
|
}
|
|
259
307
|
|
|
260
308
|
const { proposeFigure } = getDeps()
|
|
309
|
+
const feedback = feedbackForTool(ctx?.state?.host, base ? figureIdFrom(args, ctx?.state?.host) : undefined)
|
|
261
310
|
const result = await proposeFigure({
|
|
262
311
|
prompt,
|
|
263
312
|
palette,
|
|
264
313
|
base,
|
|
265
|
-
type: typeof args?.type === 'string' ? args.type : undefined
|
|
314
|
+
type: typeof args?.type === 'string' ? args.type : undefined,
|
|
315
|
+
feedback,
|
|
316
|
+
refine: !!feedback?.length
|
|
266
317
|
})
|
|
267
318
|
|
|
268
319
|
/*
|
|
@@ -283,6 +334,7 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
283
334
|
source: result.source,
|
|
284
335
|
score: result.score,
|
|
285
336
|
cost: result.cost,
|
|
337
|
+
quality: result.quality,
|
|
286
338
|
violations: result.violations,
|
|
287
339
|
attempts: result.attempts
|
|
288
340
|
}
|
|
@@ -22,6 +22,9 @@ export class NewFigure {
|
|
|
22
22
|
@Field({ nullable: true, description: 'Classification code used to group figures in the palette.' })
|
|
23
23
|
category?: string
|
|
24
24
|
|
|
25
|
+
@Field(type => [String], { nullable: true, description: 'Free-form tags. A figure may carry several.' })
|
|
26
|
+
tags?: string[]
|
|
27
|
+
|
|
25
28
|
@Field({ description: 'The canonical FigureSource JSON.' })
|
|
26
29
|
source: string
|
|
27
30
|
|
|
@@ -52,6 +55,13 @@ export class FigurePatch {
|
|
|
52
55
|
@Field({ nullable: true, description: 'The new classification code.' })
|
|
53
56
|
category?: string
|
|
54
57
|
|
|
58
|
+
/*
|
|
59
|
+
빈 배열과 안 준 것을 가른다. `undefined` 는 「건드리지 않는다」, `[]` 는 「전부 지운다」다.
|
|
60
|
+
둘을 뭉개면 태그를 다 지우려는 저작자가 지울 방법을 잃는다.
|
|
61
|
+
*/
|
|
62
|
+
@Field(type => [String], { nullable: true, description: 'The new tags. An empty list clears them; omitting the field leaves them.' })
|
|
63
|
+
tags?: string[]
|
|
64
|
+
|
|
55
65
|
@Field({ nullable: true, description: 'The new FigureSource JSON.' })
|
|
56
66
|
source?: string
|
|
57
67
|
|
|
@@ -118,6 +128,31 @@ export class FigureSaveResult {
|
|
|
118
128
|
* 것들이라 그 칸이 필요 없었다. 발행 판정은 막는 것과 알리는 것을 함께 내므로, 어느 쪽인지를 값이
|
|
119
129
|
* 스스로 말해야 한다 — 코드를 보고 화면이 다시 분류하면 규칙이 두 벌이 된다.
|
|
120
130
|
*/
|
|
131
|
+
/** A measured, one-anchor repair candidate for a release-gate sizing split. */
|
|
132
|
+
@ObjectType({ description: 'A reversible anchor change measured to reduce a sizing split.' })
|
|
133
|
+
export class FigureGateFix {
|
|
134
|
+
@Field({ description: 'Sizing test that this candidate improves, for example y or x-y.' })
|
|
135
|
+
findingWay: string
|
|
136
|
+
|
|
137
|
+
@Field({ description: 'Stable FigureSource part name to change.' })
|
|
138
|
+
part: string
|
|
139
|
+
|
|
140
|
+
@Field({ description: 'Axis whose anchor will change.' })
|
|
141
|
+
axis: string
|
|
142
|
+
|
|
143
|
+
@Field({ nullable: true, description: 'Current explicit anchor, if one is present.' })
|
|
144
|
+
from?: string
|
|
145
|
+
|
|
146
|
+
@Field({ description: 'Suggested explicit anchor: min, center, max, or span.' })
|
|
147
|
+
to: string
|
|
148
|
+
|
|
149
|
+
@Field(type => Int, { description: 'Number of blocking sizing-split checks removed by this one change.' })
|
|
150
|
+
resolved: number
|
|
151
|
+
|
|
152
|
+
@Field({ description: 'True only when every blocking sizing check is clear after remeasurement.' })
|
|
153
|
+
safe: boolean
|
|
154
|
+
}
|
|
155
|
+
|
|
121
156
|
@ObjectType({ description: 'One finding from the release gate.' })
|
|
122
157
|
export class FigureGateFinding {
|
|
123
158
|
@Field({ description: 'Stable code — figure-model validation codes, or sizing-splits / sizing-loose.' })
|
|
@@ -135,6 +170,12 @@ export class FigureGateFinding {
|
|
|
135
170
|
@Field({ nullable: true, description: 'Where it points: part names that came apart, or a path in the source.' })
|
|
136
171
|
at?: string
|
|
137
172
|
|
|
173
|
+
@Field(type => [FigureGateFix], {
|
|
174
|
+
nullable: true,
|
|
175
|
+
description: 'Measured repair candidates. They are previews until the author explicitly applies one.'
|
|
176
|
+
})
|
|
177
|
+
fixes?: FigureGateFix[]
|
|
178
|
+
|
|
138
179
|
@Field({ description: 'True when this finding blocks the release.' })
|
|
139
180
|
blocking: boolean
|
|
140
181
|
}
|
|
@@ -89,11 +89,31 @@ export class Figure {
|
|
|
89
89
|
@Field({ nullable: true, description: 'A detailed description for the figure.' })
|
|
90
90
|
description?: string
|
|
91
91
|
|
|
92
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* 분류 하나. **팔레트에서 묶어 보이는 기준이다.**
|
|
94
|
+
*
|
|
95
|
+
* 도형 하나에 하나뿐이다 — 팔레트는 목록을 한 벌로 나눠 보여 주므로, 한 도형이 두 묶음에
|
|
96
|
+
* 동시에 있으면 저작자가 같은 것을 두 번 만난다.
|
|
97
|
+
*/
|
|
93
98
|
@Column({ nullable: true })
|
|
94
|
-
@Field({ nullable: true, description: 'Classification code used to group figures in the palette.' })
|
|
99
|
+
@Field({ nullable: true, description: 'Classification code used to group figures in the palette. One per figure.' })
|
|
95
100
|
category?: string
|
|
96
101
|
|
|
102
|
+
/**
|
|
103
|
+
* 태그. **여럿 붙는다.**
|
|
104
|
+
*
|
|
105
|
+
* 분류와 다른 일을 한다. 분류는 「이것이 무엇인가」를 한 번 정하는 것이고, 태그는 「어디에
|
|
106
|
+
* 걸리는가」를 여러 갈래로 적는 것이다 — 컨베이어이면서 반출 구역의 것일 수 있다. 그래서
|
|
107
|
+
* 분류로는 못 하는 찾기가 된다.
|
|
108
|
+
*
|
|
109
|
+
* `simple-json` 으로 담는다. 사내 선례가 그것이고(`ops-master` · `organization` ·
|
|
110
|
+
* `personalization`), 드라이버마다 배열 타입이 다른 것을 피한다.
|
|
111
|
+
*/
|
|
112
|
+
/* `simple-json` 과 `default: null` 은 사내 선례 그대로다 — board-template · attachment 가 같은 모양이다. */
|
|
113
|
+
@Column('simple-json', { nullable: true, default: null })
|
|
114
|
+
@Field(type => [String], { nullable: true, description: 'Free-form tags. A figure may carry several.' })
|
|
115
|
+
tags?: string[]
|
|
116
|
+
|
|
97
117
|
@Column({ nullable: true, default: 'draft' })
|
|
98
118
|
@Field({ nullable: true, description: "Lifecycle state — 'draft' figures do not appear in the board palette." })
|
|
99
119
|
state?: 'draft' | 'released'
|