@things-factory/figure-service 10.1.13 → 10.1.16
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-propose.js +2 -1
- package/dist-server/service/figure/figure-propose.js.map +1 -1
- package/dist-server/service/figure/figure-query.d.ts +25 -0
- package/dist-server/service/figure/figure-query.js +83 -0
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-source.d.ts +1 -1
- package/dist-server/service/figure/figure-source.js +8 -1
- package/dist-server/service/figure/figure-source.js.map +1 -1
- package/dist-server/service/figure/figure-tools.js +79 -17
- package/dist-server/service/figure/figure-tools.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/logs/.08636eb59927f12972f6774f5947c8507b3564c2-audit.json +15 -0
- package/logs/application-2026-09-14-23.log +0 -0
- package/package.json +6 -5
- package/server/service/figure/figure-blueprint-query.test.ts +207 -0
- package/server/service/figure/figure-propose.ts +3 -1
- package/server/service/figure/figure-query.ts +84 -0
- package/server/service/figure/figure-source.test.ts +15 -9
- package/server/service/figure/figure-source.ts +11 -3
- package/server/service/figure/figure-tools.test.ts +21 -0
- package/server/service/figure/figure-tools.ts +87 -15
|
@@ -93,6 +93,48 @@ async function figureInDomain(figureId: string, domainId: string): Promise<any>
|
|
|
93
93
|
return found
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* figureId 또는 hostContext로부터 대상 Figure row(또는 미저장 초안 가상 row)를 해소한다.
|
|
98
|
+
*/
|
|
99
|
+
async function resolveFigureTarget(figureId: string | undefined, domainId: string, host: any): Promise<any> {
|
|
100
|
+
if (
|
|
101
|
+
host?.editorMode === 'new' &&
|
|
102
|
+
host?.draftSource &&
|
|
103
|
+
(!figureId || figureId === host.draftSource.type || figureId.startsWith('FIGURE'))
|
|
104
|
+
) {
|
|
105
|
+
return unsavedFigure(host)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (figureId) {
|
|
109
|
+
const { getRepository, Figure } = getDeps()
|
|
110
|
+
let found = await getRepository(Figure).findOne({
|
|
111
|
+
where: { id: figureId, domain: { id: domainId } }
|
|
112
|
+
})
|
|
113
|
+
if (!found) {
|
|
114
|
+
found = await getRepository(Figure).findOne({
|
|
115
|
+
where: { type: figureId, domain: { id: domainId } }
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
if (!found) {
|
|
119
|
+
if (
|
|
120
|
+
host?.editorMode === 'new' ||
|
|
121
|
+
(host?.draftSource &&
|
|
122
|
+
(host.draftSource.type === figureId ||
|
|
123
|
+
figureId.startsWith('FIGURE') ||
|
|
124
|
+
!host.figureId ||
|
|
125
|
+
host.figureId === figureId))
|
|
126
|
+
) {
|
|
127
|
+
const unsaved = unsavedFigure(host)
|
|
128
|
+
if (unsaved) return unsaved
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`figure not found in this tenant: ${figureId}`)
|
|
131
|
+
}
|
|
132
|
+
return found
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return unsavedFigure(host)
|
|
136
|
+
}
|
|
137
|
+
|
|
96
138
|
/**
|
|
97
139
|
* 모델러가 아직 저장하지 않은 source를 도구가 읽는 유일한 길.
|
|
98
140
|
*
|
|
@@ -101,7 +143,8 @@ async function figureInDomain(figureId: string, domainId: string): Promise<any>
|
|
|
101
143
|
* 프롬프트로 옮기면 비용만 태우고, DB에 있던 Figure와 다른 것을 고친다고 잘못 말할 수 있다.
|
|
102
144
|
*/
|
|
103
145
|
function sourceForTool(row: any, host: any): { source: any; draft: boolean } {
|
|
104
|
-
const
|
|
146
|
+
const isMatch = host?.figureId === row.id || (!row.id && host?.editorMode === 'new') || (!row.id && !host?.figureId)
|
|
147
|
+
const draft = isMatch ? host?.draftSource : undefined
|
|
105
148
|
if (!draft) {
|
|
106
149
|
return { source: row.source ? JSON.parse(row.source) : null, draft: false }
|
|
107
150
|
}
|
|
@@ -120,11 +163,20 @@ function sourceForTool(row: any, host: any): { source: any; draft: boolean } {
|
|
|
120
163
|
if (checked.errors.length > 0) {
|
|
121
164
|
throw new Error(`the unsaved draft is invalid: ${checked.errors.map((one: any) => one.message).join(' · ')}`)
|
|
122
165
|
}
|
|
166
|
+
|
|
167
|
+
let normalizedDraft = draft
|
|
123
168
|
if (draft.type !== row.type) {
|
|
124
|
-
|
|
169
|
+
// 템플릿 기본값 'FIGURE', 임시 draftType('FIGURE_...'), 또는 빈 값인 경우
|
|
170
|
+
// 현재 열려 있는 Figure의 실제 type으로 보정/정규화한다.
|
|
171
|
+
const isGenericOrTempType = !draft.type || draft.type === 'FIGURE' || draft.type.startsWith('FIGURE_')
|
|
172
|
+
if (isGenericOrTempType && row.type) {
|
|
173
|
+
normalizedDraft = { ...draft, type: row.type }
|
|
174
|
+
} else {
|
|
175
|
+
throw new Error(`the unsaved draft type '${draft.type}' does not match Figure '${row.type}'`)
|
|
176
|
+
}
|
|
125
177
|
}
|
|
126
178
|
|
|
127
|
-
return { source:
|
|
179
|
+
return { source: normalizedDraft, draft: true }
|
|
128
180
|
}
|
|
129
181
|
|
|
130
182
|
/**
|
|
@@ -134,8 +186,14 @@ function sourceForTool(row: any, host: any): { source: any; draft: boolean } {
|
|
|
134
186
|
* 도메인에서 Figure를 찾은 뒤, `proposeFigure`가 다시 길이·형식을 정리할 참고 데이터만 건넨다.
|
|
135
187
|
*/
|
|
136
188
|
function feedbackForTool(host: any, figureId?: string): unknown[] | undefined {
|
|
137
|
-
if (!
|
|
138
|
-
|
|
189
|
+
if (!Array.isArray(host?.proposalFeedback)) return undefined
|
|
190
|
+
if (figureId && host?.figureId === figureId) {
|
|
191
|
+
return host.proposalFeedback.slice(-5)
|
|
192
|
+
}
|
|
193
|
+
if (host?.editorMode === 'new' || !host?.figureId) {
|
|
194
|
+
return host.proposalFeedback.slice(-5)
|
|
195
|
+
}
|
|
196
|
+
return undefined
|
|
139
197
|
}
|
|
140
198
|
|
|
141
199
|
/**
|
|
@@ -163,8 +221,8 @@ function missingFigureTarget() {
|
|
|
163
221
|
}
|
|
164
222
|
|
|
165
223
|
function unsavedFigure(host: any): any | undefined {
|
|
166
|
-
if (host?.editorMode !== 'new' ||
|
|
167
|
-
const row = { id: host.figureId, type: host.draftSource.type, name: '', state: 'draft' }
|
|
224
|
+
if (host?.editorMode !== 'new' || !host?.draftSource) return undefined
|
|
225
|
+
const row = { id: host.figureId, type: host.draftSource.type || 'FIGURE', name: '', state: 'draft' }
|
|
168
226
|
// Reuse size, shape and vocabulary validation before trusting browser content.
|
|
169
227
|
const current = sourceForTool(row, host)
|
|
170
228
|
return { ...row, source: JSON.stringify(current.source) }
|
|
@@ -223,7 +281,7 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
223
281
|
builder: async (args: any, ctx: any) => {
|
|
224
282
|
const domainId = requireDomainId(ctx?.state)
|
|
225
283
|
const figureId = figureIdFrom(args, ctx?.host)
|
|
226
|
-
const row =
|
|
284
|
+
const row = await resolveFigureTarget(figureId, domainId, ctx?.host)
|
|
227
285
|
if (!row) return missingFigureTarget()
|
|
228
286
|
|
|
229
287
|
const current = sourceForTool(row, ctx?.host)
|
|
@@ -258,7 +316,7 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
258
316
|
builder: async (args: any, ctx: any) => {
|
|
259
317
|
const domainId = requireDomainId(ctx?.state)
|
|
260
318
|
const figureId = figureIdFrom(args, ctx?.host)
|
|
261
|
-
const row =
|
|
319
|
+
const row = await resolveFigureTarget(figureId, domainId, ctx?.host)
|
|
262
320
|
if (!row) return missingFigureTarget()
|
|
263
321
|
|
|
264
322
|
const current = sourceForTool(row, ctx?.host)
|
|
@@ -308,12 +366,9 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
308
366
|
let base: unknown
|
|
309
367
|
if (!args?.create && args?.figureId !== null) {
|
|
310
368
|
const open = typeof args?.figureId === 'string' ? args.figureId : ctx?.host?.figureId
|
|
311
|
-
|
|
312
|
-
|
|
369
|
+
const row = await resolveFigureTarget(open ? open.trim() : undefined, domainId, ctx?.host)
|
|
370
|
+
if (row) {
|
|
313
371
|
base = sourceForTool(row, ctx?.host).source ?? undefined
|
|
314
|
-
} else {
|
|
315
|
-
const draft = unsavedFigure(ctx?.host)
|
|
316
|
-
if (draft) base = JSON.parse(draft.source)
|
|
317
372
|
}
|
|
318
373
|
}
|
|
319
374
|
|
|
@@ -328,13 +383,23 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
328
383
|
|
|
329
384
|
const { proposeFigure } = getDeps()
|
|
330
385
|
const feedback = feedbackForTool(ctx?.host, base ? figureIdFrom(args, ctx?.host) : undefined)
|
|
386
|
+
|
|
387
|
+
let hostImage: { data: Buffer; mediaType: any } | undefined
|
|
388
|
+
const rawImage = ctx?.host?.image || (Array.isArray(ctx?.host?.attachments) ? ctx.host.attachments.find((a: any) => a?.mediaType?.startsWith('image/')) : undefined)
|
|
389
|
+
if (rawImage?.data) {
|
|
390
|
+
const rawData = typeof rawImage.data === 'string' ? rawImage.data.replace(/^data:image\/[a-zA-Z]+;base64,/, '') : rawImage.data
|
|
391
|
+
const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)
|
|
392
|
+
hostImage = { data: buf, mediaType: rawImage.mediaType || 'image/png' }
|
|
393
|
+
}
|
|
394
|
+
|
|
331
395
|
const result = await proposeFigure({
|
|
332
396
|
prompt,
|
|
333
397
|
palette,
|
|
334
398
|
base,
|
|
335
399
|
type: typeof args?.type === 'string' ? args.type : undefined,
|
|
336
400
|
feedback,
|
|
337
|
-
refine: !!feedback?.length
|
|
401
|
+
refine: !!feedback?.length,
|
|
402
|
+
image: hostImage
|
|
338
403
|
})
|
|
339
404
|
|
|
340
405
|
/*
|
|
@@ -351,6 +416,13 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
351
416
|
reason:
|
|
352
417
|
`등급 ${result.score.grade} · 삼각형 ${result.cost.triangles} · ` +
|
|
353
418
|
`한 도면 ${result.cost.at.drawCalls}회 · ${result.attempts}번에 만들었다`,
|
|
419
|
+
choices: [
|
|
420
|
+
{ id: 'save', action: 'save', label: '초안 저장', icon: 'save', primary: true },
|
|
421
|
+
{ id: 'repropose', action: 'prompt', label: '다른 콘셉트 재제안', icon: 'refresh', prompt: '현재 제안과 완전히 다른 스타일과 콘셉트로 새로 제안해줘' },
|
|
422
|
+
{ id: 'slim', action: 'prompt', label: '비율 슬림 조정', icon: 'aspect_ratio', prompt: '전체적인 가로세로 비율을 좀 더 슬림하고 날렵하게 조정해줘' },
|
|
423
|
+
{ id: 'palette', action: 'prompt', label: '색상 테마 변경', icon: 'palette', prompt: '다른 팔레트 색상 테마로 부품 색상을 변경해줘' },
|
|
424
|
+
{ id: 'detail', action: 'prompt', label: '디테일 보강', icon: 'extension', prompt: '주요 디테일과 센서 부품을 좀 더 풍부하게 보강해줘' }
|
|
425
|
+
],
|
|
354
426
|
args: {
|
|
355
427
|
baseSource: base ? JSON.stringify(base) : undefined,
|
|
356
428
|
prompt, figureId: base ? figureIdFrom(args, ctx?.host) : undefined,
|