@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,409 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BoardAIChatResolver = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/**
|
|
6
|
+
* GraphQL resolver — AI 주도 보드 모델링 (chat).
|
|
7
|
+
*
|
|
8
|
+
* Mutation: boardAIChat — 자연어 채팅. sessionId 가 있으면 메시지/패치 영속.
|
|
9
|
+
* sessionId 가 없으면 ad-hoc 모드로 단발 호출 (영속 없음).
|
|
10
|
+
*/
|
|
11
|
+
const type_graphql_1 = require("type-graphql");
|
|
12
|
+
const graphql_scalars_1 = require("graphql-scalars");
|
|
13
|
+
require("@things-factory/auth-base");
|
|
14
|
+
const env_1 = require("@things-factory/env");
|
|
15
|
+
const shell_1 = require("@things-factory/shell");
|
|
16
|
+
const ai_client_base_1 = require("@things-factory/ai-client-base");
|
|
17
|
+
const assistant_js_1 = require("./assistant.js");
|
|
18
|
+
const chat_session_js_1 = require("./chat-session/chat-session.js");
|
|
19
|
+
const chat_message_js_1 = require("./chat-message/chat-message.js");
|
|
20
|
+
const chat_message_publish_js_1 = require("./chat-message/chat-message-publish.js");
|
|
21
|
+
const patch_entry_js_1 = require("./patch-entry/patch-entry.js");
|
|
22
|
+
const board_patch_subscription_js_1 = require("./patch-entry/board-patch-subscription.js");
|
|
23
|
+
const chat_session_participant_js_1 = require("./chat-session-participant/chat-session-participant.js");
|
|
24
|
+
const invite_notification_js_1 = require("./chat-session-participant/invite-notification.js");
|
|
25
|
+
const mention_marker_js_1 = require("./mention-marker.js");
|
|
26
|
+
let BoardAILLMMessageInput = class BoardAILLMMessageInput {
|
|
27
|
+
};
|
|
28
|
+
tslib_1.__decorate([
|
|
29
|
+
(0, type_graphql_1.Field)({ description: "Role: 'user' or 'assistant'" }),
|
|
30
|
+
tslib_1.__metadata("design:type", String)
|
|
31
|
+
], BoardAILLMMessageInput.prototype, "role", void 0);
|
|
32
|
+
tslib_1.__decorate([
|
|
33
|
+
(0, type_graphql_1.Field)({ description: 'Message content' }),
|
|
34
|
+
tslib_1.__metadata("design:type", String)
|
|
35
|
+
], BoardAILLMMessageInput.prototype, "content", void 0);
|
|
36
|
+
BoardAILLMMessageInput = tslib_1.__decorate([
|
|
37
|
+
(0, type_graphql_1.InputType)()
|
|
38
|
+
], BoardAILLMMessageInput);
|
|
39
|
+
let BoardAIMentionPickInput = class BoardAIMentionPickInput {
|
|
40
|
+
};
|
|
41
|
+
tslib_1.__decorate([
|
|
42
|
+
(0, type_graphql_1.Field)({ description: 'Token used by user (e.g. "경광등1" — the part after #).' }),
|
|
43
|
+
tslib_1.__metadata("design:type", String)
|
|
44
|
+
], BoardAIMentionPickInput.prototype, "token", void 0);
|
|
45
|
+
tslib_1.__decorate([
|
|
46
|
+
(0, type_graphql_1.Field)(() => type_graphql_1.Int, {
|
|
47
|
+
description: 'Refid explicitly picked from popup. Server uses this for deterministic resolution — bypasses fallback name/id/type matching for this token.'
|
|
48
|
+
}),
|
|
49
|
+
tslib_1.__metadata("design:type", Number)
|
|
50
|
+
], BoardAIMentionPickInput.prototype, "refid", void 0);
|
|
51
|
+
BoardAIMentionPickInput = tslib_1.__decorate([
|
|
52
|
+
(0, type_graphql_1.InputType)()
|
|
53
|
+
], BoardAIMentionPickInput);
|
|
54
|
+
let BoardAIChatInput = class BoardAIChatInput {
|
|
55
|
+
};
|
|
56
|
+
tslib_1.__decorate([
|
|
57
|
+
(0, type_graphql_1.Field)({
|
|
58
|
+
nullable: true,
|
|
59
|
+
description: 'ChatSession id. Omit for ad-hoc (no persistence). When given, messages/patches are persisted.'
|
|
60
|
+
}),
|
|
61
|
+
tslib_1.__metadata("design:type", String)
|
|
62
|
+
], BoardAIChatInput.prototype, "sessionId", void 0);
|
|
63
|
+
tslib_1.__decorate([
|
|
64
|
+
(0, type_graphql_1.Field)(() => [BoardAILLMMessageInput], {
|
|
65
|
+
description: 'Conversation history (latest user message at the end). For persisted sessions, only the last user message is appended; older are loaded from DB.'
|
|
66
|
+
}),
|
|
67
|
+
tslib_1.__metadata("design:type", Array)
|
|
68
|
+
], BoardAIChatInput.prototype, "messages", void 0);
|
|
69
|
+
tslib_1.__decorate([
|
|
70
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, { nullable: true, description: 'Current BoardModel JSON.' }),
|
|
71
|
+
tslib_1.__metadata("design:type", Object)
|
|
72
|
+
], BoardAIChatInput.prototype, "currentBoard", void 0);
|
|
73
|
+
tslib_1.__decorate([
|
|
74
|
+
(0, type_graphql_1.Field)(() => [String], {
|
|
75
|
+
nullable: true,
|
|
76
|
+
description: "Capability scopes the AI is allowed to use (e.g. 'create', 'edit', 'style'). Filters which tools are exposed to the LLM."
|
|
77
|
+
}),
|
|
78
|
+
tslib_1.__metadata("design:type", Array)
|
|
79
|
+
], BoardAIChatInput.prototype, "scopes", void 0);
|
|
80
|
+
tslib_1.__decorate([
|
|
81
|
+
(0, type_graphql_1.Field)(() => [String], {
|
|
82
|
+
nullable: true,
|
|
83
|
+
description: 'Component types the LLM is allowed to emit (e.g. ["rect", "label"]). Constrains generation to the current solution.'
|
|
84
|
+
}),
|
|
85
|
+
tslib_1.__metadata("design:type", Array)
|
|
86
|
+
], BoardAIChatInput.prototype, "knownTypes", void 0);
|
|
87
|
+
tslib_1.__decorate([
|
|
88
|
+
(0, type_graphql_1.Field)(() => [String], {
|
|
89
|
+
nullable: true,
|
|
90
|
+
description: 'Component categories to surface (e.g. ["3D", "form"]). Subset filter for groupings exposed in component-schemas.'
|
|
91
|
+
}),
|
|
92
|
+
tslib_1.__metadata("design:type", Array)
|
|
93
|
+
], BoardAIChatInput.prototype, "categories", void 0);
|
|
94
|
+
tslib_1.__decorate([
|
|
95
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, {
|
|
96
|
+
nullable: true,
|
|
97
|
+
description: 'Component schemas — array of { type, description?, group?, properties? } so the LLM knows valid props per type.'
|
|
98
|
+
}),
|
|
99
|
+
tslib_1.__metadata("design:type", Object)
|
|
100
|
+
], BoardAIChatInput.prototype, "componentSchemas", void 0);
|
|
101
|
+
tslib_1.__decorate([
|
|
102
|
+
(0, type_graphql_1.Field)(() => [type_graphql_1.Int], {
|
|
103
|
+
nullable: true,
|
|
104
|
+
description: 'Refids of components currently selected in the modeller (universal numeric handle, things-scene auto-assigned). For "selected" / "선택한" intent. Note: distinct from `id` which is a data-binding name.'
|
|
105
|
+
}),
|
|
106
|
+
tslib_1.__metadata("design:type", Array)
|
|
107
|
+
], BoardAIChatInput.prototype, "selectedRefids", void 0);
|
|
108
|
+
tslib_1.__decorate([
|
|
109
|
+
(0, type_graphql_1.Field)(() => [BoardAIMentionPickInput], {
|
|
110
|
+
nullable: true,
|
|
111
|
+
description: 'Explicit #mention picks from popup — {token, refid}. User text stays clean (#token only); these picks let server resolve deterministically without exposing refid in the user-visible message. Tokens not in this list fall back to free name/id/type matching.'
|
|
112
|
+
}),
|
|
113
|
+
tslib_1.__metadata("design:type", Array)
|
|
114
|
+
], BoardAIChatInput.prototype, "mentions", void 0);
|
|
115
|
+
tslib_1.__decorate([
|
|
116
|
+
(0, type_graphql_1.Field)({
|
|
117
|
+
nullable: true,
|
|
118
|
+
description: 'AI model identifier override (e.g. "claude-opus-4-7"). Falls back to the configured default when omitted.'
|
|
119
|
+
}),
|
|
120
|
+
tslib_1.__metadata("design:type", String)
|
|
121
|
+
], BoardAIChatInput.prototype, "model", void 0);
|
|
122
|
+
tslib_1.__decorate([
|
|
123
|
+
(0, type_graphql_1.Field)({
|
|
124
|
+
nullable: true,
|
|
125
|
+
description: 'Sampling temperature override. Falls back to assistant default when omitted.'
|
|
126
|
+
}),
|
|
127
|
+
tslib_1.__metadata("design:type", Number)
|
|
128
|
+
], BoardAIChatInput.prototype, "temperature", void 0);
|
|
129
|
+
tslib_1.__decorate([
|
|
130
|
+
(0, type_graphql_1.Field)({
|
|
131
|
+
nullable: true,
|
|
132
|
+
description: 'Max output tokens override per LLM call. Falls back to assistant default when omitted.'
|
|
133
|
+
}),
|
|
134
|
+
tslib_1.__metadata("design:type", Number)
|
|
135
|
+
], BoardAIChatInput.prototype, "maxTokens", void 0);
|
|
136
|
+
BoardAIChatInput = tslib_1.__decorate([
|
|
137
|
+
(0, type_graphql_1.InputType)()
|
|
138
|
+
], BoardAIChatInput);
|
|
139
|
+
let BoardAIChatOutput = class BoardAIChatOutput {
|
|
140
|
+
};
|
|
141
|
+
tslib_1.__decorate([
|
|
142
|
+
(0, type_graphql_1.Field)({ description: 'Conversational reply.' }),
|
|
143
|
+
tslib_1.__metadata("design:type", String)
|
|
144
|
+
], BoardAIChatOutput.prototype, "reply", void 0);
|
|
145
|
+
tslib_1.__decorate([
|
|
146
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, { nullable: true, description: 'BoardEditPatch.' }),
|
|
147
|
+
tslib_1.__metadata("design:type", Object)
|
|
148
|
+
], BoardAIChatOutput.prototype, "patch", void 0);
|
|
149
|
+
tslib_1.__decorate([
|
|
150
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Clarifying question when input is ambiguous.' }),
|
|
151
|
+
tslib_1.__metadata("design:type", String)
|
|
152
|
+
], BoardAIChatOutput.prototype, "followUp", void 0);
|
|
153
|
+
tslib_1.__decorate([
|
|
154
|
+
(0, type_graphql_1.Field)({ description: 'AI client identifier (provider:model).' }),
|
|
155
|
+
tslib_1.__metadata("design:type", String)
|
|
156
|
+
], BoardAIChatOutput.prototype, "clientId", void 0);
|
|
157
|
+
tslib_1.__decorate([
|
|
158
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Echo of session id (when persisted).' }),
|
|
159
|
+
tslib_1.__metadata("design:type", String)
|
|
160
|
+
], BoardAIChatOutput.prototype, "sessionId", void 0);
|
|
161
|
+
tslib_1.__decorate([
|
|
162
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Persisted ChatMessage id of the user input.' }),
|
|
163
|
+
tslib_1.__metadata("design:type", String)
|
|
164
|
+
], BoardAIChatOutput.prototype, "userMessageId", void 0);
|
|
165
|
+
tslib_1.__decorate([
|
|
166
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Persisted ChatMessage id of the AI reply.' }),
|
|
167
|
+
tslib_1.__metadata("design:type", String)
|
|
168
|
+
], BoardAIChatOutput.prototype, "assistantMessageId", void 0);
|
|
169
|
+
tslib_1.__decorate([
|
|
170
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Persisted PatchEntry id (when patch was generated).' }),
|
|
171
|
+
tslib_1.__metadata("design:type", String)
|
|
172
|
+
], BoardAIChatOutput.prototype, "patchId", void 0);
|
|
173
|
+
tslib_1.__decorate([
|
|
174
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, {
|
|
175
|
+
nullable: true,
|
|
176
|
+
description: 'Tool usages collected during agentic loop — sequence of {name, arguments, result, kind}. UI fold-able box for transparency / debug.'
|
|
177
|
+
}),
|
|
178
|
+
tslib_1.__metadata("design:type", Object)
|
|
179
|
+
], BoardAIChatOutput.prototype, "toolUsages", void 0);
|
|
180
|
+
tslib_1.__decorate([
|
|
181
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, {
|
|
182
|
+
nullable: true,
|
|
183
|
+
description: 'Ephemeral scene actions (selection/view/mode) — sequence of BoardActionOp. Applied via board-action-execute event on the host. Distinct from `patch` which carries persistent model changes.'
|
|
184
|
+
}),
|
|
185
|
+
tslib_1.__metadata("design:type", Object)
|
|
186
|
+
], BoardAIChatOutput.prototype, "actions", void 0);
|
|
187
|
+
BoardAIChatOutput = tslib_1.__decorate([
|
|
188
|
+
(0, type_graphql_1.ObjectType)()
|
|
189
|
+
], BoardAIChatOutput);
|
|
190
|
+
let BoardAIChatResolver = class BoardAIChatResolver {
|
|
191
|
+
/**
|
|
192
|
+
* NOTE: 일부러 @Directive('@transaction') 사용하지 않는다.
|
|
193
|
+
* LLM 호출은 수 초~수십 초 — 트랜잭션 안에서 호출하면 SQLite write lock 을
|
|
194
|
+
* 그 시간 동안 보유 → 다른 작업 (cache 정리 등) 과 SQLITE_BUSY 충돌.
|
|
195
|
+
* 따라서 DB 쓰기를 LLM 호출 전후의 짧은 단위로 나누고 각각 auto-commit.
|
|
196
|
+
* 데이터 일관성: 채팅 이력은 부분 누락보다 부분 보존이 안전 (대화 컨텍스트).
|
|
197
|
+
*/
|
|
198
|
+
async boardAIChat(input, context) {
|
|
199
|
+
const base = resolveAIClient();
|
|
200
|
+
if (!base) {
|
|
201
|
+
throw new Error('No AI client configured. Set config.aiClient = { provider: "anthropic" | "gemini", model, apiKey } or call setDefaultAIClient(...).');
|
|
202
|
+
}
|
|
203
|
+
const { domain, user } = context.state;
|
|
204
|
+
const sessionRepo = (0, shell_1.getRepository)(chat_session_js_1.ChatSession);
|
|
205
|
+
const messageRepo = (0, shell_1.getRepository)(chat_message_js_1.ChatMessage);
|
|
206
|
+
const patchRepo = (0, shell_1.getRepository)(patch_entry_js_1.PatchEntry);
|
|
207
|
+
// ── Phase A: LLM 호출 전 짧은 DB 쓰기 ─────────────────────────
|
|
208
|
+
let session;
|
|
209
|
+
if (input.sessionId) {
|
|
210
|
+
const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } });
|
|
211
|
+
if (!found)
|
|
212
|
+
throw new Error(`ChatSession ${input.sessionId} not found`);
|
|
213
|
+
session = found;
|
|
214
|
+
}
|
|
215
|
+
const lastUserMsg = input.messages[input.messages.length - 1];
|
|
216
|
+
let userMessageId;
|
|
217
|
+
if (session && lastUserMsg && lastUserMsg.role === 'user') {
|
|
218
|
+
// popup pick 의 refid 를 #token{refid:N} inline marker 로 content 에 박아 저장.
|
|
219
|
+
// 단일 source-of-truth — 별도 컬럼 없이 content 안에서 token-refid 매핑 보존.
|
|
220
|
+
const enriched = (0, mention_marker_js_1.injectMentionRefids)(lastUserMsg.content, input.mentions);
|
|
221
|
+
// parentMessage = 이 세션의 직전 메시지 (보통 assistant / system / 또는 첫 turn 이면 null).
|
|
222
|
+
// tree 구조 인프라 — branching (regenerate / 사용자별 분기) 는 같은 부모 child 로.
|
|
223
|
+
const previousLast = await messageRepo.findOne({
|
|
224
|
+
where: { session: { id: session.id } },
|
|
225
|
+
order: { createdAt: 'DESC' }
|
|
226
|
+
});
|
|
227
|
+
const saved = await messageRepo.save({
|
|
228
|
+
session: { id: session.id },
|
|
229
|
+
role: 'user',
|
|
230
|
+
content: enriched,
|
|
231
|
+
creator: user,
|
|
232
|
+
updater: user,
|
|
233
|
+
parentMessage: previousLast ? { id: previousLast.id } : undefined
|
|
234
|
+
});
|
|
235
|
+
userMessageId = saved.id;
|
|
236
|
+
// C4: 사용자 메시지를 같은 세션 구독자에게 방송 (그룹챗 실시간). best-effort.
|
|
237
|
+
// 자기 발신 에코는 클라이언트가 message.id 로 dedup.
|
|
238
|
+
(0, chat_message_publish_js_1.publishChatMessage)({ domainId: domain.id, sessionId: session.id, message: saved });
|
|
239
|
+
// C4: @user 멘션 → 세션 참여자(member) 자동 등록 (초대). best-effort — 실패해도 chat 흐름 무영향.
|
|
240
|
+
// enriched content 의 {userId:UUID} 마커에서 추출 → 기존 참여자/자기 제외한 신규만 member 로 등록.
|
|
241
|
+
try {
|
|
242
|
+
await this._inviteMentionedUsers(enriched, session.id, domain.id, user, session.boardId, context.state.notify);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
/* 초대 실패는 대화를 막지 않는다 */
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// ── Phase B: LLM 호출 (DB 잠금 없음, 오래 걸려도 OK) ───────────
|
|
249
|
+
const assistant = new assistant_js_1.DefaultBoardAIAssistant(base, {
|
|
250
|
+
scopes: input.scopes,
|
|
251
|
+
knownTypes: input.knownTypes,
|
|
252
|
+
categories: input.categories
|
|
253
|
+
});
|
|
254
|
+
// 영속된 메시지에는 #token{refid:N} inline marker 가 박혀 있을 수 있음. LLM 한테는
|
|
255
|
+
// 깨끗한 텍스트만 보여 주의 분산 방지 + 토큰 낭비 방지.
|
|
256
|
+
const llmMessages = input.messages.map(m => ({
|
|
257
|
+
role: m.role === 'assistant' ? 'assistant' : 'user',
|
|
258
|
+
content: (0, mention_marker_js_1.stripMentionRefids)(m.content)
|
|
259
|
+
}));
|
|
260
|
+
const r = await assistant.chat(llmMessages, input.currentBoard ?? undefined, {
|
|
261
|
+
scopes: input.scopes,
|
|
262
|
+
knownTypes: input.knownTypes,
|
|
263
|
+
categories: input.categories,
|
|
264
|
+
selectedRefids: input.selectedRefids,
|
|
265
|
+
mentionPicks: input.mentions,
|
|
266
|
+
componentSchemas: Array.isArray(input.componentSchemas) ? input.componentSchemas : undefined,
|
|
267
|
+
model: input.model,
|
|
268
|
+
temperature: input.temperature ?? undefined,
|
|
269
|
+
maxTokens: input.maxTokens ?? undefined,
|
|
270
|
+
// 외부 도메인 (board-import 등) 의 read/external tool builder 가 server-side
|
|
271
|
+
// 의존성 (도메인 / 유저 / 트랜잭션) 을 사용할 수 있도록 ResolverContext.state 전달.
|
|
272
|
+
// board-ai 코어 자체는 이 값을 알지 않고 dispatch 단계에서 closure 로만 흘림.
|
|
273
|
+
toolCallContext: context.state
|
|
274
|
+
});
|
|
275
|
+
// ── Phase C: LLM 호출 후 짧은 DB 쓰기 ─────────────────────────
|
|
276
|
+
let assistantMessageId;
|
|
277
|
+
let patchId;
|
|
278
|
+
if (session) {
|
|
279
|
+
let patchEntry;
|
|
280
|
+
if (r.patch) {
|
|
281
|
+
patchEntry = await patchRepo.save({
|
|
282
|
+
session: { id: session.id },
|
|
283
|
+
source: 'ai',
|
|
284
|
+
ops: JSON.stringify(r.patch.ops),
|
|
285
|
+
summary: r.patch.summary,
|
|
286
|
+
confidence: r.patch.confidence,
|
|
287
|
+
reverted: false
|
|
288
|
+
});
|
|
289
|
+
patchId = patchEntry.id;
|
|
290
|
+
// C1: 같은 board 구독자에게 방송 (실시간 공동편집 spine). best-effort.
|
|
291
|
+
(0, board_patch_subscription_js_1.publishBoardPatch)({ domainId: domain.id, boardId: session.boardId, patch: patchEntry });
|
|
292
|
+
}
|
|
293
|
+
// AI 응답의 #token 들을 currentBoard 에 대해 resolve 한 뒤 inline marker 로 박음.
|
|
294
|
+
// patch 로 새로 만든 컴포넌트는 input.currentBoard 에 없으므로 marker 없이 저장 →
|
|
295
|
+
// 클릭 시 runtime fallback 으로 해석 (그때는 patch 적용 후라 scene 에 존재).
|
|
296
|
+
const enrichedReply = (0, mention_marker_js_1.injectAiMentionRefids)(r.reply, input.currentBoard ?? null);
|
|
297
|
+
const aiMsg = await messageRepo.save({
|
|
298
|
+
session: { id: session.id },
|
|
299
|
+
role: 'assistant',
|
|
300
|
+
content: enrichedReply,
|
|
301
|
+
relatedPatchId: patchEntry?.id,
|
|
302
|
+
toolUsages: r.toolUsages && r.toolUsages.length > 0 ? JSON.stringify(r.toolUsages) : undefined,
|
|
303
|
+
// assistant 메시지의 creator = 이 응답을 트리거한 user (= 직전 user 메시지의 작성자).
|
|
304
|
+
// 같은 turn 으로 묶임 → 향후 사용자별 필터에서 user/assistant 가 함께 따라옴.
|
|
305
|
+
creator: user,
|
|
306
|
+
updater: user,
|
|
307
|
+
// parentMessage = 이 응답이 답하는 user 메시지. regenerate 시 같은 user 메시지를
|
|
308
|
+
// 부모로 하는 형제 assistant 들이 생기면 자연스럽게 branching tree.
|
|
309
|
+
parentMessage: userMessageId ? { id: userMessageId } : undefined
|
|
310
|
+
});
|
|
311
|
+
assistantMessageId = aiMsg.id;
|
|
312
|
+
// C4: AI 응답도 같은 세션 구독자에게 방송 (트리거한 본인 포함 — id dedup). best-effort.
|
|
313
|
+
(0, chat_message_publish_js_1.publishChatMessage)({ domainId: domain.id, sessionId: session.id, message: aiMsg });
|
|
314
|
+
await sessionRepo.update(session.id, {
|
|
315
|
+
aiClientId: base.id,
|
|
316
|
+
updater: user
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
reply: r.reply,
|
|
321
|
+
patch: r.patch ?? null,
|
|
322
|
+
followUp: r.followUp ?? null,
|
|
323
|
+
clientId: base.id,
|
|
324
|
+
sessionId: session?.id,
|
|
325
|
+
userMessageId,
|
|
326
|
+
assistantMessageId,
|
|
327
|
+
patchId,
|
|
328
|
+
toolUsages: r.toolUsages ?? null,
|
|
329
|
+
actions: r.actions && r.actions.length > 0 ? r.actions : null
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* C4: 메시지에 @멘션된 사용자를 세션 참여자(member)로 자동 등록 — 초대.
|
|
334
|
+
* enriched content 의 {userId:UUID} 마커에서 추출 → 기존 참여자·자기 제외한 신규만 등록.
|
|
335
|
+
* 소프트삭제(deletedAt)된 과거 참여자는 다시 살린다(재초대). 신규 등록 후 협업 초대 알림 발송(C4b-2, best-effort).
|
|
336
|
+
*/
|
|
337
|
+
async _inviteMentionedUsers(content, sessionId, domainId, inviter, boardId, notify) {
|
|
338
|
+
const mentioned = (0, mention_marker_js_1.extractMentionUserIds)(content);
|
|
339
|
+
if (mentioned.length === 0)
|
|
340
|
+
return;
|
|
341
|
+
const repo = (0, shell_1.getRepository)(chat_session_participant_js_1.ChatSessionParticipant);
|
|
342
|
+
const existing = await repo.find({
|
|
343
|
+
where: { session: { id: sessionId }, domain: { id: domainId } },
|
|
344
|
+
withDeleted: true
|
|
345
|
+
});
|
|
346
|
+
const activeIds = existing.filter(p => !p.deletedAt).map(p => p.userId).filter(Boolean);
|
|
347
|
+
const fresh = (0, mention_marker_js_1.newParticipantUserIds)(mentioned, activeIds, inviter?.id);
|
|
348
|
+
if (fresh.length === 0)
|
|
349
|
+
return;
|
|
350
|
+
for (const userId of fresh) {
|
|
351
|
+
// 과거에 나갔던(soft-deleted) 참여자면 되살리고, 아니면 새로 등록.
|
|
352
|
+
const prior = existing.find(p => p.userId === userId && p.deletedAt);
|
|
353
|
+
if (prior) {
|
|
354
|
+
await repo.update(prior.id, { deletedAt: null, role: 'member', updater: inviter });
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
await repo.save({
|
|
358
|
+
domain: { id: domainId },
|
|
359
|
+
session: { id: sessionId },
|
|
360
|
+
user: { id: userId },
|
|
361
|
+
role: 'member',
|
|
362
|
+
creator: inviter,
|
|
363
|
+
updater: inviter
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// C4b-2: 초대 알림 — notification 모듈의 기존 발송 메커니즘 재사용(중복 구현 금지).
|
|
368
|
+
// context.state.notify 는 notification-middleware 가 전역 주입(FCM/pubsub). duck-typed →
|
|
369
|
+
// board-ai 에 의존성 추가 없이 런타임 호출. 미로드 환경이면 undefined → graceful skip.
|
|
370
|
+
this._notifyInvited(notify, fresh, boardId, inviter);
|
|
371
|
+
}
|
|
372
|
+
/** 초대된 사용자들에게 협업 초대 알림 발송 (best-effort, capability 있을 때만). */
|
|
373
|
+
_notifyInvited(notify, userIds, boardId, inviter) {
|
|
374
|
+
if (typeof notify !== 'function')
|
|
375
|
+
return;
|
|
376
|
+
const payload = (0, invite_notification_js_1.buildInviteNotification)(userIds, boardId, inviter);
|
|
377
|
+
if (!payload)
|
|
378
|
+
return;
|
|
379
|
+
Promise.resolve(notify(payload)).catch(() => {
|
|
380
|
+
/* 알림 실패는 초대·대화를 막지 않는다 */
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
exports.BoardAIChatResolver = BoardAIChatResolver;
|
|
385
|
+
tslib_1.__decorate([
|
|
386
|
+
(0, type_graphql_1.Mutation)(() => BoardAIChatOutput, {
|
|
387
|
+
description: 'AI 주도 보드 모델링 — 자연어 채팅으로 보드 생성·구조편집·스타일링. sessionId 로 영속 컨텍스트.'
|
|
388
|
+
}),
|
|
389
|
+
(0, type_graphql_1.Directive)('@privilege(category: "board-ai", privilege: "mutation")'),
|
|
390
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
|
|
391
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
392
|
+
tslib_1.__metadata("design:type", Function),
|
|
393
|
+
tslib_1.__metadata("design:paramtypes", [BoardAIChatInput, Object]),
|
|
394
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
395
|
+
], BoardAIChatResolver.prototype, "boardAIChat", null);
|
|
396
|
+
exports.BoardAIChatResolver = BoardAIChatResolver = tslib_1.__decorate([
|
|
397
|
+
(0, type_graphql_1.Resolver)()
|
|
398
|
+
], BoardAIChatResolver);
|
|
399
|
+
function resolveAIClient() {
|
|
400
|
+
const existing = (0, ai_client_base_1.getDefaultAIClient)();
|
|
401
|
+
if (existing)
|
|
402
|
+
return existing;
|
|
403
|
+
const cfg = env_1.config.get('aiClient', null);
|
|
404
|
+
if (cfg && cfg.provider) {
|
|
405
|
+
return (0, ai_client_base_1.createAIClient)(cfg);
|
|
406
|
+
}
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
//# sourceMappingURL=board-ai-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-ai-resolver.js","sourceRoot":"","sources":["../../server/service/board-ai-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GraphQL resolver — AI 주도 보드 모델링 (chat).\n *\n * Mutation: boardAIChat — 자연어 채팅. sessionId 가 있으면 메시지/패치 영속.\n * sessionId 가 없으면 ad-hoc 모드로 단발 호출 (영속 없음).\n */\nimport { Resolver, Mutation, Arg, Ctx, Directive, Field, InputType, Int, ObjectType } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { config } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\nimport { createAIClient, getDefaultAIClient, type AIClient } from '@things-factory/ai-client-base'\nimport type { ComponentCategory } from '@things-factory/board-import'\n\nimport { DefaultBoardAIAssistant } from './assistant.js'\nimport type { LLMMessage } from './types.js'\nimport { ChatSession } from './chat-session/chat-session.js'\nimport { ChatMessage } from './chat-message/chat-message.js'\nimport { publishChatMessage } from './chat-message/chat-message-publish.js'\nimport { PatchEntry } from './patch-entry/patch-entry.js'\nimport { publishBoardPatch } from './patch-entry/board-patch-subscription.js'\nimport { ChatSessionParticipant } from './chat-session-participant/chat-session-participant.js'\nimport { buildInviteNotification } from './chat-session-participant/invite-notification.js'\nimport {\n injectMentionRefids,\n injectAiMentionRefids,\n stripMentionRefids,\n extractMentionUserIds,\n newParticipantUserIds\n} from './mention-marker.js'\n\n@InputType()\nclass BoardAILLMMessageInput {\n @Field({ description: \"Role: 'user' or 'assistant'\" })\n role: string\n\n @Field({ description: 'Message content' })\n content: string\n}\n\n@InputType()\nclass BoardAIMentionPickInput {\n @Field({ description: 'Token used by user (e.g. \"경광등1\" — the part after #).' })\n token: string\n\n @Field(() => Int, {\n description:\n 'Refid explicitly picked from popup. Server uses this for deterministic resolution — bypasses fallback name/id/type matching for this token.'\n })\n refid: number\n}\n\n@InputType()\nclass BoardAIChatInput {\n @Field({\n nullable: true,\n description: 'ChatSession id. Omit for ad-hoc (no persistence). When given, messages/patches are persisted.'\n })\n sessionId?: string\n\n @Field(() => [BoardAILLMMessageInput], {\n description: 'Conversation history (latest user message at the end). For persisted sessions, only the last user message is appended; older are loaded from DB.'\n })\n messages: BoardAILLMMessageInput[]\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Current BoardModel JSON.' })\n currentBoard?: any\n\n @Field(() => [String], {\n nullable: true,\n description:\n \"Capability scopes the AI is allowed to use (e.g. 'create', 'edit', 'style'). Filters which tools are exposed to the LLM.\"\n })\n scopes?: string[]\n\n @Field(() => [String], {\n nullable: true,\n description: 'Component types the LLM is allowed to emit (e.g. [\"rect\", \"label\"]). Constrains generation to the current solution.'\n })\n knownTypes?: string[]\n\n @Field(() => [String], {\n nullable: true,\n description: 'Component categories to surface (e.g. [\"3D\", \"form\"]). Subset filter for groupings exposed in component-schemas.'\n })\n categories?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Component schemas — array of { type, description?, group?, properties? } so the LLM knows valid props per type.'\n })\n componentSchemas?: any\n\n @Field(() => [Int], {\n nullable: true,\n description:\n 'Refids of components currently selected in the modeller (universal numeric handle, things-scene auto-assigned). For \"selected\" / \"선택한\" intent. Note: distinct from `id` which is a data-binding name.'\n })\n selectedRefids?: number[]\n\n @Field(() => [BoardAIMentionPickInput], {\n nullable: true,\n description:\n 'Explicit #mention picks from popup — {token, refid}. User text stays clean (#token only); these picks let server resolve deterministically without exposing refid in the user-visible message. Tokens not in this list fall back to free name/id/type matching.'\n })\n mentions?: BoardAIMentionPickInput[]\n\n @Field({\n nullable: true,\n description: 'AI model identifier override (e.g. \"claude-opus-4-7\"). Falls back to the configured default when omitted.'\n })\n model?: string\n\n @Field({\n nullable: true,\n description: 'Sampling temperature override. Falls back to assistant default when omitted.'\n })\n temperature?: number\n\n @Field({\n nullable: true,\n description: 'Max output tokens override per LLM call. Falls back to assistant default when omitted.'\n })\n maxTokens?: number\n}\n\n@ObjectType()\nclass BoardAIChatOutput {\n @Field({ description: 'Conversational reply.' })\n reply: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'BoardEditPatch.' })\n patch?: any\n\n @Field({ nullable: true, description: 'Clarifying question when input is ambiguous.' })\n followUp?: string\n\n @Field({ description: 'AI client identifier (provider:model).' })\n clientId: string\n\n @Field({ nullable: true, description: 'Echo of session id (when persisted).' })\n sessionId?: string\n\n @Field({ nullable: true, description: 'Persisted ChatMessage id of the user input.' })\n userMessageId?: string\n\n @Field({ nullable: true, description: 'Persisted ChatMessage id of the AI reply.' })\n assistantMessageId?: string\n\n @Field({ nullable: true, description: 'Persisted PatchEntry id (when patch was generated).' })\n patchId?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Tool usages collected during agentic loop — sequence of {name, arguments, result, kind}. UI fold-able box for transparency / debug.'\n })\n toolUsages?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Ephemeral scene actions (selection/view/mode) — sequence of BoardActionOp. Applied via board-action-execute event on the host. Distinct from `patch` which carries persistent model changes.'\n })\n actions?: any\n}\n\n@Resolver()\nexport class BoardAIChatResolver {\n /**\n * NOTE: 일부러 @Directive('@transaction') 사용하지 않는다.\n * LLM 호출은 수 초~수십 초 — 트랜잭션 안에서 호출하면 SQLite write lock 을\n * 그 시간 동안 보유 → 다른 작업 (cache 정리 등) 과 SQLITE_BUSY 충돌.\n * 따라서 DB 쓰기를 LLM 호출 전후의 짧은 단위로 나누고 각각 auto-commit.\n * 데이터 일관성: 채팅 이력은 부분 누락보다 부분 보존이 안전 (대화 컨텍스트).\n */\n @Mutation(() => BoardAIChatOutput, {\n description: 'AI 주도 보드 모델링 — 자연어 채팅으로 보드 생성·구조편집·스타일링. sessionId 로 영속 컨텍스트.'\n })\n @Directive('@privilege(category: \"board-ai\", privilege: \"mutation\")')\n async boardAIChat(\n @Arg('input') input: BoardAIChatInput,\n @Ctx() context: ResolverContext\n ): Promise<BoardAIChatOutput> {\n const base = resolveAIClient()\n if (!base) {\n throw new Error(\n 'No AI client configured. Set config.aiClient = { provider: \"anthropic\" | \"gemini\", model, apiKey } or call setDefaultAIClient(...).'\n )\n }\n\n const { domain, user } = context.state\n const sessionRepo = getRepository(ChatSession)\n const messageRepo = getRepository(ChatMessage)\n const patchRepo = getRepository(PatchEntry)\n\n // ── Phase A: LLM 호출 전 짧은 DB 쓰기 ─────────────────────────\n let session: ChatSession | undefined\n if (input.sessionId) {\n const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } })\n if (!found) throw new Error(`ChatSession ${input.sessionId} not found`)\n session = found\n }\n\n const lastUserMsg = input.messages[input.messages.length - 1]\n let userMessageId: string | undefined\n if (session && lastUserMsg && lastUserMsg.role === 'user') {\n // popup pick 의 refid 를 #token{refid:N} inline marker 로 content 에 박아 저장.\n // 단일 source-of-truth — 별도 컬럼 없이 content 안에서 token-refid 매핑 보존.\n const enriched = injectMentionRefids(lastUserMsg.content, input.mentions)\n // parentMessage = 이 세션의 직전 메시지 (보통 assistant / system / 또는 첫 turn 이면 null).\n // tree 구조 인프라 — branching (regenerate / 사용자별 분기) 는 같은 부모 child 로.\n const previousLast = await messageRepo.findOne({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'DESC' }\n })\n const saved = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'user',\n content: enriched,\n creator: user,\n updater: user,\n parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined\n })\n userMessageId = saved.id\n\n // C4: 사용자 메시지를 같은 세션 구독자에게 방송 (그룹챗 실시간). best-effort.\n // 자기 발신 에코는 클라이언트가 message.id 로 dedup.\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: saved })\n\n // C4: @user 멘션 → 세션 참여자(member) 자동 등록 (초대). best-effort — 실패해도 chat 흐름 무영향.\n // enriched content 의 {userId:UUID} 마커에서 추출 → 기존 참여자/자기 제외한 신규만 member 로 등록.\n try {\n await this._inviteMentionedUsers(enriched, session.id!, domain.id, user, session.boardId, (context.state as any).notify)\n } catch {\n /* 초대 실패는 대화를 막지 않는다 */\n }\n }\n\n // ── Phase B: LLM 호출 (DB 잠금 없음, 오래 걸려도 OK) ───────────\n const assistant = new DefaultBoardAIAssistant(base, {\n scopes: input.scopes,\n knownTypes: input.knownTypes,\n categories: input.categories as ComponentCategory[] | undefined\n })\n\n // 영속된 메시지에는 #token{refid:N} inline marker 가 박혀 있을 수 있음. LLM 한테는\n // 깨끗한 텍스트만 보여 주의 분산 방지 + 토큰 낭비 방지.\n const llmMessages: LLMMessage[] = input.messages.map(m => ({\n role: m.role === 'assistant' ? 'assistant' : 'user',\n content: stripMentionRefids(m.content)\n }))\n\n const r = await assistant.chat(llmMessages, input.currentBoard ?? undefined, {\n scopes: input.scopes,\n knownTypes: input.knownTypes,\n categories: input.categories as ComponentCategory[] | undefined,\n selectedRefids: input.selectedRefids,\n mentionPicks: input.mentions,\n componentSchemas: Array.isArray(input.componentSchemas) ? input.componentSchemas : undefined,\n model: input.model,\n temperature: input.temperature ?? undefined,\n maxTokens: input.maxTokens ?? undefined,\n // 외부 도메인 (board-import 등) 의 read/external tool builder 가 server-side\n // 의존성 (도메인 / 유저 / 트랜잭션) 을 사용할 수 있도록 ResolverContext.state 전달.\n // board-ai 코어 자체는 이 값을 알지 않고 dispatch 단계에서 closure 로만 흘림.\n toolCallContext: context.state\n })\n\n // ── Phase C: LLM 호출 후 짧은 DB 쓰기 ─────────────────────────\n let assistantMessageId: string | undefined\n let patchId: string | undefined\n if (session) {\n let patchEntry: PatchEntry | undefined\n if (r.patch) {\n patchEntry = await patchRepo.save({\n session: { id: session.id } as any,\n source: 'ai',\n ops: JSON.stringify(r.patch.ops),\n summary: r.patch.summary,\n confidence: r.patch.confidence,\n reverted: false\n })\n patchId = patchEntry.id\n // C1: 같은 board 구독자에게 방송 (실시간 공동편집 spine). best-effort.\n publishBoardPatch({ domainId: domain.id, boardId: session.boardId, patch: patchEntry })\n }\n\n // AI 응답의 #token 들을 currentBoard 에 대해 resolve 한 뒤 inline marker 로 박음.\n // patch 로 새로 만든 컴포넌트는 input.currentBoard 에 없으므로 marker 없이 저장 →\n // 클릭 시 runtime fallback 으로 해석 (그때는 patch 적용 후라 scene 에 존재).\n const enrichedReply = injectAiMentionRefids(r.reply, input.currentBoard ?? null)\n\n const aiMsg = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'assistant',\n content: enrichedReply,\n relatedPatchId: patchEntry?.id,\n toolUsages: r.toolUsages && r.toolUsages.length > 0 ? JSON.stringify(r.toolUsages) : undefined,\n // assistant 메시지의 creator = 이 응답을 트리거한 user (= 직전 user 메시지의 작성자).\n // 같은 turn 으로 묶임 → 향후 사용자별 필터에서 user/assistant 가 함께 따라옴.\n creator: user,\n updater: user,\n // parentMessage = 이 응답이 답하는 user 메시지. regenerate 시 같은 user 메시지를\n // 부모로 하는 형제 assistant 들이 생기면 자연스럽게 branching tree.\n parentMessage: userMessageId ? ({ id: userMessageId } as any) : undefined\n })\n assistantMessageId = aiMsg.id\n\n // C4: AI 응답도 같은 세션 구독자에게 방송 (트리거한 본인 포함 — id dedup). best-effort.\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: aiMsg })\n\n await sessionRepo.update(session.id, {\n aiClientId: base.id,\n updater: user\n })\n }\n\n return {\n reply: r.reply,\n patch: r.patch ?? null,\n followUp: r.followUp ?? null,\n clientId: base.id,\n sessionId: session?.id,\n userMessageId,\n assistantMessageId,\n patchId,\n toolUsages: r.toolUsages ?? null,\n actions: r.actions && r.actions.length > 0 ? r.actions : null\n }\n }\n\n /**\n * C4: 메시지에 @멘션된 사용자를 세션 참여자(member)로 자동 등록 — 초대.\n * enriched content 의 {userId:UUID} 마커에서 추출 → 기존 참여자·자기 제외한 신규만 등록.\n * 소프트삭제(deletedAt)된 과거 참여자는 다시 살린다(재초대). 신규 등록 후 협업 초대 알림 발송(C4b-2, best-effort).\n */\n private async _inviteMentionedUsers(\n content: string,\n sessionId: string,\n domainId: string,\n inviter: any,\n boardId?: string,\n notify?: (arg: any) => Promise<void>\n ): Promise<void> {\n const mentioned = extractMentionUserIds(content)\n if (mentioned.length === 0) return\n\n const repo = getRepository(ChatSessionParticipant)\n const existing = await repo.find({\n where: { session: { id: sessionId } as any, domain: { id: domainId } as any },\n withDeleted: true\n })\n const activeIds = existing.filter(p => !p.deletedAt).map(p => p.userId!).filter(Boolean)\n const fresh = newParticipantUserIds(mentioned, activeIds, inviter?.id)\n if (fresh.length === 0) return\n\n for (const userId of fresh) {\n // 과거에 나갔던(soft-deleted) 참여자면 되살리고, 아니면 새로 등록.\n const prior = existing.find(p => p.userId === userId && p.deletedAt)\n if (prior) {\n await repo.update(prior.id!, { deletedAt: null as any, role: 'member', updater: inviter } as any)\n } else {\n await repo.save({\n domain: { id: domainId } as any,\n session: { id: sessionId } as any,\n user: { id: userId } as any,\n role: 'member',\n creator: inviter,\n updater: inviter\n } as any)\n }\n }\n\n // C4b-2: 초대 알림 — notification 모듈의 기존 발송 메커니즘 재사용(중복 구현 금지).\n // context.state.notify 는 notification-middleware 가 전역 주입(FCM/pubsub). duck-typed →\n // board-ai 에 의존성 추가 없이 런타임 호출. 미로드 환경이면 undefined → graceful skip.\n this._notifyInvited(notify, fresh, boardId, inviter)\n }\n\n /** 초대된 사용자들에게 협업 초대 알림 발송 (best-effort, capability 있을 때만). */\n private _notifyInvited(\n notify: ((arg: any) => Promise<void>) | undefined,\n userIds: string[],\n boardId: string | undefined,\n inviter: any\n ): void {\n if (typeof notify !== 'function') return\n const payload = buildInviteNotification(userIds, boardId, inviter)\n if (!payload) return\n Promise.resolve(notify(payload)).catch(() => {\n /* 알림 실패는 초대·대화를 막지 않는다 */\n })\n }\n}\n\nfunction resolveAIClient(): AIClient | undefined {\n const existing = getDefaultAIClient()\n if (existing) return existing\n const cfg = config.get('aiClient', null) as any\n if (cfg && cfg.provider) {\n return createAIClient(cfg)\n }\n return undefined\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type CatalogKind = 'prefab' | 'palette' | 'layout';
|
|
2
|
+
/**
|
|
3
|
+
* 텍스트의 `@token` 들을 카탈로그와 매칭해 LLM 친화 형태로 변환.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveCatalogMentions(text: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Token 의 매칭 kind 목록 (case-sensitive — 카탈로그 식별자는 정확 매칭).
|
|
8
|
+
*
|
|
9
|
+
* 같은 이름이 여러 카탈로그에 존재해도 모두 반환. UI 가 disambiguation 안내.
|
|
10
|
+
*/
|
|
11
|
+
export declare function lookupCatalogKinds(token: string): CatalogKind[];
|
|
12
|
+
/**
|
|
13
|
+
* 텍스트의 모든 `@token` 매칭 결과 — UI / 디버그용.
|
|
14
|
+
*/
|
|
15
|
+
export declare function findCatalogMentions(text: string): Array<{
|
|
16
|
+
token: string;
|
|
17
|
+
kinds: CatalogKind[];
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveCatalogMentions = resolveCatalogMentions;
|
|
4
|
+
exports.lookupCatalogKinds = lookupCatalogKinds;
|
|
5
|
+
exports.findCatalogMentions = findCatalogMentions;
|
|
6
|
+
/**
|
|
7
|
+
* `@name` 카탈로그 멘션 → kind disambiguation.
|
|
8
|
+
*
|
|
9
|
+
* Phase 1A 의 mention-resolver (#name → 'name (refid 35)') 와 같은 패턴을
|
|
10
|
+
* 카탈로그 (prefab / color palette / layout template) 식별자에 적용.
|
|
11
|
+
*
|
|
12
|
+
* "@monitor-card 3개 추가"
|
|
13
|
+
* → "monitor-card (prefab) 3개 추가"
|
|
14
|
+
*
|
|
15
|
+
* "@industrial-steel 팔레트로 통일"
|
|
16
|
+
* → "industrial-steel (palette) 팔레트로 통일"
|
|
17
|
+
*
|
|
18
|
+
* 다중 kind 매치 (이름이 prefab + palette 둘 다인 희귀 케이스):
|
|
19
|
+
* → "name (prefab and palette)"
|
|
20
|
+
*
|
|
21
|
+
* 매칭 0 — 원문 보존 (AI 가 자연어로 폴백).
|
|
22
|
+
*
|
|
23
|
+
* Token 정의: `@` 뒤의 공백/`@` 이 아닌 모든 글자. 정규식 /@([^\s@]+)/g.
|
|
24
|
+
*
|
|
25
|
+
* 의도: AI 가 이름을 자유 작곡 / hallucinate 하지 않고 정확한 카탈로그 식별자를
|
|
26
|
+
* 사용하도록 호출자가 명시한 의도를 server-side 에서 보강.
|
|
27
|
+
*/
|
|
28
|
+
const prefab_registry_1 = require("./prefab-registry");
|
|
29
|
+
const assistant_1 = require("./assistant");
|
|
30
|
+
const CATALOG_PATTERN = /@([^\s@]+)/g;
|
|
31
|
+
/**
|
|
32
|
+
* 텍스트의 `@token` 들을 카탈로그와 매칭해 LLM 친화 형태로 변환.
|
|
33
|
+
*/
|
|
34
|
+
function resolveCatalogMentions(text) {
|
|
35
|
+
if (!text || typeof text !== 'string')
|
|
36
|
+
return text;
|
|
37
|
+
const matches = collectCatalogMatches(text);
|
|
38
|
+
if (matches.length === 0)
|
|
39
|
+
return text;
|
|
40
|
+
let out = text;
|
|
41
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
42
|
+
const m = matches[i];
|
|
43
|
+
if (m.kinds.length === 0)
|
|
44
|
+
continue; // 매칭 실패 — 그대로
|
|
45
|
+
const replacement = formatCatalogReplacement(m.token, m.kinds);
|
|
46
|
+
out = out.slice(0, m.start) + replacement + out.slice(m.end);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function collectCatalogMatches(text) {
|
|
51
|
+
const matches = [];
|
|
52
|
+
const pattern = new RegExp(CATALOG_PATTERN.source, 'g');
|
|
53
|
+
let m;
|
|
54
|
+
while ((m = pattern.exec(text)) !== null) {
|
|
55
|
+
const token = m[1];
|
|
56
|
+
const kinds = lookupCatalogKinds(token);
|
|
57
|
+
matches.push({
|
|
58
|
+
token,
|
|
59
|
+
start: m.index,
|
|
60
|
+
end: m.index + m[0].length,
|
|
61
|
+
kinds
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return matches;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Token 의 매칭 kind 목록 (case-sensitive — 카탈로그 식별자는 정확 매칭).
|
|
68
|
+
*
|
|
69
|
+
* 같은 이름이 여러 카탈로그에 존재해도 모두 반환. UI 가 disambiguation 안내.
|
|
70
|
+
*/
|
|
71
|
+
function lookupCatalogKinds(token) {
|
|
72
|
+
const kinds = [];
|
|
73
|
+
const prefabs = (0, prefab_registry_1.listBoardPrefabs)();
|
|
74
|
+
if (prefabs.some(p => p.name === token))
|
|
75
|
+
kinds.push('prefab');
|
|
76
|
+
if ((0, assistant_1.listColorPaletteNames)().includes(token))
|
|
77
|
+
kinds.push('palette');
|
|
78
|
+
if ((0, assistant_1.listLayoutTemplateNames)().includes(token))
|
|
79
|
+
kinds.push('layout');
|
|
80
|
+
return kinds;
|
|
81
|
+
}
|
|
82
|
+
function formatCatalogReplacement(token, kinds) {
|
|
83
|
+
if (kinds.length === 1)
|
|
84
|
+
return `${token} (${kinds[0]})`;
|
|
85
|
+
return `${token} (${kinds.join(' and ')})`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 텍스트의 모든 `@token` 매칭 결과 — UI / 디버그용.
|
|
89
|
+
*/
|
|
90
|
+
function findCatalogMentions(text) {
|
|
91
|
+
if (!text || typeof text !== 'string')
|
|
92
|
+
return [];
|
|
93
|
+
return collectCatalogMatches(text).map(m => ({
|
|
94
|
+
token: m.token,
|
|
95
|
+
kinds: m.kinds
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=catalog-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-resolver.js","sourceRoot":"","sources":["../../server/service/catalog-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `@name` 카탈로그 멘션 → kind disambiguation.\n *\n * Phase 1A 의 mention-resolver (#name → 'name (refid 35)') 와 같은 패턴을\n * 카탈로그 (prefab / color palette / layout template) 식별자에 적용.\n *\n * \"@monitor-card 3개 추가\"\n * → \"monitor-card (prefab) 3개 추가\"\n *\n * \"@industrial-steel 팔레트로 통일\"\n * → \"industrial-steel (palette) 팔레트로 통일\"\n *\n * 다중 kind 매치 (이름이 prefab + palette 둘 다인 희귀 케이스):\n * → \"name (prefab and palette)\"\n *\n * 매칭 0 — 원문 보존 (AI 가 자연어로 폴백).\n *\n * Token 정의: `@` 뒤의 공백/`@` 이 아닌 모든 글자. 정규식 /@([^\\s@]+)/g.\n *\n * 의도: AI 가 이름을 자유 작곡 / hallucinate 하지 않고 정확한 카탈로그 식별자를\n * 사용하도록 호출자가 명시한 의도를 server-side 에서 보강.\n */\nimport { listBoardPrefabs } from './prefab-registry'\nimport { listColorPaletteNames, listLayoutTemplateNames } from './assistant'\n\nexport type CatalogKind = 'prefab' | 'palette' | 'layout'\n\nconst CATALOG_PATTERN = /@([^\\s@]+)/g\n\n/**\n * 텍스트의 `@token` 들을 카탈로그와 매칭해 LLM 친화 형태로 변환.\n */\nexport function resolveCatalogMentions(text: string): string {\n if (!text || typeof text !== 'string') return text\n const matches = collectCatalogMatches(text)\n if (matches.length === 0) return text\n\n let out = text\n for (let i = matches.length - 1; i >= 0; i--) {\n const m = matches[i]\n if (m.kinds.length === 0) continue // 매칭 실패 — 그대로\n const replacement = formatCatalogReplacement(m.token, m.kinds)\n out = out.slice(0, m.start) + replacement + out.slice(m.end)\n }\n return out\n}\n\ninterface CatalogMatch {\n token: string\n start: number\n end: number\n kinds: CatalogKind[]\n}\n\nfunction collectCatalogMatches(text: string): CatalogMatch[] {\n const matches: CatalogMatch[] = []\n const pattern = new RegExp(CATALOG_PATTERN.source, 'g')\n let m: RegExpExecArray | null\n while ((m = pattern.exec(text)) !== null) {\n const token = m[1]\n const kinds = lookupCatalogKinds(token)\n matches.push({\n token,\n start: m.index,\n end: m.index + m[0].length,\n kinds\n })\n }\n return matches\n}\n\n/**\n * Token 의 매칭 kind 목록 (case-sensitive — 카탈로그 식별자는 정확 매칭).\n *\n * 같은 이름이 여러 카탈로그에 존재해도 모두 반환. UI 가 disambiguation 안내.\n */\nexport function lookupCatalogKinds(token: string): CatalogKind[] {\n const kinds: CatalogKind[] = []\n const prefabs = listBoardPrefabs()\n if (prefabs.some(p => p.name === token)) kinds.push('prefab')\n if (listColorPaletteNames().includes(token)) kinds.push('palette')\n if (listLayoutTemplateNames().includes(token)) kinds.push('layout')\n return kinds\n}\n\nfunction formatCatalogReplacement(token: string, kinds: CatalogKind[]): string {\n if (kinds.length === 1) return `${token} (${kinds[0]})`\n return `${token} (${kinds.join(' and ')})`\n}\n\n/**\n * 텍스트의 모든 `@token` 매칭 결과 — UI / 디버그용.\n */\nexport function findCatalogMentions(\n text: string\n): Array<{ token: string; kinds: CatalogKind[] }> {\n if (!text || typeof text !== 'string') return []\n return collectCatalogMatches(text).map(m => ({\n token: m.token,\n kinds: m.kinds\n }))\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChatMessage } from './chat-message.js';
|
|
2
|
+
/** chat-message 브로드캐스트 토픽. */
|
|
3
|
+
export declare const CHAT_MESSAGE_TOPIC = "board-ai-chat-message";
|
|
4
|
+
/** 방송 페이로드 — 구독 필터(domain+session)용 메타 + ChatMessage 본체. */
|
|
5
|
+
export interface ChatMessagePayload {
|
|
6
|
+
domainId: string;
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
message: ChatMessage;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 같은 domain + session 구독자에게만 통과시키는 필터 (도메인 격리 = 보안 핵심).
|
|
12
|
+
*/
|
|
13
|
+
export declare function chatMessageFilter(domainId: string, sessionId: string): (payload: ChatMessagePayload) => boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CHAT_MESSAGE_TOPIC = void 0;
|
|
4
|
+
exports.chatMessageFilter = chatMessageFilter;
|
|
5
|
+
/** chat-message 브로드캐스트 토픽. */
|
|
6
|
+
exports.CHAT_MESSAGE_TOPIC = 'board-ai-chat-message';
|
|
7
|
+
/**
|
|
8
|
+
* 같은 domain + session 구독자에게만 통과시키는 필터 (도메인 격리 = 보안 핵심).
|
|
9
|
+
*/
|
|
10
|
+
function chatMessageFilter(domainId, sessionId) {
|
|
11
|
+
return (payload) => !!payload && payload.domainId === domainId && payload.sessionId === sessionId;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=chat-message-broadcast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-message-broadcast.js","sourceRoot":"","sources":["../../../server/service/chat-message/chat-message-broadcast.ts"],"names":[],"mappings":";;;AAuBA,8CAGC;AAhBD,8BAA8B;AACjB,QAAA,kBAAkB,GAAG,uBAAuB,CAAA;AASzD;;GAEG;AACH,SAAgB,iBAAiB,CAAC,QAAgB,EAAE,SAAiB;IACnE,OAAO,CAAC,OAA2B,EAAW,EAAE,CAC9C,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,CAAA;AACjF,CAAC","sourcesContent":["/*\n * chat-message 브로드캐스트 — 순수 로직 (토픽/페이로드/필터). 데코레이터·pubsub import 없음\n * → 단위테스트 가능. 데코레이터 resolver(chat-message-subscription.ts)와 pubsub 의존\n * publishChatMessage 는 별도.\n *\n * patch spine(board-patch-broadcast.ts)과 동일 컨벤션. 다만 채팅 메시지는 board 가 아니라\n * **ChatSession 단위**로 묶이므로 필터 키가 sessionId (+ domain 격리).\n */\nimport type { ChatMessage } from './chat-message.js' // type-only — 런타임 import 아님\n\n/** chat-message 브로드캐스트 토픽. */\nexport const CHAT_MESSAGE_TOPIC = 'board-ai-chat-message'\n\n/** 방송 페이로드 — 구독 필터(domain+session)용 메타 + ChatMessage 본체. */\nexport interface ChatMessagePayload {\n domainId: string\n sessionId?: string\n message: ChatMessage\n}\n\n/**\n * 같은 domain + session 구독자에게만 통과시키는 필터 (도메인 격리 = 보안 핵심).\n */\nexport function chatMessageFilter(domainId: string, sessionId: string) {\n return (payload: ChatMessagePayload): boolean =>\n !!payload && payload.domainId === domainId && payload.sessionId === sessionId\n}\n"]}
|