@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
|
@@ -16,8 +16,6 @@ import {
|
|
|
16
16
|
} from '@things-factory/board-import'
|
|
17
17
|
import type {
|
|
18
18
|
BoardAIAssistant,
|
|
19
|
-
BoardEditOp,
|
|
20
|
-
BoardEditPatch,
|
|
21
19
|
ChatOptions,
|
|
22
20
|
ChatResponse,
|
|
23
21
|
ComponentSchema,
|
|
@@ -49,7 +47,7 @@ import {
|
|
|
49
47
|
type ToolGate as NeutralToolGate,
|
|
50
48
|
type AgenticLoopResult
|
|
51
49
|
} from '@things-factory/ai-client-base'
|
|
52
|
-
import type {
|
|
50
|
+
import type { SceneActionOp, SceneEditOp, SceneEditPatch } from '@operato/scene-ops'
|
|
53
51
|
|
|
54
52
|
/** 이 대화면의 도구가 알아야 하는 것 — 루프는 나르기만 한다. */
|
|
55
53
|
type BoardToolContext = { board: BoardModel | undefined; selectedRefids: number[] }
|
|
@@ -246,7 +244,7 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
|
|
|
246
244
|
return undefined
|
|
247
245
|
}
|
|
248
246
|
|
|
249
|
-
const loopResult = await runAgenticLoop<BoardToolContext,
|
|
247
|
+
const loopResult = await runAgenticLoop<BoardToolContext, SceneActionOp>({
|
|
250
248
|
initialConversation: conversation,
|
|
251
249
|
tools,
|
|
252
250
|
options: {
|
|
@@ -266,7 +264,7 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
|
|
|
266
264
|
executeReadTool: (tc, ctx) => wrappedExecuteReadTool(tc, ctx.board, ctx.selectedRefids),
|
|
267
265
|
/* 문서에서 부품을 꺼내는 일은 **여기서** 한다 — 루프는 board 의 모양을 모른다. */
|
|
268
266
|
validateWriteToolCall: (tc, ctx) => validateWriteToolCall(tc, (ctx.board?.components ?? []) as any[]),
|
|
269
|
-
toolCallToOp:
|
|
267
|
+
toolCallToOp: toolCallToSceneActionOp,
|
|
270
268
|
summarizeToolResult
|
|
271
269
|
},
|
|
272
270
|
context: { board: currentBoard, selectedRefids }
|
|
@@ -277,11 +275,11 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
|
|
|
277
275
|
const lastText = loopResult.lastText
|
|
278
276
|
const stopReason = loopResult.stopReason
|
|
279
277
|
|
|
280
|
-
// accumulated write calls →
|
|
278
|
+
// accumulated write calls → SceneEditPatch (tool 이 array 반환 가능 — flat)
|
|
281
279
|
const components = (currentBoard?.components ?? []) as any[]
|
|
282
280
|
const ops = accumulatedWriteCalls
|
|
283
281
|
.flatMap(call => {
|
|
284
|
-
const r =
|
|
282
|
+
const r = toolCallToSceneEditOp(call, components, gate)
|
|
285
283
|
if (r === null) return []
|
|
286
284
|
return Array.isArray(r) ? r : [r]
|
|
287
285
|
})
|
|
@@ -291,7 +289,7 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
|
|
|
291
289
|
const baseReply = lastText || (ops.length > 0 ? '변경했습니다.' : '')
|
|
292
290
|
const reply = baseReply + formatAbortNotice(loopResult.abortReason)
|
|
293
291
|
|
|
294
|
-
let patch:
|
|
292
|
+
let patch: SceneEditPatch | undefined
|
|
295
293
|
if (ops.length > 0) {
|
|
296
294
|
patch = {
|
|
297
295
|
ops,
|
|
@@ -390,7 +388,7 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
|
|
|
390
388
|
아무것도 안 바뀌었기 때문.
|
|
391
389
|
|
|
392
390
|
룰:
|
|
393
|
-
1. 변경 요청 → write tool 호출 필수 (
|
|
391
|
+
1. 변경 요청 → write tool 호출 필수 (modifyScene / setFill / setStroke / setMaterial /
|
|
394
392
|
setShadow / setOpacity / setVisible / setFont / setText / setMaterial / addComponent /
|
|
395
393
|
removeComponentByRefid / modifyComponentByRefid / align/distribute/group/zorder 등)
|
|
396
394
|
2. 호출 + 짧은 reply 텍스트 동시 작성 OK — reply 는 *결과* 를 한국어로 한두 문장 요약
|
|
@@ -414,22 +412,22 @@ root-level properties separate from any child:
|
|
|
414
412
|
- 3D 환경: \`sky\` / \`skyColor\` / \`exposure\` / \`hemi*\` / \`dirLight*\` / \`dirShadow*\` / \`camera*\` / \`autoRotate\`
|
|
415
413
|
- 식별: \`id\`, \`refid\`, \`type\` ("model-layer")
|
|
416
414
|
⚠ 보드 \*\*name\*\* 은 root attribute 가 아님 — Board 엔티티의 DB 컬럼이지 scene
|
|
417
|
-
MODEL 의 필드가 아님 (model-layer 가 무시). 보드 라벨 변경은
|
|
415
|
+
MODEL 의 필드가 아님 (model-layer 가 무시). 보드 라벨 변경은 modifyScene 영역 X.
|
|
418
416
|
• Children: an array of components inside the board (rect / circle / text /
|
|
419
417
|
gauge / etc.). 컴포넌트들은 \`name\` (user label) 를 가질 수 있음 — 이는 child
|
|
420
418
|
component 의 속성이지 board root 의 속성이 아님.
|
|
421
419
|
|
|
422
420
|
Rules:
|
|
423
421
|
- To change a board-level attribute (background, board size, 3D atmosphere)
|
|
424
|
-
→ call \`
|
|
422
|
+
→ call \`modifyScene({ patch: { fillStyle: ..., width: ..., sky: ..., exposure: ..., dirLight*: ..., hemi*: ... } })\`.
|
|
425
423
|
NOTE: 보드 라벨/제목 ("보드 이름") 은 scene model 이 아니라 Board *엔티티* 의 컬럼.
|
|
426
|
-
things-scene 의 model-layer 는 \`name\` 을 인식하지 않음.
|
|
424
|
+
things-scene 의 model-layer 는 \`name\` 을 인식하지 않음. modifyScene 의 patch 에
|
|
427
425
|
\`name\` 을 넣지 말 것 — 죽은 필드로 박힐 뿐 효과 없음.
|
|
428
426
|
- To change a CHILD COMPONENT's attribute → call \`modifyComponentByRefid\`.
|
|
429
|
-
- DO NOT use
|
|
427
|
+
- DO NOT use modifyScene to change a child. DO NOT use modifyComponentByRefid
|
|
430
428
|
to change board background.
|
|
431
429
|
- "보드 배경색", "board background", "테두리/캔버스 크기" 같은 요청은 root
|
|
432
|
-
attribute 변경 → \`
|
|
430
|
+
attribute 변경 → \`modifyScene\`.
|
|
433
431
|
|
|
434
432
|
==== COMPONENT TREE (NESTING) ====
|
|
435
433
|
컴포넌트는 *재귀 구조*. group / container 같은 부모 컴포넌트는 자기 \`components\`
|
|
@@ -495,8 +493,8 @@ to that wording in your reply. Do not echo refid back to the user.
|
|
|
495
493
|
|
|
496
494
|
==== AVAILABLE TOOLS ====
|
|
497
495
|
Board-level (root attributes):
|
|
498
|
-
-
|
|
499
|
-
- replaceBoard: wholesale board reset (heavy — prefer
|
|
496
|
+
- modifyScene: change board's own fillStyle / width / height / fitMode / translate / scale AND 3D atmosphere (sky / skyColor / exposure / hemiSkyColor / hemiGroundColor / hemiIntensity / dirLightEnabled / dirLightColor / dirLightIntensity / dirLightFollowCamera / dirLightAzimuth / dirLightElevation / dirShadow* / camera* / autoRotate / initialCameraView). ⚠ "name" 금지 — scene MODEL 이 아니라 Board 엔티티 컬럼.
|
|
497
|
+
- replaceBoard: wholesale board reset (heavy — prefer modifyScene for most changes)
|
|
500
498
|
Child components — data:
|
|
501
499
|
- addComponent: add a new component (parallel calls for multiple adds)
|
|
502
500
|
- removeComponentByRefid: remove a child by refid
|
|
@@ -553,10 +551,10 @@ Modeller actions (ephemeral — does NOT modify the board model, no undo, no sav
|
|
|
553
551
|
- fitToView: reset view so the board fits the canvas (mode: fit/ratio/width/height)
|
|
554
552
|
- setSceneMode: switch modeller between 'edit' and 'view' modes
|
|
555
553
|
Knowledge:
|
|
556
|
-
- getAtmosphereGuide: 3D atmosphere synthesis primer — parameter physics, ranges, color theory, archetype recipes (time-of-day / indoor / surreal). Call BEFORE applying atmospheric/styling
|
|
554
|
+
- getAtmosphereGuide: 3D atmosphere synthesis primer — parameter physics, ranges, color theory, archetype recipes (time-of-day / indoor / surreal). Call BEFORE applying atmospheric/styling modifyScene for any natural-language mood request.
|
|
557
555
|
|
|
558
556
|
==== 3D ATMOSPHERE SYNTHESIS ====
|
|
559
|
-
3D scene atmosphere = combination of (1) sky preset / color (2) exposure (3) hemisphere ambient (4) directional sun/key (5) floor material. ALL of these are root-level board attributes — applied via a SINGLE
|
|
557
|
+
3D scene atmosphere = combination of (1) sky preset / color (2) exposure (3) hemisphere ambient (4) directional sun/key (5) floor material. ALL of these are root-level board attributes — applied via a SINGLE modifyScene call.
|
|
560
558
|
|
|
561
559
|
For ANY natural-language atmospheric request — "공장 분위기로", "심해 컬러", "황혼", "사이버펑크", "안개 자욱한 새벽", "1980 우주정거장", "따뜻하게", "차갑고 극적으로" — follow this workflow:
|
|
562
560
|
|
|
@@ -564,11 +562,11 @@ For ANY natural-language atmospheric request — "공장 분위기로", "심해
|
|
|
564
562
|
2. From the guide's "archetypes" section, pick the closest base recipe (time-of-day / indoor / surreal). Interpolate if user combines multiple cues.
|
|
565
563
|
3. Apply user's nuance via the guide's "modifiers" (warm/cool, drama, saturation, contrast).
|
|
566
564
|
4. Sanity-check: avoid washout (all params maxed) or mud (all minned). Confirm hemi sky/ground colors and dir color are consistent with the mood.
|
|
567
|
-
5. Apply ALL keys in ONE
|
|
565
|
+
5. Apply ALL keys in ONE modifyScene({patch}) call (sky, skyColor?, exposure, hemiIntensity, hemiSkyColor, hemiGroundColor, dirLightEnabled, dirLightColor, dirLightIntensity, dirLightFollowCamera=false, dirLightAzimuth, dirLightElevation, fillStyle?). ONE patch — not multiple modifyScene calls.
|
|
568
566
|
6. In the reply, briefly state which archetype + modifier you composed.
|
|
569
567
|
|
|
570
568
|
Avoid:
|
|
571
|
-
- Calling
|
|
569
|
+
- Calling modifyScene with only "fillStyle" for atmospheric requests — that only changes 2D background and floor tint, NOT the 3D scene mood.
|
|
572
570
|
- Mixing a sky preset with explicit skyColor (preset wins, skyColor ignored).
|
|
573
571
|
- Setting dirLightAzimuth/Elevation while leaving dirLightFollowCamera=true (no effect — set follow=false first).
|
|
574
572
|
- Using only canned presets when the user describes a custom mood — the guide enables synthesis beyond presets.
|
|
@@ -761,7 +759,7 @@ export function buildBoardEditTools(knownTypes: string[], exposure: ToolExposure
|
|
|
761
759
|
}
|
|
762
760
|
},
|
|
763
761
|
{
|
|
764
|
-
name: '
|
|
762
|
+
name: 'modifyScene',
|
|
765
763
|
description:
|
|
766
764
|
"Modify the BOARD's OWN root-level properties — the board itself is the topmost parent (model-layer) and has its own attributes (NOT child components). Valid root keys (things-scene 의 model-layer 가 실제 인식하는 것만):\n • Layout/Geometry: `width`, `height`, `fitMode` ('ratio'|'both'|'width'|'height'|'center'|'none'), `translate` ({x,y}), `scale` ({x,y})\n • 2D 배경: `fillStyle` (string color or object)\n • 3D Sky: `sky` (preset 이름), `skyColor` (preset 미지정 시 단색)\n • 3D Tone: `exposure` (toneMappingExposure, 기본 1.0~1.2)\n • 3D Hemisphere light: `hemiSkyColor`, `hemiGroundColor`, `hemiIntensity` (기본 4.0)\n • 3D Directional light: `dirLightEnabled` (bool), `dirLightColor`, `dirLightIntensity` (기본 2.0), `dirLightFollowCamera` (bool, 기본 true), `dirLightAzimuth` (deg, follow=false 일 때만), `dirLightElevation` (deg, follow=false 일 때만)\n • 3D Shadow: `dirShadowEnabled`, `dirShadowMapSize` (기본 2048), `dirShadowBias`\n • 3D Camera: `cameraX`, `cameraY`, `cameraZ`, `cameraAutoPlay` ('orbit' 등), `autoRotate` (bool), `initialCameraView`\n\n⚠ 절대 사용 금지: `name` — Board *엔티티* 의 컬럼이지 scene MODEL 의 필드가 아님. things-scene 이 인식 안 함. 보드 라벨 변경은 GraphQL boardPatch 의 영역.\n\nDo NOT use for child component edits (use modifyComponentByRefid for those). Do NOT pass a `components` array here — children are managed by add/remove/modify ops separately.",
|
|
767
765
|
parameters: {
|
|
@@ -780,7 +778,7 @@ export function buildBoardEditTools(knownTypes: string[], exposure: ToolExposure
|
|
|
780
778
|
{
|
|
781
779
|
name: 'replaceBoard',
|
|
782
780
|
description:
|
|
783
|
-
'Replace the entire board (use ONLY for fresh start or wholesale redesign — heavy operation that resets undo history). For just changing background or size, prefer
|
|
781
|
+
'Replace the entire board (use ONLY for fresh start or wholesale redesign — heavy operation that resets undo history). For just changing background or size, prefer modifyScene. Provide width/height and the full components array.',
|
|
784
782
|
parameters: {
|
|
785
783
|
type: 'object',
|
|
786
784
|
properties: {
|
|
@@ -856,7 +854,7 @@ export function buildBoardEditTools(knownTypes: string[], exposure: ToolExposure
|
|
|
856
854
|
{
|
|
857
855
|
name: 'getAtmosphereGuide',
|
|
858
856
|
description:
|
|
859
|
-
"Get a comprehensive 3D atmosphere synthesis guide — parameter physics (sky / exposure / hemisphere / directional / shadow / floor / camera), sane ranges, color theory, time-of-day archetypes, and indoor/surreal mood recipes. Call this BEFORE applying atmospheric/styling changes via
|
|
857
|
+
"Get a comprehensive 3D atmosphere synthesis guide — parameter physics (sky / exposure / hemisphere / directional / shadow / floor / camera), sane ranges, color theory, time-of-day archetypes, and indoor/surreal mood recipes. Call this BEFORE applying atmospheric/styling changes via modifyScene whenever the user describes a mood / scene / time-of-day in natural language (e.g. '심해 컬러', '황혼', '공장 분위기', '사이버펑크', '안개 자욱한 새벽'). The guide enables you to synthesize concrete parameter values yourself, beyond canned presets.",
|
|
860
858
|
parameters: { type: 'object', properties: {} }
|
|
861
859
|
},
|
|
862
860
|
{
|
|
@@ -1215,7 +1213,7 @@ const WRITE_TOOL_NAMES = new Set([
|
|
|
1215
1213
|
'setShadowProperties',
|
|
1216
1214
|
'clearStyle',
|
|
1217
1215
|
'copyStyle',
|
|
1218
|
-
'
|
|
1216
|
+
'modifyScene',
|
|
1219
1217
|
'replaceBoard',
|
|
1220
1218
|
// Phase 2 — model 차원 scene 조작 (좌표/구조/zorder)
|
|
1221
1219
|
'alignComponents',
|
|
@@ -1232,7 +1230,7 @@ const WRITE_TOOL_NAMES = new Set([
|
|
|
1232
1230
|
])
|
|
1233
1231
|
/**
|
|
1234
1232
|
* C-1 — ephemeral scene 조작 (selection / view / mode). model 변경 X, undo 영향 X.
|
|
1235
|
-
* 별도 ChatResponse.actions 채널로 전달 → 호스트가
|
|
1233
|
+
* 별도 ChatResponse.actions 채널로 전달 → 호스트가 scene-action-execute 로 적용.
|
|
1236
1234
|
*/
|
|
1237
1235
|
const ACTION_TOOL_NAMES = new Set([
|
|
1238
1236
|
'selectComponents',
|
|
@@ -1377,14 +1375,14 @@ export function executeReadTool(
|
|
|
1377
1375
|
*
|
|
1378
1376
|
* 의도 — preset 한 단어로 끝나지 않는 사용자 요청 ("심해", "황혼", "1980 우주정거장",
|
|
1379
1377
|
* "안개 새벽") 에 AI 가 구체적 파라미터 값을 직접 합성하도록 물리/색채/미학 지식
|
|
1380
|
-
* 동시 제공. AI 는 이 가이드를 받은 뒤
|
|
1378
|
+
* 동시 제공. AI 는 이 가이드를 받은 뒤 modifyScene 로 한 번에 적용.
|
|
1381
1379
|
*
|
|
1382
|
-
* things-scene 의 board model 에 들어가는 root level 키만 다룸 (
|
|
1380
|
+
* things-scene 의 board model 에 들어가는 root level 키만 다룸 (modifyScene 로
|
|
1383
1381
|
* 적용 가능한 것). camera position 같은 ephemeral 한 것은 별도 영역.
|
|
1384
1382
|
*/
|
|
1385
1383
|
const ATMOSPHERE_GUIDE = {
|
|
1386
1384
|
overview:
|
|
1387
|
-
'3D 보드 분위기는 (1) sky preset 또는 단색 (2) tone mapping exposure (3) hemisphere ambient (4) directional sun/key (5) floor 의 조합. 모두 board root model 의 속성이라
|
|
1385
|
+
'3D 보드 분위기는 (1) sky preset 또는 단색 (2) tone mapping exposure (3) hemisphere ambient (4) directional sun/key (5) floor 의 조합. 모두 board root model 의 속성이라 modifyScene 단일 op 로 한 번에 적용. 자연어 요청은 archetype 레시피를 base 로 잡고 modifier 로 fine-tune.',
|
|
1388
1386
|
|
|
1389
1387
|
parameters: {
|
|
1390
1388
|
sky: {
|
|
@@ -1724,7 +1722,7 @@ const ATMOSPHERE_GUIDE = {
|
|
|
1724
1722
|
'2. archetypes 에서 가장 가까운 base 레시피 선택.',
|
|
1725
1723
|
'3. modifiers 로 사용자 요청 뉘앙스 fine-tune (warm/cool/drama/...).',
|
|
1726
1724
|
'4. sanity check — exposure × hemi × dir 어느 한쪽 극단 아닌지, hemi sky/ground 색 모순 없는지.',
|
|
1727
|
-
"5.
|
|
1725
|
+
"5. modifyScene({ patch: { ...combined keys... } }) 한 번에 적용. 사용자에게 어떤 archetype + modifier 조합으로 갔는지 한 줄로 설명."
|
|
1728
1726
|
],
|
|
1729
1727
|
|
|
1730
1728
|
pitfalls: [
|
|
@@ -1746,7 +1744,7 @@ const ATMOSPHERE_GUIDE = {
|
|
|
1746
1744
|
* 사용 흐름:
|
|
1747
1745
|
* 1. 사용자 의도 (산업 / 금융 / 의료 / SF / ...) 식별
|
|
1748
1746
|
* 2. recommendations 에서 매칭 후보 1~2개 추림
|
|
1749
|
-
* 3.
|
|
1747
|
+
* 3. modifyScene 로 board.fillStyle 을 palette.base 로 설정
|
|
1750
1748
|
* 4. addPrefab / addComponent 시 palette 의 surface/accent/text 사용
|
|
1751
1749
|
* 5. 일관된 룩 → 한 번 정한 팔레트 안에서만 색 선택
|
|
1752
1750
|
*
|
|
@@ -1877,7 +1875,7 @@ const COLOR_PALETTES = {
|
|
|
1877
1875
|
workflow: [
|
|
1878
1876
|
'1. 사용자 도메인 / 분위기 키워드 식별 — 산업/금융/관제/의료/SF/프레젠테이션',
|
|
1879
1877
|
'2. recommendations 에서 매칭 후보 추림. 사용자 모호하면 가장 흔한 industrial-steel 디폴트.',
|
|
1880
|
-
"3.
|
|
1878
|
+
"3. modifyScene({ patch: { fillStyle: palette.base } }) 으로 보드 배경 set.",
|
|
1881
1879
|
'4. 이후 모든 add / addPrefab 호출 시 palette.surface / accent / text / border 사용. 자유 색 X.',
|
|
1882
1880
|
'5. status 표시는 success / warning / danger / info 4종에서만 — 자체 hex 만들지 마라.'
|
|
1883
1881
|
],
|
|
@@ -2109,13 +2107,13 @@ function buildExternalCategoryDefinitions(
|
|
|
2109
2107
|
}
|
|
2110
2108
|
|
|
2111
2109
|
/**
|
|
2112
|
-
* Tool call →
|
|
2110
|
+
* Tool call → SceneActionOp 변환 (C-1 ephemeral scene 조작).
|
|
2113
2111
|
* 잘못된 인자는 null — agentic loop 가 Unknown tool 비슷하게 처리.
|
|
2114
2112
|
*/
|
|
2115
|
-
export function
|
|
2113
|
+
export function toolCallToSceneActionOp(
|
|
2116
2114
|
tc: AIToolCall,
|
|
2117
2115
|
gate: ToolGate = buildToolGate()
|
|
2118
|
-
): import('
|
|
2116
|
+
): import('@operato/scene-ops').SceneActionOp | null {
|
|
2119
2117
|
const args = tc.arguments ?? {}
|
|
2120
2118
|
/* 씬 조작 액션은 전부 코어(보드) 능력 — 노출 안 된 대화에서는 만들지 않는다.
|
|
2121
2119
|
* loop 는 gate.isAction 으로 이미 걸러내지만, 다른 호출부의 오용도 막는다. */
|
|
@@ -2147,7 +2145,7 @@ export function toolCallToBoardActionOp(
|
|
|
2147
2145
|
}
|
|
2148
2146
|
}
|
|
2149
2147
|
|
|
2150
|
-
// ── Tool call →
|
|
2148
|
+
// ── Tool call → SceneEditOp 변환 ───────────────────────────────────
|
|
2151
2149
|
|
|
2152
2150
|
/**
|
|
2153
2151
|
* AIToolChatResult → ChatResponse.
|
|
@@ -2160,14 +2158,14 @@ export function toolCallToBoardActionOp(
|
|
|
2160
2158
|
export function toolResultToChatResponse(result: AIToolChatResult): ChatResponse {
|
|
2161
2159
|
const ops = result.toolCalls
|
|
2162
2160
|
.flatMap(call => {
|
|
2163
|
-
const r =
|
|
2161
|
+
const r = toolCallToSceneEditOp(call)
|
|
2164
2162
|
if (r === null) return []
|
|
2165
2163
|
return Array.isArray(r) ? r : [r]
|
|
2166
2164
|
})
|
|
2167
2165
|
|
|
2168
2166
|
const reply = result.text || (ops.length > 0 ? '변경했습니다.' : '')
|
|
2169
2167
|
|
|
2170
|
-
let patch:
|
|
2168
|
+
let patch: SceneEditPatch | undefined
|
|
2171
2169
|
if (ops.length > 0) {
|
|
2172
2170
|
patch = {
|
|
2173
2171
|
ops,
|
|
@@ -2179,11 +2177,11 @@ export function toolResultToChatResponse(result: AIToolChatResult): ChatResponse
|
|
|
2179
2177
|
return { reply, patch, followUp: undefined }
|
|
2180
2178
|
}
|
|
2181
2179
|
|
|
2182
|
-
export function
|
|
2180
|
+
export function toolCallToSceneEditOp(
|
|
2183
2181
|
tc: AIToolCall,
|
|
2184
2182
|
components: ReadonlyArray<any> = [],
|
|
2185
2183
|
gate: ToolGate = buildToolGate()
|
|
2186
|
-
):
|
|
2184
|
+
): SceneEditOp | SceneEditOp[] | null {
|
|
2187
2185
|
const args = tc.arguments ?? {}
|
|
2188
2186
|
/* 노출되지 않은 코어(보드 편집) 도구는 op 를 만들지 않는다 — 정의를 숨겼어도 호출은 올 수 있다. */
|
|
2189
2187
|
if (!gate.coreOn && !gate.spec(tc.name)) return null
|
|
@@ -2194,7 +2192,7 @@ export function toolCallToBoardEditOp(
|
|
|
2194
2192
|
return dispatchStylingTool(tc.name, args, components)
|
|
2195
2193
|
}
|
|
2196
2194
|
// tool-registry plugin point — 외부 등록 'write' 카테고리 dispatch.
|
|
2197
|
-
// builder 결과는
|
|
2195
|
+
// builder 결과는 SceneEditOp[] | { ops, errors? } | null 셋 중 하나.
|
|
2198
2196
|
// (read/external 카테고리는 별도 dispatch path — agentic loop 의 executeReadTool 이 직접 처리.)
|
|
2199
2197
|
const registrySpec = gate.spec(tc.name)
|
|
2200
2198
|
if (registrySpec && registrySpec.kind === 'write') {
|
|
@@ -2220,15 +2218,15 @@ export function toolCallToBoardEditOp(
|
|
|
2220
2218
|
// 모든 styling tool (setFill/setStroke/setText/setFont/setShadow/setOpacity/
|
|
2221
2219
|
// setVisible/setMaterial/setShadowProperties/clearStyle/copyStyle) 은 registry
|
|
2222
2220
|
// 가 처리 — 상단 isStylingTool 분기 참조
|
|
2223
|
-
case '
|
|
2221
|
+
case 'modifyScene': {
|
|
2224
2222
|
if (!args.patch || typeof args.patch !== 'object' || Array.isArray(args.patch)) return null
|
|
2225
|
-
return { op: '
|
|
2223
|
+
return { op: 'modifyScene', patch: args.patch as Partial<BoardModel> }
|
|
2226
2224
|
}
|
|
2227
2225
|
case 'replaceBoard': {
|
|
2228
2226
|
if (!Array.isArray(args.components)) return null
|
|
2229
2227
|
return {
|
|
2230
2228
|
op: 'replace',
|
|
2231
|
-
|
|
2229
|
+
model: {
|
|
2232
2230
|
width: typeof args.width === 'number' ? args.width : undefined,
|
|
2233
2231
|
height: typeof args.height === 'number' ? args.height : undefined,
|
|
2234
2232
|
fillStyle: typeof args.fillStyle === 'string' ? args.fillStyle : undefined,
|
|
@@ -190,7 +190,7 @@ class BoardAIChatOutput {
|
|
|
190
190
|
@Field({ description: 'Conversational reply.' })
|
|
191
191
|
reply: string
|
|
192
192
|
|
|
193
|
-
@Field(() => GraphQLJSON, { nullable: true, description: '
|
|
193
|
+
@Field(() => GraphQLJSON, { nullable: true, description: 'SceneEditPatch.' })
|
|
194
194
|
patch?: any
|
|
195
195
|
|
|
196
196
|
@Field({ nullable: true, description: 'Clarifying question when input is ambiguous.' })
|
|
@@ -221,7 +221,7 @@ class BoardAIChatOutput {
|
|
|
221
221
|
@Field(() => GraphQLJSON, {
|
|
222
222
|
nullable: true,
|
|
223
223
|
description:
|
|
224
|
-
'Ephemeral scene actions (selection/view/mode) — sequence of
|
|
224
|
+
'Ephemeral scene actions (selection/view/mode) — sequence of SceneActionOp. Applied via scene-action-execute event on the host. Distinct from `patch` which carries persistent model changes.'
|
|
225
225
|
})
|
|
226
226
|
actions?: any
|
|
227
227
|
|
package/server/service/index.ts
CHANGED
|
@@ -16,7 +16,6 @@ import { entities as PatchEntryEntities } from './patch-entry/index.js'
|
|
|
16
16
|
|
|
17
17
|
export * from './types.js'
|
|
18
18
|
export * from './assistant.js'
|
|
19
|
-
export * from './apply-patch.js'
|
|
20
19
|
export * from './prefab-registry.js'
|
|
21
20
|
export * from './mention-resolver.js'
|
|
22
21
|
export * from './catalog-resolver.js'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* BoardPatchSubscription (Phase Board-AI C1 — op-브로드캐스트 spine).
|
|
3
3
|
*
|
|
4
4
|
* PatchEntry(AI 또는 user-direct 편집) 가 생성될 때마다 같은 board 구독자에게 방송 →
|
|
5
|
-
* 실시간 공동편집의 척추. 클라이언트(C2)가 구독해 원격 patch 를
|
|
5
|
+
* 실시간 공동편집의 척추. 클라이언트(C2)가 구독해 원격 patch 를 applyScenePatch 로 적용.
|
|
6
6
|
*
|
|
7
7
|
* 전송/패턴은 기존 board-service/board-subscription.ts 복제 (PubSub + graphql-ws, domain 격리).
|
|
8
8
|
* 순수 로직(토픽/필터)은 board-patch-broadcast.ts (테스트 가능). 여기엔 pubsub 의존만.
|
|
@@ -48,7 +48,7 @@ export class PatchEntry {
|
|
|
48
48
|
@Field({ description: "'ai' | 'user-direct' | 'import'" })
|
|
49
49
|
source!: string
|
|
50
50
|
|
|
51
|
-
/**
|
|
51
|
+
/** SceneEditOp[] JSON-stringified — column 으로는 long text. */
|
|
52
52
|
@Column({
|
|
53
53
|
type:
|
|
54
54
|
DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
|
@@ -63,7 +63,7 @@ export class PatchEntry {
|
|
|
63
63
|
ops!: string
|
|
64
64
|
|
|
65
65
|
/** GraphQL 노출용 — JSON 객체로 변환. */
|
|
66
|
-
@Field(type => GraphQLJSON, { description: '
|
|
66
|
+
@Field(type => GraphQLJSON, { description: 'SceneEditOp[] (parsed JSON array).' })
|
|
67
67
|
get opsJson(): any {
|
|
68
68
|
try {
|
|
69
69
|
return JSON.parse(this.ops || '[]')
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* preset 적용 전 / 데이터 바인딩 적용 전 깨끗한 상태로 돌리기 위한 helper.
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
6
|
+
import type { SceneEditOp } from '@operato/scene-ops'
|
|
7
7
|
|
|
8
8
|
export interface ClearStyleInput {
|
|
9
9
|
refids: number[]
|
|
@@ -11,7 +11,7 @@ export interface ClearStyleInput {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface OpsResult {
|
|
14
|
-
ops:
|
|
14
|
+
ops: SceneEditOp[]
|
|
15
15
|
errors: string[]
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 입력 — sourceRefid 의 실제 styling 값을 미리 알 필요 없음. server 가 components
|
|
10
10
|
* 에서 source 찾아 styling 추출 후 target 들에 patch 생성.
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { SceneEditOp } from '@operato/scene-ops'
|
|
13
13
|
|
|
14
14
|
export interface CopyStyleInput {
|
|
15
15
|
sourceRefid: number
|
|
@@ -18,7 +18,7 @@ export interface CopyStyleInput {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface OpsResult {
|
|
21
|
-
ops:
|
|
21
|
+
ops: SceneEditOp[]
|
|
22
22
|
errors: string[]
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -52,7 +52,7 @@ const DEFAULT_ASPECTS: Array<keyof typeof ASPECT_FIELDS> = [
|
|
|
52
52
|
]
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* input + components 배열 →
|
|
55
|
+
* input + components 배열 → SceneEditOp[].
|
|
56
56
|
*
|
|
57
57
|
* components 는 board 의 모든 컴포넌트 (caller 가 전달). source 찾아 styling 추출.
|
|
58
58
|
*/
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* setOpacity: state.alpha (컴포넌트 전체 알파, 0..1)
|
|
6
6
|
* setVisible: state.hidden 토글 (true → 숨김)
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
8
|
+
import type { SceneEditOp } from '@operato/scene-ops'
|
|
9
9
|
import { normalizeColor, validateColor } from './color-utils'
|
|
10
10
|
|
|
11
11
|
export interface SetShadowInput {
|
|
@@ -34,7 +34,7 @@ export interface SetVisibleInput {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export interface OpsResult {
|
|
37
|
-
ops:
|
|
37
|
+
ops: SceneEditOp[]
|
|
38
38
|
errors: string[]
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture
|
|
18
18
|
* 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제는 자체 해결.
|
|
19
19
|
*/
|
|
20
|
-
import type {
|
|
20
|
+
import type { SceneEditOp } from '@operato/scene-ops'
|
|
21
21
|
import { parseColor, normalizeColor, validateColor } from './color-utils'
|
|
22
22
|
|
|
23
23
|
export interface SetFillInput {
|
|
@@ -59,12 +59,12 @@ export interface SetFillInput {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export interface SetFillResult {
|
|
62
|
-
ops:
|
|
62
|
+
ops: SceneEditOp[]
|
|
63
63
|
errors: string[] // refid 별 에러 (성공한 것은 op 만 발사)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* setFill 입력 검증 +
|
|
67
|
+
* setFill 입력 검증 + SceneEditOp[] 생성.
|
|
68
68
|
*
|
|
69
69
|
* 기본 정책:
|
|
70
70
|
* - refids 비어있거나 mode 도 색도 없으면 에러
|
|
@@ -73,7 +73,7 @@ export interface SetFillResult {
|
|
|
73
73
|
*/
|
|
74
74
|
export function buildSetFillOps(input: SetFillInput): SetFillResult {
|
|
75
75
|
const errors: string[] = []
|
|
76
|
-
const ops:
|
|
76
|
+
const ops: SceneEditOp[] = []
|
|
77
77
|
|
|
78
78
|
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
79
79
|
return { ops: [], errors: ['refids must be a non-empty array of numbers'] }
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Material3D (`things-scene/src/threed/material-3d.ts`) 와 1:1 매핑.
|
|
5
5
|
* preset 만 줘도 변경 가능 (resolveMaterial3d 가 default 위 override 적용).
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type { SceneEditOp } from '@operato/scene-ops'
|
|
8
8
|
import { normalizeColor, validateColor } from './color-utils'
|
|
9
9
|
|
|
10
10
|
const MATERIAL_PRESETS = ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom'] as const
|
|
@@ -29,7 +29,7 @@ export interface SetShadowPropertiesInput {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface OpsResult {
|
|
32
|
-
ops:
|
|
32
|
+
ops: SceneEditOp[]
|
|
33
33
|
errors: string[]
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
* STYLING_TOOLS 는 초기엔 비어있고, 한 tool 씩 점진 이전한다. 각 단계마다
|
|
13
13
|
* 기존 hardcoded 정의/dispatch 와 byte-단위 동등성을 단위 테스트가 잠금.
|
|
14
14
|
*
|
|
15
|
-
* 비-styling write tool (addComponent /
|
|
15
|
+
* 비-styling write tool (addComponent / modifyScene / alignComponents 등) 은
|
|
16
16
|
* 본 registry 의 영역이 아니다 — 의미·구조가 styling 패턴과 달라 별도 dispatch.
|
|
17
17
|
*/
|
|
18
|
-
import type {
|
|
18
|
+
import type { SceneEditOp } from '@operato/scene-ops'
|
|
19
19
|
import { buildSetFillOps } from './fill-tools'
|
|
20
20
|
import { buildSetStrokeOps } from './stroke-tools'
|
|
21
21
|
import { buildSetTextOps, buildSetFontOps } from './text-tools'
|
|
@@ -25,7 +25,7 @@ import { buildClearStyleOps } from './clear-tools'
|
|
|
25
25
|
import { buildCopyStyleOps } from './copy-tools'
|
|
26
26
|
|
|
27
27
|
export interface OpsResult {
|
|
28
|
-
ops:
|
|
28
|
+
ops: SceneEditOp[]
|
|
29
29
|
errors: string[]
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -393,12 +393,12 @@ export function buildStylingToolDefinitions(
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
/**
|
|
396
|
-
* AIToolCall name 에 매칭되는 styling tool 의 builder 호출 →
|
|
396
|
+
* AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → SceneEditOp[] | null.
|
|
397
397
|
*
|
|
398
398
|
* 반환:
|
|
399
399
|
* - 매칭 spec 없음 → null (caller 가 다른 카테고리 dispatch 시도)
|
|
400
400
|
* - 빈 ops (validation 실패 등) → null (assistant.ts 의 기존 동작과 동일)
|
|
401
|
-
* - 정상 →
|
|
401
|
+
* - 정상 → SceneEditOp[]
|
|
402
402
|
*
|
|
403
403
|
* components 인자는 copyStyle 같은 source-lookup 필요 tool 에만 의미 있음.
|
|
404
404
|
*/
|
|
@@ -407,7 +407,7 @@ export function dispatchStylingTool(
|
|
|
407
407
|
args: any,
|
|
408
408
|
components: ReadonlyArray<any> = [],
|
|
409
409
|
registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS
|
|
410
|
-
):
|
|
410
|
+
): SceneEditOp[] | null {
|
|
411
411
|
const spec = registry.find(s => s.name === name)
|
|
412
412
|
if (!spec) return null
|
|
413
413
|
const result = spec.builder(args, components)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* stroke-tools — buildSetStrokeOps 입력 →
|
|
2
|
+
* stroke-tools — buildSetStrokeOps 입력 → SceneEditOp[] 변환 검증.
|
|
3
3
|
*
|
|
4
4
|
* things-scene canonical fields (모두 top-level): strokeStyle (string color), lineWidth,
|
|
5
5
|
* lineDash (preset 이름 string), lineCap, lineJoin. tool 파라미터 이름이 engine 필드와
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 입력 단순화 — AI 친화적 alias 도 받아 things-scene preset 으로 매핑:
|
|
15
15
|
* AI 'dashed' → 'dash', 'dotted' → 'square-dot', 'dashdot' → 'dash-dot'.
|
|
16
16
|
*/
|
|
17
|
-
import type {
|
|
17
|
+
import type { SceneEditOp } from '@operato/scene-ops'
|
|
18
18
|
import { normalizeColor, validateColor } from './color-utils'
|
|
19
19
|
|
|
20
20
|
export interface SetStrokeInput {
|
|
@@ -44,7 +44,7 @@ export interface SetStrokeInput {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export interface SetStrokeResult {
|
|
47
|
-
ops:
|
|
47
|
+
ops: SceneEditOp[]
|
|
48
48
|
errors: string[]
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -140,6 +140,6 @@ export function buildSetStrokeOps(input: SetStrokeInput): SetStrokeResult {
|
|
|
140
140
|
patch.lineWidth = 0
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
const ops:
|
|
143
|
+
const ops: SceneEditOp[] = refids.map(refid => ({ op: 'modify', refid, patch }))
|
|
144
144
|
return { ops, errors: [] }
|
|
145
145
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 두 tool 분리 — 의도가 다름. 내용 변경 vs 시각 스타일.
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
10
|
+
import type { SceneEditOp } from '@operato/scene-ops'
|
|
11
11
|
import { normalizeColor, validateColor } from './color-utils'
|
|
12
12
|
|
|
13
13
|
export interface SetTextInput {
|
|
@@ -44,7 +44,7 @@ const VALID_TEXT_BASELINE = new Set([
|
|
|
44
44
|
])
|
|
45
45
|
|
|
46
46
|
export interface OpsResult {
|
|
47
|
-
ops:
|
|
47
|
+
ops: SceneEditOp[]
|
|
48
48
|
errors: string[]
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
import {
|
|
21
21
|
buildBoardEditTools,
|
|
22
22
|
buildToolGate,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
toolCallToSceneEditOp,
|
|
24
|
+
toolCallToSceneActionOp
|
|
25
25
|
} from './assistant'
|
|
26
26
|
|
|
27
27
|
/* 두 개의 가짜 외부 카테고리 — 하나는 보드 저작 계열, 하나는 운영 계열. */
|
|
@@ -95,29 +95,29 @@ describe('도구 노출 게이트', () => {
|
|
|
95
95
|
const gate = buildToolGate({ boardTools: false, toolCategories: ['test-ops'] })
|
|
96
96
|
const components = [{ refid: 7, type: 'rect' }]
|
|
97
97
|
|
|
98
|
-
const add =
|
|
98
|
+
const add = toolCallToSceneEditOp({ id: 'x', name: 'addComponent', arguments: { type: 'rect' } } as any, components, gate)
|
|
99
99
|
expect(add).toBeNull()
|
|
100
100
|
|
|
101
|
-
const fill =
|
|
101
|
+
const fill = toolCallToSceneEditOp(
|
|
102
102
|
{ id: 'x', name: 'setFill', arguments: { refids: [7], fillStyle: '#f00' } } as any,
|
|
103
103
|
components,
|
|
104
104
|
gate
|
|
105
105
|
)
|
|
106
106
|
expect(fill).toBeNull()
|
|
107
107
|
|
|
108
|
-
const action =
|
|
108
|
+
const action = toolCallToSceneActionOp({ id: 'x', name: 'selectComponents', arguments: { refids: [7] } } as any, gate)
|
|
109
109
|
expect(action).toBeNull()
|
|
110
110
|
})
|
|
111
111
|
|
|
112
112
|
it('허용되지 않은 외부 카테고리의 write 도구도 실행되지 않는다', () => {
|
|
113
113
|
const gate = buildToolGate({ toolCategories: ['test-ops'] })
|
|
114
|
-
const r =
|
|
114
|
+
const r = toolCallToSceneEditOp({ id: 'x', name: 'testBindThing', arguments: { refid: 7 } } as any, [{ refid: 7 }], gate)
|
|
115
115
|
expect(r).toBeNull()
|
|
116
116
|
})
|
|
117
117
|
|
|
118
118
|
it('허용된 카테고리의 write 도구는 정상 실행된다(게이트가 과잉 차단하지 않는다)', () => {
|
|
119
119
|
const gate = buildToolGate({ toolCategories: ['test-authoring'] })
|
|
120
|
-
const r: any =
|
|
120
|
+
const r: any = toolCallToSceneEditOp({ id: 'x', name: 'testBindThing', arguments: { refid: 7 } } as any, [{ refid: 7 }], gate)
|
|
121
121
|
expect(r).toEqual([{ op: 'modify', refid: 7, patch: { tag: 'bound' } }])
|
|
122
122
|
})
|
|
123
123
|
|