@things-factory/board-ai 10.0.0-beta.64

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.
Files changed (95) hide show
  1. package/client/components/board-ai-chat.test.ts +120 -0
  2. package/client/components/board-ai-chat.ts +1502 -0
  3. package/client/components/chat-input-builder.ts +40 -0
  4. package/client/components/markdown.test.ts +220 -0
  5. package/client/components/markdown.ts +184 -0
  6. package/client/index.ts +11 -0
  7. package/client/tsconfig.json +13 -0
  8. package/client/utils/board-edit-patch.ts +200 -0
  9. package/config/config.development.js +43 -0
  10. package/config/config.production.js +15 -0
  11. package/dist-client/components/board-ai-chat.d.ts +127 -0
  12. package/dist-client/components/board-ai-chat.js +1455 -0
  13. package/dist-client/components/board-ai-chat.js.map +1 -0
  14. package/dist-client/components/board-ai-chat.test.d.ts +1 -0
  15. package/dist-client/components/board-ai-chat.test.js +112 -0
  16. package/dist-client/components/board-ai-chat.test.js.map +1 -0
  17. package/dist-client/components/chat-input-builder.d.ts +30 -0
  18. package/dist-client/components/chat-input-builder.js +25 -0
  19. package/dist-client/components/chat-input-builder.js.map +1 -0
  20. package/dist-client/components/markdown.d.ts +16 -0
  21. package/dist-client/components/markdown.js +167 -0
  22. package/dist-client/components/markdown.js.map +1 -0
  23. package/dist-client/components/markdown.test.d.ts +1 -0
  24. package/dist-client/components/markdown.test.js +187 -0
  25. package/dist-client/components/markdown.test.js.map +1 -0
  26. package/dist-client/index.d.ts +11 -0
  27. package/dist-client/index.js +12 -0
  28. package/dist-client/index.js.map +1 -0
  29. package/dist-client/tsconfig.tsbuildinfo +1 -0
  30. package/dist-client/utils/board-edit-patch.d.ts +73 -0
  31. package/dist-client/utils/board-edit-patch.js +159 -0
  32. package/dist-client/utils/board-edit-patch.js.map +1 -0
  33. package/dist-server/index.d.ts +21 -0
  34. package/dist-server/index.js +25 -0
  35. package/dist-server/index.js.map +1 -0
  36. package/dist-server/service/apply-patch.d.ts +46 -0
  37. package/dist-server/service/apply-patch.js +211 -0
  38. package/dist-server/service/apply-patch.js.map +1 -0
  39. package/dist-server/service/assistant.d.ts +75 -0
  40. package/dist-server/service/assistant.js +1298 -0
  41. package/dist-server/service/assistant.js.map +1 -0
  42. package/dist-server/service/board-ai-resolver.d.ts +40 -0
  43. package/dist-server/service/board-ai-resolver.js +260 -0
  44. package/dist-server/service/board-ai-resolver.js.map +1 -0
  45. package/dist-server/service/chat-message/chat-message.d.ts +24 -0
  46. package/dist-server/service/chat-message/chat-message.js +108 -0
  47. package/dist-server/service/chat-message/chat-message.js.map +1 -0
  48. package/dist-server/service/chat-message/index.d.ts +3 -0
  49. package/dist-server/service/chat-message/index.js +7 -0
  50. package/dist-server/service/chat-message/index.js.map +1 -0
  51. package/dist-server/service/chat-session/chat-session.d.ts +22 -0
  52. package/dist-server/service/chat-session/chat-session.js +109 -0
  53. package/dist-server/service/chat-session/chat-session.js.map +1 -0
  54. package/dist-server/service/chat-session/index.d.ts +3 -0
  55. package/dist-server/service/chat-session/index.js +7 -0
  56. package/dist-server/service/chat-session/index.js.map +1 -0
  57. package/dist-server/service/chat-session-resolver.d.ts +13 -0
  58. package/dist-server/service/chat-session-resolver.js +178 -0
  59. package/dist-server/service/chat-session-resolver.js.map +1 -0
  60. package/dist-server/service/index.d.ts +14 -0
  61. package/dist-server/service/index.js +26 -0
  62. package/dist-server/service/index.js.map +1 -0
  63. package/dist-server/service/patch-entry/index.d.ts +3 -0
  64. package/dist-server/service/patch-entry/index.js +7 -0
  65. package/dist-server/service/patch-entry/index.js.map +1 -0
  66. package/dist-server/service/patch-entry/patch-entry.d.ts +16 -0
  67. package/dist-server/service/patch-entry/patch-entry.js +96 -0
  68. package/dist-server/service/patch-entry/patch-entry.js.map +1 -0
  69. package/dist-server/service/types.d.ts +137 -0
  70. package/dist-server/service/types.js +3 -0
  71. package/dist-server/service/types.js.map +1 -0
  72. package/dist-server/tsconfig.tsbuildinfo +1 -0
  73. package/package.json +47 -0
  74. package/server/index.ts +21 -0
  75. package/server/service/apply-patch.test.ts +640 -0
  76. package/server/service/apply-patch.ts +250 -0
  77. package/server/service/assistant.test.ts +1317 -0
  78. package/server/service/assistant.ts +1431 -0
  79. package/server/service/board-ai-resolver.ts +239 -0
  80. package/server/service/chat-message/chat-message.ts +110 -0
  81. package/server/service/chat-message/index.ts +5 -0
  82. package/server/service/chat-session/chat-session.ts +103 -0
  83. package/server/service/chat-session/index.ts +5 -0
  84. package/server/service/chat-session-resolver.ts +154 -0
  85. package/server/service/index.ts +24 -0
  86. package/server/service/patch-entry/index.ts +5 -0
  87. package/server/service/patch-entry/patch-entry.ts +89 -0
  88. package/server/service/types.ts +138 -0
  89. package/things-factory.config.js +1 -0
  90. package/translations/en.json +39 -0
  91. package/translations/ja.json +39 -0
  92. package/translations/ko.json +40 -0
  93. package/translations/ms.json +39 -0
  94. package/translations/zh.json +39 -0
  95. package/tsconfig.json +9 -0
