@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.
- package/PLAN-styling-tools.md +1253 -0
- package/client/components/board-ai-chat.test.ts +149 -0
- package/client/components/board-ai-chat.ts +2574 -0
- package/client/components/chat-echo-dedup.test.ts +116 -0
- package/client/components/chat-echo-dedup.ts +89 -0
- package/client/components/chat-input-builder.ts +47 -0
- 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 +129 -0
- package/client/components/markdown.test.ts +518 -0
- package/client/components/markdown.ts +508 -0
- 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/index.ts +11 -0
- package/client/tsconfig.json +13 -0
- package/client/utils/board-edit-patch.ts +374 -0
- package/config/config.development.js +43 -0
- package/config/config.production.js +15 -0
- package/dist-client/client/components/board-ai-chat.d.ts +277 -0
- package/dist-client/client/components/board-ai-chat.js +2444 -0
- package/dist-client/client/components/board-ai-chat.js.map +1 -0
- package/dist-client/client/components/board-ai-chat.test.d.ts +1 -0
- package/dist-client/client/components/board-ai-chat.test.js +138 -0
- package/dist-client/client/components/board-ai-chat.test.js.map +1 -0
- package/dist-client/client/components/chat-echo-dedup.d.ts +45 -0
- package/dist-client/client/components/chat-echo-dedup.js +58 -0
- package/dist-client/client/components/chat-echo-dedup.js.map +1 -0
- package/dist-client/client/components/chat-echo-dedup.test.d.ts +1 -0
- package/dist-client/client/components/chat-echo-dedup.test.js +101 -0
- package/dist-client/client/components/chat-echo-dedup.test.js.map +1 -0
- package/dist-client/client/components/chat-input-builder.d.ts +39 -0
- package/dist-client/client/components/chat-input-builder.js +26 -0
- 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.d.ts +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 +113 -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 +60 -0
- package/dist-client/client/components/markdown.js +422 -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/client/components/markdown.test.js +418 -0
- 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.d.ts +11 -0
- package/dist-client/client/index.js +12 -0
- package/dist-client/client/index.js.map +1 -0
- package/dist-client/client/utils/board-edit-patch.d.ts +157 -0
- package/dist-client/client/utils/board-edit-patch.js +284 -0
- package/dist-client/client/utils/board-edit-patch.js.map +1 -0
- package/dist-client/server/service/agentic-loop.d.ts +87 -0
- package/dist-client/server/service/agentic-loop.js +276 -0
- package/dist-client/server/service/agentic-loop.js.map +1 -0
- package/dist-client/server/service/assistant.d.ts +84 -0
- package/dist-client/server/service/assistant.js +2220 -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/component-tree.d.ts +68 -0
- package/dist-client/server/service/component-tree.js +68 -0
- package/dist-client/server/service/component-tree.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 +50 -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 +45 -0
- package/dist-client/server/service/styling/effect-tools.js +85 -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/registry.d.ts +91 -0
- package/dist-client/server/service/styling/registry.js +345 -0
- package/dist-client/server/service/styling/registry.js.map +1 -0
- package/dist-client/server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-client/server/service/styling/stroke-tools.js +94 -0
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -0
- package/dist-client/server/service/styling/text-tools.d.ts +32 -0
- package/dist-client/server/service/styling/text-tools.js +101 -0
- package/dist-client/server/service/styling/text-tools.js.map +1 -0
- package/dist-client/server/service/types.d.ts +265 -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 +8311 -0
- package/dist-client/server/service/validation/board-model-schema.js +402 -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 +193 -0
- package/dist-client/server/service/validation/tool-validation.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/index.d.ts +23 -0
- package/dist-server/index.js +27 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/service/agentic-loop.d.ts +87 -0
- package/dist-server/service/agentic-loop.js +280 -0
- package/dist-server/service/agentic-loop.js.map +1 -0
- package/dist-server/service/apply-patch.d.ts +47 -0
- package/dist-server/service/apply-patch.js +369 -0
- package/dist-server/service/apply-patch.js.map +1 -0
- package/dist-server/service/assistant.d.ts +84 -0
- package/dist-server/service/assistant.js +2237 -0
- package/dist-server/service/assistant.js.map +1 -0
- package/dist-server/service/board-ai-resolver.d.ts +54 -0
- package/dist-server/service/board-ai-resolver.js +409 -0
- package/dist-server/service/board-ai-resolver.js.map +1 -0
- 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-broadcast.d.ts +13 -0
- package/dist-server/service/chat-message/chat-message-broadcast.js +13 -0
- package/dist-server/service/chat-message/chat-message-broadcast.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-publish.d.ts +6 -0
- package/dist-server/service/chat-message/chat-message-publish.js +27 -0
- package/dist-server/service/chat-message/chat-message-publish.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-subscription.d.ts +8 -0
- package/dist-server/service/chat-message/chat-message-subscription.js +53 -0
- package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -0
- package/dist-server/service/chat-message/chat-message.d.ts +56 -0
- package/dist-server/service/chat-message/chat-message.js +149 -0
- package/dist-server/service/chat-message/chat-message.js.map +1 -0
- package/dist-server/service/chat-message/index.d.ts +5 -0
- package/dist-server/service/chat-message/index.js +12 -0
- package/dist-server/service/chat-message/index.js.map +1 -0
- package/dist-server/service/chat-session/chat-session.d.ts +31 -0
- package/dist-server/service/chat-session/chat-session.js +131 -0
- package/dist-server/service/chat-session/chat-session.js.map +1 -0
- package/dist-server/service/chat-session/index.d.ts +3 -0
- package/dist-server/service/chat-session/index.js +7 -0
- package/dist-server/service/chat-session/index.js.map +1 -0
- 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 +4 -0
- package/dist-server/service/chat-session-participant/index.js +10 -0
- package/dist-server/service/chat-session-participant/index.js.map +1 -0
- package/dist-server/service/chat-session-participant/invite-notification.d.ts +27 -0
- package/dist-server/service/chat-session-participant/invite-notification.js +30 -0
- package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -0
- package/dist-server/service/chat-session-participant/presence.d.ts +4 -0
- package/dist-server/service/chat-session-participant/presence.js +22 -0
- package/dist-server/service/chat-session-participant/presence.js.map +1 -0
- package/dist-server/service/chat-session-resolver.d.ts +20 -0
- package/dist-server/service/chat-session-resolver.js +356 -0
- package/dist-server/service/chat-session-resolver.js.map +1 -0
- package/dist-server/service/component-tree.d.ts +68 -0
- package/dist-server/service/component-tree.js +73 -0
- package/dist-server/service/component-tree.js.map +1 -0
- package/dist-server/service/index.d.ts +20 -0
- package/dist-server/service/index.js +34 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/mention-marker.d.ts +78 -0
- package/dist-server/service/mention-marker.js +162 -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/patch-entry/board-patch-broadcast.d.ts +13 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.js +13 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.js.map +1 -0
- package/dist-server/service/patch-entry/board-patch-subscription.d.ts +12 -0
- package/dist-server/service/patch-entry/board-patch-subscription.js +63 -0
- package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -0
- package/dist-server/service/patch-entry/index.d.ts +4 -0
- package/dist-server/service/patch-entry/index.js +12 -0
- package/dist-server/service/patch-entry/index.js.map +1 -0
- package/dist-server/service/patch-entry/patch-entry.d.ts +16 -0
- package/dist-server/service/patch-entry/patch-entry.js +96 -0
- package/dist-server/service/patch-entry/patch-entry.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 +53 -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 +45 -0
- package/dist-server/service/styling/effect-tools.js +90 -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/registry.d.ts +91 -0
- package/dist-server/service/styling/registry.js +352 -0
- package/dist-server/service/styling/registry.js.map +1 -0
- package/dist-server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-server/service/styling/stroke-tools.js +97 -0
- package/dist-server/service/styling/stroke-tools.js.map +1 -0
- package/dist-server/service/styling/text-tools.d.ts +32 -0
- package/dist-server/service/styling/text-tools.js +105 -0
- package/dist-server/service/styling/text-tools.js.map +1 -0
- package/dist-server/service/subscribe-auth.d.ts +14 -0
- package/dist-server/service/subscribe-auth.js +33 -0
- package/dist-server/service/subscribe-auth.js.map +1 -0
- package/dist-server/service/types.d.ts +265 -0
- package/dist-server/service/types.js +3 -0
- package/dist-server/service/types.js.map +1 -0
- package/dist-server/service/validation/board-model-schema.d.ts +8311 -0
- package/dist-server/service/validation/board-model-schema.js +409 -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 +196 -0
- package/dist-server/service/validation/tool-validation.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +48 -0
- package/server/index.ts +23 -0
- package/server/service/agentic-loop.test.ts +705 -0
- package/server/service/agentic-loop.ts +431 -0
- package/server/service/apply-patch-drift.test.ts +829 -0
- package/server/service/apply-patch.test.ts +757 -0
- package/server/service/apply-patch.ts +411 -0
- 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 +2034 -0
- package/server/service/assistant.ts +2378 -0
- package/server/service/board-ai-resolver.ts +407 -0
- package/server/service/catalog-resolver.test.ts +137 -0
- package/server/service/catalog-resolver.ts +102 -0
- package/server/service/chat-message/chat-message-broadcast.integration.test.ts +73 -0
- package/server/service/chat-message/chat-message-broadcast.test.ts +40 -0
- package/server/service/chat-message/chat-message-broadcast.ts +27 -0
- package/server/service/chat-message/chat-message-publish.ts +22 -0
- package/server/service/chat-message/chat-message-subscription.ts +41 -0
- package/server/service/chat-message/chat-message.ts +169 -0
- package/server/service/chat-message/index.ts +12 -0
- package/server/service/chat-session/chat-session.ts +130 -0
- package/server/service/chat-session/index.ts +5 -0
- package/server/service/chat-session-participant/chat-session-participant.ts +110 -0
- package/server/service/chat-session-participant/index.ts +6 -0
- package/server/service/chat-session-participant/invite-notification.test.ts +30 -0
- package/server/service/chat-session-participant/invite-notification.ts +48 -0
- package/server/service/chat-session-participant/presence.test.ts +39 -0
- package/server/service/chat-session-participant/presence.ts +21 -0
- package/server/service/chat-session-resolver.ts +312 -0
- package/server/service/component-tree.test.ts +248 -0
- package/server/service/component-tree.ts +135 -0
- package/server/service/index.ts +32 -0
- package/server/service/mention-marker.extract.test.ts +61 -0
- package/server/service/mention-marker.test.ts +228 -0
- package/server/service/mention-marker.ts +184 -0
- package/server/service/mention-resolver.test.ts +273 -0
- package/server/service/mention-resolver.ts +264 -0
- package/server/service/patch-entry/board-patch-broadcast.ts +24 -0
- package/server/service/patch-entry/board-patch-subscription.test.ts +40 -0
- package/server/service/patch-entry/board-patch-subscription.ts +50 -0
- package/server/service/patch-entry/index.ts +12 -0
- package/server/service/patch-entry/patch-entry.ts +89 -0
- package/server/service/prefab-registry.test.ts +172 -0
- package/server/service/prefab-registry.ts +332 -0
- package/server/service/styling/clear-tools.test.ts +164 -0
- package/server/service/styling/clear-tools.ts +69 -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.test.ts +277 -0
- package/server/service/styling/effect-tools.ts +124 -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.test.ts +294 -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/registry.test.ts +203 -0
- package/server/service/styling/registry.ts +423 -0
- package/server/service/styling/stroke-tools.test.ts +245 -0
- package/server/service/styling/stroke-tools.ts +145 -0
- package/server/service/styling/text-tools.test.ts +253 -0
- package/server/service/styling/text-tools.ts +131 -0
- package/server/service/styling/tier1-tools.test.ts +300 -0
- package/server/service/subscribe-auth.test.ts +57 -0
- package/server/service/subscribe-auth.ts +34 -0
- package/server/service/types.ts +235 -0
- package/server/service/validation/board-model-schema.test.ts +778 -0
- package/server/service/validation/board-model-schema.ts +446 -0
- package/server/service/validation/tool-validation.ts +228 -0
- package/things-factory.config.js +1 -0
- package/translations/en.json +49 -0
- package/translations/ja.json +48 -0
- package/translations/ko.json +49 -0
- package/translations/ms.json +48 -0
- package/translations/zh.json +48 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 보드의 모든 컴포넌트를 재귀 walk 해 평면 list 로 반환.
|
|
3
|
+
*
|
|
4
|
+
* 순서: depth-first pre-order — 부모 먼저, 그 다음 자식. 사용자/AI 가 보드를
|
|
5
|
+
* 좌상단부터 시각적으로 읽는 순서와 일치.
|
|
6
|
+
*
|
|
7
|
+
* 재귀 깊이 안전 — 일반적인 보드 (depth < 10) 에서 stack overflow 없음.
|
|
8
|
+
* 비정상적으로 깊은 (cycle 가능성?) 케이스는 maxDepth 로 cap.
|
|
9
|
+
*/
|
|
10
|
+
export function collectAllComponents(board, options = {}) {
|
|
11
|
+
if (!board || !Array.isArray(board.components))
|
|
12
|
+
return [];
|
|
13
|
+
const maxDepth = options.maxDepth ?? 32;
|
|
14
|
+
const out = [];
|
|
15
|
+
walk(board.components, undefined, 0, [], maxDepth, out);
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
function walk(components, parentRefid, depth, parentPath, maxDepth, out) {
|
|
19
|
+
if (depth > maxDepth)
|
|
20
|
+
return;
|
|
21
|
+
for (const c of components) {
|
|
22
|
+
if (!c || typeof c !== 'object')
|
|
23
|
+
continue;
|
|
24
|
+
const refid = typeof c.refid === 'number' ? c.refid : undefined;
|
|
25
|
+
const path = refid !== undefined ? [...parentPath, refid] : parentPath;
|
|
26
|
+
out.push({ component: c, refid, parentRefid, depth, path });
|
|
27
|
+
// 자식 컴포넌트 — group / container 의 components 배열
|
|
28
|
+
const children = c.components;
|
|
29
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
30
|
+
walk(children, refid, depth + 1, path, maxDepth, out);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function findComponentByRefid(board, refid) {
|
|
35
|
+
if (!board || !Array.isArray(board.components))
|
|
36
|
+
return undefined;
|
|
37
|
+
return findInArray(board.components, refid);
|
|
38
|
+
}
|
|
39
|
+
function findInArray(arr, refid) {
|
|
40
|
+
for (let i = 0; i < arr.length; i++) {
|
|
41
|
+
const c = arr[i];
|
|
42
|
+
if (!c || typeof c !== 'object')
|
|
43
|
+
continue;
|
|
44
|
+
if (c.refid === refid) {
|
|
45
|
+
return { component: c, parentArray: arr, index: i };
|
|
46
|
+
}
|
|
47
|
+
const children = c.components;
|
|
48
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
49
|
+
const sub = findInArray(children, refid);
|
|
50
|
+
if (sub)
|
|
51
|
+
return sub;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* collectAllComponents 결과를 LLM 표시용으로 가볍게 요약 — 메타만.
|
|
58
|
+
* 본 컴포넌트 데이터는 별도 summarize 단계에서 처리.
|
|
59
|
+
*/
|
|
60
|
+
export function describeComponentMeta(flat) {
|
|
61
|
+
return {
|
|
62
|
+
refid: flat.refid,
|
|
63
|
+
type: typeof flat.component.type === 'string' ? flat.component.type : 'unknown',
|
|
64
|
+
parentRefid: flat.parentRefid,
|
|
65
|
+
depth: flat.depth
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=component-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-tree.js","sourceRoot":"","sources":["../../../server/service/component-tree.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 보드의 컴포넌트 트리 walk helper.\n *\n * things-scene 의 보드 모델은 group / container 같은 부모 컴포넌트가 자기\n * `components` 배열로 자식을 갖는 *재귀 구조*. AI / styling tool 의 입장에선\n * \"어떤 컴포넌트가 어디에 있든\" 동일하게 다뤄야 함 — depth 와 parent 정보만\n * 메타로 첨부해 *평면 list* 로 펼쳐 노출.\n *\n * 본 모듈은 *순수* 함수만 — board model 입력만으로 결과 결정. mutation 없음.\n */\nimport type { BoardComponent, BoardModel } from '@things-factory/board-import'\n\n/**\n * 평면화된 컴포넌트 — 원본 컴포넌트 + 메타 (parent, depth, path).\n *\n * 메타는 원본을 mutate 하지 않고 별도로 노출 — LLM 이 \"어느 group 안인지\" 알\n * 수 있되 영속 데이터엔 영향 없음.\n */\nexport interface FlatComponent {\n /** 원본 컴포넌트 (참조 — 변경 시 보드에 반영). */\n component: BoardComponent\n /** 컴포넌트의 refid (없으면 undefined). 타깃팅 용도. */\n refid: number | undefined\n /** 부모 컴포넌트의 refid (root 직속이면 undefined). */\n parentRefid: number | undefined\n /** 0=root 직속, 1=group 안, 2=group 안 group 안, ... */\n depth: number\n /** 부모-자식 chain 의 refid 시퀀스 (root 부터). 본 컴포넌트의 refid 도 끝에 포함. */\n path: number[]\n}\n\n/**\n * 보드의 모든 컴포넌트를 재귀 walk 해 평면 list 로 반환.\n *\n * 순서: depth-first pre-order — 부모 먼저, 그 다음 자식. 사용자/AI 가 보드를\n * 좌상단부터 시각적으로 읽는 순서와 일치.\n *\n * 재귀 깊이 안전 — 일반적인 보드 (depth < 10) 에서 stack overflow 없음.\n * 비정상적으로 깊은 (cycle 가능성?) 케이스는 maxDepth 로 cap.\n */\nexport function collectAllComponents(\n board: BoardModel | null | undefined,\n options: { maxDepth?: number } = {}\n): FlatComponent[] {\n if (!board || !Array.isArray(board.components)) return []\n const maxDepth = options.maxDepth ?? 32\n const out: FlatComponent[] = []\n walk(board.components, undefined, 0, [], maxDepth, out)\n return out\n}\n\nfunction walk(\n components: BoardComponent[],\n parentRefid: number | undefined,\n depth: number,\n parentPath: number[],\n maxDepth: number,\n out: FlatComponent[]\n): void {\n if (depth > maxDepth) return\n for (const c of components) {\n if (!c || typeof c !== 'object') continue\n const refid = typeof (c as any).refid === 'number' ? (c as any).refid : undefined\n const path = refid !== undefined ? [...parentPath, refid] : parentPath\n out.push({ component: c, refid, parentRefid, depth, path })\n\n // 자식 컴포넌트 — group / container 의 components 배열\n const children = (c as any).components\n if (Array.isArray(children) && children.length > 0) {\n walk(children, refid, depth + 1, path, maxDepth, out)\n }\n }\n}\n\n/**\n * 보드 트리 안에서 refid 로 컴포넌트 찾기 — 깊은 search.\n *\n * apply-patch 의 modify / remove 가 자식 refid 도 매칭하도록 해주는 핵심 helper.\n * top-level 만 보던 기존 동작과 달리 group 안 자식도 찾는다.\n *\n * 반환: 찾은 컴포넌트 + 그 컴포넌트가 들어있는 array (parent 또는 board.components)\n * + array 내 index. parent 정보로 in-place mutation 가능.\n */\nexport interface FoundComponent {\n component: BoardComponent\n /** 본 컴포넌트가 속한 array (부모의 components 또는 board.components). 직접 mutation 가능. */\n parentArray: BoardComponent[]\n /** parentArray 내에서의 index. */\n index: number\n}\n\nexport function findComponentByRefid(\n board: BoardModel | null | undefined,\n refid: number\n): FoundComponent | undefined {\n if (!board || !Array.isArray(board.components)) return undefined\n return findInArray(board.components, refid)\n}\n\nfunction findInArray(\n arr: BoardComponent[],\n refid: number\n): FoundComponent | undefined {\n for (let i = 0; i < arr.length; i++) {\n const c = arr[i]\n if (!c || typeof c !== 'object') continue\n if ((c as any).refid === refid) {\n return { component: c, parentArray: arr, index: i }\n }\n const children = (c as any).components\n if (Array.isArray(children) && children.length > 0) {\n const sub = findInArray(children, refid)\n if (sub) return sub\n }\n }\n return undefined\n}\n\n/**\n * collectAllComponents 결과를 LLM 표시용으로 가볍게 요약 — 메타만.\n * 본 컴포넌트 데이터는 별도 summarize 단계에서 처리.\n */\nexport function describeComponentMeta(flat: FlatComponent): {\n refid: number | undefined\n type: string\n parentRefid: number | undefined\n depth: number\n} {\n return {\n refid: flat.refid,\n type: typeof flat.component.type === 'string' ? flat.component.type : 'unknown',\n parentRefid: flat.parentRefid,\n depth: flat.depth\n }\n}\n"]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `#token` 멘션 해석 (things-factory 식별자 컨벤션 준수).
|
|
3
|
+
*
|
|
4
|
+
* things-factory 정책:
|
|
5
|
+
* - `#` 은 사용자가 컴포넌트를 가리키는 SELECTOR. things-scene Finder 의
|
|
6
|
+
* CSS 셀렉터 컨셉과 같은 맥락 — 와이드 검색 (name / id / type 셋 다 OR).
|
|
7
|
+
* - **refid 는 시스템 내부 핸들** — 사용자에게 노출 금지. 사용자가 본 적도
|
|
8
|
+
* 없는 숫자라 `(refid 35)` 같은 표기는 혼란만 가중.
|
|
9
|
+
* - id 는 데이터 바인딩 식별자 (human-readable). unique 제약 없음.
|
|
10
|
+
* - name 은 사용자 라벨. 가장 친숙.
|
|
11
|
+
*
|
|
12
|
+
* Resolver 정책:
|
|
13
|
+
* - 본문의 `#token` 은 LITERAL 보존 (refid 본문 노출 0)
|
|
14
|
+
* - 매칭 결과는 본문 끝에 구조화된 "mention map" 블록으로 첨부
|
|
15
|
+
* - AI 는 시스템 프롬프트의 안내에 따라 mention map 의 refid 를 tool
|
|
16
|
+
* 호출 인자로만 사용. 사용자 답변 텍스트에서는 name/id 로 지칭.
|
|
17
|
+
*
|
|
18
|
+
* Token 매칭 (모두 시도, OR):
|
|
19
|
+
* - name 정확 일치 (case-insensitive)
|
|
20
|
+
* - id 정확 일치 (case-insensitive)
|
|
21
|
+
* - type 정확 일치 (case-insensitive) — `#rect` 같이 모든 type 가리키기
|
|
22
|
+
*
|
|
23
|
+
* 부분 / fuzzy 매칭 안 함 — popup 측에서 사용자가 정확한 식별자 선택.
|
|
24
|
+
*
|
|
25
|
+
* Token 정규식: /#([^\s#]+)/g (한글/영문/하이픈/언더스코어/숫자).
|
|
26
|
+
*/
|
|
27
|
+
import type { BoardModel } from '@things-factory/board-import';
|
|
28
|
+
/** 매칭된 컴포넌트의 핵심 정보 — refid 는 시스템 핸들, 나머지는 사용자 친화적 식별.
|
|
29
|
+
* things-scene F-Finder 와 동일한 검색 필드 (id/name/type/tag/class/text)
|
|
30
|
+
* + position (AI 가 식별 + 조작 좌표 판단에 활용). */
|
|
31
|
+
export interface MentionedComponent {
|
|
32
|
+
refid: number;
|
|
33
|
+
name?: string;
|
|
34
|
+
id?: string;
|
|
35
|
+
type: string;
|
|
36
|
+
tag?: string;
|
|
37
|
+
class?: string;
|
|
38
|
+
text?: string;
|
|
39
|
+
/** 중심 좌표 (rect: left+w/2, top+h/2 / circle: cx, cy) — 정수 반올림 */
|
|
40
|
+
position?: {
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** popup 에서 사용자가 명시 선택한 컴포넌트 — server 가 정확한 매칭에 사용. */
|
|
46
|
+
export interface MentionPick {
|
|
47
|
+
/** 사용자 텍스트의 #token 부분 (예: '경광등1') */
|
|
48
|
+
token: string;
|
|
49
|
+
/** popup 에서 선택된 정확한 refid */
|
|
50
|
+
refid: number;
|
|
51
|
+
}
|
|
52
|
+
export interface ResolveMentionsOptions {
|
|
53
|
+
/** popup 에서 명시 선택된 mentions — 해당 token 은 fallback 매칭 안 거치고 정확한 refid 로 즉시 해석 */
|
|
54
|
+
picks?: MentionPick[];
|
|
55
|
+
}
|
|
56
|
+
export interface MentionMatch {
|
|
57
|
+
/** 사용자가 입력한 #token 의 token 부분 */
|
|
58
|
+
token: string;
|
|
59
|
+
/** 토큰 위치 (start: '#' 위치, end: 마지막 글자 다음) */
|
|
60
|
+
start: number;
|
|
61
|
+
end: number;
|
|
62
|
+
/** name/id/type 매칭으로 발견한 컴포넌트들 */
|
|
63
|
+
matched: MentionedComponent[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 텍스트의 모든 #token 을 보드에서 매칭. UI / 디버그 / 테스트용.
|
|
67
|
+
*/
|
|
68
|
+
export declare function findMentions(text: string, currentBoard: BoardModel | null | undefined, options?: ResolveMentionsOptions): MentionMatch[];
|
|
69
|
+
/**
|
|
70
|
+
* Token 으로 컴포넌트 매칭 — name/id/type/tag/class/text OR (things-scene F-Finder
|
|
71
|
+
* 동일 필드 셋). 여러 기준에서 동시 일치하더라도 같은 refid 는 한 번만 반환 (dedup).
|
|
72
|
+
*
|
|
73
|
+
* picks 에 해당 token 이 있으면 fallback 안 거치고 그 refid 로 직접 매칭 (popup 에서
|
|
74
|
+
* 사용자가 명시 선택한 결정적 매핑). 그 refid 가 보드에 없으면 fallback 으로.
|
|
75
|
+
*/
|
|
76
|
+
export declare function lookupComponents(token: string, components: readonly any[], picks?: readonly MentionPick[]): MentionedComponent[];
|
|
77
|
+
/**
|
|
78
|
+
* 텍스트의 #token 을 LLM 친화 형태로 변환.
|
|
79
|
+
*
|
|
80
|
+
* 본문은 그대로 + 매칭 성공한 토큰의 mention map 을 끝에 첨부:
|
|
81
|
+
*
|
|
82
|
+
* "[원본 본문]
|
|
83
|
+
*
|
|
84
|
+
* --- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---
|
|
85
|
+
* #경광등1 →
|
|
86
|
+
* { name='경광등1', id='motor-1', type='rect', refid=35 }
|
|
87
|
+
* #펌프 →
|
|
88
|
+
* { name='펌프A', id='pump-1', type='rect', refid=42 }
|
|
89
|
+
* { name='펌프B', id='pump-2', type='rect', refid=51 }"
|
|
90
|
+
*
|
|
91
|
+
* AI 는 system prompt 안내에 따라 refid 를 tool 호출 인자로만 사용. 답변
|
|
92
|
+
* 텍스트에서는 refid 직접 언급 금지.
|
|
93
|
+
*
|
|
94
|
+
* 매칭 0 → 변경 없음 (본문 그대로 반환).
|
|
95
|
+
*/
|
|
96
|
+
export declare function resolveMentions(text: string, currentBoard: BoardModel | null | undefined, options?: ResolveMentionsOptions): string;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const MENTION_PATTERN = /#([^\s#]+)/g;
|
|
2
|
+
/**
|
|
3
|
+
* 텍스트의 모든 #token 을 보드에서 매칭. UI / 디버그 / 테스트용.
|
|
4
|
+
*/
|
|
5
|
+
export function findMentions(text, currentBoard, options = {}) {
|
|
6
|
+
if (!text || typeof text !== 'string')
|
|
7
|
+
return [];
|
|
8
|
+
const components = currentBoard?.components ?? [];
|
|
9
|
+
return collectMatches(text, components, options.picks);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Token 으로 컴포넌트 매칭 — name/id/type/tag/class/text OR (things-scene F-Finder
|
|
13
|
+
* 동일 필드 셋). 여러 기준에서 동시 일치하더라도 같은 refid 는 한 번만 반환 (dedup).
|
|
14
|
+
*
|
|
15
|
+
* picks 에 해당 token 이 있으면 fallback 안 거치고 그 refid 로 직접 매칭 (popup 에서
|
|
16
|
+
* 사용자가 명시 선택한 결정적 매핑). 그 refid 가 보드에 없으면 fallback 으로.
|
|
17
|
+
*/
|
|
18
|
+
export function lookupComponents(token, components, picks) {
|
|
19
|
+
if (!token || components.length === 0)
|
|
20
|
+
return [];
|
|
21
|
+
// picks 우선 — popup 으로 명시 선택된 token 은 그 refid 로 직접 매칭
|
|
22
|
+
if (picks) {
|
|
23
|
+
const pick = picks.find(p => p.token === token);
|
|
24
|
+
if (pick) {
|
|
25
|
+
const c = components.find((x) => x && typeof x.refid === 'number' && x.refid === pick.refid);
|
|
26
|
+
if (c) {
|
|
27
|
+
const fields = readSearchFields(c);
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
refid: pick.refid,
|
|
31
|
+
name: fields.name,
|
|
32
|
+
id: fields.id,
|
|
33
|
+
type: fields.type,
|
|
34
|
+
tag: fields.tag,
|
|
35
|
+
class: fields.cls,
|
|
36
|
+
text: fields.text,
|
|
37
|
+
position: extractPosition(c)
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
// pick 이 stale (refid 가 더 이상 보드에 없음) — fallback 으로 진행
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const lower = token.toLowerCase();
|
|
45
|
+
const out = [];
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
for (const c of components) {
|
|
48
|
+
if (!c || typeof c.refid !== 'number')
|
|
49
|
+
continue;
|
|
50
|
+
const refid = c.refid;
|
|
51
|
+
if (seen.has(refid))
|
|
52
|
+
continue;
|
|
53
|
+
const fields = readSearchFields(c);
|
|
54
|
+
if ((fields.name && fields.name.toLowerCase() === lower) ||
|
|
55
|
+
(fields.id && fields.id.toLowerCase() === lower) ||
|
|
56
|
+
fields.type.toLowerCase() === lower ||
|
|
57
|
+
(fields.tag && fields.tag.toLowerCase() === lower) ||
|
|
58
|
+
(fields.cls && fields.cls.toLowerCase() === lower) ||
|
|
59
|
+
(fields.text && fields.text.toLowerCase() === lower)) {
|
|
60
|
+
seen.add(refid);
|
|
61
|
+
out.push({
|
|
62
|
+
refid,
|
|
63
|
+
name: fields.name,
|
|
64
|
+
id: fields.id,
|
|
65
|
+
type: fields.type,
|
|
66
|
+
tag: fields.tag,
|
|
67
|
+
class: fields.cls,
|
|
68
|
+
text: fields.text,
|
|
69
|
+
position: extractPosition(c)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/** 컴포넌트 중심 좌표 — rect: left+w/2 / circle: cx,cy. */
|
|
76
|
+
function extractPosition(c) {
|
|
77
|
+
if (typeof c.cx === 'number' && typeof c.cy === 'number') {
|
|
78
|
+
return { x: Math.round(c.cx), y: Math.round(c.cy) };
|
|
79
|
+
}
|
|
80
|
+
if (typeof c.left === 'number' && typeof c.top === 'number') {
|
|
81
|
+
const w = typeof c.width === 'number' ? c.width : 0;
|
|
82
|
+
const h = typeof c.height === 'number' ? c.height : 0;
|
|
83
|
+
return { x: Math.round(c.left + w / 2), y: Math.round(c.top + h / 2) };
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
function readSearchFields(c) {
|
|
88
|
+
const str = (v) => typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
89
|
+
return {
|
|
90
|
+
name: str(c.name),
|
|
91
|
+
id: str(c.id),
|
|
92
|
+
type: typeof c.type === 'string' ? c.type : 'unknown',
|
|
93
|
+
tag: str(c.tag),
|
|
94
|
+
cls: str(c.class),
|
|
95
|
+
text: str(c.text)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 텍스트의 #token 을 LLM 친화 형태로 변환.
|
|
100
|
+
*
|
|
101
|
+
* 본문은 그대로 + 매칭 성공한 토큰의 mention map 을 끝에 첨부:
|
|
102
|
+
*
|
|
103
|
+
* "[원본 본문]
|
|
104
|
+
*
|
|
105
|
+
* --- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---
|
|
106
|
+
* #경광등1 →
|
|
107
|
+
* { name='경광등1', id='motor-1', type='rect', refid=35 }
|
|
108
|
+
* #펌프 →
|
|
109
|
+
* { name='펌프A', id='pump-1', type='rect', refid=42 }
|
|
110
|
+
* { name='펌프B', id='pump-2', type='rect', refid=51 }"
|
|
111
|
+
*
|
|
112
|
+
* AI 는 system prompt 안내에 따라 refid 를 tool 호출 인자로만 사용. 답변
|
|
113
|
+
* 텍스트에서는 refid 직접 언급 금지.
|
|
114
|
+
*
|
|
115
|
+
* 매칭 0 → 변경 없음 (본문 그대로 반환).
|
|
116
|
+
*/
|
|
117
|
+
export function resolveMentions(text, currentBoard, options = {}) {
|
|
118
|
+
if (!text || typeof text !== 'string')
|
|
119
|
+
return text;
|
|
120
|
+
const components = currentBoard?.components ?? [];
|
|
121
|
+
if (components.length === 0)
|
|
122
|
+
return text;
|
|
123
|
+
const matches = collectMatches(text, components, options.picks);
|
|
124
|
+
const resolved = matches.filter(m => m.matched.length > 0);
|
|
125
|
+
if (resolved.length === 0)
|
|
126
|
+
return text;
|
|
127
|
+
const block = [
|
|
128
|
+
'',
|
|
129
|
+
'--- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---'
|
|
130
|
+
];
|
|
131
|
+
for (const m of resolved) {
|
|
132
|
+
block.push(`#${m.token} →`);
|
|
133
|
+
for (const c of m.matched) {
|
|
134
|
+
block.push(` ${formatComponentRef(c)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return text + '\n' + block.join('\n');
|
|
138
|
+
}
|
|
139
|
+
function collectMatches(text, components, picks) {
|
|
140
|
+
const out = [];
|
|
141
|
+
const pattern = new RegExp(MENTION_PATTERN.source, 'g');
|
|
142
|
+
let m;
|
|
143
|
+
while ((m = pattern.exec(text)) !== null) {
|
|
144
|
+
const token = m[1];
|
|
145
|
+
out.push({
|
|
146
|
+
token,
|
|
147
|
+
start: m.index,
|
|
148
|
+
end: m.index + m[0].length,
|
|
149
|
+
matched: lookupComponents(token, components, picks)
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
function formatComponentRef(c) {
|
|
155
|
+
const parts = [];
|
|
156
|
+
if (c.name)
|
|
157
|
+
parts.push(`name='${c.name}'`);
|
|
158
|
+
if (c.id)
|
|
159
|
+
parts.push(`id='${c.id}'`);
|
|
160
|
+
parts.push(`type='${c.type}'`);
|
|
161
|
+
if (c.tag)
|
|
162
|
+
parts.push(`tag='${c.tag}'`);
|
|
163
|
+
if (c.class)
|
|
164
|
+
parts.push(`class='${c.class}'`);
|
|
165
|
+
if (c.text)
|
|
166
|
+
parts.push(`text='${c.text}'`);
|
|
167
|
+
if (c.position)
|
|
168
|
+
parts.push(`pos=(${c.position.x},${c.position.y})`);
|
|
169
|
+
parts.push(`refid=${c.refid}`);
|
|
170
|
+
return `{ ${parts.join(', ')} }`;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=mention-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mention-resolver.js","sourceRoot":"","sources":["../../../server/service/mention-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `#token` 멘션 해석 (things-factory 식별자 컨벤션 준수).\n *\n * things-factory 정책:\n * - `#` 은 사용자가 컴포넌트를 가리키는 SELECTOR. things-scene Finder 의\n * CSS 셀렉터 컨셉과 같은 맥락 — 와이드 검색 (name / id / type 셋 다 OR).\n * - **refid 는 시스템 내부 핸들** — 사용자에게 노출 금지. 사용자가 본 적도\n * 없는 숫자라 `(refid 35)` 같은 표기는 혼란만 가중.\n * - id 는 데이터 바인딩 식별자 (human-readable). unique 제약 없음.\n * - name 은 사용자 라벨. 가장 친숙.\n *\n * Resolver 정책:\n * - 본문의 `#token` 은 LITERAL 보존 (refid 본문 노출 0)\n * - 매칭 결과는 본문 끝에 구조화된 \"mention map\" 블록으로 첨부\n * - AI 는 시스템 프롬프트의 안내에 따라 mention map 의 refid 를 tool\n * 호출 인자로만 사용. 사용자 답변 텍스트에서는 name/id 로 지칭.\n *\n * Token 매칭 (모두 시도, OR):\n * - name 정확 일치 (case-insensitive)\n * - id 정확 일치 (case-insensitive)\n * - type 정확 일치 (case-insensitive) — `#rect` 같이 모든 type 가리키기\n *\n * 부분 / fuzzy 매칭 안 함 — popup 측에서 사용자가 정확한 식별자 선택.\n *\n * Token 정규식: /#([^\\s#]+)/g (한글/영문/하이픈/언더스코어/숫자).\n */\nimport type { BoardModel } from '@things-factory/board-import'\n\nconst MENTION_PATTERN = /#([^\\s#]+)/g\n\n/** 매칭된 컴포넌트의 핵심 정보 — refid 는 시스템 핸들, 나머지는 사용자 친화적 식별.\n * things-scene F-Finder 와 동일한 검색 필드 (id/name/type/tag/class/text)\n * + position (AI 가 식별 + 조작 좌표 판단에 활용). */\nexport interface MentionedComponent {\n refid: number\n name?: string\n id?: string\n type: string\n tag?: string\n class?: string\n text?: string\n /** 중심 좌표 (rect: left+w/2, top+h/2 / circle: cx, cy) — 정수 반올림 */\n position?: { x: number; y: number }\n}\n\n/** popup 에서 사용자가 명시 선택한 컴포넌트 — server 가 정확한 매칭에 사용. */\nexport interface MentionPick {\n /** 사용자 텍스트의 #token 부분 (예: '경광등1') */\n token: string\n /** popup 에서 선택된 정확한 refid */\n refid: number\n}\n\nexport interface ResolveMentionsOptions {\n /** popup 에서 명시 선택된 mentions — 해당 token 은 fallback 매칭 안 거치고 정확한 refid 로 즉시 해석 */\n picks?: MentionPick[]\n}\n\nexport interface MentionMatch {\n /** 사용자가 입력한 #token 의 token 부분 */\n token: string\n /** 토큰 위치 (start: '#' 위치, end: 마지막 글자 다음) */\n start: number\n end: number\n /** name/id/type 매칭으로 발견한 컴포넌트들 */\n matched: MentionedComponent[]\n}\n\n/**\n * 텍스트의 모든 #token 을 보드에서 매칭. UI / 디버그 / 테스트용.\n */\nexport function findMentions(\n text: string,\n currentBoard: BoardModel | null | undefined,\n options: ResolveMentionsOptions = {}\n): MentionMatch[] {\n if (!text || typeof text !== 'string') return []\n const components = currentBoard?.components ?? []\n return collectMatches(text, components, options.picks)\n}\n\n\n/**\n * Token 으로 컴포넌트 매칭 — name/id/type/tag/class/text OR (things-scene F-Finder\n * 동일 필드 셋). 여러 기준에서 동시 일치하더라도 같은 refid 는 한 번만 반환 (dedup).\n *\n * picks 에 해당 token 이 있으면 fallback 안 거치고 그 refid 로 직접 매칭 (popup 에서\n * 사용자가 명시 선택한 결정적 매핑). 그 refid 가 보드에 없으면 fallback 으로.\n */\nexport function lookupComponents(\n token: string,\n components: readonly any[],\n picks?: readonly MentionPick[]\n): MentionedComponent[] {\n if (!token || components.length === 0) return []\n\n // picks 우선 — popup 으로 명시 선택된 token 은 그 refid 로 직접 매칭\n if (picks) {\n const pick = picks.find(p => p.token === token)\n if (pick) {\n const c = components.find(\n (x: any) => x && typeof x.refid === 'number' && x.refid === pick.refid\n )\n if (c) {\n const fields = readSearchFields(c)\n return [\n {\n refid: pick.refid,\n name: fields.name,\n id: fields.id,\n type: fields.type,\n tag: fields.tag,\n class: fields.cls,\n text: fields.text,\n position: extractPosition(c)\n }\n ]\n }\n // pick 이 stale (refid 가 더 이상 보드에 없음) — fallback 으로 진행\n }\n }\n\n const lower = token.toLowerCase()\n const out: MentionedComponent[] = []\n const seen = new Set<number>()\n for (const c of components) {\n if (!c || typeof (c as any).refid !== 'number') continue\n const refid = (c as any).refid\n if (seen.has(refid)) continue\n const fields = readSearchFields(c)\n if (\n (fields.name && fields.name.toLowerCase() === lower) ||\n (fields.id && fields.id.toLowerCase() === lower) ||\n fields.type.toLowerCase() === lower ||\n (fields.tag && fields.tag.toLowerCase() === lower) ||\n (fields.cls && fields.cls.toLowerCase() === lower) ||\n (fields.text && fields.text.toLowerCase() === lower)\n ) {\n seen.add(refid)\n out.push({\n refid,\n name: fields.name,\n id: fields.id,\n type: fields.type,\n tag: fields.tag,\n class: fields.cls,\n text: fields.text,\n position: extractPosition(c)\n })\n }\n }\n return out\n}\n\n/** 컴포넌트 중심 좌표 — rect: left+w/2 / circle: cx,cy. */\nfunction extractPosition(c: any): { x: number; y: number } | undefined {\n if (typeof c.cx === 'number' && typeof c.cy === 'number') {\n return { x: Math.round(c.cx), y: Math.round(c.cy) }\n }\n if (typeof c.left === 'number' && typeof c.top === 'number') {\n const w = typeof c.width === 'number' ? c.width : 0\n const h = typeof c.height === 'number' ? c.height : 0\n return { x: Math.round(c.left + w / 2), y: Math.round(c.top + h / 2) }\n }\n return undefined\n}\n\nfunction readSearchFields(c: any): {\n name?: string\n id?: string\n type: string\n tag?: string\n cls?: string\n text?: string\n} {\n const str = (v: any): string | undefined =>\n typeof v === 'string' && v.length > 0 ? v : undefined\n return {\n name: str(c.name),\n id: str(c.id),\n type: typeof c.type === 'string' ? c.type : 'unknown',\n tag: str(c.tag),\n cls: str(c.class),\n text: str(c.text)\n }\n}\n\n/**\n * 텍스트의 #token 을 LLM 친화 형태로 변환.\n *\n * 본문은 그대로 + 매칭 성공한 토큰의 mention map 을 끝에 첨부:\n *\n * \"[원본 본문]\n *\n * --- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---\n * #경광등1 →\n * { name='경광등1', id='motor-1', type='rect', refid=35 }\n * #펌프 →\n * { name='펌프A', id='pump-1', type='rect', refid=42 }\n * { name='펌프B', id='pump-2', type='rect', refid=51 }\"\n *\n * AI 는 system prompt 안내에 따라 refid 를 tool 호출 인자로만 사용. 답변\n * 텍스트에서는 refid 직접 언급 금지.\n *\n * 매칭 0 → 변경 없음 (본문 그대로 반환).\n */\nexport function resolveMentions(\n text: string,\n currentBoard: BoardModel | null | undefined,\n options: ResolveMentionsOptions = {}\n): string {\n if (!text || typeof text !== 'string') return text\n const components = currentBoard?.components ?? []\n if (components.length === 0) return text\n\n const matches = collectMatches(text, components, options.picks)\n const resolved = matches.filter(m => m.matched.length > 0)\n if (resolved.length === 0) return text\n\n const block: string[] = [\n '',\n '--- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---'\n ]\n for (const m of resolved) {\n block.push(`#${m.token} →`)\n for (const c of m.matched) {\n block.push(` ${formatComponentRef(c)}`)\n }\n }\n return text + '\\n' + block.join('\\n')\n}\n\nfunction collectMatches(\n text: string,\n components: readonly any[],\n picks?: readonly MentionPick[]\n): MentionMatch[] {\n const out: MentionMatch[] = []\n const pattern = new RegExp(MENTION_PATTERN.source, 'g')\n let m: RegExpExecArray | null\n while ((m = pattern.exec(text)) !== null) {\n const token = m[1]\n out.push({\n token,\n start: m.index,\n end: m.index + m[0].length,\n matched: lookupComponents(token, components, picks)\n })\n }\n return out\n}\n\nfunction formatComponentRef(c: MentionedComponent): string {\n const parts: string[] = []\n if (c.name) parts.push(`name='${c.name}'`)\n if (c.id) parts.push(`id='${c.id}'`)\n parts.push(`type='${c.type}'`)\n if (c.tag) parts.push(`tag='${c.tag}'`)\n if (c.class) parts.push(`class='${c.class}'`)\n if (c.text) parts.push(`text='${c.text}'`)\n if (c.position) parts.push(`pos=(${c.position.x},${c.position.y})`)\n parts.push(`refid=${c.refid}`)\n return `{ ${parts.join(', ')} }`\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
export interface BoardPrefab {
|
|
26
|
+
/** Unique 식별자 — 'kebab-case' 권장 */
|
|
27
|
+
name: string;
|
|
28
|
+
/** 자유 분류 — 'monitoring' | 'control' | 'decoration' | 'kpi' 등 */
|
|
29
|
+
category?: string;
|
|
30
|
+
/** 사용자 / LLM 이 이해할 한 문장 설명 */
|
|
31
|
+
description: string;
|
|
32
|
+
/**
|
|
33
|
+
* 자연어 매칭 힌트 — '온도', '게이지', '카드' 등.
|
|
34
|
+
* findComponents 같은 검색이 아니라 LLM 의 의도 매칭용 메타데이터.
|
|
35
|
+
*/
|
|
36
|
+
keywords?: string[];
|
|
37
|
+
/** 권장 기본 크기 — options 에 width/height 미지정 시 사용 */
|
|
38
|
+
defaultSize?: {
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Prefab 의 컴포넌트 트리를 만든다. 단일 `BoardComponent` 반환 — 보통 group 타입이며
|
|
44
|
+
* `components` 자식을 가진다. 단일 add op 으로 매핑되어 undo 도 한 단위.
|
|
45
|
+
*
|
|
46
|
+
* options:
|
|
47
|
+
* - left/top: anchor 좌표 (top-left 원점)
|
|
48
|
+
* - width/height: 크기 (없으면 defaultSize)
|
|
49
|
+
* - 추가 키: prefab 별 자유 (title 텍스트, 색상 등)
|
|
50
|
+
*/
|
|
51
|
+
build(options: PrefabBuildOptions): BoardComponent;
|
|
52
|
+
}
|
|
53
|
+
export interface PrefabBuildOptions {
|
|
54
|
+
left: number;
|
|
55
|
+
top: number;
|
|
56
|
+
width?: number;
|
|
57
|
+
height?: number;
|
|
58
|
+
/** prefab 별 커스터마이징 — 자유 형태 */
|
|
59
|
+
[k: string]: any;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Prefab 등록. 같은 name 은 덮어씀 (호스트가 자기 prefab 으로 default 를 교체 가능).
|
|
63
|
+
*/
|
|
64
|
+
export declare function registerBoardPrefab(prefab: BoardPrefab): void;
|
|
65
|
+
/** 이름으로 조회. 없으면 undefined. */
|
|
66
|
+
export declare function findBoardPrefab(name: string): BoardPrefab | undefined;
|
|
67
|
+
/** 등록된 prefab 의 메타데이터 (build 함수 제외) — getAvailablePrefabs 결과용. */
|
|
68
|
+
export declare function listBoardPrefabs(filter?: {
|
|
69
|
+
category?: string;
|
|
70
|
+
keywordPattern?: string;
|
|
71
|
+
}): Array<Omit<BoardPrefab, 'build'>>;
|
|
72
|
+
/** 테스트용 — 등록 상태 초기화. */
|
|
73
|
+
export declare function clearBoardPrefabsForTest(): void;
|