@things-factory/board-ai 10.0.0-beta.64 → 10.0.0-beta.65
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 +1253 -0
- package/client/components/board-ai-chat.test.ts +29 -0
- package/client/components/board-ai-chat.ts +842 -71
- package/client/components/chat-input-builder.ts +8 -1
- package/client/components/default-catalog-entries.test.ts +59 -0
- package/client/components/default-catalog-entries.ts +100 -0
- package/client/components/default-slash-templates.test.ts +55 -0
- package/client/components/default-slash-templates.ts +69 -0
- package/client/components/markdown.test.ts +221 -1
- package/client/components/markdown.ts +246 -5
- package/client/components/mention-popup-helpers.ts +433 -0
- package/client/components/mention-popup.test.ts +588 -0
- package/client/components/mention-popup.ts +238 -0
- package/client/utils/board-edit-patch.ts +119 -7
- package/dist-client/client/components/board-ai-chat.d.ts +241 -0
- package/dist-client/{components → client/components}/board-ai-chat.js +769 -70
- package/dist-client/client/components/board-ai-chat.js.map +1 -0
- package/dist-client/{components → client/components}/board-ai-chat.test.js +26 -0
- package/dist-client/client/components/board-ai-chat.test.js.map +1 -0
- package/dist-client/{components → client/components}/chat-input-builder.d.ts +9 -0
- package/dist-client/{components → client/components}/chat-input-builder.js +2 -1
- package/dist-client/client/components/chat-input-builder.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.d.ts +15 -0
- package/dist-client/client/components/default-catalog-entries.js +86 -0
- package/dist-client/client/components/default-catalog-entries.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.test.js +52 -0
- package/dist-client/client/components/default-catalog-entries.test.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.d.ts +17 -0
- package/dist-client/client/components/default-slash-templates.js +53 -0
- package/dist-client/client/components/default-slash-templates.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.test.d.ts +1 -0
- package/dist-client/client/components/default-slash-templates.test.js +51 -0
- package/dist-client/client/components/default-slash-templates.test.js.map +1 -0
- package/dist-client/client/components/markdown.d.ts +52 -0
- package/dist-client/client/components/markdown.js +343 -0
- package/dist-client/client/components/markdown.js.map +1 -0
- package/dist-client/client/components/markdown.test.d.ts +1 -0
- package/dist-client/{components → client/components}/markdown.test.js +168 -1
- package/dist-client/client/components/markdown.test.js.map +1 -0
- package/dist-client/client/components/mention-popup-helpers.d.ts +165 -0
- package/dist-client/client/components/mention-popup-helpers.js +330 -0
- package/dist-client/client/components/mention-popup-helpers.js.map +1 -0
- package/dist-client/client/components/mention-popup.d.ts +35 -0
- package/dist-client/client/components/mention-popup.js +223 -0
- package/dist-client/client/components/mention-popup.js.map +1 -0
- package/dist-client/client/components/mention-popup.test.d.ts +1 -0
- package/dist-client/client/components/mention-popup.test.js +474 -0
- package/dist-client/client/components/mention-popup.test.js.map +1 -0
- package/dist-client/client/index.js.map +1 -0
- package/dist-client/{utils → client/utils}/board-edit-patch.d.ts +84 -0
- package/dist-client/{utils → client/utils}/board-edit-patch.js +68 -7
- package/dist-client/client/utils/board-edit-patch.js.map +1 -0
- package/dist-client/server/service/assistant.d.ts +76 -0
- package/dist-client/server/service/assistant.js +2448 -0
- package/dist-client/server/service/assistant.js.map +1 -0
- package/dist-client/server/service/catalog-resolver.d.ts +18 -0
- package/dist-client/server/service/catalog-resolver.js +93 -0
- package/dist-client/server/service/catalog-resolver.js.map +1 -0
- package/dist-client/server/service/mention-resolver.d.ts +96 -0
- package/dist-client/server/service/mention-resolver.js +172 -0
- package/dist-client/server/service/mention-resolver.js.map +1 -0
- package/dist-client/server/service/prefab-registry.d.ts +73 -0
- package/dist-client/server/service/prefab-registry.js +252 -0
- package/dist-client/server/service/prefab-registry.js.map +1 -0
- package/dist-client/server/service/styling/clear-tools.d.ts +15 -0
- package/dist-client/server/service/styling/clear-tools.js +43 -0
- package/dist-client/server/service/styling/clear-tools.js.map +1 -0
- package/dist-client/server/service/styling/color-utils.d.ts +43 -0
- package/dist-client/server/service/styling/color-utils.js +205 -0
- package/dist-client/server/service/styling/color-utils.js.map +1 -0
- package/dist-client/server/service/styling/copy-tools.d.ts +27 -0
- package/dist-client/server/service/styling/copy-tools.js +78 -0
- package/dist-client/server/service/styling/copy-tools.js.map +1 -0
- package/dist-client/server/service/styling/effect-tools.d.ts +38 -0
- package/dist-client/server/service/styling/effect-tools.js +76 -0
- package/dist-client/server/service/styling/effect-tools.js.map +1 -0
- package/dist-client/server/service/styling/fill-tools.d.ts +60 -0
- package/dist-client/server/service/styling/fill-tools.js +226 -0
- package/dist-client/server/service/styling/fill-tools.js.map +1 -0
- package/dist-client/server/service/styling/material-tools.d.ts +32 -0
- package/dist-client/server/service/styling/material-tools.js +88 -0
- package/dist-client/server/service/styling/material-tools.js.map +1 -0
- package/dist-client/server/service/styling/read-tools.d.ts +34 -0
- package/dist-client/server/service/styling/read-tools.js +42 -0
- package/dist-client/server/service/styling/read-tools.js.map +1 -0
- package/dist-client/server/service/styling/stroke-tools.d.ts +32 -0
- package/dist-client/server/service/styling/stroke-tools.js +77 -0
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -0
- package/dist-client/server/service/styling/text-tools.d.ts +31 -0
- package/dist-client/server/service/styling/text-tools.js +69 -0
- package/dist-client/server/service/styling/text-tools.js.map +1 -0
- package/dist-client/server/service/types.d.ts +254 -0
- package/dist-client/server/service/types.js +2 -0
- package/dist-client/server/service/types.js.map +1 -0
- package/dist-client/server/service/validation/board-model-schema.d.ts +3130 -0
- package/dist-client/server/service/validation/board-model-schema.js +284 -0
- package/dist-client/server/service/validation/board-model-schema.js.map +1 -0
- package/dist-client/server/service/validation/tool-validation.d.ts +31 -0
- package/dist-client/server/service/validation/tool-validation.js +156 -0
- package/dist-client/server/service/validation/tool-validation.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/apply-patch.d.ts +1 -0
- package/dist-server/service/apply-patch.js +90 -8
- package/dist-server/service/apply-patch.js.map +1 -1
- package/dist-server/service/assistant.d.ts +16 -15
- package/dist-server/service/assistant.js +1324 -158
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.d.ts +6 -0
- package/dist-server/service/board-ai-resolver.js +62 -5
- package/dist-server/service/board-ai-resolver.js.map +1 -1
- package/dist-server/service/catalog-resolver.d.ts +18 -0
- package/dist-server/service/catalog-resolver.js +98 -0
- package/dist-server/service/catalog-resolver.js.map +1 -0
- package/dist-server/service/chat-message/chat-message.d.ts +32 -0
- package/dist-server/service/chat-message/chat-message.js +42 -1
- package/dist-server/service/chat-message/chat-message.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.d.ts +10 -1
- package/dist-server/service/chat-session/chat-session.js +38 -16
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +30 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js +112 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
- package/dist-server/service/chat-session-participant/index.d.ts +3 -0
- package/dist-server/service/chat-session-participant/index.js +7 -0
- package/dist-server/service/chat-session-participant/index.js.map +1 -0
- package/dist-server/service/chat-session-resolver.d.ts +7 -1
- package/dist-server/service/chat-session-resolver.js +155 -7
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/index.d.ts +5 -1
- package/dist-server/service/index.js +8 -2
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/mention-marker.d.ts +67 -0
- package/dist-server/service/mention-marker.js +121 -0
- package/dist-server/service/mention-marker.js.map +1 -0
- package/dist-server/service/mention-resolver.d.ts +96 -0
- package/dist-server/service/mention-resolver.js +177 -0
- package/dist-server/service/mention-resolver.js.map +1 -0
- package/dist-server/service/prefab-registry.d.ts +73 -0
- package/dist-server/service/prefab-registry.js +258 -0
- package/dist-server/service/prefab-registry.js.map +1 -0
- package/dist-server/service/styling/clear-tools.d.ts +15 -0
- package/dist-server/service/styling/clear-tools.js +46 -0
- package/dist-server/service/styling/clear-tools.js.map +1 -0
- package/dist-server/service/styling/color-utils.d.ts +43 -0
- package/dist-server/service/styling/color-utils.js +210 -0
- package/dist-server/service/styling/color-utils.js.map +1 -0
- package/dist-server/service/styling/copy-tools.d.ts +27 -0
- package/dist-server/service/styling/copy-tools.js +81 -0
- package/dist-server/service/styling/copy-tools.js.map +1 -0
- package/dist-server/service/styling/effect-tools.d.ts +38 -0
- package/dist-server/service/styling/effect-tools.js +81 -0
- package/dist-server/service/styling/effect-tools.js.map +1 -0
- package/dist-server/service/styling/fill-tools.d.ts +60 -0
- package/dist-server/service/styling/fill-tools.js +229 -0
- package/dist-server/service/styling/fill-tools.js.map +1 -0
- package/dist-server/service/styling/material-tools.d.ts +32 -0
- package/dist-server/service/styling/material-tools.js +92 -0
- package/dist-server/service/styling/material-tools.js.map +1 -0
- package/dist-server/service/styling/read-tools.d.ts +34 -0
- package/dist-server/service/styling/read-tools.js +45 -0
- package/dist-server/service/styling/read-tools.js.map +1 -0
- package/dist-server/service/styling/stroke-tools.d.ts +32 -0
- package/dist-server/service/styling/stroke-tools.js +80 -0
- package/dist-server/service/styling/stroke-tools.js.map +1 -0
- package/dist-server/service/styling/text-tools.d.ts +31 -0
- package/dist-server/service/styling/text-tools.js +73 -0
- package/dist-server/service/styling/text-tools.js.map +1 -0
- package/dist-server/service/types.d.ts +120 -3
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/service/validation/board-model-schema.d.ts +3130 -0
- package/dist-server/service/validation/board-model-schema.js +290 -0
- package/dist-server/service/validation/board-model-schema.js.map +1 -0
- package/dist-server/service/validation/tool-validation.d.ts +31 -0
- package/dist-server/service/validation/tool-validation.js +159 -0
- package/dist-server/service/validation/tool-validation.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/server/service/apply-patch-drift.test.ts +313 -0
- package/server/service/apply-patch.test.ts +122 -5
- package/server/service/apply-patch.ts +90 -10
- package/server/service/assistant-integration.test.ts +922 -0
- package/server/service/assistant-llm-smoke.test.ts +296 -0
- package/server/service/assistant.test.ts +803 -101
- package/server/service/assistant.ts +1407 -237
- package/server/service/board-ai-resolver.ts +64 -5
- package/server/service/catalog-resolver.test.ts +137 -0
- package/server/service/catalog-resolver.ts +102 -0
- package/server/service/chat-message/chat-message.ts +61 -2
- package/server/service/chat-session/chat-session.ts +40 -13
- package/server/service/chat-session-participant/chat-session-participant.ts +110 -0
- package/server/service/chat-session-participant/index.ts +5 -0
- package/server/service/chat-session-resolver.ts +141 -7
- package/server/service/index.ts +6 -0
- package/server/service/mention-marker.test.ts +228 -0
- package/server/service/mention-marker.ts +142 -0
- package/server/service/mention-resolver.test.ts +273 -0
- package/server/service/mention-resolver.ts +264 -0
- package/server/service/prefab-registry.test.ts +172 -0
- package/server/service/prefab-registry.ts +332 -0
- package/server/service/styling/clear-tools.ts +62 -0
- package/server/service/styling/color-utils.test.ts +130 -0
- package/server/service/styling/color-utils.ts +226 -0
- package/server/service/styling/copy-tools.test.ts +152 -0
- package/server/service/styling/copy-tools.ts +115 -0
- package/server/service/styling/effect-tools.ts +109 -0
- package/server/service/styling/fill-tools.test.ts +331 -0
- package/server/service/styling/fill-tools.ts +293 -0
- package/server/service/styling/material-tools.ts +114 -0
- package/server/service/styling/read-tools.test.ts +78 -0
- package/server/service/styling/read-tools.ts +67 -0
- package/server/service/styling/stroke-tools.ts +124 -0
- package/server/service/styling/text-tools.ts +97 -0
- package/server/service/styling/tier1-tools.test.ts +298 -0
- package/server/service/types.ts +89 -3
- package/server/service/validation/board-model-schema.test.ts +315 -0
- package/server/service/validation/board-model-schema.ts +312 -0
- package/server/service/validation/tool-validation.ts +187 -0
- package/translations/en.json +12 -4
- package/translations/ja.json +11 -3
- package/translations/ko.json +12 -4
- package/translations/ms.json +11 -3
- package/translations/zh.json +11 -3
- package/dist-client/components/board-ai-chat.d.ts +0 -127
- package/dist-client/components/board-ai-chat.js.map +0 -1
- package/dist-client/components/board-ai-chat.test.js.map +0 -1
- package/dist-client/components/chat-input-builder.js.map +0 -1
- package/dist-client/components/markdown.d.ts +0 -16
- package/dist-client/components/markdown.js +0 -167
- package/dist-client/components/markdown.js.map +0 -1
- package/dist-client/components/markdown.test.js.map +0 -1
- package/dist-client/index.js.map +0 -1
- package/dist-client/utils/board-edit-patch.js.map +0 -1
- /package/dist-client/{components → client/components}/board-ai-chat.test.d.ts +0 -0
- /package/dist-client/{components/markdown.test.d.ts → client/components/default-catalog-entries.test.d.ts} +0 -0
- /package/dist-client/{index.d.ts → client/index.d.ts} +0 -0
- /package/dist-client/{index.js → client/index.js} +0 -0
|
@@ -0,0 +1,1253 @@
|
|
|
1
|
+
# Component Styling Tools — Plan
|
|
2
|
+
|
|
3
|
+
> **Goal**: AI 어시스턴트가 컴포넌트의 2D / 3D 스타일링을 델리키트하고 정확하게
|
|
4
|
+
> 처리하도록 — fillStyle (4 modes), strokeStyle, text/font, shadow, 3D material,
|
|
5
|
+
> texture 까지 보편 속성 모두 다루는 tool 군 설계 + 구현.
|
|
6
|
+
>
|
|
7
|
+
> 스타일링 품질 = AI 서포트 효용성 + 생산성 직결. AI 가 "원샷에 모드 전환 + 색
|
|
8
|
+
> 변경 + 부분 업데이트" 같은 케이스를 자연스럽게 처리해야 한다.
|
|
9
|
+
>
|
|
10
|
+
> **Status**: planning (작성 2026-04-28). 단계별 구현 진행.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. 스타일 표면 inventory
|
|
15
|
+
|
|
16
|
+
things-scene 의 styling 표면 audit 결과. 각 인터페이스가 그대로 컴포넌트 state 에
|
|
17
|
+
존재 (e.g., `state.fillStyle` / `state.strokeStyle` / `state.fontFamily` / `state.material3d`).
|
|
18
|
+
|
|
19
|
+
### 1.1 `FillStyle` — 4 modes (`src/interfaces/rendering.ts:32`)
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
interface FillStyle extends StyleOptions {
|
|
23
|
+
type: 'solid' | 'gradient' | 'pattern' | 'image'
|
|
24
|
+
color?: string
|
|
25
|
+
opacity?: number
|
|
26
|
+
// gradient extras
|
|
27
|
+
gradientType?: string // 'linear' | 'radial'
|
|
28
|
+
rotation?: number
|
|
29
|
+
colors?: string[]
|
|
30
|
+
stops?: number[]
|
|
31
|
+
// pattern/image extras
|
|
32
|
+
image?: string // URL or asset ref
|
|
33
|
+
// 그 외 모드별 임의 키 (additionalProperties: true)
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Modes (사용자 의도)**:
|
|
38
|
+
- **none**: `state.fillStyle` undefined / null / `{type:'none'}` — 배경 없음
|
|
39
|
+
- **solid**: `{type:'solid', color, opacity?}` — 단색
|
|
40
|
+
- **gradient**: `{type:'gradient', gradientType:'linear'|'radial', colors:[], stops:[], rotation?, opacity?}`
|
|
41
|
+
- **pattern**: `{type:'pattern', image, opacity?, repeat?, ...}` — 이미지 반복
|
|
42
|
+
- **image**: `{type:'image', image, ...}` — 이미지 1회 (stretch/contain)
|
|
43
|
+
|
|
44
|
+
### 1.2 `StrokeStyle` (`src/interfaces/rendering.ts:41`)
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
interface StrokeStyle {
|
|
48
|
+
width?: number
|
|
49
|
+
color?: string
|
|
50
|
+
opacity?: number
|
|
51
|
+
lineCap?: 'butt' | 'round' | 'square'
|
|
52
|
+
lineJoin?: 'miter' | 'round' | 'bevel'
|
|
53
|
+
lineDash?: number[] // [] = solid, [4,4] = dashed, [1,4] = dotted...
|
|
54
|
+
lineDashOffset?: number
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Dash presets (시멘틱)**: solid / dashed / dotted / dashdot — 사용자 의도 → 배열 변환.
|
|
59
|
+
|
|
60
|
+
### 1.3 `TextStyle` (`src/interfaces/rendering.ts:54`)
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
interface TextStyle {
|
|
64
|
+
font?: string // shorthand
|
|
65
|
+
fontSize?: number
|
|
66
|
+
fontColor?: string
|
|
67
|
+
fontFamily?: string
|
|
68
|
+
fontStyle?: string // 'normal' | 'italic' | 'oblique'
|
|
69
|
+
fontWeight?: string // 'normal' | 'bold' | '100'..'900'
|
|
70
|
+
textAlign?: 'left' | 'center' | 'right' | 'start' | 'end'
|
|
71
|
+
textBaseline?: 'top' | 'middle' | 'bottom' | 'hanging' | 'alphabetic' | 'ideographic'
|
|
72
|
+
lineHeight?: number
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**참고**: text content (`state.text`) 는 별도 필드. font 스타일과 분리 처리.
|
|
77
|
+
|
|
78
|
+
### 1.4 `Shadow` (`src/interfaces/rendering.ts:69`)
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
interface Shadow {
|
|
82
|
+
color?: string
|
|
83
|
+
offsetX?: number
|
|
84
|
+
offsetY?: number
|
|
85
|
+
blur?: number
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 1.5 `Material3D` (`src/threed/material-3d.ts`)
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
interface Material3D {
|
|
93
|
+
preset?: 'default' | 'metal' | 'glass' | 'plastic' | 'wood' | 'ceramic' | 'rubber' | 'custom'
|
|
94
|
+
metalness?: number // 0..1
|
|
95
|
+
roughness?: number // 0..1
|
|
96
|
+
emissive?: string // 발광색 hex
|
|
97
|
+
emissiveIntensity?: number // 0..5
|
|
98
|
+
opacity?: number // 0..1 (component alpha 와 독립)
|
|
99
|
+
envMapIntensity?: number // 0..3 환경맵 반사 강도
|
|
100
|
+
side?: 'double' | 'front' | 'back'
|
|
101
|
+
castShadow?: boolean // default true
|
|
102
|
+
receiveShadow?: boolean // default false
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Preset 베이스 + override**: `resolveMaterial3d(material)` 함수가 preset 의 default 위에
|
|
107
|
+
명시 속성 덮어쓰는 구조. preset 만 주면 그 시맨틱 재질, 개별 속성 추가 시 fine-tune.
|
|
108
|
+
|
|
109
|
+
### 1.6 Texture / UV (3D)
|
|
110
|
+
|
|
111
|
+
`src/threed/texture/fillstyle-texture.ts:58` `buildPatternTexture` / `:180` `buildFillStyleTexture` —
|
|
112
|
+
2D fillStyle 을 3D mesh 의 texture 로 변환하는 시스템. UV offset/repeat 자동 계산.
|
|
113
|
+
|
|
114
|
+
**현재는 자동 처리** — AI 가 직접 UV 조작할 일 적음. Phase 후반에 manual 조정 tool 추가 가능.
|
|
115
|
+
|
|
116
|
+
### 1.7 Scene-level Lighting (`src/threed/managers/types.ts:19`)
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
interface LightingConfig {
|
|
120
|
+
hemisphere: { skyColor, groundColor, intensity }
|
|
121
|
+
directional?: { color, intensity, attachToCamera?, position?, castShadow?, ... }
|
|
122
|
+
point?: { color, intensity, decay, power, castShadow, position, ... }
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
씬 전체 조명 — 별도 phase (Environment + Light Mood 프리셋이 이미 있음, 메모리
|
|
127
|
+
[project_preset_architecture.md] 참고).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 2. 설계 철학
|
|
132
|
+
|
|
133
|
+
### 2.1 8가지 핵심 원칙
|
|
134
|
+
|
|
135
|
+
| # | 원칙 | 실천 |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| 1 | **Mode-aware** | 'solid' → 'gradient' 같은 모드 전환 시 fillStyle 통째 교체. partial merge X. |
|
|
138
|
+
| 2 | **Idempotent** | 같은 입력 반복 = 같은 결과. 누적 X. inverse op 정확. |
|
|
139
|
+
| 3 | **Partial-update friendly** | "투명도만" / "stroke 색만" 자연스럽게. 다른 속성 보존. |
|
|
140
|
+
| 4 | **Semantic-first** | `applyEmphasis('subtle')` / `applySemantic('warning')` 같은 시맨틱 우선. raw 는 fallback. |
|
|
141
|
+
| 5 | **Inverse 자동** | 모든 styling tool 이 inverse op 생성 → 기존 patch revert 시스템과 통합. |
|
|
142
|
+
| 6 | **Multi-target** | 한 번에 여러 컴포넌트 (refids 배열). |
|
|
143
|
+
| 7 | **Validation graceful** | 잘못된 색/숫자 → 명확한 에러 + 정정 제안 (AI 가 다시 시도 가능). |
|
|
144
|
+
| 8 | **Data-binding 인지** | 데이터 바인딩 적용 중 컴포넌트는 충돌 경고 (또는 unbind 후 적용 옵션). |
|
|
145
|
+
|
|
146
|
+
### 2.2 토큰 효율
|
|
147
|
+
|
|
148
|
+
AI 가 자주 쓰는 의도를 **짧은 호출** 로 표현 가능해야 한다. 예시:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
// 길고 모호 (BAD)
|
|
152
|
+
modifyComponentByRefid(refid=35, patch={
|
|
153
|
+
fillStyle: { type: 'solid', color: '#ff0000', opacity: 0.5 },
|
|
154
|
+
strokeStyle: { color: '#000000', width: 2, lineDash: [4, 4] }
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// 짧고 명확 (GOOD)
|
|
158
|
+
setFill(refids=[35], mode='solid', color='red', opacity=0.5)
|
|
159
|
+
setStroke(refids=[35], color='black', width=2, dash='dashed')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 2.3 시맨틱 Layer
|
|
163
|
+
|
|
164
|
+
다단계 추상화:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
Tier 3: applySemantic('warning') — 의도 (Material 3 토큰)
|
|
168
|
+
Tier 2: applyStylePreset('card-elevated') — 명명된 스타일
|
|
169
|
+
Tier 1: setFill(mode='solid', color='red') — 구조화 raw
|
|
170
|
+
Tier 0: modifyComponentByRefid(...) — full raw (escape hatch)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
AI 는 의도 명확하면 Tier 3 / 2 사용, 정밀 제어 필요 시 Tier 1, 모르는 영역은 Tier 0.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 3. Tool 카탈로그
|
|
178
|
+
|
|
179
|
+
### 3.1 Tier 1 — Core Styling (10 tools)
|
|
180
|
+
|
|
181
|
+
#### `setFill`
|
|
182
|
+
컴포넌트 배경 (fillStyle) 설정. mode 명시 필수.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
input: {
|
|
186
|
+
refids: number[]
|
|
187
|
+
mode: 'none' | 'solid' | 'gradient' | 'pattern' | 'image'
|
|
188
|
+
// solid:
|
|
189
|
+
color?: string
|
|
190
|
+
// gradient:
|
|
191
|
+
gradientType?: 'linear' | 'radial' // default 'linear'
|
|
192
|
+
gradientStops?: Array<{ color: string; position: number }> // position 0..1
|
|
193
|
+
rotation?: number // degrees, default 0
|
|
194
|
+
// pattern/image:
|
|
195
|
+
image?: string // URL or asset ref
|
|
196
|
+
// 공통:
|
|
197
|
+
opacity?: number // 0..1
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Mode 전환 룰**: 새 mode 명시 시 fillStyle 통째 교체. 같은 mode 면 부분 merge.
|
|
202
|
+
|
|
203
|
+
#### `setStroke`
|
|
204
|
+
컴포넌트 테두리.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
input: {
|
|
208
|
+
refids: number[]
|
|
209
|
+
enabled?: boolean // false → strokeStyle 제거
|
|
210
|
+
color?: string
|
|
211
|
+
width?: number
|
|
212
|
+
dash?: 'solid' | 'dashed' | 'dotted' | 'dashdot' | number[] // preset → 배열 변환
|
|
213
|
+
lineCap?: 'butt' | 'round' | 'square'
|
|
214
|
+
lineJoin?: 'miter' | 'round' | 'bevel'
|
|
215
|
+
opacity?: number
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### `setText`
|
|
220
|
+
텍스트 *내용* 변경. 폰트 스타일은 `setFont` 별개.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
input: {
|
|
224
|
+
refids: number[]
|
|
225
|
+
text: string
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### `setFont`
|
|
230
|
+
폰트 / 텍스트 스타일.
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
input: {
|
|
234
|
+
refids: number[]
|
|
235
|
+
fontFamily?: string
|
|
236
|
+
fontSize?: number
|
|
237
|
+
fontWeight?: 'normal' | 'bold' | 'lighter' | 'bolder' | number
|
|
238
|
+
fontStyle?: 'normal' | 'italic' | 'oblique'
|
|
239
|
+
fontColor?: string
|
|
240
|
+
textAlign?: 'left' | 'center' | 'right' | 'justify'
|
|
241
|
+
textBaseline?: 'top' | 'middle' | 'bottom'
|
|
242
|
+
lineHeight?: number
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### `setShadow`
|
|
247
|
+
그림자.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
input: {
|
|
251
|
+
refids: number[]
|
|
252
|
+
enabled?: boolean // false → shadow 제거
|
|
253
|
+
color?: string
|
|
254
|
+
blur?: number
|
|
255
|
+
offsetX?: number
|
|
256
|
+
offsetY?: number
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### `setOpacity`
|
|
261
|
+
컴포넌트 전체 알파 (`state.alpha` 또는 `state.opacity`).
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
input: {
|
|
265
|
+
refids: number[]
|
|
266
|
+
opacity: number // 0..1
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### `setVisible`
|
|
271
|
+
표시 / 숨김 (`state.hidden`).
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
input: {
|
|
275
|
+
refids: number[]
|
|
276
|
+
visible: boolean
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### `setMaterial`
|
|
281
|
+
3D PBR 재질.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
input: {
|
|
285
|
+
refids: number[]
|
|
286
|
+
preset?: 'default' | 'metal' | 'glass' | 'plastic' | 'wood' | 'ceramic' | 'rubber' | 'custom'
|
|
287
|
+
metalness?: number // 0..1
|
|
288
|
+
roughness?: number // 0..1
|
|
289
|
+
emissive?: string
|
|
290
|
+
emissiveIntensity?: number // 0..5
|
|
291
|
+
opacity?: number // 0..1, 3D-only (component alpha 와 별개)
|
|
292
|
+
envMapIntensity?: number // 0..3
|
|
293
|
+
side?: 'double' | 'front' | 'back'
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### `setShadowProperties`
|
|
298
|
+
3D 그림자 동작.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
input: {
|
|
302
|
+
refids: number[]
|
|
303
|
+
castShadow?: boolean
|
|
304
|
+
receiveShadow?: boolean
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### `clearStyle`
|
|
309
|
+
특정 styling aspect 초기화 — 데이터 바인딩 / preset 으로 돌리기 전.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
input: {
|
|
313
|
+
refids: number[]
|
|
314
|
+
aspects: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font'>
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### 3.2 Tier 2 — Productivity (5 tools)
|
|
319
|
+
|
|
320
|
+
#### `copyStyle`
|
|
321
|
+
한 컴포넌트의 스타일을 다른 컴포넌트들에 복사.
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
input: {
|
|
325
|
+
sourceRefid: number
|
|
326
|
+
targetRefids: number[]
|
|
327
|
+
aspects?: Array<'fill' | 'stroke' | 'font' | 'shadow' | 'material' | 'opacity'>
|
|
328
|
+
// default: 모든 styling aspect 복사
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### `harmonizeStyles`
|
|
333
|
+
선택된 컴포넌트들의 한 aspect 를 동일하게 (가장 흔한 값으로).
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
input: {
|
|
337
|
+
refids: number[]
|
|
338
|
+
aspect: 'fill' | 'stroke' | 'font' | 'shadow'
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
#### `getComponentStyle`
|
|
343
|
+
현재 styling 전체 또는 부분 조회 (read-only).
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
input: {
|
|
347
|
+
refid: number
|
|
348
|
+
aspects?: Array<'fill' | 'stroke' | 'font' | 'shadow' | 'material' | 'opacity'>
|
|
349
|
+
}
|
|
350
|
+
output: { fillStyle?, strokeStyle?, fontStyle?, shadow?, material3d?, opacity? }
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### `summarizeStyles`
|
|
354
|
+
여러 컴포넌트 스타일 요약 — AI 컨텍스트 토큰 절감.
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
input: { refids: number[] }
|
|
358
|
+
output: string // "5개 컴포넌트: 모두 빨간 solid + 검정 1px stroke + shadow 없음"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
#### `applyEmphasis`
|
|
362
|
+
강조 단계 시맨틱.
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
input: {
|
|
366
|
+
refids: number[]
|
|
367
|
+
level: 'none' | 'subtle' | 'medium' | 'strong'
|
|
368
|
+
}
|
|
369
|
+
// 구현: level → shadow + stroke + opacity 자동 조합
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### 3.3 Tier 3 — Semantic & Theming (6 tools)
|
|
373
|
+
|
|
374
|
+
#### `applySemantic`
|
|
375
|
+
Material 3 시맨틱 컬러 적용.
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
input: {
|
|
379
|
+
refids: number[]
|
|
380
|
+
intent: 'success' | 'warning' | 'error' | 'info' | 'primary' | 'neutral'
|
|
381
|
+
variant?: 'filled' | 'outlined' | 'tonal' // default 'filled'
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### `applyState`
|
|
386
|
+
UI 상태 시맨틱.
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
input: {
|
|
390
|
+
refids: number[]
|
|
391
|
+
state: 'active' | 'disabled' | 'highlighted' | 'preview'
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### `getStylePresets`
|
|
396
|
+
사용 가능한 preset 목록 (시스템 + 사용자 정의).
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
output: Array<{ name: string; description?: string; aspects: string[] }>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### `applyStylePreset`
|
|
403
|
+
이름으로 preset 적용.
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
input: { refids: number[]; presetName: string }
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
#### `extractColorPalette`
|
|
410
|
+
컴포넌트들에서 사용 색상 추출 → 팔레트.
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
input: { refids: number[] }
|
|
414
|
+
output: Array<{ color: string; usage: number; sources: string[] }>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
#### `recolor`
|
|
418
|
+
일괄 색상 매핑 변경.
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
input: {
|
|
422
|
+
refids: number[]
|
|
423
|
+
mapping: Array<{ from: string; to: string }>
|
|
424
|
+
// tolerance?: number // 색상 유사 매칭 허용 (later)
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### 3.4 Tier 4 — 3D Advanced (4 tools)
|
|
429
|
+
|
|
430
|
+
#### `setTexture`
|
|
431
|
+
3D mesh 의 텍스처 매핑 manual 조정.
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
input: {
|
|
435
|
+
refids: number[]
|
|
436
|
+
url?: string
|
|
437
|
+
repeat?: { x: number; y: number }
|
|
438
|
+
offset?: { x: number; y: number }
|
|
439
|
+
rotation?: number
|
|
440
|
+
encoding?: 'sRGB' | 'linear'
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### `setMaterialMood`
|
|
445
|
+
씬 환경 감각에 맞는 material 보정 — Environment preset 과 결합.
|
|
446
|
+
|
|
447
|
+
```ts
|
|
448
|
+
input: {
|
|
449
|
+
refids: number[]
|
|
450
|
+
mood: 'studio' | 'outdoor-sunny' | 'outdoor-cloudy' | 'warm-indoor' | 'cool-night'
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
#### `setEmissive`
|
|
455
|
+
발광 quick-access — 전구 / LED / 화면 표현.
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
input: {
|
|
459
|
+
refids: number[]
|
|
460
|
+
color?: string
|
|
461
|
+
intensity?: number // 0..5
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### `set3DTransform`
|
|
466
|
+
3D 위치/회전/크기 (위치는 2D centerTo 로도 가능하지만 3D 만 의미 있는 회전/오프셋).
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
input: {
|
|
470
|
+
refids: number[]
|
|
471
|
+
rotation3D?: { x?: number; y?: number; z?: number } // degrees
|
|
472
|
+
offset3D?: { x?: number; y?: number; z?: number }
|
|
473
|
+
scale3D?: { x?: number; y?: number; z?: number }
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 3.5 Tier 5 — Animation / Interaction (later, optional)
|
|
478
|
+
|
|
479
|
+
- `setHoverStyle(refids, ...)` — hover 시 변경 스타일
|
|
480
|
+
- `setActiveStyle(refids, ...)` — active 시 변경 스타일
|
|
481
|
+
- `setTransitionDuration(refids, ms)` — animation 속도
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## 4. Mode 전환 매트릭스
|
|
486
|
+
|
|
487
|
+
### 4.1 setFill mode 전환
|
|
488
|
+
|
|
489
|
+
| from → to | 처리 |
|
|
490
|
+
|---|---|
|
|
491
|
+
| `solid` → `gradient` | 기존 `color` 가 첫 stop, 자동 생성 두 번째 stop (어두운 변형) — 사용자가 stops 명시하면 그대로 |
|
|
492
|
+
| `gradient` → `solid` | 첫 stop 색 또는 평균색이 새 `color` |
|
|
493
|
+
| `solid` → `pattern`/`image` | 색상 정보 버림, image 필수 |
|
|
494
|
+
| `*` → `none` | fillStyle 제거 (state 에서 delete) |
|
|
495
|
+
| 같은 mode | 부분 merge (color 만 줘도 stops/rotation 보존) |
|
|
496
|
+
|
|
497
|
+
### 4.2 setMaterial preset 처리
|
|
498
|
+
|
|
499
|
+
| 입력 | 결과 |
|
|
500
|
+
|---|---|
|
|
501
|
+
| `preset` 만 | preset 의 기본값 reset (resolveMaterial3d) |
|
|
502
|
+
| `preset` + 개별 속성 | preset 위에 개별 속성 override |
|
|
503
|
+
| 개별 속성만 | 기존 preset 유지 + 개별 속성만 변경 |
|
|
504
|
+
| `preset='custom'` + 속성 | preset 효과 X, 속성만 raw 적용 |
|
|
505
|
+
|
|
506
|
+
### 4.3 색상 입력 정규화
|
|
507
|
+
|
|
508
|
+
- hex (`#ff0000`, `#f00`)
|
|
509
|
+
- 명명 색 (`red`, `transparent`, ...) → CSS named colors 매핑
|
|
510
|
+
- rgb/rgba/hsl/hsla — 파싱 후 hex 또는 그대로 보존
|
|
511
|
+
- Material 3 토큰 (`var(--md-sys-color-primary)`) — board theme 적용 시점에 resolve
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## 5. 검증 (Validation) 정책
|
|
516
|
+
|
|
517
|
+
### 5.1 입력 검증
|
|
518
|
+
|
|
519
|
+
각 tool 의 검증 책임:
|
|
520
|
+
- 색상 — CSS color string 파싱 가능?
|
|
521
|
+
- 숫자 범위 — opacity 0..1, metalness 0..1, fontSize > 0 등
|
|
522
|
+
- 모드별 필수 필드 — `mode='gradient'` 면 `gradientStops` 또는 자동 생성
|
|
523
|
+
|
|
524
|
+
### 5.2 충돌 감지
|
|
525
|
+
|
|
526
|
+
- 데이터 바인딩 적용 컴포넌트 → 경고 + `force` 옵션 또는 에러
|
|
527
|
+
- Group / 잠긴 컴포넌트 → 정책 결정 필요
|
|
528
|
+
|
|
529
|
+
### 5.3 에러 메시지
|
|
530
|
+
|
|
531
|
+
AI 가 자동 회복 가능하도록:
|
|
532
|
+
```json
|
|
533
|
+
{
|
|
534
|
+
"error": "Invalid color value: 'reddish'. Did you mean 'red'? Valid: hex (#ff0000) or CSS named colors.",
|
|
535
|
+
"suggestion": "red"
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## 6. 구현 전략
|
|
542
|
+
|
|
543
|
+
### 6.1 파일 구성
|
|
544
|
+
|
|
545
|
+
```
|
|
546
|
+
packages/board-ai/server/service/
|
|
547
|
+
├── styling/
|
|
548
|
+
│ ├── index.ts — tool 등록 + dispatch
|
|
549
|
+
│ ├── color-utils.ts — 색상 파싱/정규화/시맨틱 매핑
|
|
550
|
+
│ ├── fill-tools.ts — setFill / mode 전환 로직
|
|
551
|
+
│ ├── stroke-tools.ts — setStroke / dash preset
|
|
552
|
+
│ ├── text-tools.ts — setText / setFont
|
|
553
|
+
│ ├── shadow-tools.ts — setShadow
|
|
554
|
+
│ ├── material-tools.ts — setMaterial / setShadowProperties (3D)
|
|
555
|
+
│ ├── opacity-visibility.ts — setOpacity / setVisible
|
|
556
|
+
│ ├── compose-tools.ts — copyStyle / harmonizeStyles
|
|
557
|
+
│ ├── read-tools.ts — getComponentStyle / summarizeStyles
|
|
558
|
+
│ ├── semantic-tools.ts — applyEmphasis / applySemantic / applyState
|
|
559
|
+
│ ├── preset-tools.ts — getStylePresets / applyStylePreset
|
|
560
|
+
│ ├── color-palette-tools.ts — extractColorPalette / recolor
|
|
561
|
+
│ └── 3d-advanced-tools.ts — setTexture / setEmissive / set3DTransform
|
|
562
|
+
├── styling-presets/
|
|
563
|
+
│ ├── system-presets.ts — 빌트인 preset 정의
|
|
564
|
+
│ └── semantic-tokens.ts — Material 3 시맨틱 매핑
|
|
565
|
+
└── assistant.ts — tool registry 통합
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### 6.2 dispatch 통합
|
|
569
|
+
|
|
570
|
+
각 tool 은 기존 `apply-patch.ts` 의 op 형태로 변환되어 적용. 새 op type 필요 없음 —
|
|
571
|
+
`modify` op 의 patch 부분으로 흡수 (기존 inverse 자동 생성 시스템 그대로 활용).
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
// 예: setFill(refids=[35], mode='solid', color='red')
|
|
575
|
+
// → BoardEditPatch:
|
|
576
|
+
{
|
|
577
|
+
ops: [
|
|
578
|
+
{ op: 'modify', refid: 35, patch: { fillStyle: { type: 'solid', color: '#ff0000' } } }
|
|
579
|
+
]
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
inverse 는 dispatcher 가 적용 직전 state 에서 자동 추출.
|
|
584
|
+
|
|
585
|
+
### 6.3 테스트 전략
|
|
586
|
+
|
|
587
|
+
각 tool 별:
|
|
588
|
+
- Unit: input validation, mode 전환, 색상 정규화
|
|
589
|
+
- Integration: apply → assertions on resulting state, inverse 정확성
|
|
590
|
+
- Edge: 데이터 바인딩 충돌, 빈 refids, 잘못된 mode
|
|
591
|
+
|
|
592
|
+
### 6.4 AI prompt 갱신
|
|
593
|
+
|
|
594
|
+
- Tool 별 description 에 사용 예시
|
|
595
|
+
- "AI: Fill 변경 시 setFill 우선 사용. modifyComponentByRefid 는 위 tool 들이 못
|
|
596
|
+
처리하는 fallback 만." 같은 가이드
|
|
597
|
+
- Semantic tool 우선 (Tier 3) → Tier 2 → Tier 1 → Tier 0 (raw modify)
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## 7. Phasing
|
|
602
|
+
|
|
603
|
+
| Phase | 범위 | 추정 | Deliverable |
|
|
604
|
+
|---|---|---|---|
|
|
605
|
+
| **P0** | 추가 audit (Component state 키 정리, 컴포넌트별 specific styling) + plan 보완 | 0.5 일 | 문서 갱신 |
|
|
606
|
+
| **P1** | Tier 1 — 10 tools 구현 + 테스트 | 2 일 | 핵심 styling 동작 |
|
|
607
|
+
| **P2** | Tier 2 — 5 tools (productivity) | 1 일 | copy / harmonize / read |
|
|
608
|
+
| **P3** | Tier 3 — 6 tools (semantic / theming) | 1.5 일 | applyEmphasis / Semantic / preset |
|
|
609
|
+
| **P4** | Tier 4 — 4 tools (3D advanced) | 1 일 | texture / emissive / mood |
|
|
610
|
+
| **P5** | AI prompt 갱신 + 통합 테스트 + 사용자 가이드 | 0.5 일 | 사용 준비 완료 |
|
|
611
|
+
|
|
612
|
+
**총 6 ~ 7 일** — 신중히 진행.
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## 8. 결정 필요 사항 (체크리스트)
|
|
617
|
+
|
|
618
|
+
- [ ] **Tier 5 (Animation/Interaction) 포함 여부** — 후순위? 별도 phase?
|
|
619
|
+
- [ ] **시맨틱 색 출처** — Material 3 시스템 토큰 vs board theme 자체 정의 vs 둘 다
|
|
620
|
+
- [ ] **데이터 바인딩 충돌 정책** — error / warn+force / auto-unbind
|
|
621
|
+
- [ ] **Group 컴포넌트 처리** — 자식까지 propagate? 자식만? 그룹 자체만?
|
|
622
|
+
- [ ] **Locked / readonly 컴포넌트** — error / 무시 / 경고
|
|
623
|
+
- [ ] **호버/액티브 스타일 영속 모델** — 별도 state 필드 vs 기존 변형
|
|
624
|
+
- [ ] **Color 입력 normalize 정책** — hex 변환 후 저장? 입력 형태 보존?
|
|
625
|
+
- [ ] **Multi-target 의 partial failure** — 일부 실패 시 전체 롤백 vs 성공한 것만 적용
|
|
626
|
+
- [ ] **사용자 정의 preset 영속** — 도메인별? 사용자별? 보드별?
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
## 9. 향후 확장 가능성
|
|
631
|
+
|
|
632
|
+
- **Style snapshot / version**: 컴포넌트의 스타일 시점별 저장 → 롤백
|
|
633
|
+
- **Style diff visualization**: 두 컴포넌트 / 두 시점 스타일 비교 시각화
|
|
634
|
+
- **AI 의 색감 학습**: 사용자가 자주 쓰는 색조합 → 추천
|
|
635
|
+
- **외부 디자인 시스템 import**: Figma 토큰 / Material 3 파일 가져오기
|
|
636
|
+
- **접근성 검증**: 대비비 (contrast ratio) 자동 체크 + 개선 제안
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## 10. 사용자에게 보일 결과 (예시)
|
|
641
|
+
|
|
642
|
+
**Before** (현재):
|
|
643
|
+
> 사용자: "이 모터들을 빨간 solid 배경으로 + 파란 dashed 1px 테두리로 + 0.5 투명도"
|
|
644
|
+
>
|
|
645
|
+
> AI: `modifyComponentByRefid(refid=35, patch={fillStyle:{type:'solid', color:'#ff0000', opacity:0.5}, strokeStyle:{color:'#0000ff', width:1, lineDash:[4,4]}})` × N번
|
|
646
|
+
>
|
|
647
|
+
> 토큰 많고 모드 전환 시 실수 빈도 높음.
|
|
648
|
+
|
|
649
|
+
**After** (구현 완료):
|
|
650
|
+
> AI: `setFill(refids=[35,36,37], mode='solid', color='red', opacity=0.5)` +
|
|
651
|
+
> `setStroke(refids=[35,36,37], color='blue', width=1, dash='dashed')`
|
|
652
|
+
>
|
|
653
|
+
> 토큰 1/3, 의도 명확, 모드 전환 안전, multi-target 한 번.
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
**다음 액션**: P0 (추가 audit) → P1 (Tier 1 구현) 시작. 결정 사항 (Section 8) 확인하며 진행.
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## 11. Critical Success Factors
|
|
662
|
+
|
|
663
|
+
> 이 기능의 성공이 board-ai 전체의 성공을 좌우한다.
|
|
664
|
+
> 단순 tool 25개 추가 ≠ 성공. 사용자가 자연어로 의도를 표현하면 AI 가 정확한
|
|
665
|
+
> tool 을 골라 정확한 인자로 호출, 결과가 의도와 일치 — 이게 됐을 때 성공.
|
|
666
|
+
|
|
667
|
+
### 11.1 Discoverability — AI 가 올바른 tool 을 고르게
|
|
668
|
+
|
|
669
|
+
25+ tool 중 AI 가 옳은 걸 고르려면:
|
|
670
|
+
|
|
671
|
+
**Tool naming convention**
|
|
672
|
+
- 동사 + 대상 일관: `setFill` / `setStroke` / `setText` (`update*`/`change*` 혼재 X)
|
|
673
|
+
- 시맨틱 tier 는 `apply*` 접두 (`applyEmphasis` / `applySemantic`) — Tier 구분 시각화
|
|
674
|
+
- 읽기는 `get*` (`getComponentStyle`)
|
|
675
|
+
- 일괄/관계는 `copy*` / `harmonize*` / `extract*`
|
|
676
|
+
|
|
677
|
+
**Tool description 의 우선순위 가이드**
|
|
678
|
+
각 tool description 에 명시:
|
|
679
|
+
```
|
|
680
|
+
"Use this when the user asks to change the BACKGROUND/FILL of components.
|
|
681
|
+
Prefer this over modifyComponentByRefid for fill changes — handles mode
|
|
682
|
+
transitions safely."
|
|
683
|
+
```
|
|
684
|
+
AI 는 description 의 "Use this when" 으로 매칭. 따라서 자연어 의도 키워드를 description 에 풍부하게.
|
|
685
|
+
|
|
686
|
+
**Tool grouping in system prompt**
|
|
687
|
+
```
|
|
688
|
+
Styling tools (in order of preference):
|
|
689
|
+
- Semantic: applySemantic, applyEmphasis, applyState
|
|
690
|
+
- Specific: setFill, setStroke, setFont, setShadow, setMaterial
|
|
691
|
+
- Read: getComponentStyle, summarizeStyles
|
|
692
|
+
- Compose: copyStyle, harmonizeStyles
|
|
693
|
+
- Raw fallback: modifyComponentByRefid
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### 11.2 AI prompting 전략
|
|
697
|
+
|
|
698
|
+
**원칙: AI 가 선호 순서를 학습하도록 system prompt 가 명시**
|
|
699
|
+
|
|
700
|
+
```
|
|
701
|
+
1. 사용자 의도가 시맨틱하면 (강조 / 경고 / 비활성)
|
|
702
|
+
→ applySemantic / applyEmphasis / applyState 우선
|
|
703
|
+
2. 명시적 색/굵기 지정이면
|
|
704
|
+
→ setFill / setStroke / setFont
|
|
705
|
+
3. 여러 컴포넌트 동일 변경이면
|
|
706
|
+
→ 한 번의 tool call 에 refids 배열로
|
|
707
|
+
4. 한 컴포넌트 → 다른 컴포넌트 같게
|
|
708
|
+
→ copyStyle
|
|
709
|
+
5. 위 어디에도 안 맞으면
|
|
710
|
+
→ modifyComponentByRefid (raw fallback)
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
**현재 상태 인지**
|
|
714
|
+
AI 가 변경 전에 `getComponentStyle` / `summarizeStyles` 호출 → 현재 상태 파악 후
|
|
715
|
+
의도적 변경. "이미 빨간 모터를 더 빨갛게 해줘" 같은 모호 케이스 정확히 처리.
|
|
716
|
+
|
|
717
|
+
**색상 입력 변환**
|
|
718
|
+
AI 가 "warm red" 같은 자연어 색을 입력 시 tool 이 자체 변환 — color-utils 가 시맨틱
|
|
719
|
+
이름 → hex 매핑. AI prompt 에 "named colors are accepted: red, warm-red, ..." 명시.
|
|
720
|
+
|
|
721
|
+
### 11.3 사용자 시나리오 (실제)
|
|
722
|
+
|
|
723
|
+
**Scenario 1 — 일괄 디자인 변경**
|
|
724
|
+
> "이 대시보드의 게이지 3개를 모두 다크 배경 + 청록 강조 + 그림자 살짝"
|
|
725
|
+
>
|
|
726
|
+
> AI:
|
|
727
|
+
> 1. `findComponents(query='gauge')` → refids
|
|
728
|
+
> 2. `setFill(refids, mode='solid', color='#0a1929')`
|
|
729
|
+
> 3. `setStroke(refids, color='#06b6d4', width=2)`
|
|
730
|
+
> 4. `setShadow(refids, blur=8, offsetY=2, color='rgba(0,0,0,0.3)')`
|
|
731
|
+
|
|
732
|
+
**Scenario 2 — 시맨틱**
|
|
733
|
+
> "이 알람 영역들 위험 표시 강조"
|
|
734
|
+
>
|
|
735
|
+
> AI:
|
|
736
|
+
> 1. `findComponents(query='alarm')`
|
|
737
|
+
> 2. `applySemantic(refids, intent='error', variant='filled')`
|
|
738
|
+
> 3. `applyEmphasis(refids, level='strong')`
|
|
739
|
+
|
|
740
|
+
**Scenario 3 — 반복 작업 (copy 패턴)**
|
|
741
|
+
> "1번 모터처럼 다른 모터들도 같게 만들어줘"
|
|
742
|
+
>
|
|
743
|
+
> AI:
|
|
744
|
+
> 1. `findComponents(query='motor')` → refids
|
|
745
|
+
> 2. `copyStyle(sourceRefid=35, targetRefids=[36,37,38], aspects=['fill','stroke'])`
|
|
746
|
+
|
|
747
|
+
**Scenario 4 — 점진 조정**
|
|
748
|
+
> "조금 더 부드럽게" (이전 대화: 빨강 적용)
|
|
749
|
+
>
|
|
750
|
+
> AI:
|
|
751
|
+
> 1. `getComponentStyle(refid=35)` — 현재 색 #ff0000 파악
|
|
752
|
+
> 2. `setFill(refids=[35], color='#ff6666', opacity=0.85)` — saturation/lightness 조정
|
|
753
|
+
|
|
754
|
+
**Scenario 5 — 3D 재질 시맨틱**
|
|
755
|
+
> "이 트랙들은 닳은 고무 느낌으로"
|
|
756
|
+
>
|
|
757
|
+
> AI:
|
|
758
|
+
> 1. `setMaterial(refids, preset='rubber', roughness=0.95)` — preset + roughness override
|
|
759
|
+
|
|
760
|
+
**Scenario 6 — 모드 전환**
|
|
761
|
+
> "이 영역 단색 빨강 대신 위→아래 빨강→어두운빨강 그라디언트"
|
|
762
|
+
>
|
|
763
|
+
> AI:
|
|
764
|
+
> 1. `setFill(refids=[35], mode='gradient', gradientType='linear', rotation=90,
|
|
765
|
+
> gradientStops=[{color:'#ff0000', position:0},{color:'#660000', position:1}])`
|
|
766
|
+
|
|
767
|
+
### 11.4 품질 지표 (성공 측정)
|
|
768
|
+
|
|
769
|
+
**Tool 사용 정확도**
|
|
770
|
+
- AI 가 첫 호출에 올바른 tool 선택 비율 — 목표 ≥ 90%
|
|
771
|
+
- modifyComponentByRefid (raw fallback) 사용 비율 — 목표 ≤ 5% (styling 영역에서)
|
|
772
|
+
- 평균 tool call 수 / 의도 — 단순할수록 좋음 (한 의도 1~2 call)
|
|
773
|
+
|
|
774
|
+
**사용자 만족도**
|
|
775
|
+
- "다시 시도" / 수정 요청 빈도 — 낮을수록
|
|
776
|
+
- 완료 후 사용자 직접 후가공 빈도 — 낮을수록
|
|
777
|
+
|
|
778
|
+
**견고성**
|
|
779
|
+
- 잘못된 색/숫자 입력 시 graceful 회복 — AI 가 에러 보고 재시도 성공
|
|
780
|
+
- Mode 전환 시 의도치 않은 결과 — 0 목표
|
|
781
|
+
|
|
782
|
+
**관찰 방법**
|
|
783
|
+
- ChatMessage 의 toolUsages 에 styling tool 사용 trace 영속됨 (이미 구현)
|
|
784
|
+
- 추후 dashboard / 분석으로 위 지표 측정 가능
|
|
785
|
+
|
|
786
|
+
### 11.5 디테일 매개체 — Color Utils
|
|
787
|
+
|
|
788
|
+
`color-utils.ts` 가 핵심 보조. 잘 만들면 AI 호출이 자연스러움:
|
|
789
|
+
|
|
790
|
+
**파싱 / 정규화**
|
|
791
|
+
- hex / hex-short / rgb / rgba / hsl / hsla / 명명 색 / Material 3 토큰
|
|
792
|
+
- → 정규 hex (저장용) / hex+alpha 분리 (opacity 별도)
|
|
793
|
+
|
|
794
|
+
**시맨틱 매핑**
|
|
795
|
+
- 'warm-red' → `#e74c3c`
|
|
796
|
+
- 'forest-green' → `#228b22`
|
|
797
|
+
- 색 위계 (light/dark/muted/vibrant) — `lighten('red', 30%)` / `darken` / `desaturate`
|
|
798
|
+
|
|
799
|
+
**색상 추론**
|
|
800
|
+
- `complement(color)` — 보색
|
|
801
|
+
- `analogous(color, n)` — 인접 색
|
|
802
|
+
- `pickContrast(bg, options)` — 가독성 좋은 텍스트 색
|
|
803
|
+
|
|
804
|
+
**Material 3 토큰 resolve**
|
|
805
|
+
- `--md-sys-color-primary` → 현재 board theme 의 hex
|
|
806
|
+
|
|
807
|
+
이 utils 가 두꺼울수록 AI 가 자연어 색상 표현 자유로워짐.
|
|
808
|
+
|
|
809
|
+
### 11.6 Iteration loop 존중
|
|
810
|
+
|
|
811
|
+
사용자가 한 번에 끝내는 경우 거의 없음 — 점진 조정이 정상.
|
|
812
|
+
|
|
813
|
+
**상태 보존 패턴**
|
|
814
|
+
- AI 가 "조금 더 어둡게" 하면 → 직전 상태에서 -10% lightness, 다른 속성 보존
|
|
815
|
+
- AI 가 시맨틱 후 raw 조정 → 시맨틱 메타 보존 (다음에 시맨틱 변경 시 raw 변경 무시 X)
|
|
816
|
+
|
|
817
|
+
이를 위해 `getComponentStyle` 응답에 "마지막 styling op type" 메타 같이 — 이건 P3
|
|
818
|
+
이후 검토.
|
|
819
|
+
|
|
820
|
+
### 11.7 Anti-patterns (금지)
|
|
821
|
+
|
|
822
|
+
- ❌ Tool 마다 input 형식 제멋대로 (어떤 건 `refid: number`, 어떤 건 `target: id` — 일관성 X)
|
|
823
|
+
- ❌ Mode 전환 시 partial merge 시도 (예: solid → gradient 인데 기존 color 만 바꾸려 함)
|
|
824
|
+
- ❌ "이 tool 은 fillStyle 만 다룸" 같은 좁은 description — AI 가 의도 매칭 못 함
|
|
825
|
+
- ❌ AI 가 raw modifyComponentByRefid 자주 사용 → 시맨틱 tool 가이드 부족
|
|
826
|
+
- ❌ Validation 에러를 raw "Bad input" 메시지로 — 자동 회복 불가
|
|
827
|
+
|
|
828
|
+
### 11.8 무엇이 "완성" 인가
|
|
829
|
+
|
|
830
|
+
> "사용자가 디자인을 자연어로 설명하면, AI 가 우리 tool 군으로 정확히 표현하고,
|
|
831
|
+
> 결과가 사용자 의도와 90%+ 일치한다."
|
|
832
|
+
|
|
833
|
+
이게 안 되면 AI 가치 = 0. 위 11.1 ~ 11.6 항목들이 다 작동해야 비로소 "완성".
|
|
834
|
+
|
|
835
|
+
---
|
|
836
|
+
|
|
837
|
+
## 12. **데이터 바인딩 styling — 생산성의 핵심**
|
|
838
|
+
|
|
839
|
+
> 정적 스타일링은 시작점일 뿐. **스타일과 데이터가 결합된 동적 표현**이 things-scene
|
|
840
|
+
> 의 진짜 가치이고, AI 가 이걸 자연스럽게 다루면 사용자 생산성이 폭증한다.
|
|
841
|
+
> 동시에 가장 어려운 영역 — 룰 / accessor / converter / 조건 / 상태 모두 다뤄야.
|
|
842
|
+
|
|
843
|
+
### 12.1 things-scene 의 데이터 바인딩 모델
|
|
844
|
+
|
|
845
|
+
`src/interfaces/data.ts:4` `Mapping`:
|
|
846
|
+
|
|
847
|
+
```ts
|
|
848
|
+
interface Mapping {
|
|
849
|
+
rule: string // 평가식 (값 가공 — jsonata-style 또는 함수)
|
|
850
|
+
target: string // 적용 대상 — 'fillStyle' / 'strokeStyle' / '[fillStyle:color]'
|
|
851
|
+
property?: string // 타입 힌트 — 'fillStyle' / 'strokeStyle' / 'ref' 등
|
|
852
|
+
accessor?: string // 데이터 소스 경로 — 'tags.MOTOR1.value'
|
|
853
|
+
source?: any // 데이터 소스 참조
|
|
854
|
+
converter?: string // 추가 변환 함수
|
|
855
|
+
param?: any // 룰에 전달되는 파라미터
|
|
856
|
+
}
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
컴포넌트의 `state.mappings: Mapping[]` 으로 영속. 런타임에 `_mappings` 가 binding
|
|
860
|
+
객체로 활성화되어 데이터 변경 시 자동 styling 업데이트.
|
|
861
|
+
|
|
862
|
+
**Target 표현법**:
|
|
863
|
+
- `fillStyle` — fillStyle 전체 교체
|
|
864
|
+
- `[fillStyle:color]` — fillStyle.color 만 변경 (sub-property)
|
|
865
|
+
- `[strokeStyle:width]` — strokeStyle.width
|
|
866
|
+
- `[material3d:emissive]` — 3D 발광색
|
|
867
|
+
- `hidden` — 표시 여부
|
|
868
|
+
- `text` — 텍스트 내용
|
|
869
|
+
|
|
870
|
+
### 12.2 Common 시나리오 (실제 사용)
|
|
871
|
+
|
|
872
|
+
#### 시나리오 A — 임계치 색상
|
|
873
|
+
> "온도 80도 넘으면 빨강, 50~80은 주황, 그 아래는 초록"
|
|
874
|
+
|
|
875
|
+
이 한 문장이 mapping 으로 변환:
|
|
876
|
+
```js
|
|
877
|
+
{
|
|
878
|
+
accessor: 'tags.TEMP.value',
|
|
879
|
+
target: '[fillStyle:color]',
|
|
880
|
+
rule: 'value > 80 ? "#ef4444" : value > 50 ? "#f59e0b" : "#22c55e"'
|
|
881
|
+
}
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
#### 시나리오 B — 값-색상 그라디언트 매핑
|
|
885
|
+
> "0~100 값을 파랑→빨강 색 스펙트럼으로"
|
|
886
|
+
|
|
887
|
+
```js
|
|
888
|
+
{
|
|
889
|
+
accessor: 'tags.LEVEL.value',
|
|
890
|
+
target: '[fillStyle:color]',
|
|
891
|
+
rule: 'interpolateColor(value, 0, 100, "#3b82f6", "#ef4444")'
|
|
892
|
+
}
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
#### 시나리오 C — 상태 기반 발광 (3D)
|
|
896
|
+
> "MOTOR 가 running 일 때 노란 발광, stopped 면 끄기"
|
|
897
|
+
|
|
898
|
+
```js
|
|
899
|
+
{
|
|
900
|
+
accessor: 'tags.MOTOR.status',
|
|
901
|
+
target: '[material3d:emissive]',
|
|
902
|
+
rule: 'value === "running" ? "#fde047" : "#000000"'
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
accessor: 'tags.MOTOR.status',
|
|
906
|
+
target: '[material3d:emissiveIntensity]',
|
|
907
|
+
rule: 'value === "running" ? 2 : 0'
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
#### 시나리오 D — 알람 시 표시 / 깜빡임
|
|
912
|
+
> "ALARM 활성 시 빨간 테두리 굵게 + 점멸"
|
|
913
|
+
|
|
914
|
+
```js
|
|
915
|
+
{
|
|
916
|
+
accessor: 'tags.ALARM.value',
|
|
917
|
+
target: '[strokeStyle:color]',
|
|
918
|
+
rule: 'value ? "#dc2626" : "#94a3b8"'
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
accessor: 'tags.ALARM.value',
|
|
922
|
+
target: '[strokeStyle:width]',
|
|
923
|
+
rule: 'value ? 3 : 1'
|
|
924
|
+
},
|
|
925
|
+
// 점멸은 animation API 와 결합 — 별 phase
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
#### 시나리오 E — 조건부 가시성
|
|
929
|
+
> "REJECT 갯수가 0이면 알람 패널 숨김"
|
|
930
|
+
|
|
931
|
+
```js
|
|
932
|
+
{
|
|
933
|
+
accessor: 'tags.REJECT_COUNT.value',
|
|
934
|
+
target: 'hidden',
|
|
935
|
+
rule: 'value === 0'
|
|
936
|
+
}
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
#### 시나리오 F — 텍스트 + 색상 동시
|
|
940
|
+
> "현재 가동률을 큰 글자로 보여주고, 90% 미만이면 빨간색"
|
|
941
|
+
|
|
942
|
+
```js
|
|
943
|
+
[
|
|
944
|
+
{ accessor: 'tags.UTIL.value', target: 'text', rule: 'value + "%"' },
|
|
945
|
+
{ accessor: 'tags.UTIL.value', target: '[fontColor]',
|
|
946
|
+
rule: 'value < 90 ? "#ef4444" : "#0f172a"' }
|
|
947
|
+
]
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
### 12.3 Tool 카탈로그 — 데이터 바인딩 styling
|
|
951
|
+
|
|
952
|
+
**대원칙**: AI 가 "온도 80 넘으면 빨강" 같은 자연어 → 정확한 mapping 객체로 변환.
|
|
953
|
+
정적 styling tool 의 high-level 시맨틱과 **동일한 톤** 으로 설계. 차이는 "값에 따라"
|
|
954
|
+
가 들어가는 것.
|
|
955
|
+
|
|
956
|
+
#### Tier B1 — Read
|
|
957
|
+
|
|
958
|
+
##### `getDataBindings`
|
|
959
|
+
컴포넌트의 현재 mapping 들 조회 (요약 포함).
|
|
960
|
+
|
|
961
|
+
```ts
|
|
962
|
+
input: { refid: number }
|
|
963
|
+
output: Array<{
|
|
964
|
+
index: number
|
|
965
|
+
target: string // 'fillStyle' / '[fillStyle:color]' 등
|
|
966
|
+
accessor: string // 데이터 소스 경로
|
|
967
|
+
rule: string // 평가식 요약 (긴 경우 압축)
|
|
968
|
+
ruleType: 'threshold' | 'range-map' | 'state-map' | 'expression' // AI 가 의도 추론 가능
|
|
969
|
+
description: string // 사람 친화 설명 — "TEMP 값이 80 초과 시 빨강"
|
|
970
|
+
}>
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
##### `summarizeDataBindings`
|
|
974
|
+
여러 컴포넌트의 binding 패턴 요약 — AI 토큰 절감.
|
|
975
|
+
|
|
976
|
+
```ts
|
|
977
|
+
input: { refids: number[] }
|
|
978
|
+
output: string // "5개 컴포넌트가 모두 tags.LEVEL.value 의 임계치 (>80 빨강) 로 fillStyle 바인딩"
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
##### `findComponentsWithBinding`
|
|
982
|
+
특정 데이터 소스 사용하는 컴포넌트 검색.
|
|
983
|
+
|
|
984
|
+
```ts
|
|
985
|
+
input: {
|
|
986
|
+
accessor?: string // 'tags.TEMP.*' (와일드카드)
|
|
987
|
+
targetPattern?: string // 'fillStyle' / '[material3d:*]'
|
|
988
|
+
}
|
|
989
|
+
output: Array<{ refid, target, rule }>
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
#### Tier B2 — Create / Modify (semantic high-level)
|
|
993
|
+
|
|
994
|
+
##### `bindThresholdStyle`
|
|
995
|
+
**가장 자주 쓸 tool**. 임계치 기반 색상/속성 매핑.
|
|
996
|
+
|
|
997
|
+
```ts
|
|
998
|
+
input: {
|
|
999
|
+
refids: number[]
|
|
1000
|
+
accessor: string // 데이터 소스 경로
|
|
1001
|
+
target: 'fill' | 'stroke' | 'emissive' | 'border-color' | 'text-color' | 'opacity' | string
|
|
1002
|
+
thresholds: Array<{ above: number; value: any; label?: string }>
|
|
1003
|
+
default?: any // 어느 임계치도 안 맞으면
|
|
1004
|
+
}
|
|
1005
|
+
// 예: above 80 → red, above 50 → orange, default → green
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
서버가 자동으로 `Mapping` 객체로 변환 (rule expression 생성).
|
|
1009
|
+
|
|
1010
|
+
##### `bindValueRangeStyle`
|
|
1011
|
+
값 범위 → 연속 색상 그라디언트.
|
|
1012
|
+
|
|
1013
|
+
```ts
|
|
1014
|
+
input: {
|
|
1015
|
+
refids: number[]
|
|
1016
|
+
accessor: string
|
|
1017
|
+
target: 'fill' | 'emissive' | string
|
|
1018
|
+
range: { min: number; max: number }
|
|
1019
|
+
colors: { atMin: string; atMax: string; atMid?: string }
|
|
1020
|
+
// 또는 colorStops: [{ position, color }]
|
|
1021
|
+
}
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
##### `bindStateStyle`
|
|
1025
|
+
상태 (state machine) 기반 매핑.
|
|
1026
|
+
|
|
1027
|
+
```ts
|
|
1028
|
+
input: {
|
|
1029
|
+
refids: number[]
|
|
1030
|
+
accessor: string
|
|
1031
|
+
target: string
|
|
1032
|
+
states: Record<string, any> // { running: '#fde047', stopped: '#000000', error: '#ef4444' }
|
|
1033
|
+
default?: any
|
|
1034
|
+
}
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
##### `bindConditionalVisibility`
|
|
1038
|
+
값 조건 → 표시/숨김.
|
|
1039
|
+
|
|
1040
|
+
```ts
|
|
1041
|
+
input: {
|
|
1042
|
+
refids: number[]
|
|
1043
|
+
accessor: string
|
|
1044
|
+
condition: 'truthy' | 'falsy' | 'equals' | 'not-equals' | 'gt' | 'lt' | 'between' | 'in' | 'expression'
|
|
1045
|
+
param?: any // condition 별 인자
|
|
1046
|
+
hideWhen: 'matches' | 'not-matches'
|
|
1047
|
+
}
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
##### `bindTextValue`
|
|
1051
|
+
값 → 텍스트 (포맷 옵션 포함).
|
|
1052
|
+
|
|
1053
|
+
```ts
|
|
1054
|
+
input: {
|
|
1055
|
+
refids: number[]
|
|
1056
|
+
accessor: string
|
|
1057
|
+
format?: 'number' | 'percent' | 'currency' | 'date' | 'time' | 'custom'
|
|
1058
|
+
customFormat?: string // e.g., '{value} ℃' 또는 strftime-like
|
|
1059
|
+
precision?: number // 소수점 자릿수
|
|
1060
|
+
}
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
##### `bindExpression` (raw escape hatch)
|
|
1064
|
+
임의 룰 — 위 시맨틱 tool 로 표현 안 되는 케이스.
|
|
1065
|
+
|
|
1066
|
+
```ts
|
|
1067
|
+
input: {
|
|
1068
|
+
refids: number[]
|
|
1069
|
+
accessor: string
|
|
1070
|
+
target: string
|
|
1071
|
+
rule: string // jsonata 또는 함수 표현식
|
|
1072
|
+
description?: string // AI 가 의도 기록 (디버깅용)
|
|
1073
|
+
}
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
#### Tier B3 — Remove / Inspect
|
|
1077
|
+
|
|
1078
|
+
##### `removeBinding`
|
|
1079
|
+
특정 binding 제거.
|
|
1080
|
+
|
|
1081
|
+
```ts
|
|
1082
|
+
input: {
|
|
1083
|
+
refid: number
|
|
1084
|
+
target?: string // 특정 target 만 / 미지정 시 모든 binding
|
|
1085
|
+
index?: number // 특정 index 의 binding (precise)
|
|
1086
|
+
}
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
##### `clearBindings`
|
|
1090
|
+
모든 binding 제거 (정적 styling 으로 복귀).
|
|
1091
|
+
|
|
1092
|
+
```ts
|
|
1093
|
+
input: { refids: number[] }
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
##### `replaceBinding`
|
|
1097
|
+
기존 binding 통째 교체 (안전한 in-place 수정).
|
|
1098
|
+
|
|
1099
|
+
```ts
|
|
1100
|
+
input: {
|
|
1101
|
+
refid: number
|
|
1102
|
+
index: number
|
|
1103
|
+
newBinding: { accessor, target, rule, ... }
|
|
1104
|
+
}
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
#### Tier B4 — Composite
|
|
1108
|
+
|
|
1109
|
+
##### `copyBindings`
|
|
1110
|
+
한 컴포넌트의 binding 들을 다른 컴포넌트들에 복제.
|
|
1111
|
+
|
|
1112
|
+
```ts
|
|
1113
|
+
input: {
|
|
1114
|
+
sourceRefid: number
|
|
1115
|
+
targetRefids: number[]
|
|
1116
|
+
rebindAccessor?: { from: string; to: string }
|
|
1117
|
+
// 예: tags.MOTOR1.value → tags.MOTOR{n}.value 식 패턴
|
|
1118
|
+
}
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
##### `harmonizeBindings`
|
|
1122
|
+
여러 컴포넌트의 styling binding 통일 — 일관된 임계치/색상.
|
|
1123
|
+
|
|
1124
|
+
```ts
|
|
1125
|
+
input: {
|
|
1126
|
+
refids: number[]
|
|
1127
|
+
master: number // 이 컴포넌트의 binding 을 기준으로
|
|
1128
|
+
aspects?: Array<'fill' | 'stroke' | 'visibility' | ...>
|
|
1129
|
+
}
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
##### `convertStaticToBinding`
|
|
1133
|
+
정적 styling 을 데이터 바인딩으로 전환 — 디자인 → 동적 변환 헬퍼.
|
|
1134
|
+
|
|
1135
|
+
```ts
|
|
1136
|
+
input: {
|
|
1137
|
+
refids: number[]
|
|
1138
|
+
aspect: 'fill' | 'stroke' | 'visibility' | ...
|
|
1139
|
+
accessor: string
|
|
1140
|
+
// 자동 추론: 현재 색상 보존 + accessor 의 임계치 고민 → AI 와 협업해 thresholds 결정
|
|
1141
|
+
}
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
### 12.4 핵심 도전 과제
|
|
1145
|
+
|
|
1146
|
+
#### Rule expression 생성
|
|
1147
|
+
|
|
1148
|
+
AI 가 자연어 → `rule` (코드) 로 변환해야 하는데:
|
|
1149
|
+
- jsonata 와 일반 JS 표현식 구별
|
|
1150
|
+
- 보안: arbitrary code 실행 방지 — sandbox / whitelist
|
|
1151
|
+
- 검증: 평가 시도 + 에러 잡기
|
|
1152
|
+
- AI 가 자체 생성한 rule 이 동작하는지 server-side 사전 평가 필요
|
|
1153
|
+
|
|
1154
|
+
**해결**:
|
|
1155
|
+
- 시맨틱 tool (bindThresholdStyle 등) 이 rule 을 자동 생성 — AI 는 raw rule 거의 안 씀
|
|
1156
|
+
- bindExpression (raw) 만 rule 받지만 server 가 sandbox 평가 + 결과 타입 검증
|
|
1157
|
+
|
|
1158
|
+
#### Accessor 자동완성
|
|
1159
|
+
|
|
1160
|
+
사용자: "MOTOR1 의 값으로 색 바꿔줘"
|
|
1161
|
+
AI: 어떤 accessor 가 MOTOR1 인지 모름. → `findDataSources(query)` tool 필요.
|
|
1162
|
+
|
|
1163
|
+
##### `findDataSources` (B1 추가)
|
|
1164
|
+
```ts
|
|
1165
|
+
input: { query: string; limit?: number }
|
|
1166
|
+
output: Array<{ accessor: string; type: string; sample?: any; description?: string }>
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
things-factory 의 데이터 시스템과 연동 — tags / variables / metrics / sensors 등.
|
|
1170
|
+
이 tool 의 품질이 binding 정확도 결정.
|
|
1171
|
+
|
|
1172
|
+
#### Type 호환성
|
|
1173
|
+
|
|
1174
|
+
target `fillStyle` 은 string 또는 객체 받음. AI 가 임계치로 `'#ff0000'` 같은 string
|
|
1175
|
+
을 정확히 생성해야. type hint (mapping.property) 가 helping.
|
|
1176
|
+
|
|
1177
|
+
#### Mapping 충돌
|
|
1178
|
+
|
|
1179
|
+
같은 컴포넌트에 fillStyle 과 [fillStyle:color] 동시 binding 시 충돌. server 가 감지 +
|
|
1180
|
+
경고 + 자동 정리 옵션.
|
|
1181
|
+
|
|
1182
|
+
### 12.5 사용자 시나리오 (binding)
|
|
1183
|
+
|
|
1184
|
+
**Scenario 1 — 한 줄 자연어 → 작동하는 dashboard**
|
|
1185
|
+
> "이 게이지 5개를 각자의 sensor 값에 맞게 0~100 범위 파랑→빨강 그라디언트로"
|
|
1186
|
+
|
|
1187
|
+
AI:
|
|
1188
|
+
1. `findDataSources(query='sensor')` → [tags.S1.value, tags.S2.value, ...]
|
|
1189
|
+
2. `findComponents(query='gauge')` → [refids]
|
|
1190
|
+
3. `bindValueRangeStyle(refids[i], accessor='tags.S{i}.value', target='fill',
|
|
1191
|
+
range={min:0,max:100}, colors={atMin:'#3b82f6',atMax:'#ef4444'})`
|
|
1192
|
+
|
|
1193
|
+
**Scenario 2 — 기존 mapping 패턴 복제**
|
|
1194
|
+
> "MOTOR1 처럼 다른 모터들도 같은 룰로 emissive 변경"
|
|
1195
|
+
|
|
1196
|
+
AI:
|
|
1197
|
+
1. `getDataBindings(refid=motor1_refid)` → 룰 파악
|
|
1198
|
+
2. `copyBindings(sourceRefid=motor1, targetRefids=[m2,m3,m4],
|
|
1199
|
+
rebindAccessor={from:'tags.MOTOR1.', to:'tags.MOTOR{n}.'})`
|
|
1200
|
+
|
|
1201
|
+
**Scenario 3 — 디자인 → 동적 전환**
|
|
1202
|
+
> "이 빨간 박스들을 ALARM 값에 따라 켜지는 걸로 바꿔"
|
|
1203
|
+
|
|
1204
|
+
AI:
|
|
1205
|
+
1. `getComponentStyle(refid)` → 현재 빨강 #ef4444 파악
|
|
1206
|
+
2. `convertStaticToBinding(refids, aspect='fill', accessor='tags.ALARM.value')`
|
|
1207
|
+
3. AI 가 임계치 추론 (값 truthy 일 때 빨강, falsy 일 때 회색) → `bindStateStyle`
|
|
1208
|
+
호출 with `{1: '#ef4444', 0: '#94a3b8'}`
|
|
1209
|
+
|
|
1210
|
+
### 12.6 binding tool 의 phasing
|
|
1211
|
+
|
|
1212
|
+
기존 plan 에 추가:
|
|
1213
|
+
|
|
1214
|
+
| Phase | 추가 범위 | 추정 |
|
|
1215
|
+
|---|---|---|
|
|
1216
|
+
| **PB0** | data-binding 모델 정밀 audit (evaluator / accessor / converter 동작) | 0.5 일 |
|
|
1217
|
+
| **PB1** | Tier B1 (read) — getDataBindings / summarize / findComponentsWithBinding / findDataSources | 1 일 |
|
|
1218
|
+
| **PB2** | Tier B2 (create) — bindThresholdStyle / bindValueRangeStyle / bindStateStyle / bindConditionalVisibility / bindTextValue | 2 일 |
|
|
1219
|
+
| **PB3** | Tier B3 (remove) + Tier B4 (composite — copyBindings / convertStaticToBinding) | 1 일 |
|
|
1220
|
+
| **PB4** | bindExpression (raw) + sandbox 평가 + 검증 | 1 일 |
|
|
1221
|
+
| **PB5** | AI prompt 갱신 + 시나리오 통합 테스트 | 0.5 일 |
|
|
1222
|
+
|
|
1223
|
+
**총 binding 부분 6 일** + 정적 styling 6~7 일 = **약 12~13일** 의 풀 풀 plan.
|
|
1224
|
+
|
|
1225
|
+
### 12.7 왜 이게 게임 체인저인가
|
|
1226
|
+
|
|
1227
|
+
**정적 styling 만**: AI 는 멋진 디자인 만들 수 있음. 그러나 "운영 화면" 으로 가면
|
|
1228
|
+
값에 따라 변하는 표현이 필수. AI 가 정적까지만 하면 사용자가 손으로 binding 작업 →
|
|
1229
|
+
AI 의 도움 가치 50%.
|
|
1230
|
+
|
|
1231
|
+
**Binding 까지 잘하면**: 사용자 한 문장 → 동적 dashboard 완성. 디자인 + 데이터 흐름
|
|
1232
|
+
같이 자동화. 진짜 "operator AI" 가 됨. 생산성 차이 10배 이상.
|
|
1233
|
+
|
|
1234
|
+
특히 things-factory 의 도메인 (제조 / 공정 / 시설 모니터링) 에서는 정적 디자인 가치
|
|
1235
|
+
보다 **동적 운영 화면** 가치가 훨씬 큼. binding tool 이 약하면 board-ai 는 디자인
|
|
1236
|
+
도구에 머물고, 운영 도구로 못 큼.
|
|
1237
|
+
|
|
1238
|
+
### 12.8 priorities — 정적 vs binding
|
|
1239
|
+
|
|
1240
|
+
**같이 가야 함**. 둘 중 하나만 잘 만들면 의미 적음:
|
|
1241
|
+
- 정적 styling 좋고 binding 약함 → "예쁜 게 다는 아니다" — 사용자 실망
|
|
1242
|
+
- binding 좋고 정적 styling 약함 → "기능은 되는데 못생겼다" — 사용자 실망
|
|
1243
|
+
|
|
1244
|
+
**제 권고 phasing**:
|
|
1245
|
+
1. P1 정적 Tier 1 (10 tools) ← 시각적 즉각 가치
|
|
1246
|
+
2. PB1 binding read tools ← AI 가 컨텍스트 파악
|
|
1247
|
+
3. PB2 binding create (high-level — threshold/range/state) ← 핵심 가치
|
|
1248
|
+
4. P2~P3 정적 productivity & semantic
|
|
1249
|
+
5. PB3+ binding 마무리
|
|
1250
|
+
|
|
1251
|
+
순환식 — 정적과 binding 을 번갈아 layer 쌓는 게 양쪽 다 단단해짐.
|
|
1252
|
+
|
|
1253
|
+
---
|