@@ -0,0 +1,250 @@
1
+ /**
2
+ * BoardEditPatch 를 BoardModel 에 적용하는 표준 helper.
3
+ *
4
+ * 호출자가 patch 응답을 받아 보드에 반영할 때 사용.
5
+ * Pure function — 입력을 mutate 하지 않음.
6
+ */
7
+ import type { BoardComponent, BoardModel } from '@things-factory/board-import'
8
+ import type { BoardEditOp, BoardEditPatch } from './types.js'
9
+
10
+ const EMPTY_BOARD: BoardModel = {
11
+ width: 1000,
12
+ height: 600,
13
+ components: []
14
+ }
15
+
16
+ export interface PatchApplyReport {
17
+ /** 패치 적용 후 보드. 모든 op 가 noop 이어도 입력 그대로 반환. */
18
+ board: BoardModel
19
+ /** 실제로 보드를 바꾼 op 들. */
20
+ applied: BoardEditOp[]
21
+ /** id 매칭 실패 등으로 noop 이 된 op 들 — 호출자가 사용자에게 알릴 단서. */
22
+ missed: BoardEditOp[]
23
+ }
24
+
25
+ export function applyBoardEditPatch(
26
+ board: BoardModel | undefined,
27
+ patch: BoardEditPatch
28
+ ): BoardModel {
29
+ return applyBoardEditPatchVerbose(board, patch).board
30
+ }
31
+
32
+ /**
33
+ * Verbose 변형 — 각 op 의 적용 여부를 보고.
34
+ *
35
+ * `modify` 와 `remove` 는 id 가 보드에 없으면 silent no-op 이 된다 (patch 함수의
36
+ * 의도적 단순화). LLM 이 잘못된 id 를 만들어 보내면 사용자에게 "수정했습니다"
37
+ * 라고 답하지만 실제로는 아무 변화도 없는 상황이 발생 — 호스트가 missed 를
38
+ * 보고 사용자에게 경고할 수 있도록 별도 entry point 제공.
39
+ */
40
+ export function applyBoardEditPatchVerbose(
41
+ board: BoardModel | undefined,
42
+ patch: BoardEditPatch
43
+ ): PatchApplyReport {
44
+ let result: BoardModel = board ?? EMPTY_BOARD
45
+ const applied: BoardEditOp[] = []
46
+ const missed: BoardEditOp[] = []
47
+
48
+ for (const op of patch.ops) {
49
+ const next = applyOp(result, op)
50
+ if (next === result || componentsUnchanged(result, next)) {
51
+ missed.push(op)
52
+ } else {
53
+ applied.push(op)
54
+ result = next
55
+ }
56
+ }
57
+
58
+ return { board: result, applied, missed }
59
+ }
60
+
61
+ function componentsUnchanged(prev: BoardModel, next: BoardModel): boolean {
62
+ // applyOp 는 항상 새 객체를 만든다 (`{ ...board, components: ... }`). 따라서 reference
63
+ // 비교가 안 되고 내용 비교가 필요. components 는 map/filter 결과 reference 도 다를 수
64
+ // 있으므로 length + JSON 깊이 비교.
65
+ const a = prev.components ?? []
66
+ const b = next.components ?? []
67
+ if (a.length !== b.length) return false
68
+ for (let i = 0; i < a.length; i++) {
69
+ if (a[i] !== b[i]) return false
70
+ }
71
+ // root meta (width/height/...) 비교
72
+ return prev.width === next.width && prev.height === next.height && prev.fillStyle === next.fillStyle
73
+ }
74
+
75
+ /**
76
+ * 주어진 board 상태에서 op 의 inverse 를 계산.
77
+ *
78
+ * Revert 기능의 코어 — patch 적용 직전 board 와 op 만 알면 그 op 의 역연산을
79
+ * 만들 수 있으므로, 호스트가 in-place 적용하면서 함께 누적해 두면 나중에
80
+ * 역순 실행만으로 복원 가능.
81
+ *
82
+ * 반환:
83
+ * - inverse op 또는 null (unsupported / 데이터 부족)
84
+ * - add 의 inverse 는 add 후 발급되는 refid 가 필요해서 모델 단계에서 계산 불가 →
85
+ * 호스트가 scene.add 직후 refid 를 캡처해 직접 만들 것 (이 함수는 pre-applied
86
+ * board 만 보고 만들 수 있는 종류만 처리: remove / modify / modifyBoard / replace)
87
+ */
88
+ export function computeInverseOp(
89
+ board: BoardModel | undefined,
90
+ op: BoardEditOp
91
+ ): BoardEditOp | null {
92
+ if (!board) return null
93
+ const components = board.components ?? []
94
+
95
+ switch (op.op) {
96
+ case 'add':
97
+ // add 의 inverse 는 새로 발급될 refid 를 알아야 → 호스트 측에서 처리
98
+ return null
99
+
100
+ case 'remove': {
101
+ const target = components.find(c => (c as any)?.refid === op.refid)
102
+ if (!target) return null // 매칭 실패 — silent no-op 이라 inverse 도 없음
103
+ return { op: 'add', component: JSON.parse(JSON.stringify(target)) }
104
+ }
105
+
106
+ case 'modify': {
107
+ const target = components.find(c => (c as any)?.refid === op.refid)
108
+ if (!target) return null
109
+ // patch 가 건드린 키만 보관. nested 는 통째로.
110
+ const oldValues: any = {}
111
+ for (const k of Object.keys(op.patch || {})) {
112
+ const v = (target as any)[k]
113
+ oldValues[k] = v === undefined ? null : JSON.parse(JSON.stringify(v))
114
+ }
115
+ return { op: 'modify', refid: op.refid, patch: oldValues }
116
+ }
117
+
118
+ case 'modifyBoard': {
119
+ const oldValues: any = {}
120
+ const patch = op.patch || {}
121
+ for (const k of Object.keys(patch)) {
122
+ if (k === 'components') continue
123
+ const v = (board as any)[k]
124
+ oldValues[k] = v === undefined ? null : JSON.parse(JSON.stringify(v))
125
+ }
126
+ return { op: 'modifyBoard', patch: oldValues }
127
+ }
128
+
129
+ case 'replace':
130
+ // 이전 보드 통째로 보관 — replace 의 자연스러운 inverse 는 또 다른 replace
131
+ return { op: 'replace', board: JSON.parse(JSON.stringify(board)) }
132
+
133
+ default:
134
+ return null
135
+ }
136
+ }
137
+
138
+ export function applyOp(board: BoardModel, op: BoardEditOp): BoardModel {
139
+ const components = board.components ?? []
140
+ switch (op.op) {
141
+ case 'replace':
142
+ return op.board
143
+
144
+ case 'add':
145
+ return { ...board, components: [...components, op.component] }
146
+
147
+ case 'remove':
148
+ return { ...board, components: components.filter(c => (c as any)?.refid !== op.refid) }
149
+
150
+ case 'modify':
151
+ return {
152
+ ...board,
153
+ components: components.map(c =>
154
+ (c as any)?.refid === op.refid ? mergeComponent(c, op.patch) : c
155
+ )
156
+ }
157
+
158
+ case 'modifyBoard': {
159
+ // 루트 속성 (fillStyle / width / height / name 등) 만 deep merge.
160
+ // components 키는 무시 — 자식 변경은 add/remove/modify 별도 op 로.
161
+ const patch = { ...op.patch } as any
162
+ delete patch.components
163
+ return mergeBoardRoot(board, patch)
164
+ }
165
+
166
+ default:
167
+ return board
168
+ }
169
+ }
170
+
171
+ function mergeBoardRoot(board: BoardModel, patch: any): BoardModel {
172
+ const out: any = { ...board }
173
+ for (const key of Object.keys(patch)) {
174
+ const bv = (board as any)[key]
175
+ const pv = patch[key]
176
+ if (
177
+ bv !== null &&
178
+ pv !== null &&
179
+ typeof bv === 'object' &&
180
+ typeof pv === 'object' &&
181
+ !Array.isArray(bv) &&
182
+ !Array.isArray(pv)
183
+ ) {
184
+ out[key] = deepMergeRoot(bv, pv)
185
+ } else {
186
+ out[key] = pv
187
+ }
188
+ }
189
+ return out
190
+ }
191
+
192
+ function deepMergeRoot(a: any, b: any): any {
193
+ const out: any = { ...a }
194
+ for (const key of Object.keys(b)) {
195
+ const av = a[key]
196
+ const bv = b[key]
197
+ if (
198
+ av !== null &&
199
+ bv !== null &&
200
+ typeof av === 'object' &&
201
+ typeof bv === 'object' &&
202
+ !Array.isArray(av) &&
203
+ !Array.isArray(bv)
204
+ ) {
205
+ out[key] = deepMergeRoot(av, bv)
206
+ } else {
207
+ out[key] = bv
208
+ }
209
+ }
210
+ return out
211
+ }
212
+
213
+ /**
214
+ * 컴포넌트에 부분 patch 를 적용.
215
+ * threeD 등 nested object 는 deep merge — 호출자가 색만 바꾸려고 했을 때 geometry 까지 사라지지 않도록.
216
+ */
217
+ export function mergeComponent(base: BoardComponent, patch: Partial<BoardComponent>): BoardComponent {
218
+ const out: any = { ...base }
219
+ for (const key of Object.keys(patch)) {
220
+ const baseVal = (base as any)[key]
221
+ const patchVal = (patch as any)[key]
222
+ if (
223
+ isPlainObject(baseVal) &&
224
+ isPlainObject(patchVal)
225
+ ) {
226
+ out[key] = deepMerge(baseVal, patchVal)
227
+ } else {
228
+ out[key] = patchVal
229
+ }
230
+ }
231
+ return out as BoardComponent
232
+ }
233
+
234
+ function deepMerge(a: any, b: any): any {
235
+ const out: any = { ...a }
236
+ for (const key of Object.keys(b)) {
237
+ const av = a[key]
238
+ const bv = b[key]
239
+ if (isPlainObject(av) && isPlainObject(bv)) {
240
+ out[key] = deepMerge(av, bv)
241
+ } else {
242
+ out[key] = bv
243
+ }
244
+ }
245
+ return out
246
+ }
247
+
248
+ function isPlainObject(v: any): boolean {
249
+ return v !== null && typeof v === 'object' && !Array.isArray(v)
250
+ }