@things-factory/board-ai 10.1.24 → 10.1.26
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/PLAN-styling-tools.md +1 -1
- package/dist-client/server/service/assistant.d.ts +5 -4
- package/dist-client/server/service/assistant.js +35 -35
- package/dist-client/server/service/assistant.js.map +1 -1
- package/dist-client/server/service/styling/clear-tools.d.ts +2 -2
- package/dist-client/server/service/styling/clear-tools.js.map +1 -1
- package/dist-client/server/service/styling/copy-tools.d.ts +3 -3
- package/dist-client/server/service/styling/copy-tools.js +1 -1
- package/dist-client/server/service/styling/copy-tools.js.map +1 -1
- package/dist-client/server/service/styling/effect-tools.d.ts +2 -2
- package/dist-client/server/service/styling/effect-tools.js.map +1 -1
- package/dist-client/server/service/styling/fill-tools.d.ts +3 -3
- package/dist-client/server/service/styling/fill-tools.js +1 -1
- package/dist-client/server/service/styling/fill-tools.js.map +1 -1
- package/dist-client/server/service/styling/material-tools.d.ts +2 -2
- package/dist-client/server/service/styling/material-tools.js.map +1 -1
- package/dist-client/server/service/styling/registry.d.ts +6 -6
- package/dist-client/server/service/styling/registry.js +2 -2
- package/dist-client/server/service/styling/registry.js.map +1 -1
- package/dist-client/server/service/styling/stroke-tools.d.ts +2 -2
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -1
- package/dist-client/server/service/styling/text-tools.d.ts +2 -2
- package/dist-client/server/service/styling/text-tools.js.map +1 -1
- package/dist-client/server/service/types.d.ts +6 -148
- package/dist-client/server/service/types.js.map +1 -1
- package/dist-client/server/service/validation/board-model-schema.d.ts +262 -262
- package/dist-client/server/service/validation/board-model-schema.js +4 -4
- package/dist-client/server/service/validation/board-model-schema.js.map +1 -1
- package/dist-client/server/service/validation/tool-validation.js +5 -5
- package/dist-client/server/service/validation/tool-validation.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.d.ts +2 -2
- package/dist-server/index.js +2 -2
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/assistant.d.ts +5 -4
- package/dist-server/service/assistant.js +37 -37
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.js +2 -2
- package/dist-server/service/board-ai-resolver.js.map +1 -1
- package/dist-server/service/index.d.ts +0 -1
- package/dist-server/service/index.js +0 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/patch-entry/board-patch-subscription.js +1 -1
- package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -1
- package/dist-server/service/patch-entry/patch-entry.d.ts +1 -1
- package/dist-server/service/patch-entry/patch-entry.js +1 -1
- package/dist-server/service/patch-entry/patch-entry.js.map +1 -1
- package/dist-server/service/styling/clear-tools.d.ts +2 -2
- package/dist-server/service/styling/clear-tools.js.map +1 -1
- package/dist-server/service/styling/copy-tools.d.ts +3 -3
- package/dist-server/service/styling/copy-tools.js +1 -1
- package/dist-server/service/styling/copy-tools.js.map +1 -1
- package/dist-server/service/styling/effect-tools.d.ts +2 -2
- package/dist-server/service/styling/effect-tools.js.map +1 -1
- package/dist-server/service/styling/fill-tools.d.ts +3 -3
- package/dist-server/service/styling/fill-tools.js +1 -1
- package/dist-server/service/styling/fill-tools.js.map +1 -1
- package/dist-server/service/styling/material-tools.d.ts +2 -2
- package/dist-server/service/styling/material-tools.js.map +1 -1
- package/dist-server/service/styling/registry.d.ts +6 -6
- package/dist-server/service/styling/registry.js +2 -2
- package/dist-server/service/styling/registry.js.map +1 -1
- package/dist-server/service/styling/stroke-tools.d.ts +2 -2
- package/dist-server/service/styling/stroke-tools.js.map +1 -1
- package/dist-server/service/styling/text-tools.d.ts +2 -2
- package/dist-server/service/styling/text-tools.js.map +1 -1
- package/dist-server/service/types.d.ts +6 -148
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/service/validation/board-model-schema.d.ts +262 -262
- package/dist-server/service/validation/board-model-schema.js +4 -4
- package/dist-server/service/validation/board-model-schema.js.map +1 -1
- package/dist-server/service/validation/tool-validation.js +5 -5
- package/dist-server/service/validation/tool-validation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/logs/.5e5d741d8b7784a2fbad65eedc0fd46946aaf6f2-audit.json +5 -0
- package/logs/connections-2026-09-17-16.log +0 -0
- package/package.json +8 -7
- package/server/index.ts +2 -2
- package/server/service/assistant-integration.test.ts +44 -44
- package/server/service/assistant-llm-smoke.test.ts +5 -5
- package/server/service/assistant.test.ts +86 -86
- package/server/service/assistant.ts +41 -43
- package/server/service/board-ai-resolver.ts +2 -2
- package/server/service/index.ts +0 -1
- package/server/service/patch-entry/board-patch-subscription.ts +1 -1
- package/server/service/patch-entry/patch-entry.ts +2 -2
- package/server/service/styling/clear-tools.ts +2 -2
- package/server/service/styling/copy-tools.ts +3 -3
- package/server/service/styling/effect-tools.ts +2 -2
- package/server/service/styling/fill-tools.test.ts +1 -1
- package/server/service/styling/fill-tools.ts +4 -4
- package/server/service/styling/material-tools.ts +2 -2
- package/server/service/styling/registry.ts +6 -6
- package/server/service/styling/stroke-tools.test.ts +1 -1
- package/server/service/styling/stroke-tools.ts +3 -3
- package/server/service/styling/text-tools.ts +2 -2
- package/server/service/tool-exposure.test.ts +7 -7
- package/server/service/types.ts +9 -110
- package/server/service/validation/board-model-schema.test.ts +3 -3
- package/server/service/validation/board-model-schema.ts +4 -4
- package/server/service/validation/tool-validation.ts +5 -5
- package/dist-server/service/apply-patch.d.ts +0 -47
- package/dist-server/service/apply-patch.js +0 -369
- package/dist-server/service/apply-patch.js.map +0 -1
- package/server/service/apply-patch-drift.test.ts +0 -831
- package/server/service/apply-patch.test.ts +0 -757
- package/server/service/apply-patch.ts +0 -411
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
"date": 1789195002942,
|
|
10
10
|
"name": "logs/connections-2026-09-12-15.log",
|
|
11
11
|
"hash": "cf643fb0881a2a037b9657d9f0cab3f858825cbc597ef955ba6335a9305911c5"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"date": 1789629007157,
|
|
15
|
+
"name": "logs/connections-2026-09-17-16.log",
|
|
16
|
+
"hash": "c1197ab32a8e488c0eca99467d70519d581cbe046549df9f7106554417a0f184"
|
|
12
17
|
}
|
|
13
18
|
],
|
|
14
19
|
"hashType": "sha256"
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/board-ai",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.26",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -30,13 +30,14 @@
|
|
|
30
30
|
"@apollo/client": "^3.13.0",
|
|
31
31
|
"@operato/graphql": "^10.0.0",
|
|
32
32
|
"@operato/i18n": "^10.0.0",
|
|
33
|
+
"@operato/scene-ops": "^10.14.1",
|
|
33
34
|
"@operato/styles": "^10.0.0",
|
|
34
|
-
"@things-factory/ai-assistant": "^10.1.
|
|
35
|
-
"@things-factory/ai-client-base": "^10.1.
|
|
36
|
-
"@things-factory/auth-base": "^10.1.
|
|
37
|
-
"@things-factory/board-import": "^10.1.
|
|
35
|
+
"@things-factory/ai-assistant": "^10.1.26",
|
|
36
|
+
"@things-factory/ai-client-base": "^10.1.26",
|
|
37
|
+
"@things-factory/auth-base": "^10.1.26",
|
|
38
|
+
"@things-factory/board-import": "^10.1.26",
|
|
38
39
|
"@things-factory/env": "^10.1.20",
|
|
39
|
-
"@things-factory/shell": "^10.1.
|
|
40
|
+
"@things-factory/shell": "^10.1.26",
|
|
40
41
|
"dompurify": "^3.0.0",
|
|
41
42
|
"graphql-tag": "^2.12.6",
|
|
42
43
|
"lit": "^3.1.2",
|
|
@@ -46,5 +47,5 @@
|
|
|
46
47
|
"copyfiles": "^2.4.1",
|
|
47
48
|
"rimraf": "^5.0.0"
|
|
48
49
|
},
|
|
49
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "151bda14d899442c6846532ddded1e2bfe23a2f5"
|
|
50
51
|
}
|
package/server/index.ts
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* @example
|
|
10
10
|
* ```ts
|
|
11
11
|
* import { createAIClient } from '@things-factory/ai-client-base'
|
|
12
|
-
* import { DefaultBoardAIAssistant,
|
|
12
|
+
* import { DefaultBoardAIAssistant, applyScenePatch } from '@things-factory/board-ai'
|
|
13
13
|
*
|
|
14
14
|
* const base = createAIClient({ provider: 'anthropic', apiKey: '...' })
|
|
15
15
|
* const ai = new DefaultBoardAIAssistant(base, { scopes: ['fmsim'] })
|
|
16
16
|
*
|
|
17
17
|
* let board
|
|
18
18
|
* const r = await ai.chat([{ role: 'user', content: 'AGV 3대를 우측에 추가' }], board)
|
|
19
|
-
* if (r.patch) board =
|
|
19
|
+
* if (r.patch) board = applyScenePatch(board, r.patch)
|
|
20
20
|
* console.log(r.reply)
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 통합 테스트 — assistant.chat() →
|
|
2
|
+
* 통합 테스트 — assistant.chat() → SceneEditPatch → apply-patch 풀 파이프라인.
|
|
3
3
|
*
|
|
4
4
|
* 단위 테스트의 공백 보완:
|
|
5
5
|
* - assistant.test.ts 는 patch 가 "올바르게 만들어지는지" 까지만 검증
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { AIClient } from '@things-factory/ai-client-base'
|
|
15
15
|
import { DefaultBoardAIAssistant } from './assistant'
|
|
16
|
-
import {
|
|
16
|
+
import { applyScenePatch, applyScenePatchVerbose } from '@operato/scene-ops'
|
|
17
17
|
import type { BoardModel } from '@things-factory/board-import'
|
|
18
18
|
|
|
19
19
|
// ── Mock provider — assistant.test.ts 의 패턴과 동일 ──────────────────
|
|
@@ -59,11 +59,11 @@ describe('통합 — addComponent 시나리오', () => {
|
|
|
59
59
|
const r = await a.chat([{ role: 'user', content: 'rect 하나 추가' }], initial)
|
|
60
60
|
|
|
61
61
|
expect(r.patch?.ops).toHaveLength(1)
|
|
62
|
-
const verbose =
|
|
62
|
+
const verbose = applyScenePatchVerbose(initial, r.patch!)
|
|
63
63
|
expect(verbose.applied).toHaveLength(1)
|
|
64
64
|
expect(verbose.missed).toHaveLength(0)
|
|
65
|
-
expect(verbose.
|
|
66
|
-
const added: any = verbose.
|
|
65
|
+
expect(verbose.model.components).toHaveLength(1)
|
|
66
|
+
const added: any = verbose.model.components[0]
|
|
67
67
|
expect(added.type).toBe('rect')
|
|
68
68
|
expect(added.left).toBe(50)
|
|
69
69
|
expect(added.top).toBe(60)
|
|
@@ -84,9 +84,9 @@ describe('통합 — addComponent 시나리오', () => {
|
|
|
84
84
|
const a = new DefaultBoardAIAssistant(client)
|
|
85
85
|
const r = await a.chat([{ role: 'user', content: 'rect, circle, text 추가' }], initial)
|
|
86
86
|
|
|
87
|
-
const verbose =
|
|
88
|
-
expect(verbose.
|
|
89
|
-
expect(verbose.
|
|
87
|
+
const verbose = applyScenePatchVerbose(initial, r.patch!)
|
|
88
|
+
expect(verbose.model.components).toHaveLength(3)
|
|
89
|
+
expect(verbose.model.components.map((c: any) => c.type)).toEqual(['rect', 'circle', 'text'])
|
|
90
90
|
expect(verbose.applied).toHaveLength(3)
|
|
91
91
|
expect(verbose.missed).toHaveLength(0)
|
|
92
92
|
})
|
|
@@ -113,11 +113,11 @@ describe('통합 — modifyComponentByRefid 시나리오', () => {
|
|
|
113
113
|
const a = new DefaultBoardAIAssistant(client)
|
|
114
114
|
const r = await a.chat([{ role: 'user', content: '왼쪽 200으로' }], initial)
|
|
115
115
|
|
|
116
|
-
const verbose =
|
|
116
|
+
const verbose = applyScenePatchVerbose(initial, r.patch!)
|
|
117
117
|
expect(verbose.applied).toHaveLength(1)
|
|
118
118
|
expect(verbose.missed).toHaveLength(0)
|
|
119
|
-
const c1: any = verbose.
|
|
120
|
-
const c2: any = verbose.
|
|
119
|
+
const c1: any = verbose.model.components[0]
|
|
120
|
+
const c2: any = verbose.model.components[1]
|
|
121
121
|
expect(c1.left).toBe(200)
|
|
122
122
|
expect(c1.top).toBe(20) // 다른 속성 보존
|
|
123
123
|
expect(c1.width).toBe(100)
|
|
@@ -145,15 +145,15 @@ describe('통합 — modifyComponentByRefid 시나리오', () => {
|
|
|
145
145
|
const a = new DefaultBoardAIAssistant(client)
|
|
146
146
|
const r = await a.chat([{ role: 'user', content: '999 left=200' }], initial)
|
|
147
147
|
|
|
148
|
-
const verbose =
|
|
148
|
+
const verbose = applyScenePatchVerbose(initial, r.patch!)
|
|
149
149
|
expect(verbose.applied).toHaveLength(0)
|
|
150
150
|
expect(verbose.missed).toHaveLength(1)
|
|
151
151
|
// 보드는 그대로
|
|
152
|
-
expect((verbose.
|
|
152
|
+
expect((verbose.model.components[0] as any).left).toBe(10)
|
|
153
153
|
})
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
-
describe('통합 —
|
|
156
|
+
describe('통합 — modifyScene 시나리오 (root 속성)', () => {
|
|
157
157
|
test('"배경색 #0a1929" → root.fillStyle 만 변경, children 영향 없음', async () => {
|
|
158
158
|
const initial = makeBoard([
|
|
159
159
|
{ refid: 1, type: 'rect', fillStyle: '#fff' }
|
|
@@ -164,7 +164,7 @@ describe('통합 — modifyBoard 시나리오 (root 속성)', () => {
|
|
|
164
164
|
toolCalls: [
|
|
165
165
|
{
|
|
166
166
|
id: 'c1',
|
|
167
|
-
name: '
|
|
167
|
+
name: 'modifyScene',
|
|
168
168
|
arguments: { patch: { fillStyle: '#0a1929' } }
|
|
169
169
|
}
|
|
170
170
|
]
|
|
@@ -174,15 +174,15 @@ describe('통합 — modifyBoard 시나리오 (root 속성)', () => {
|
|
|
174
174
|
const a = new DefaultBoardAIAssistant(client)
|
|
175
175
|
const r = await a.chat([{ role: 'user', content: '심해 컬러' }], initial)
|
|
176
176
|
|
|
177
|
-
const after: any =
|
|
177
|
+
const after: any = applyScenePatch(initial, r.patch!)
|
|
178
178
|
expect(after.fillStyle).toBe('#0a1929')
|
|
179
179
|
expect(after.components).toHaveLength(1)
|
|
180
180
|
expect((after.components[0] as any).fillStyle).toBe('#fff') // 자식은 그대로
|
|
181
181
|
})
|
|
182
182
|
|
|
183
|
-
test('"심해 분위기" multi-turn — getAtmosphereGuide 후
|
|
183
|
+
test('"심해 분위기" multi-turn — getAtmosphereGuide 후 modifyScene 패치가 root 에 반영', async () => {
|
|
184
184
|
// 실제 사용 패턴: AI 가 atmosphere primer 를 먼저 read tool 로 가져와 archetype
|
|
185
|
-
// 보고, 거기서 추출한 patch (sky/exposure/dirLight 등) 로
|
|
185
|
+
// 보고, 거기서 추출한 patch (sky/exposure/dirLight 등) 로 modifyScene 호출.
|
|
186
186
|
const initial = makeBoard()
|
|
187
187
|
const { client, callLog } = makeMockClient([
|
|
188
188
|
// turn 1: read tool — guide 조회
|
|
@@ -194,7 +194,7 @@ describe('통합 — modifyBoard 시나리오 (root 속성)', () => {
|
|
|
194
194
|
toolCalls: [
|
|
195
195
|
{
|
|
196
196
|
id: 'c2',
|
|
197
|
-
name: '
|
|
197
|
+
name: 'modifyScene',
|
|
198
198
|
arguments: {
|
|
199
199
|
patch: { sky: 'color', skyColor: '#0a1929', exposure: 0.7, hemiIntensity: 1.5 }
|
|
200
200
|
}
|
|
@@ -207,8 +207,8 @@ describe('통합 — modifyBoard 시나리오 (root 속성)', () => {
|
|
|
207
207
|
const r = await a.chat([{ role: 'user', content: '심해 분위기로' }], initial)
|
|
208
208
|
|
|
209
209
|
expect(callLog).toHaveLength(3) // read → write → finalize
|
|
210
|
-
expect(r.patch?.ops).toHaveLength(1) // read 는 patch 에 안 잡히고
|
|
211
|
-
const after: any =
|
|
210
|
+
expect(r.patch?.ops).toHaveLength(1) // read 는 patch 에 안 잡히고 modifyScene 만
|
|
211
|
+
const after: any = applyScenePatch(initial, r.patch!)
|
|
212
212
|
expect(after.sky).toBe('color')
|
|
213
213
|
expect(after.skyColor).toBe('#0a1929')
|
|
214
214
|
expect(after.exposure).toBe(0.7)
|
|
@@ -237,7 +237,7 @@ describe('통합 — removeComponentByRefid', () => {
|
|
|
237
237
|
const a = new DefaultBoardAIAssistant(client)
|
|
238
238
|
const r = await a.chat([{ role: 'user', content: '36 삭제' }], initial)
|
|
239
239
|
|
|
240
|
-
const after =
|
|
240
|
+
const after = applyScenePatch(initial, r.patch!)
|
|
241
241
|
expect(after.components).toHaveLength(2)
|
|
242
242
|
expect(after.components.map((c: any) => c.refid)).toEqual([35, 37])
|
|
243
243
|
})
|
|
@@ -265,7 +265,7 @@ describe('통합 — replaceBoard', () => {
|
|
|
265
265
|
const a = new DefaultBoardAIAssistant(client)
|
|
266
266
|
const r = await a.chat([{ role: 'user', content: '갈아엎어' }], initial)
|
|
267
267
|
|
|
268
|
-
const after: any =
|
|
268
|
+
const after: any = applyScenePatch(initial, r.patch!)
|
|
269
269
|
expect(after.width).toBe(1920)
|
|
270
270
|
expect(after.height).toBe(1080)
|
|
271
271
|
expect(after.fillStyle).toBe('#000')
|
|
@@ -296,7 +296,7 @@ describe('통합 — read-then-write 다중 turn', () => {
|
|
|
296
296
|
expect(callLog).toHaveLength(3)
|
|
297
297
|
expect(r.patch?.ops).toHaveLength(1) // read 는 patch 에 안 들어감
|
|
298
298
|
expect(r.patch?.ops[0].op).toBe('modify')
|
|
299
|
-
const after =
|
|
299
|
+
const after = applyScenePatch(initial, r.patch!)
|
|
300
300
|
expect((after.components[0] as any).left).toBe(100)
|
|
301
301
|
})
|
|
302
302
|
|
|
@@ -324,7 +324,7 @@ describe('통합 — read-then-write 다중 turn', () => {
|
|
|
324
324
|
const r = await a.chat([{ role: 'user', content: 'rect 다 빨갛게' }], initial)
|
|
325
325
|
|
|
326
326
|
expect(r.patch?.ops).toHaveLength(2)
|
|
327
|
-
const after =
|
|
327
|
+
const after = applyScenePatch(initial, r.patch!)
|
|
328
328
|
expect((after.components[0] as any).fillStyle).toBe('#ff0000') // rect
|
|
329
329
|
expect((after.components[1] as any).fillStyle).toBe('#ff0000') // rect
|
|
330
330
|
expect((after.components[2] as any).fillStyle).toBe('#fff') // circle 무영향
|
|
@@ -353,7 +353,7 @@ describe('통합 — write + action 혼합', () => {
|
|
|
353
353
|
expect(r.actions![0]).toEqual({ action: 'setSceneMode', mode: 'view' })
|
|
354
354
|
|
|
355
355
|
// apply-patch 는 patch.ops 만 처리 (actions 는 호스트가 별도 처리)
|
|
356
|
-
const after =
|
|
356
|
+
const after = applyScenePatch(initial, r.patch!)
|
|
357
357
|
expect((after.components[0] as any).left).toBe(100)
|
|
358
358
|
})
|
|
359
359
|
|
|
@@ -393,11 +393,11 @@ describe('통합 — 다중 op 한 patch 안 순차 적용', () => {
|
|
|
393
393
|
const a = new DefaultBoardAIAssistant(client)
|
|
394
394
|
const r = await a.chat([{ role: 'user', content: 'circle 추가하고 1 left=50' }], initial)
|
|
395
395
|
|
|
396
|
-
const verbose =
|
|
396
|
+
const verbose = applyScenePatchVerbose(initial, r.patch!)
|
|
397
397
|
expect(verbose.applied).toHaveLength(2)
|
|
398
|
-
expect(verbose.
|
|
399
|
-
expect((verbose.
|
|
400
|
-
expect((verbose.
|
|
398
|
+
expect(verbose.model.components).toHaveLength(2)
|
|
399
|
+
expect((verbose.model.components[0] as any).left).toBe(50)
|
|
400
|
+
expect((verbose.model.components[1] as any).type).toBe('circle')
|
|
401
401
|
})
|
|
402
402
|
|
|
403
403
|
test('일부 applied + 일부 missed 혼합', async () => {
|
|
@@ -415,10 +415,10 @@ describe('통합 — 다중 op 한 patch 안 순차 적용', () => {
|
|
|
415
415
|
const a = new DefaultBoardAIAssistant(client)
|
|
416
416
|
const r = await a.chat([{ role: 'user', content: 'do' }], initial)
|
|
417
417
|
|
|
418
|
-
const verbose =
|
|
418
|
+
const verbose = applyScenePatchVerbose(initial, r.patch!)
|
|
419
419
|
expect(verbose.applied).toHaveLength(2) // refid 1 modify + add
|
|
420
420
|
expect(verbose.missed).toHaveLength(1) // refid 999 modify
|
|
421
|
-
expect(verbose.
|
|
421
|
+
expect(verbose.model.components).toHaveLength(2)
|
|
422
422
|
})
|
|
423
423
|
})
|
|
424
424
|
|
|
@@ -441,10 +441,10 @@ describe('통합 — Phase 2 scene 조작 op (모델 차원 noop, scene API 정
|
|
|
441
441
|
|
|
442
442
|
expect(r.patch?.ops).toHaveLength(1)
|
|
443
443
|
expect(r.patch?.ops[0]).toEqual({ op: 'align', refids: [1, 2], direction: 'left' })
|
|
444
|
-
const verbose =
|
|
444
|
+
const verbose = applyScenePatchVerbose(initial, r.patch!)
|
|
445
445
|
expect(verbose.applied).toHaveLength(1)
|
|
446
446
|
expect(verbose.missed).toHaveLength(0)
|
|
447
|
-
expect(verbose.
|
|
447
|
+
expect(verbose.model).toBe(initial) // model 차원에선 reference 동일
|
|
448
448
|
})
|
|
449
449
|
|
|
450
450
|
test('group + zorder 혼합', async () => {
|
|
@@ -465,7 +465,7 @@ describe('통합 — Phase 2 scene 조작 op (모델 차원 noop, scene API 정
|
|
|
465
465
|
const r = await a.chat([{ role: 'user', content: '그룹 + 앞으로' }], initial)
|
|
466
466
|
|
|
467
467
|
expect(r.patch?.ops).toHaveLength(2)
|
|
468
|
-
const verbose =
|
|
468
|
+
const verbose = applyScenePatchVerbose(initial, r.patch!)
|
|
469
469
|
expect(verbose.applied).toHaveLength(2)
|
|
470
470
|
expect(verbose.missed).toHaveLength(0)
|
|
471
471
|
})
|
|
@@ -482,7 +482,7 @@ describe('통합 — text-only 응답 (patch 없음)', () => {
|
|
|
482
482
|
expect(r.patch).toBeUndefined()
|
|
483
483
|
expect(r.actions).toBeUndefined()
|
|
484
484
|
// 보드도 변경 없음
|
|
485
|
-
const after =
|
|
485
|
+
const after = applyScenePatch(initial, { ops: [], summary: '', confidence: 0 })
|
|
486
486
|
expect(after).toBe(initial)
|
|
487
487
|
})
|
|
488
488
|
})
|
|
@@ -517,7 +517,7 @@ describe('통합 — Prefab (검증된 복합 컴포넌트 한 번에 추가)',
|
|
|
517
517
|
|
|
518
518
|
expect(callLog).toHaveLength(3) // read → write → finalize
|
|
519
519
|
expect(r.patch?.ops).toHaveLength(1) // addPrefab → 단일 add op
|
|
520
|
-
const after =
|
|
520
|
+
const after = applyScenePatch(initial, r.patch!)
|
|
521
521
|
expect(after.components).toHaveLength(1)
|
|
522
522
|
const added: any = after.components[0]
|
|
523
523
|
expect(added.type).toBe('group') // prefab 은 group 으로 wrapping
|
|
@@ -573,7 +573,7 @@ describe('통합 — Prefab (검증된 복합 컴포넌트 한 번에 추가)',
|
|
|
573
573
|
const r = await a.chat([{ role: 'user', content: 'KPI 타일 3개' }], initial)
|
|
574
574
|
|
|
575
575
|
expect(r.patch?.ops).toHaveLength(3)
|
|
576
|
-
const after =
|
|
576
|
+
const after = applyScenePatch(initial, r.patch!)
|
|
577
577
|
expect(after.components).toHaveLength(3)
|
|
578
578
|
// 각 타일이 group 이고 fillStyle 이 다름 (옵션 반영)
|
|
579
579
|
const fills = (after.components as any[])
|
|
@@ -582,7 +582,7 @@ describe('통합 — Prefab (검증된 복합 컴포넌트 한 번에 추가)',
|
|
|
582
582
|
expect(fills).toEqual(['#0ea5e9', '#10b981', '#ef4444'])
|
|
583
583
|
})
|
|
584
584
|
|
|
585
|
-
test('잘못된 prefab name → patch 에 누락 (silent skip —
|
|
585
|
+
test('잘못된 prefab name → patch 에 누락 (silent skip — toolCallToSceneEditOp 가 null 반환)', async () => {
|
|
586
586
|
const initial = makeBoard()
|
|
587
587
|
const { client } = makeMockClient([
|
|
588
588
|
{
|
|
@@ -598,7 +598,7 @@ describe('통합 — Prefab (검증된 복합 컴포넌트 한 번에 추가)',
|
|
|
598
598
|
])
|
|
599
599
|
const a = new DefaultBoardAIAssistant(client)
|
|
600
600
|
const r = await a.chat([{ role: 'user', content: 'x' }], initial)
|
|
601
|
-
// 잘못된 prefab →
|
|
601
|
+
// 잘못된 prefab → toolCallToSceneEditOp 가 null → patch 에 op 미추가
|
|
602
602
|
expect(r.patch).toBeUndefined()
|
|
603
603
|
})
|
|
604
604
|
})
|
|
@@ -622,7 +622,7 @@ describe('통합 — Design system 카탈로그 (color palette + layout template
|
|
|
622
622
|
toolCalls: [
|
|
623
623
|
{
|
|
624
624
|
id: 'w1',
|
|
625
|
-
name: '
|
|
625
|
+
name: 'modifyScene',
|
|
626
626
|
arguments: { patch: { fillStyle: '#0f1722' } } // industrial-steel.base
|
|
627
627
|
},
|
|
628
628
|
{
|
|
@@ -663,7 +663,7 @@ describe('통합 — Design system 카탈로그 (color palette + layout template
|
|
|
663
663
|
expect(readNames).toContain('getLayoutTemplates')
|
|
664
664
|
|
|
665
665
|
// patch 적용 후 보드 배경 + 2 prefab tile
|
|
666
|
-
const after: any =
|
|
666
|
+
const after: any = applyScenePatch(initial, r.patch!)
|
|
667
667
|
expect(after.fillStyle).toBe('#0f1722') // industrial-steel.base
|
|
668
668
|
expect(after.components).toHaveLength(2)
|
|
669
669
|
// 모두 group prefab
|
|
@@ -868,10 +868,10 @@ describe('통합 — Sugar layout (arrange grid/row/column)', () => {
|
|
|
868
868
|
expect(r.patch.ops[0].layout).toEqual({ type: 'grid', cols: 3 })
|
|
869
869
|
}
|
|
870
870
|
// model 차원 noop — 호스트가 정본 (board-edit-dispatch 가 위치 계산)
|
|
871
|
-
const verbose =
|
|
871
|
+
const verbose = applyScenePatchVerbose(initial, r.patch!)
|
|
872
872
|
expect(verbose.applied).toHaveLength(1)
|
|
873
873
|
expect(verbose.missed).toHaveLength(0)
|
|
874
|
-
expect(verbose.
|
|
874
|
+
expect(verbose.model).toBe(initial) // model 동일
|
|
875
875
|
})
|
|
876
876
|
|
|
877
877
|
test('"한 줄로 가운데 정렬" → arrangeInRow + align:center', async () => {
|
|
@@ -171,7 +171,7 @@ describeIf('LLM 스모크 — 실제 모델 tool 선택 검증', () => {
|
|
|
171
171
|
)
|
|
172
172
|
|
|
173
173
|
test(
|
|
174
|
-
'"보드 배경 어둡게" →
|
|
174
|
+
'"보드 배경 어둡게" → modifyScene',
|
|
175
175
|
async () => {
|
|
176
176
|
const a = new DefaultBoardAIAssistant(client)
|
|
177
177
|
const r = await a.chat(
|
|
@@ -181,14 +181,14 @@ describeIf('LLM 스모크 — 실제 모델 tool 선택 검증', () => {
|
|
|
181
181
|
)
|
|
182
182
|
const names = (r.toolUsages ?? []).map((u: any) => u.name)
|
|
183
183
|
dumpUsages(r.toolUsages)
|
|
184
|
-
//
|
|
185
|
-
expect(names).toContain('
|
|
184
|
+
// modifyScene 또는 atmosphere primer + modifyScene 둘 다 OK
|
|
185
|
+
expect(names).toContain('modifyScene')
|
|
186
186
|
},
|
|
187
187
|
TIMEOUT_MS
|
|
188
188
|
)
|
|
189
189
|
|
|
190
190
|
test(
|
|
191
|
-
'"심해 분위기로" → getAtmosphereGuide +
|
|
191
|
+
'"심해 분위기로" → getAtmosphereGuide + modifyScene 패턴',
|
|
192
192
|
async () => {
|
|
193
193
|
const a = new DefaultBoardAIAssistant(client)
|
|
194
194
|
const r = await a.chat(
|
|
@@ -200,7 +200,7 @@ describeIf('LLM 스모크 — 실제 모델 tool 선택 검증', () => {
|
|
|
200
200
|
dumpUsages(r.toolUsages)
|
|
201
201
|
// 핵심: AI 가 atmosphere primer 를 활용 (LLM 이 atmosphere 모르더라도 우리
|
|
202
202
|
// primer 가이드를 호출해야 일관된 결과 — 시스템 프롬프트의 의도)
|
|
203
|
-
expect(names).toContain('
|
|
203
|
+
expect(names).toContain('modifyScene')
|
|
204
204
|
},
|
|
205
205
|
TIMEOUT_MS
|
|
206
206
|
)
|