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

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 (329) hide show
  1. package/PLAN-styling-tools.md +1253 -0
  2. package/client/components/board-ai-chat.test.ts +149 -0
  3. package/client/components/board-ai-chat.ts +2574 -0
  4. package/client/components/chat-echo-dedup.test.ts +116 -0
  5. package/client/components/chat-echo-dedup.ts +89 -0
  6. package/client/components/chat-input-builder.ts +47 -0
  7. package/client/components/default-catalog-entries.test.ts +59 -0
  8. package/client/components/default-catalog-entries.ts +100 -0
  9. package/client/components/default-slash-templates.test.ts +55 -0
  10. package/client/components/default-slash-templates.ts +129 -0
  11. package/client/components/markdown.test.ts +518 -0
  12. package/client/components/markdown.ts +508 -0
  13. package/client/components/mention-popup-helpers.ts +433 -0
  14. package/client/components/mention-popup.test.ts +588 -0
  15. package/client/components/mention-popup.ts +238 -0
  16. package/client/index.ts +11 -0
  17. package/client/tsconfig.json +13 -0
  18. package/client/utils/board-edit-patch.ts +374 -0
  19. package/config/config.development.js +43 -0
  20. package/config/config.production.js +15 -0
  21. package/dist-client/client/components/board-ai-chat.d.ts +277 -0
  22. package/dist-client/client/components/board-ai-chat.js +2444 -0
  23. package/dist-client/client/components/board-ai-chat.js.map +1 -0
  24. package/dist-client/client/components/board-ai-chat.test.d.ts +1 -0
  25. package/dist-client/client/components/board-ai-chat.test.js +138 -0
  26. package/dist-client/client/components/board-ai-chat.test.js.map +1 -0
  27. package/dist-client/client/components/chat-echo-dedup.d.ts +45 -0
  28. package/dist-client/client/components/chat-echo-dedup.js +58 -0
  29. package/dist-client/client/components/chat-echo-dedup.js.map +1 -0
  30. package/dist-client/client/components/chat-echo-dedup.test.d.ts +1 -0
  31. package/dist-client/client/components/chat-echo-dedup.test.js +101 -0
  32. package/dist-client/client/components/chat-echo-dedup.test.js.map +1 -0
  33. package/dist-client/client/components/chat-input-builder.d.ts +39 -0
  34. package/dist-client/client/components/chat-input-builder.js +26 -0
  35. package/dist-client/client/components/chat-input-builder.js.map +1 -0
  36. package/dist-client/client/components/default-catalog-entries.d.ts +15 -0
  37. package/dist-client/client/components/default-catalog-entries.js +86 -0
  38. package/dist-client/client/components/default-catalog-entries.js.map +1 -0
  39. package/dist-client/client/components/default-catalog-entries.test.d.ts +1 -0
  40. package/dist-client/client/components/default-catalog-entries.test.js +52 -0
  41. package/dist-client/client/components/default-catalog-entries.test.js.map +1 -0
  42. package/dist-client/client/components/default-slash-templates.d.ts +17 -0
  43. package/dist-client/client/components/default-slash-templates.js +113 -0
  44. package/dist-client/client/components/default-slash-templates.js.map +1 -0
  45. package/dist-client/client/components/default-slash-templates.test.d.ts +1 -0
  46. package/dist-client/client/components/default-slash-templates.test.js +51 -0
  47. package/dist-client/client/components/default-slash-templates.test.js.map +1 -0
  48. package/dist-client/client/components/markdown.d.ts +60 -0
  49. package/dist-client/client/components/markdown.js +422 -0
  50. package/dist-client/client/components/markdown.js.map +1 -0
  51. package/dist-client/client/components/markdown.test.d.ts +1 -0
  52. package/dist-client/client/components/markdown.test.js +418 -0
  53. package/dist-client/client/components/markdown.test.js.map +1 -0
  54. package/dist-client/client/components/mention-popup-helpers.d.ts +165 -0
  55. package/dist-client/client/components/mention-popup-helpers.js +330 -0
  56. package/dist-client/client/components/mention-popup-helpers.js.map +1 -0
  57. package/dist-client/client/components/mention-popup.d.ts +35 -0
  58. package/dist-client/client/components/mention-popup.js +223 -0
  59. package/dist-client/client/components/mention-popup.js.map +1 -0
  60. package/dist-client/client/components/mention-popup.test.d.ts +1 -0
  61. package/dist-client/client/components/mention-popup.test.js +474 -0
  62. package/dist-client/client/components/mention-popup.test.js.map +1 -0
  63. package/dist-client/client/index.d.ts +11 -0
  64. package/dist-client/client/index.js +12 -0
  65. package/dist-client/client/index.js.map +1 -0
  66. package/dist-client/client/utils/board-edit-patch.d.ts +157 -0
  67. package/dist-client/client/utils/board-edit-patch.js +284 -0
  68. package/dist-client/client/utils/board-edit-patch.js.map +1 -0
  69. package/dist-client/server/service/agentic-loop.d.ts +87 -0
  70. package/dist-client/server/service/agentic-loop.js +276 -0
  71. package/dist-client/server/service/agentic-loop.js.map +1 -0
  72. package/dist-client/server/service/assistant.d.ts +84 -0
  73. package/dist-client/server/service/assistant.js +2220 -0
  74. package/dist-client/server/service/assistant.js.map +1 -0
  75. package/dist-client/server/service/catalog-resolver.d.ts +18 -0
  76. package/dist-client/server/service/catalog-resolver.js +93 -0
  77. package/dist-client/server/service/catalog-resolver.js.map +1 -0
  78. package/dist-client/server/service/component-tree.d.ts +68 -0
  79. package/dist-client/server/service/component-tree.js +68 -0
  80. package/dist-client/server/service/component-tree.js.map +1 -0
  81. package/dist-client/server/service/mention-resolver.d.ts +96 -0
  82. package/dist-client/server/service/mention-resolver.js +172 -0
  83. package/dist-client/server/service/mention-resolver.js.map +1 -0
  84. package/dist-client/server/service/prefab-registry.d.ts +73 -0
  85. package/dist-client/server/service/prefab-registry.js +252 -0
  86. package/dist-client/server/service/prefab-registry.js.map +1 -0
  87. package/dist-client/server/service/styling/clear-tools.d.ts +15 -0
  88. package/dist-client/server/service/styling/clear-tools.js +50 -0
  89. package/dist-client/server/service/styling/clear-tools.js.map +1 -0
  90. package/dist-client/server/service/styling/color-utils.d.ts +43 -0
  91. package/dist-client/server/service/styling/color-utils.js +205 -0
  92. package/dist-client/server/service/styling/color-utils.js.map +1 -0
  93. package/dist-client/server/service/styling/copy-tools.d.ts +27 -0
  94. package/dist-client/server/service/styling/copy-tools.js +78 -0
  95. package/dist-client/server/service/styling/copy-tools.js.map +1 -0
  96. package/dist-client/server/service/styling/effect-tools.d.ts +45 -0
  97. package/dist-client/server/service/styling/effect-tools.js +85 -0
  98. package/dist-client/server/service/styling/effect-tools.js.map +1 -0
  99. package/dist-client/server/service/styling/fill-tools.d.ts +60 -0
  100. package/dist-client/server/service/styling/fill-tools.js +226 -0
  101. package/dist-client/server/service/styling/fill-tools.js.map +1 -0
  102. package/dist-client/server/service/styling/material-tools.d.ts +32 -0
  103. package/dist-client/server/service/styling/material-tools.js +88 -0
  104. package/dist-client/server/service/styling/material-tools.js.map +1 -0
  105. package/dist-client/server/service/styling/read-tools.d.ts +34 -0
  106. package/dist-client/server/service/styling/read-tools.js +42 -0
  107. package/dist-client/server/service/styling/read-tools.js.map +1 -0
  108. package/dist-client/server/service/styling/registry.d.ts +91 -0
  109. package/dist-client/server/service/styling/registry.js +345 -0
  110. package/dist-client/server/service/styling/registry.js.map +1 -0
  111. package/dist-client/server/service/styling/stroke-tools.d.ts +35 -0
  112. package/dist-client/server/service/styling/stroke-tools.js +94 -0
  113. package/dist-client/server/service/styling/stroke-tools.js.map +1 -0
  114. package/dist-client/server/service/styling/text-tools.d.ts +32 -0
  115. package/dist-client/server/service/styling/text-tools.js +101 -0
  116. package/dist-client/server/service/styling/text-tools.js.map +1 -0
  117. package/dist-client/server/service/types.d.ts +265 -0
  118. package/dist-client/server/service/types.js +2 -0
  119. package/dist-client/server/service/types.js.map +1 -0
  120. package/dist-client/server/service/validation/board-model-schema.d.ts +8311 -0
  121. package/dist-client/server/service/validation/board-model-schema.js +402 -0
  122. package/dist-client/server/service/validation/board-model-schema.js.map +1 -0
  123. package/dist-client/server/service/validation/tool-validation.d.ts +31 -0
  124. package/dist-client/server/service/validation/tool-validation.js +193 -0
  125. package/dist-client/server/service/validation/tool-validation.js.map +1 -0
  126. package/dist-client/tsconfig.tsbuildinfo +1 -0
  127. package/dist-server/index.d.ts +23 -0
  128. package/dist-server/index.js +27 -0
  129. package/dist-server/index.js.map +1 -0
  130. package/dist-server/service/agentic-loop.d.ts +87 -0
  131. package/dist-server/service/agentic-loop.js +280 -0
  132. package/dist-server/service/agentic-loop.js.map +1 -0
  133. package/dist-server/service/apply-patch.d.ts +47 -0
  134. package/dist-server/service/apply-patch.js +369 -0
  135. package/dist-server/service/apply-patch.js.map +1 -0
  136. package/dist-server/service/assistant.d.ts +84 -0
  137. package/dist-server/service/assistant.js +2237 -0
  138. package/dist-server/service/assistant.js.map +1 -0
  139. package/dist-server/service/board-ai-resolver.d.ts +54 -0
  140. package/dist-server/service/board-ai-resolver.js +409 -0
  141. package/dist-server/service/board-ai-resolver.js.map +1 -0
  142. package/dist-server/service/catalog-resolver.d.ts +18 -0
  143. package/dist-server/service/catalog-resolver.js +98 -0
  144. package/dist-server/service/catalog-resolver.js.map +1 -0
  145. package/dist-server/service/chat-message/chat-message-broadcast.d.ts +13 -0
  146. package/dist-server/service/chat-message/chat-message-broadcast.js +13 -0
  147. package/dist-server/service/chat-message/chat-message-broadcast.js.map +1 -0
  148. package/dist-server/service/chat-message/chat-message-publish.d.ts +6 -0
  149. package/dist-server/service/chat-message/chat-message-publish.js +27 -0
  150. package/dist-server/service/chat-message/chat-message-publish.js.map +1 -0
  151. package/dist-server/service/chat-message/chat-message-subscription.d.ts +8 -0
  152. package/dist-server/service/chat-message/chat-message-subscription.js +53 -0
  153. package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -0
  154. package/dist-server/service/chat-message/chat-message.d.ts +56 -0
  155. package/dist-server/service/chat-message/chat-message.js +149 -0
  156. package/dist-server/service/chat-message/chat-message.js.map +1 -0
  157. package/dist-server/service/chat-message/index.d.ts +5 -0
  158. package/dist-server/service/chat-message/index.js +12 -0
  159. package/dist-server/service/chat-message/index.js.map +1 -0
  160. package/dist-server/service/chat-session/chat-session.d.ts +31 -0
  161. package/dist-server/service/chat-session/chat-session.js +131 -0
  162. package/dist-server/service/chat-session/chat-session.js.map +1 -0
  163. package/dist-server/service/chat-session/index.d.ts +3 -0
  164. package/dist-server/service/chat-session/index.js +7 -0
  165. package/dist-server/service/chat-session/index.js.map +1 -0
  166. package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +30 -0
  167. package/dist-server/service/chat-session-participant/chat-session-participant.js +112 -0
  168. package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
  169. package/dist-server/service/chat-session-participant/index.d.ts +4 -0
  170. package/dist-server/service/chat-session-participant/index.js +10 -0
  171. package/dist-server/service/chat-session-participant/index.js.map +1 -0
  172. package/dist-server/service/chat-session-participant/invite-notification.d.ts +27 -0
  173. package/dist-server/service/chat-session-participant/invite-notification.js +30 -0
  174. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -0
  175. package/dist-server/service/chat-session-participant/presence.d.ts +4 -0
  176. package/dist-server/service/chat-session-participant/presence.js +22 -0
  177. package/dist-server/service/chat-session-participant/presence.js.map +1 -0
  178. package/dist-server/service/chat-session-resolver.d.ts +20 -0
  179. package/dist-server/service/chat-session-resolver.js +356 -0
  180. package/dist-server/service/chat-session-resolver.js.map +1 -0
  181. package/dist-server/service/component-tree.d.ts +68 -0
  182. package/dist-server/service/component-tree.js +73 -0
  183. package/dist-server/service/component-tree.js.map +1 -0
  184. package/dist-server/service/index.d.ts +20 -0
  185. package/dist-server/service/index.js +34 -0
  186. package/dist-server/service/index.js.map +1 -0
  187. package/dist-server/service/mention-marker.d.ts +78 -0
  188. package/dist-server/service/mention-marker.js +162 -0
  189. package/dist-server/service/mention-marker.js.map +1 -0
  190. package/dist-server/service/mention-resolver.d.ts +96 -0
  191. package/dist-server/service/mention-resolver.js +177 -0
  192. package/dist-server/service/mention-resolver.js.map +1 -0
  193. package/dist-server/service/patch-entry/board-patch-broadcast.d.ts +13 -0
  194. package/dist-server/service/patch-entry/board-patch-broadcast.js +13 -0
  195. package/dist-server/service/patch-entry/board-patch-broadcast.js.map +1 -0
  196. package/dist-server/service/patch-entry/board-patch-subscription.d.ts +12 -0
  197. package/dist-server/service/patch-entry/board-patch-subscription.js +63 -0
  198. package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -0
  199. package/dist-server/service/patch-entry/index.d.ts +4 -0
  200. package/dist-server/service/patch-entry/index.js +12 -0
  201. package/dist-server/service/patch-entry/index.js.map +1 -0
  202. package/dist-server/service/patch-entry/patch-entry.d.ts +16 -0
  203. package/dist-server/service/patch-entry/patch-entry.js +96 -0
  204. package/dist-server/service/patch-entry/patch-entry.js.map +1 -0
  205. package/dist-server/service/prefab-registry.d.ts +73 -0
  206. package/dist-server/service/prefab-registry.js +258 -0
  207. package/dist-server/service/prefab-registry.js.map +1 -0
  208. package/dist-server/service/styling/clear-tools.d.ts +15 -0
  209. package/dist-server/service/styling/clear-tools.js +53 -0
  210. package/dist-server/service/styling/clear-tools.js.map +1 -0
  211. package/dist-server/service/styling/color-utils.d.ts +43 -0
  212. package/dist-server/service/styling/color-utils.js +210 -0
  213. package/dist-server/service/styling/color-utils.js.map +1 -0
  214. package/dist-server/service/styling/copy-tools.d.ts +27 -0
  215. package/dist-server/service/styling/copy-tools.js +81 -0
  216. package/dist-server/service/styling/copy-tools.js.map +1 -0
  217. package/dist-server/service/styling/effect-tools.d.ts +45 -0
  218. package/dist-server/service/styling/effect-tools.js +90 -0
  219. package/dist-server/service/styling/effect-tools.js.map +1 -0
  220. package/dist-server/service/styling/fill-tools.d.ts +60 -0
  221. package/dist-server/service/styling/fill-tools.js +229 -0
  222. package/dist-server/service/styling/fill-tools.js.map +1 -0
  223. package/dist-server/service/styling/material-tools.d.ts +32 -0
  224. package/dist-server/service/styling/material-tools.js +92 -0
  225. package/dist-server/service/styling/material-tools.js.map +1 -0
  226. package/dist-server/service/styling/read-tools.d.ts +34 -0
  227. package/dist-server/service/styling/read-tools.js +45 -0
  228. package/dist-server/service/styling/read-tools.js.map +1 -0
  229. package/dist-server/service/styling/registry.d.ts +91 -0
  230. package/dist-server/service/styling/registry.js +352 -0
  231. package/dist-server/service/styling/registry.js.map +1 -0
  232. package/dist-server/service/styling/stroke-tools.d.ts +35 -0
  233. package/dist-server/service/styling/stroke-tools.js +97 -0
  234. package/dist-server/service/styling/stroke-tools.js.map +1 -0
  235. package/dist-server/service/styling/text-tools.d.ts +32 -0
  236. package/dist-server/service/styling/text-tools.js +105 -0
  237. package/dist-server/service/styling/text-tools.js.map +1 -0
  238. package/dist-server/service/subscribe-auth.d.ts +14 -0
  239. package/dist-server/service/subscribe-auth.js +33 -0
  240. package/dist-server/service/subscribe-auth.js.map +1 -0
  241. package/dist-server/service/types.d.ts +265 -0
  242. package/dist-server/service/types.js +3 -0
  243. package/dist-server/service/types.js.map +1 -0
  244. package/dist-server/service/validation/board-model-schema.d.ts +8311 -0
  245. package/dist-server/service/validation/board-model-schema.js +409 -0
  246. package/dist-server/service/validation/board-model-schema.js.map +1 -0
  247. package/dist-server/service/validation/tool-validation.d.ts +31 -0
  248. package/dist-server/service/validation/tool-validation.js +196 -0
  249. package/dist-server/service/validation/tool-validation.js.map +1 -0
  250. package/dist-server/tsconfig.tsbuildinfo +1 -0
  251. package/package.json +48 -0
  252. package/server/index.ts +23 -0
  253. package/server/service/agentic-loop.test.ts +705 -0
  254. package/server/service/agentic-loop.ts +431 -0
  255. package/server/service/apply-patch-drift.test.ts +829 -0
  256. package/server/service/apply-patch.test.ts +757 -0
  257. package/server/service/apply-patch.ts +411 -0
  258. package/server/service/assistant-integration.test.ts +922 -0
  259. package/server/service/assistant-llm-smoke.test.ts +296 -0
  260. package/server/service/assistant.test.ts +2034 -0
  261. package/server/service/assistant.ts +2378 -0
  262. package/server/service/board-ai-resolver.ts +407 -0
  263. package/server/service/catalog-resolver.test.ts +137 -0
  264. package/server/service/catalog-resolver.ts +102 -0
  265. package/server/service/chat-message/chat-message-broadcast.integration.test.ts +73 -0
  266. package/server/service/chat-message/chat-message-broadcast.test.ts +40 -0
  267. package/server/service/chat-message/chat-message-broadcast.ts +27 -0
  268. package/server/service/chat-message/chat-message-publish.ts +22 -0
  269. package/server/service/chat-message/chat-message-subscription.ts +41 -0
  270. package/server/service/chat-message/chat-message.ts +169 -0
  271. package/server/service/chat-message/index.ts +12 -0
  272. package/server/service/chat-session/chat-session.ts +130 -0
  273. package/server/service/chat-session/index.ts +5 -0
  274. package/server/service/chat-session-participant/chat-session-participant.ts +110 -0
  275. package/server/service/chat-session-participant/index.ts +6 -0
  276. package/server/service/chat-session-participant/invite-notification.test.ts +30 -0
  277. package/server/service/chat-session-participant/invite-notification.ts +48 -0
  278. package/server/service/chat-session-participant/presence.test.ts +39 -0
  279. package/server/service/chat-session-participant/presence.ts +21 -0
  280. package/server/service/chat-session-resolver.ts +312 -0
  281. package/server/service/component-tree.test.ts +248 -0
  282. package/server/service/component-tree.ts +135 -0
  283. package/server/service/index.ts +32 -0
  284. package/server/service/mention-marker.extract.test.ts +61 -0
  285. package/server/service/mention-marker.test.ts +228 -0
  286. package/server/service/mention-marker.ts +184 -0
  287. package/server/service/mention-resolver.test.ts +273 -0
  288. package/server/service/mention-resolver.ts +264 -0
  289. package/server/service/patch-entry/board-patch-broadcast.ts +24 -0
  290. package/server/service/patch-entry/board-patch-subscription.test.ts +40 -0
  291. package/server/service/patch-entry/board-patch-subscription.ts +50 -0
  292. package/server/service/patch-entry/index.ts +12 -0
  293. package/server/service/patch-entry/patch-entry.ts +89 -0
  294. package/server/service/prefab-registry.test.ts +172 -0
  295. package/server/service/prefab-registry.ts +332 -0
  296. package/server/service/styling/clear-tools.test.ts +164 -0
  297. package/server/service/styling/clear-tools.ts +69 -0
  298. package/server/service/styling/color-utils.test.ts +130 -0
  299. package/server/service/styling/color-utils.ts +226 -0
  300. package/server/service/styling/copy-tools.test.ts +152 -0
  301. package/server/service/styling/copy-tools.ts +115 -0
  302. package/server/service/styling/effect-tools.test.ts +277 -0
  303. package/server/service/styling/effect-tools.ts +124 -0
  304. package/server/service/styling/fill-tools.test.ts +331 -0
  305. package/server/service/styling/fill-tools.ts +293 -0
  306. package/server/service/styling/material-tools.test.ts +294 -0
  307. package/server/service/styling/material-tools.ts +114 -0
  308. package/server/service/styling/read-tools.test.ts +78 -0
  309. package/server/service/styling/read-tools.ts +67 -0
  310. package/server/service/styling/registry.test.ts +203 -0
  311. package/server/service/styling/registry.ts +423 -0
  312. package/server/service/styling/stroke-tools.test.ts +245 -0
  313. package/server/service/styling/stroke-tools.ts +145 -0
  314. package/server/service/styling/text-tools.test.ts +253 -0
  315. package/server/service/styling/text-tools.ts +131 -0
  316. package/server/service/styling/tier1-tools.test.ts +300 -0
  317. package/server/service/subscribe-auth.test.ts +57 -0
  318. package/server/service/subscribe-auth.ts +34 -0
  319. package/server/service/types.ts +235 -0
  320. package/server/service/validation/board-model-schema.test.ts +778 -0
  321. package/server/service/validation/board-model-schema.ts +446 -0
  322. package/server/service/validation/tool-validation.ts +228 -0
  323. package/things-factory.config.js +1 -0
  324. package/translations/en.json +49 -0
  325. package/translations/ja.json +48 -0
  326. package/translations/ko.json +49 -0
  327. package/translations/ms.json +48 -0
  328. package/translations/zh.json +48 -0
  329. package/tsconfig.json +9 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * PatchEntry — 보드의 한 변경 단위 (영구 이력).
3
+ *
4
+ * source 별:
5
+ * - 'ai': AI 가 chat 응답에서 생성
6
+ * - 'user-direct': 사용자가 모델러에서 직접 편집 — 변경 이벤트로 자동 기록
7
+ * - 'import': ImportSession 결과 (도면 → 보드)
8
+ *
9
+ * AI 컨텍스트 유지의 핵심: 사용자 직접 편집도 PatchEntry 로 기록되어
10
+ * 다음 chat 호출 시 system 메시지로 자동 합류 → AI 가 모든 변경을 인지.
11
+ */
12
+ import {
13
+ Column,
14
+ CreateDateColumn,
15
+ Entity,
16
+ Index,
17
+ ManyToOne,
18
+ PrimaryGeneratedColumn,
19
+ RelationId
20
+ } from 'typeorm'
21
+ import { Field, ID, Float, ObjectType } from 'type-graphql'
22
+ import { GraphQLJSON } from 'graphql-scalars'
23
+
24
+ import { config } from '@things-factory/env'
25
+
26
+ import { ChatSession } from '../chat-session/chat-session.js'
27
+
28
+ const ORMCONFIG = config.get('ormconfig', {})
29
+ const DATABASE_TYPE = ORMCONFIG.type
30
+
31
+ @Entity()
32
+ @Index('ix_patch_entry_1', (patch: PatchEntry) => [patch.session, patch.createdAt])
33
+ @ObjectType({ description: 'One board edit operation history (cascade-deleted with ChatSession).' })
34
+ export class PatchEntry {
35
+ @PrimaryGeneratedColumn('uuid')
36
+ @Field(type => ID, { nullable: true })
37
+ readonly id?: string
38
+
39
+ @ManyToOne(type => ChatSession, session => session.patches, { onDelete: 'CASCADE' })
40
+ session?: ChatSession
41
+
42
+ @RelationId((patch: PatchEntry) => patch.session)
43
+ sessionId?: string
44
+
45
+ /** 'ai' | 'user-direct' | 'import' */
46
+ @Column({ type: 'varchar', length: 32 })
47
+ @Field({ description: "'ai' | 'user-direct' | 'import'" })
48
+ source!: string
49
+
50
+ /** BoardEditOp[] JSON-stringified — column 으로는 long text. */
51
+ @Column({
52
+ type:
53
+ DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
54
+ ? 'longtext'
55
+ : DATABASE_TYPE == 'oracle'
56
+ ? 'clob'
57
+ : DATABASE_TYPE == 'mssql'
58
+ ? 'nvarchar'
59
+ : 'text',
60
+ length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
61
+ })
62
+ ops!: string
63
+
64
+ /** GraphQL 노출용 — JSON 객체로 변환. */
65
+ @Field(type => GraphQLJSON, { description: 'BoardEditOp[] (parsed JSON array).' })
66
+ get opsJson(): any {
67
+ try {
68
+ return JSON.parse(this.ops || '[]')
69
+ } catch {
70
+ return []
71
+ }
72
+ }
73
+
74
+ @Column({ type: 'text', nullable: true })
75
+ @Field({ nullable: true, description: 'Short human summary of this patch.' })
76
+ summary?: string
77
+
78
+ @Column({ type: 'float', nullable: true })
79
+ @Field(type => Float, { nullable: true, description: 'AI confidence 0..1 (null for user-direct).' })
80
+ confidence?: number
81
+
82
+ @Column({ default: false })
83
+ @Field({ description: 'Whether this patch was reverted.' })
84
+ reverted!: boolean
85
+
86
+ @CreateDateColumn()
87
+ @Field({ nullable: true })
88
+ createdAt?: Date
89
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * prefab-registry.ts 단위 테스트.
3
+ *
4
+ * 핵심 보호:
5
+ * - register / find / list 의 기본 CRUD
6
+ * - 같은 name 으로 등록 시 덮어쓰기 (호스트 커스터마이징 의도)
7
+ * - 검색 (category / keywordPattern) 동작
8
+ * - 자체 등록된 default prefab 3종 (monitor-card / gauge-panel / stat-tile) 의 build()
9
+ * 출력 형태 — group 컴포넌트 + components 자식 리스트
10
+ */
11
+ import {
12
+ registerBoardPrefab,
13
+ findBoardPrefab,
14
+ listBoardPrefabs,
15
+ clearBoardPrefabsForTest,
16
+ type BoardPrefab
17
+ } from './prefab-registry'
18
+
19
+ describe('registerBoardPrefab — 입력 검증', () => {
20
+ test('name 누락 시 throw', () => {
21
+ expect(() => registerBoardPrefab({} as any)).toThrow(/name.*required/)
22
+ })
23
+
24
+ test('build 누락 시 throw', () => {
25
+ expect(() => registerBoardPrefab({ name: 'x', description: 'd' } as any)).toThrow(
26
+ /build.*required/
27
+ )
28
+ })
29
+ })
30
+
31
+ describe('default prefabs — 자동 등록', () => {
32
+ // 모듈 로드만 해도 default 3종 이 등록되어 있어야 함 (register call 이 위 import 로 발동)
33
+ test('monitor-card / gauge-panel / stat-tile 등록 확인', () => {
34
+ expect(findBoardPrefab('monitor-card')).toBeDefined()
35
+ expect(findBoardPrefab('gauge-panel')).toBeDefined()
36
+ expect(findBoardPrefab('stat-tile')).toBeDefined()
37
+ })
38
+
39
+ test('list 결과에 이름·카테고리·기본 크기 포함', () => {
40
+ const all = listBoardPrefabs()
41
+ const monitor = all.find(p => p.name === 'monitor-card')
42
+ expect(monitor).toBeDefined()
43
+ expect(monitor!.category).toBe('monitoring')
44
+ expect(monitor!.defaultSize?.width).toBeGreaterThan(0)
45
+ expect(monitor!.description).toBeTruthy()
46
+ })
47
+
48
+ test('list 결과에 build 함수 미포함 (직렬화 친화)', () => {
49
+ const all = listBoardPrefabs()
50
+ for (const p of all) {
51
+ expect((p as any).build).toBeUndefined()
52
+ }
53
+ })
54
+ })
55
+
56
+ describe('listBoardPrefabs — 필터', () => {
57
+ test('category 필터', () => {
58
+ const monitoring = listBoardPrefabs({ category: 'monitoring' })
59
+ expect(monitoring.every(p => p.category === 'monitoring')).toBe(true)
60
+ expect(monitoring.length).toBeGreaterThanOrEqual(1)
61
+ const kpi = listBoardPrefabs({ category: 'kpi' })
62
+ expect(kpi.every(p => p.category === 'kpi')).toBe(true)
63
+ })
64
+
65
+ test('keywordPattern — case-insensitive 매칭', () => {
66
+ const r = listBoardPrefabs({ keywordPattern: 'GAUGE' })
67
+ expect(r.find(p => p.name === 'gauge-panel')).toBeDefined()
68
+ })
69
+
70
+ test('keywordPattern — 한글 매칭', () => {
71
+ const r = listBoardPrefabs({ keywordPattern: '게이지' })
72
+ expect(r.find(p => p.name === 'gauge-panel')).toBeDefined()
73
+ })
74
+
75
+ test('keywordPattern — description 에서 매칭', () => {
76
+ const r = listBoardPrefabs({ keywordPattern: 'KPI' })
77
+ expect(r.find(p => p.name === 'stat-tile')).toBeDefined()
78
+ })
79
+ })
80
+
81
+ describe('default prefab.build() — 형태 검증', () => {
82
+ test('monitor-card 의 build — group 컴포넌트 + 4 자식', () => {
83
+ const prefab = findBoardPrefab('monitor-card')!
84
+ const c = prefab.build({ left: 50, top: 30, title: '온도', value: '23.5°C' })
85
+ expect((c as any).type).toBe('group')
86
+ expect((c as any).left).toBe(50)
87
+ expect((c as any).top).toBe(30)
88
+ expect((c as any).components).toHaveLength(4)
89
+ // 자식 중에 옵션이 반영됐는지 (title / value 텍스트)
90
+ const texts = (c as any).components
91
+ .filter((cc: any) => cc.type === 'text')
92
+ .map((cc: any) => cc.text)
93
+ expect(texts).toContain('온도')
94
+ expect(texts).toContain('23.5°C')
95
+ })
96
+
97
+ test('monitor-card defaultSize 적용 (width/height 미지정)', () => {
98
+ const prefab = findBoardPrefab('monitor-card')!
99
+ const c = prefab.build({ left: 0, top: 0 })
100
+ expect((c as any).width).toBe(prefab.defaultSize!.width)
101
+ expect((c as any).height).toBe(prefab.defaultSize!.height)
102
+ })
103
+
104
+ test('gauge-panel — gauge-circle 자식 포함, value/min/max 전달', () => {
105
+ const prefab = findBoardPrefab('gauge-panel')!
106
+ const c = prefab.build({
107
+ left: 0,
108
+ top: 0,
109
+ value: 75,
110
+ min: 0,
111
+ max: 100,
112
+ title: 'CPU'
113
+ })
114
+ const gauge = (c as any).components.find((cc: any) => cc.type === 'gauge-circle')
115
+ expect(gauge).toBeDefined()
116
+ expect(gauge.value).toBe(75)
117
+ expect(gauge.max).toBe(100)
118
+ const labels = (c as any).components
119
+ .filter((cc: any) => cc.type === 'text')
120
+ .map((cc: any) => cc.text)
121
+ expect(labels).toContain('CPU')
122
+ })
123
+
124
+ test('stat-tile — fillStyle 옵션 적용', () => {
125
+ const prefab = findBoardPrefab('stat-tile')!
126
+ const c = prefab.build({ left: 0, top: 0, fillStyle: '#ff0000', value: '42' })
127
+ const bg = (c as any).components.find((cc: any) => cc.type === 'rect')
128
+ expect(bg.fillStyle).toBe('#ff0000')
129
+ })
130
+
131
+ test('width/height override — defaultSize 무시', () => {
132
+ const prefab = findBoardPrefab('monitor-card')!
133
+ const c = prefab.build({ left: 0, top: 0, width: 400, height: 200 })
134
+ expect((c as any).width).toBe(400)
135
+ expect((c as any).height).toBe(200)
136
+ // children 의 좌표 / 크기도 상대적으로 따라가야 함 (배경 rect 가 기둥)
137
+ const bg = (c as any).components.find((cc: any) => cc.type === 'rect')
138
+ expect(bg.width).toBe(400)
139
+ expect(bg.height).toBe(200)
140
+ })
141
+ })
142
+
143
+ describe('register — 같은 name 덮어쓰기 (호스트 커스터마이징 시나리오)', () => {
144
+ // default 등록 상태를 보존하기 위해 새 임시 prefab 으로만 검증.
145
+ test('동일 name 두 번 register → 두 번째가 활성', () => {
146
+ const v1: BoardPrefab = {
147
+ name: 'temp-test',
148
+ description: 'v1',
149
+ build: () => ({ type: 'rect' } as any)
150
+ }
151
+ const v2: BoardPrefab = {
152
+ name: 'temp-test',
153
+ description: 'v2',
154
+ build: () => ({ type: 'circle' } as any)
155
+ }
156
+ registerBoardPrefab(v1)
157
+ expect(findBoardPrefab('temp-test')!.description).toBe('v1')
158
+ registerBoardPrefab(v2)
159
+ expect(findBoardPrefab('temp-test')!.description).toBe('v2')
160
+ expect((findBoardPrefab('temp-test')!.build({ left: 0, top: 0 }) as any).type).toBe(
161
+ 'circle'
162
+ )
163
+ })
164
+ })
165
+
166
+ // clearBoardPrefabsForTest 자체 동작은 격리된 테스트에서만 — 다른 테스트의 default 사용을
167
+ // 망가뜨리지 않도록 마지막에 두고 직접 호출하지 않음.
168
+ describe('clearBoardPrefabsForTest', () => {
169
+ test('함수 export 확인 (시그니처)', () => {
170
+ expect(typeof clearBoardPrefabsForTest).toBe('function')
171
+ })
172
+ })
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Board Prefab Registry — 고품질 복합 컴포넌트 프리셋 카탈로그.
3
+ *
4
+ * 의도:
5
+ * - 보드 설계자가 실제로 원하는 단위는 rect/circle 같은 원시 도형이 아닌
6
+ * "온도 모니터링 카드", "AGV 배터리 상태판", "공정 KPI 타일" 같은 디자인된
7
+ * 복합 모듈. 이 layer 가 그 단위를 시스템 차원에서 제공.
8
+ * - LLM 은 자유 작곡하지 않고 검증된 prefab 카탈로그에서 고름 → 결과 일관성 ↑,
9
+ * 비용 ↓, 진입 장벽 낮춤.
10
+ *
11
+ * 통합:
12
+ * - 호스트 (operato 등) 가 패키지 로드 시 `registerBoardPrefab` 으로 자기 prefab
13
+ * 등록. board-ai 는 registry 만 관리. 초기 예시 prefab 몇 개를 자체 등록 — 진짜
14
+ * 디자인된 prefab 은 별도 패키지에서 추가.
15
+ * - LLM 도구 통합:
16
+ * getAvailablePrefabs (read) → registry 의 메타데이터 반환
17
+ * addPrefab (write) → name + 좌표/옵션 → 단일 BoardComponent (group)
18
+ *
19
+ * 식별자:
20
+ * - `name` 은 unique. 같은 name 으로 register 시 override (호스트가 자기 prefab 을
21
+ * 커스터마이징할 수 있도록 의도적 허용).
22
+ * - 카테고리는 free-form string — registry 가 강제하지 않음. 사용자/운영 자율.
23
+ */
24
+ import type { BoardComponent } from '@things-factory/board-import'
25
+
26
+ export interface BoardPrefab {
27
+ /** Unique 식별자 — 'kebab-case' 권장 */
28
+ name: string
29
+
30
+ /** 자유 분류 — 'monitoring' | 'control' | 'decoration' | 'kpi' 등 */
31
+ category?: string
32
+
33
+ /** 사용자 / LLM 이 이해할 한 문장 설명 */
34
+ description: string
35
+
36
+ /**
37
+ * 자연어 매칭 힌트 — '온도', '게이지', '카드' 등.
38
+ * findComponents 같은 검색이 아니라 LLM 의 의도 매칭용 메타데이터.
39
+ */
40
+ keywords?: string[]
41
+
42
+ /** 권장 기본 크기 — options 에 width/height 미지정 시 사용 */
43
+ defaultSize?: { width: number; height: number }
44
+
45
+ /**
46
+ * Prefab 의 컴포넌트 트리를 만든다. 단일 `BoardComponent` 반환 — 보통 group 타입이며
47
+ * `components` 자식을 가진다. 단일 add op 으로 매핑되어 undo 도 한 단위.
48
+ *
49
+ * options:
50
+ * - left/top: anchor 좌표 (top-left 원점)
51
+ * - width/height: 크기 (없으면 defaultSize)
52
+ * - 추가 키: prefab 별 자유 (title 텍스트, 색상 등)
53
+ */
54
+ build(options: PrefabBuildOptions): BoardComponent
55
+ }
56
+
57
+ export interface PrefabBuildOptions {
58
+ left: number
59
+ top: number
60
+ width?: number
61
+ height?: number
62
+ /** prefab 별 커스터마이징 — 자유 형태 */
63
+ [k: string]: any
64
+ }
65
+
66
+ const REGISTRY = new Map<string, BoardPrefab>()
67
+
68
+ /**
69
+ * Prefab 등록. 같은 name 은 덮어씀 (호스트가 자기 prefab 으로 default 를 교체 가능).
70
+ */
71
+ export function registerBoardPrefab(prefab: BoardPrefab): void {
72
+ if (!prefab || typeof prefab.name !== 'string' || prefab.name.length === 0) {
73
+ throw new Error('registerBoardPrefab: prefab.name is required')
74
+ }
75
+ if (typeof prefab.build !== 'function') {
76
+ throw new Error(`registerBoardPrefab(${prefab.name}): build() is required`)
77
+ }
78
+ REGISTRY.set(prefab.name, prefab)
79
+ }
80
+
81
+ /** 이름으로 조회. 없으면 undefined. */
82
+ export function findBoardPrefab(name: string): BoardPrefab | undefined {
83
+ return REGISTRY.get(name)
84
+ }
85
+
86
+ /** 등록된 prefab 의 메타데이터 (build 함수 제외) — getAvailablePrefabs 결과용. */
87
+ export function listBoardPrefabs(filter?: {
88
+ category?: string
89
+ keywordPattern?: string
90
+ }): Array<Omit<BoardPrefab, 'build'>> {
91
+ const items: Array<Omit<BoardPrefab, 'build'>> = []
92
+ for (const p of REGISTRY.values()) {
93
+ if (filter?.category && p.category !== filter.category) continue
94
+ if (filter?.keywordPattern) {
95
+ const needle = filter.keywordPattern.toLowerCase()
96
+ const haystack = [
97
+ p.name,
98
+ p.description,
99
+ ...(p.keywords ?? [])
100
+ ]
101
+ .join(' ')
102
+ .toLowerCase()
103
+ if (!haystack.includes(needle)) continue
104
+ }
105
+ items.push({
106
+ name: p.name,
107
+ category: p.category,
108
+ description: p.description,
109
+ keywords: p.keywords,
110
+ defaultSize: p.defaultSize
111
+ })
112
+ }
113
+ return items
114
+ }
115
+
116
+ /** 테스트용 — 등록 상태 초기화. */
117
+ export function clearBoardPrefabsForTest(): void {
118
+ REGISTRY.clear()
119
+ }
120
+
121
+ // ── 초기 예시 prefab ─────────────────────────────────────────────
122
+ //
123
+ // 패키지 차원 default — 진짜 polished 디자인은 operato / 도메인 패키지에서 register.
124
+ // 여기서는 architecture 데모 + LLM 이 선택할 후보가 0 이 되지 않도록 최소 set.
125
+
126
+ /**
127
+ * monitor-card — 라벨 + 큰 숫자값 + 부제 의 모니터링 타일.
128
+ * 가장 흔한 대시보드 단위. AGV 배터리 / 온도 / 가동률 등 한 줄 KPI 표시.
129
+ */
130
+ const MONITOR_CARD: BoardPrefab = {
131
+ name: 'monitor-card',
132
+ category: 'monitoring',
133
+ description:
134
+ 'KPI 모니터링 카드 — 상단에 라벨, 중앙에 큰 값, 하단에 부제. 단일 수치 한 줄 표시 (온도/가동률/배터리 등).',
135
+ keywords: ['모니터링', 'kpi', '카드', '값', 'value', 'card'],
136
+ defaultSize: { width: 200, height: 120 },
137
+ build(opts) {
138
+ const w = opts.width ?? 200
139
+ const h = opts.height ?? 120
140
+ const title = typeof opts.title === 'string' ? opts.title : 'Label'
141
+ const value = typeof opts.value === 'string' ? opts.value : '0'
142
+ const subtitle = typeof opts.subtitle === 'string' ? opts.subtitle : ''
143
+ const fill = typeof opts.fillStyle === 'string' ? opts.fillStyle : '#1a2332'
144
+ const accent = typeof opts.accent === 'string' ? opts.accent : '#4fc3f7'
145
+
146
+ return {
147
+ type: 'group',
148
+ left: opts.left,
149
+ top: opts.top,
150
+ width: w,
151
+ height: h,
152
+ name: 'monitor-card',
153
+ components: [
154
+ {
155
+ type: 'rect',
156
+ left: 0,
157
+ top: 0,
158
+ width: w,
159
+ height: h,
160
+ fillStyle: fill,
161
+ strokeStyle: '#2a3a52',
162
+ strokeWidth: 1
163
+ },
164
+ {
165
+ type: 'text',
166
+ left: 12,
167
+ top: 10,
168
+ width: w - 24,
169
+ height: 16,
170
+ text: title,
171
+ fillStyle: '#94a3b8',
172
+ fontSize: 12
173
+ },
174
+ {
175
+ type: 'text',
176
+ left: 12,
177
+ top: 32,
178
+ width: w - 24,
179
+ height: h - 60,
180
+ text: value,
181
+ fillStyle: accent,
182
+ fontSize: 32,
183
+ fontWeight: 'bold'
184
+ },
185
+ {
186
+ type: 'text',
187
+ left: 12,
188
+ top: h - 22,
189
+ width: w - 24,
190
+ height: 14,
191
+ text: subtitle,
192
+ fillStyle: '#64748b',
193
+ fontSize: 11
194
+ }
195
+ ]
196
+ } as any as BoardComponent
197
+ }
198
+ }
199
+
200
+ /**
201
+ * gauge-panel — 라벨 붙은 원형 게이지 패널.
202
+ * gauge-circle 컴포넌트가 things-scene에 있음을 전제. 진짜 게이지가 없으면 폴백으로 circle.
203
+ */
204
+ const GAUGE_PANEL: BoardPrefab = {
205
+ name: 'gauge-panel',
206
+ category: 'monitoring',
207
+ description:
208
+ '원형 게이지 + 라벨. 0~100 (또는 min/max) 범위의 단일 값 시각화 (CPU, 압력, 충전 등).',
209
+ keywords: ['게이지', 'gauge', '원형', '미터', 'meter', '센서'],
210
+ defaultSize: { width: 180, height: 200 },
211
+ build(opts) {
212
+ const w = opts.width ?? 180
213
+ const h = opts.height ?? 200
214
+ const title = typeof opts.title === 'string' ? opts.title : 'Gauge'
215
+ const value = typeof opts.value === 'number' ? opts.value : 50
216
+ const min = typeof opts.min === 'number' ? opts.min : 0
217
+ const max = typeof opts.max === 'number' ? opts.max : 100
218
+ const fill = typeof opts.fillStyle === 'string' ? opts.fillStyle : '#1a2332'
219
+
220
+ return {
221
+ type: 'group',
222
+ left: opts.left,
223
+ top: opts.top,
224
+ width: w,
225
+ height: h,
226
+ name: 'gauge-panel',
227
+ components: [
228
+ {
229
+ type: 'rect',
230
+ left: 0,
231
+ top: 0,
232
+ width: w,
233
+ height: h,
234
+ fillStyle: fill,
235
+ strokeStyle: '#2a3a52',
236
+ strokeWidth: 1
237
+ },
238
+ {
239
+ // gauge-circle 이 등록되어 있을 때 사용. 미등록 환경에서는 정상 추가는 되나
240
+ // render 가 fallback. operato 패키지에서 normalizeComponent 가 default 채움.
241
+ type: 'gauge-circle',
242
+ left: 20,
243
+ top: 20,
244
+ width: w - 40,
245
+ height: w - 40, // 정사각형 게이지
246
+ value,
247
+ min,
248
+ max
249
+ },
250
+ {
251
+ type: 'text',
252
+ left: 12,
253
+ top: h - 28,
254
+ width: w - 24,
255
+ height: 18,
256
+ text: title,
257
+ fillStyle: '#94a3b8',
258
+ fontSize: 13
259
+ }
260
+ ]
261
+ } as any as BoardComponent
262
+ }
263
+ }
264
+
265
+ /**
266
+ * stat-tile — 컬러 타일 + 큰 숫자. monitor-card 보다 강조된 단일 수치 표시.
267
+ * 사용자 시선이 가장 먼저 가는 위치에 배치.
268
+ */
269
+ const STAT_TILE: BoardPrefab = {
270
+ name: 'stat-tile',
271
+ category: 'kpi',
272
+ description:
273
+ '강조 컬러 타일 + 큰 숫자값 + 작은 라벨. monitor-card 보다 시각적 무게가 강함 — 핵심 KPI 1~3 개에만 사용.',
274
+ keywords: ['타일', 'tile', 'kpi', '강조', 'highlight'],
275
+ defaultSize: { width: 220, height: 140 },
276
+ build(opts) {
277
+ const w = opts.width ?? 220
278
+ const h = opts.height ?? 140
279
+ const title = typeof opts.title === 'string' ? opts.title : 'Total'
280
+ const value = typeof opts.value === 'string' ? opts.value : '0'
281
+ const fill = typeof opts.fillStyle === 'string' ? opts.fillStyle : '#0ea5e9'
282
+ const textColor = typeof opts.textColor === 'string' ? opts.textColor : '#ffffff'
283
+
284
+ return {
285
+ type: 'group',
286
+ left: opts.left,
287
+ top: opts.top,
288
+ width: w,
289
+ height: h,
290
+ name: 'stat-tile',
291
+ components: [
292
+ {
293
+ type: 'rect',
294
+ left: 0,
295
+ top: 0,
296
+ width: w,
297
+ height: h,
298
+ fillStyle: fill,
299
+ strokeStyle: 'transparent',
300
+ strokeWidth: 0
301
+ },
302
+ {
303
+ type: 'text',
304
+ left: 16,
305
+ top: 16,
306
+ width: w - 32,
307
+ height: 18,
308
+ text: title,
309
+ fillStyle: textColor,
310
+ fontSize: 13,
311
+ opacity: 0.85
312
+ },
313
+ {
314
+ type: 'text',
315
+ left: 16,
316
+ top: 40,
317
+ width: w - 32,
318
+ height: h - 56,
319
+ text: value,
320
+ fillStyle: textColor,
321
+ fontSize: 40,
322
+ fontWeight: 'bold'
323
+ }
324
+ ]
325
+ } as any as BoardComponent
326
+ }
327
+ }
328
+
329
+ // 모듈 로드 시 자동 등록 (idempotent — 다시 register 해도 덮어쓰기)
330
+ registerBoardPrefab(MONITOR_CARD)
331
+ registerBoardPrefab(GAUGE_PANEL)
332
+ registerBoardPrefab(STAT_TILE)