@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 @@
|
|
|
1
|
+
{"version":3,"file":"board-ai-chat.js","sourceRoot":"","sources":["../../../client/components/board-ai-chat.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * <ox-board-ai-chat> — AI 주도 보드 모델링 채팅 컴포넌트 (Lit).\n *\n * 입력:\n * - sessionId: 영속 ChatSession 식별자 (없으면 ad-hoc 모드, 메시지 영속 안 됨)\n * - currentBoard: 현재 BoardModel JSON (호스트가 동기화)\n * - scopes / knownTypes / categories: 도메인 컨텍스트\n *\n * 출력 (이벤트):\n * - `board-edit-patch` { detail: { patch, summary, confidence, patchId } }\n * 호스트가 받아서 보드 모델에 적용 (applyBoardEditPatch helper).\n * - `chat-followup` { detail: { question } }\n *\n * 모드 전환은 컨테이너 (워크스페이스) 의 책임. 이 컴포넌트는 자체로 풀 채팅 UX.\n */\nimport '@material/web/icon/icon.js'\n\nimport { LitElement, css, html, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js'\nimport { client } from '@operato/graphql'\nimport { auth } from '@things-factory/auth-base/dist-client/index.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport gql from 'graphql-tag'\n\nimport {\n renderMarkdown,\n injectMentionRefids,\n injectMentionUserIds,\n stripMentionRefids\n} from './markdown.js'\nimport { buildChatMutationInput } from './chat-input-builder.js'\nimport { receiveRemoteMessage, reconcileSentLine, remoteMessageToLine } from './chat-echo-dedup.js'\nimport './mention-popup.js'\nimport {\n buildMentionCandidates,\n buildCatalogCandidates,\n buildSlashCandidates,\n buildUserCandidates,\n extractMentionContext,\n filterMentionCandidates,\n applyMentionSelection,\n resolveMentionToken,\n type MentionCandidate,\n type FilterResult\n} from './mention-popup.js'\nimport { DEFAULT_CATALOG_ENTRIES } from './default-catalog-entries.js'\nimport { DEFAULT_SLASH_TEMPLATES } from './default-slash-templates.js'\n\ninterface ChatLine {\n role: 'user' | 'assistant' | 'system'\n /** 서버 ChatMessage.id — 실시간 방송 에코 dedup 의 키 (있을 때만). */\n id?: string\n /** 클라 임시 핸들 — 영속 id 부여 전 optimistic 라인을 index 비의존으로 식별/reconcile. */\n _localId?: string\n /** 보낸 사용자 id/name — 그룹챗 발신자 표시용 (다른 참여자 메시지 구분). */\n senderId?: string\n senderName?: string\n /** 메시지 본문. `#token{refid:N}` inline marker 가 포함될 수 있음 (영속된 메시지 / 사용자\n * popup pick / AI resolved mention). 렌더링 시 추출해서 `<span data-refid=\"N\">` 으로 변환. */\n content: string\n patchSummary?: string\n followUp?: string\n patchId?: string\n pending?: boolean\n /** AI 가 응답을 만드는 동안 호출한 도구 trace — fold-able 박스에서 노출. */\n toolUsages?: Array<{ name: string; arguments: any; result: any; kind: 'read' | 'write' | 'unknown' }>\n /** UI 상태: tool usage 박스 펼침 여부 (assistant 메시지마다 독립). */\n toolUsagesOpen?: boolean\n}\n\nconst BOARD_AI_CHAT_MUTATION = gql`\n mutation BoardAIChat($input: BoardAIChatInput!) {\n boardAIChat(input: $input) {\n reply\n patch\n followUp\n clientId\n sessionId\n userMessageId\n assistantMessageId\n patchId\n toolUsages\n actions\n }\n }\n`\n\nconst CHAT_MESSAGES_QUERY = gql`\n query ChatMessages($sessionId: String!, $limit: Int, $offset: Int) {\n chatMessages(sessionId: $sessionId, limit: $limit, offset: $offset) {\n id\n role\n content\n relatedPatchId\n toolUsagesJson\n createdAt\n creator {\n id\n name\n }\n }\n }\n`\n\n// C4 그룹챗 — 같은 세션의 메시지(사용자/AI/system) 실시간 방송 구독.\n// 자기 발신 에코는 message.id 로 dedup (mutation 응답으로 이미 렌더).\nconst CHAT_MESSAGE_SUBSCRIPTION = gql`\n subscription ChatMessageActivity($sessionId: String!) {\n chatMessageActivity(sessionId: $sessionId) {\n id\n role\n content\n relatedPatchId\n toolUsagesJson\n createdAt\n creator {\n id\n name\n }\n }\n }\n`\n\n// C4 presence/roster — 세션 참여자 목록 + 마지막 접속(online 판정용).\nconst CHAT_PARTICIPANTS_QUERY = gql`\n query ChatSessionParticipants($sessionId: String!) {\n chatSessionParticipants(sessionId: $sessionId) {\n id\n role\n lastSeenAt\n user {\n id\n name\n }\n }\n }\n`\nconst TOUCH_PRESENCE_MUTATION = gql`\n mutation TouchPresence($sessionId: String!) {\n touchPresence(sessionId: $sessionId)\n }\n`\n// heartbeat 주기(ms) — 서버 PRESENCE_TTL_MS(75s)의 ~1/2.5. 로컬 online 판정도 동일 TTL.\nconst PRESENCE_HEARTBEAT_MS = 30_000\nconst PRESENCE_TTL_MS = 75_000\n\n/** 이름 → roster chip 이니셜 (한글 1자 / 영문 최대 2자). */\nfunction initials(name: string): string {\n const n = (name || '?').trim()\n if (!n) return '?'\n const parts = n.split(/\\s+/)\n if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase()\n return /[a-zA-Z]/.test(n) ? n.slice(0, 2).toUpperCase() : n.slice(0, 1)\n}\n\n@customElement('ox-board-ai-chat')\nexport class OxBoardAIChat extends LitElement {\n /** 영속 세션 id. 없으면 ad-hoc (메시지 영속 안 됨). */\n @property({ type: String, attribute: 'session-id' })\n sessionId?: string\n\n /**\n * 보드의 모든 ChatSession 리스트 — 다중 세션 활성화 시 탭 UI 로 표시.\n *\n * 빈 배열 / 미지정 → 단일 세션 UX (탭 안 보임). 한 보드 = 한 세션 가정의 기존 호출자\n * 호환. host (board-modeller-page) 가 chatSessionsByBoard query 결과를 그대로 전달.\n *\n * 이 컴포넌트는 list 자체를 fetch 하지 않음 — 영속 모델 / 권한 / 페이지네이션 정책은\n * host 책임. 본 컴포넌트는 표시 + 이벤트 emission 만.\n */\n @property({ type: Array, attribute: false })\n sessions: Array<{ id: string; name?: string; createdAt?: string }> = []\n\n /**\n * 현재 BoardModel — chat() 입력으로 그대로 전달.\n *\n * 주의: 호스트가 라이브 편집 가능한 캔버스(things-scene)를 갖고 있다면, 캔버스 안에서\n * 일어나는 수작업 편집이 이 property 에는 자동 반영되지 않는다. 이 경우 `boardProvider`\n * 콜백을 같이 넘기면 send() 시점에 라이브 보드를 pull 한다.\n */\n @property({ type: Object })\n currentBoard?: any\n\n /**\n * 라이브 보드 모델 공급자 (선택).\n *\n * 호스트가 things-scene 같은 캔버스를 사용 중이면 사용자가 캔버스에서 수작업 편집한\n * 내용은 `currentBoard` property 에 자동 반영되지 않는다. 이 콜백을 등록하면 `send()`\n * 직전에 호출해 가장 최신 보드를 pull → AI 가 항상 라이브 상태를 본다.\n *\n * 우선순위: boardProvider() ?? currentBoard.\n */\n @property({ attribute: false })\n boardProvider?: () => any\n\n /**\n * `#` mention 검색 공급자 (선택). 호스트가 things-scene 의 SceneSearchEngine 으로 wrap.\n *\n * 호스트는 query 를 받아 popup 에 표시할 FilterResult[] 를 돌려준다 — chat 자체는\n * things-scene 의존성을 갖지 않고 popup 표시 로직만 담당. 검색 우선순위 / 매칭 룰은\n * SceneSearchEngine (F-Finder 와 공유) 에 위임 → 일관성 보장.\n *\n * 미설정 시 `currentBoard.components` 에서 단순 substring 매칭 (legacy fallback).\n * 빈 query 에서는 호출되지 않음 — 빈 query 는 전체 후보 enumeration 이지 검색이 아님.\n */\n @property({ attribute: false })\n searchProvider?: (query: string) => FilterResult[]\n\n /**\n * `@` mention 도메인 사용자 공급자 (선택). 호스트가 `domainUsersForMention` query 로 wrap.\n *\n * 호스트는 query 문자열을 받아 도메인 사용자 후보 (id/name/email) 배열을 돌려준다.\n * 미설정 시 `@` popup 은 빈 결과 (사용자가 멘션 못 함). 멀티유저 환경 활성화 정책에 따라\n * 호스트가 set 해야 활성화. async 가 자연스러우므로 Promise 반환 가능.\n */\n @property({ attribute: false })\n userProvider?: (\n query: string\n ) =>\n | Promise<Array<{ id: string; name?: string | null; email?: string | null }>>\n | Array<{ id: string; name?: string | null; email?: string | null }>\n\n /**\n * 모델러에서 현재 선택된 컴포넌트의 `refid` 목록.\n *\n * refid 는 things-scene 이 모든 컴포넌트에 자동 발급하는 universal numeric handle.\n * 호스트가 매 render 마다 `.selectedRefids=${...}` 로 갱신. AI 한테 selection 정보로\n * 그대로 전달되어 \"선택한\" / \"selected\" / \"this\" 류 지시어 해석에 사용.\n */\n @property({ type: Array, attribute: false })\n selectedRefids: number[] = []\n\n /** board-import registry scopes. */\n @property({ type: Array })\n scopes?: string[]\n\n @property({ type: Array, attribute: 'known-types' })\n knownTypes?: string[]\n\n @property({ type: Array })\n categories?: string[]\n\n /**\n * 컴포넌트 type 별 유효 속성 스킴.\n * 형태: [{ type, description?, group?, properties? }]\n * LLM 이 정확한 컴포넌트를 만들도록 mutation input 으로 forwarding.\n */\n @property({ type: Array, attribute: false })\n componentSchemas?: Array<{ type: string; description?: string; group?: string; properties?: Record<string, any> }>\n\n @property({ type: String })\n placeholder = '자연어로 명령하세요 (Shift+Enter 줄바꿈, Enter 전송)'\n\n @state()\n private lines: ChatLine[] = []\n\n @state()\n private input = ''\n\n /** 마지막 send 실패 시 보존 — 다시 시도 버튼 클릭 시 사용 */\n @state()\n private lastFailedInput?: string\n\n /** 되돌려진 patch id 들 — 한 번 revert 한 patch 는 버튼 비활성 */\n @state()\n private revertedPatchIds = new Set<string>()\n\n /** mini action — 인라인 예시 토글 */\n @state()\n private examplesOpen = false\n\n /** 복사 toast 표시 상태 */\n @state()\n private toastMessage?: string\n\n /** 복사 confirmation feedback (메시지 idx → 짧은 시간 동안 ✓ 표시) */\n @state()\n private copiedIdx?: number\n\n @state()\n private busy = false\n\n @state()\n private errorMessage?: string\n\n /** C4 presence/roster — 세션 참여자 목록. lastSeenAt 으로 online 판정. */\n @state()\n private participants: Array<{ id: string; role: string; lastSeenAt?: string; user?: { id: string; name?: string } }> = []\n\n /** roster online 재평가 트리거 — heartbeat tick 마다 갱신(시간 경과 반영). */\n @state()\n private _presenceTick = 0\n\n private _heartbeatTimer?: ReturnType<typeof setInterval>\n\n /** C4 그룹챗 — 메시지 실시간 방송 구독 핸들 + 에코 dedup. */\n private _messageSub?: { unsubscribe: () => void }\n /** 이미 transcript 에 반영된 서버 메시지 id 집합 — 자기 에코·중복 방송 차단. */\n private _seenMessageIds = new Set<string>()\n /** optimistic 라인 임시 핸들 시퀀스 (index 비의존 reconcile 용). */\n private _localSeq = 0\n\n /** 현재 로그인 사용자 id — 그룹챗에서 내 메시지 vs 타인 메시지 구분 (발신자 표시). */\n private get _myUserId(): string | undefined {\n return (auth.credential as any)?.id\n }\n\n /** Mention popup — `#` (컴포넌트) / `@` (카탈로그) / `/` (slash) 트리거 시 활성.\n * 단일 popup 재사용 — mentionTrigger 로 활성 trigger 추적. */\n @state()\n private mentionOpen = false\n\n /** popup 표시용 — F-Finder 처럼 matchType + matchValue 동봉된 결과 */\n @state()\n private mentionResults: FilterResult[] = []\n\n @state()\n private mentionActiveIndex = 0\n\n /** filter cap (50) 으로 잘라낸 추가 매칭 수 — popup 의 'N more' 표시용 */\n @state()\n private mentionOverflow = 0\n\n /** 현재 활성 mention 의 textarea 내 trigger 위치 */\n private mentionHashPos = -1\n\n /** 현재 활성 trigger char — '#' / '@' / '/'. popup 비활성 시 의미 없음. */\n private mentionTrigger: '#' | '@' | '/' = '#'\n\n /** popup 열기 직전 사용자 selection 스냅샷 — Esc/blur close 시 원복용 */\n private originalSelectedRefids: number[] = []\n\n /** popup 에서 명시 선택된 #mention 매핑 — {token: refid}.\n * 사용자 textarea 에는 #token 만 보이지만 server 한테는 정확한 refid 전달.\n * send 시 mutation input.mentions 로 함께 전송. 사용자가 textarea 비우거나\n * 새 채팅 시작하면 정리. */\n private pickedMentions: Map<string, number> = new Map()\n\n /** popup 에서 명시 선택된 @user 멘션 매핑 — {token: userId}.\n * textarea 에는 @username 만 보이지만 server 한테는 정확한 userId 전달.\n * send 시 mutation input.userMentions 로 함께 전송. */\n private pickedUserMentions: Map<string, string> = new Map()\n\n /** input 디바운스 timer — 150ms 후 refreshMentionPopup 실행 (F-Finder 와 동일) */\n private inputDebounceTimer?: ReturnType<typeof setTimeout>\n\n /** centerTo 디바운스 timer — arrow nav / hover 빠를 때 카메라 애니메이션 race 방지 */\n private centerDebounceTimer?: ReturnType<typeof setTimeout>\n\n /** Default 카탈로그 후보 캐시 — chat panel mount 시 1회 빌드 */\n private cachedCatalogCandidates: MentionCandidate[] = buildCatalogCandidates(\n DEFAULT_CATALOG_ENTRIES\n )\n\n /** Default slash template 후보 캐시 — chat panel mount 시 1회 빌드 */\n private cachedSlashCandidates: MentionCandidate[] = buildSlashCandidates(\n DEFAULT_SLASH_TEMPLATES\n )\n\n /** `@` 마지막 query 의 도메인 사용자 후보 캐시 — userProvider 응답 도착 시 갱신.\n * query 별 캐시가 아니라 단순 \"최근 결과\" — async race 회피. */\n private cachedUserCandidates: MentionCandidate[] = []\n\n /** 진행 중인 userProvider fetch token — 늦게 도착한 응답이 최신을 덮어쓰지 않도록 식별. */\n private userFetchToken = 0\n\n static styles = [\n // 어플리케이션 표준 스크롤바 (--scrollbar-width / --scrollbar-thumb-color 등 호스트 변수 따름)\n ScrollbarStyles,\n css`\n /*\n Material Design 3 토큰 사용 — 사용자 테마 (라이트/다크/브랜드) 자동 반영.\n 각 var() 의 두 번째 인자는 토큰 미정의 시 fallback (현 디자인 톤).\n */\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 240px;\n position: relative;\n font:\n 400 11px / 1.55 -apple-system,\n BlinkMacSystemFont,\n 'Inter',\n 'Pretendard',\n 'Segoe UI',\n Roboto,\n sans-serif;\n letter-spacing: -0.005em;\n color: var(--md-sys-color-on-surface, #0f172a);\n background: var(--md-sys-color-surface, #ffffff);\n }\n\n /* ── C4 Roster (participants + presence) ───────────── */\n .roster {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 4px;\n padding: 4px 8px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n }\n .roster-count {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n font-size: 10px;\n opacity: 0.7;\n margin-right: 2px;\n }\n .roster-count md-icon {\n font-size: 13px;\n width: 13px;\n height: 13px;\n }\n .roster-chip {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 1px 6px 1px 4px;\n border-radius: 9px;\n font-size: 10px;\n background: var(--md-sys-color-surface-variant, #f1f5f9);\n }\n .roster-chip.offline {\n opacity: 0.45;\n }\n .roster-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #cbd5e1;\n }\n .roster-chip.online .roster-dot {\n background: #22c55e;\n }\n\n /* ── Session tabs ─────────────────────────────────── */\n .session-tabs {\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 4px 6px 0 6px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n background: var(--md-sys-color-surface-container-low, #fafbfc);\n overflow-x: auto;\n scrollbar-width: thin;\n flex-shrink: 0;\n }\n .session-tab {\n flex-shrink: 0;\n padding: 4px 10px;\n font-size: 11px;\n line-height: 1.4;\n color: var(--md-sys-color-on-surface-variant, #64748b);\n background: transparent;\n border: 1px solid transparent;\n border-bottom: 0;\n border-top-left-radius: 5px;\n border-top-right-radius: 5px;\n cursor: pointer;\n max-width: 160px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .session-tab:hover {\n background: var(--md-sys-color-surface-container, #f1f5f9);\n }\n .session-tab.active {\n background: var(--md-sys-color-surface, #ffffff);\n color: var(--md-sys-color-on-surface, #0f172a);\n border-color: var(--md-sys-color-outline-variant, #e2e8f0);\n font-weight: 500;\n position: relative;\n }\n .session-tab.active::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n bottom: -1px;\n height: 1px;\n background: var(--md-sys-color-surface, #ffffff);\n }\n .session-tab-new {\n flex-shrink: 0;\n padding: 4px 8px 4px 6px;\n background: transparent;\n border: 0;\n cursor: pointer;\n color: var(--md-sys-color-on-surface-variant, #64748b);\n display: inline-flex;\n align-items: center;\n gap: 2px;\n font-size: 11px;\n line-height: 1.4;\n border-radius: 5px;\n white-space: nowrap;\n }\n .session-tab-new:hover {\n color: var(--md-sys-color-primary, #2563eb);\n background: var(--md-sys-color-surface-container, #f1f5f9);\n }\n .session-tab-new md-icon {\n --md-icon-size: 14px;\n font-size: 14px;\n }\n\n /* ── Messages ─────────────────────────────────────── */\n /* 스크롤바는 ScrollbarStyles (위쪽 styles 배열) 가 처리 — 어플리케이션 표준 따름 */\n .messages {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden; /* 가로 스크롤 차단 — 자식 (empty / msg) 내부에서 wrap 처리 */\n padding: 10px 16px 4px;\n display: flex;\n flex-direction: column;\n gap: 4px; /* 단일 spacing source — .msg-wrap 의 자체 margin 은 0 */\n scroll-behavior: smooth;\n min-width: 0; /* flex item 의 기본 min-content 무시 — 자식 ellipsis/wrap 동작 */\n }\n\n /* ── Empty state — onboarding 가이드 ───────────────── */\n .empty {\n margin: auto;\n text-align: left;\n width: 100%;\n max-width: 360px;\n box-sizing: border-box;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 16px;\n word-break: break-word; /* 긴 한글 / URL 도 줄바꿈해 가로 overflow 방지 */\n overflow-wrap: anywhere;\n }\n .empty .header {\n text-align: center;\n padding: 8px 0 4px;\n }\n .empty .icon {\n font-size: 22px;\n opacity: 0.4;\n margin-bottom: 8px;\n color: var(--md-sys-color-primary, currentColor);\n display: block;\n }\n .empty .title {\n color: var(--md-sys-color-on-surface, #0f172a);\n font-size: 12.5px;\n font-weight: 600;\n margin-bottom: 4px;\n letter-spacing: -0.01em;\n }\n .empty .subtitle {\n color: var(--md-sys-color-on-surface-variant, #475569);\n font-size: 10.5px;\n line-height: 1.5;\n }\n\n .empty .group {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .empty .group-label {\n font-size: 9.5px;\n color: var(--md-sys-color-outline, #94a3b8);\n letter-spacing: 0.06em;\n text-transform: uppercase;\n font-weight: 600;\n padding: 0 2px;\n }\n .empty .example {\n all: unset;\n display: block;\n cursor: pointer;\n padding: 7px 10px;\n font-size: 11px;\n color: var(--md-sys-color-on-surface, #0f172a);\n background: var(--md-sys-color-surface-container, #f8fafc);\n border: 1px solid var(--md-sys-color-outline-variant, #eef2f6);\n border-radius: 8px;\n line-height: 1.5;\n box-sizing: border-box;\n width: 100%;\n max-width: 100%;\n word-break: break-word;\n overflow-wrap: anywhere;\n transition:\n background 0.15s,\n border-color 0.15s,\n transform 0.1s;\n }\n .empty .example:hover {\n background: var(--md-sys-color-surface-container-high, #f1f5f9);\n border-color: var(--md-sys-color-outline, #cbd5e1);\n }\n .empty .example:active {\n transform: scale(0.99);\n }\n .empty .example .arrow {\n float: right;\n opacity: 0.4;\n margin-left: 8px;\n font-size: 11px;\n }\n\n .empty .footer {\n margin-top: 4px;\n padding: 8px 4px 0;\n border-top: 1px solid var(--md-sys-color-outline-variant, #eef2f6);\n color: var(--md-sys-color-outline, #94a3b8);\n font-size: 10px;\n line-height: 1.6;\n text-align: center;\n }\n .empty .footer .badge {\n display: inline-block;\n margin: 0 4px;\n padding: 1px 7px;\n background: var(--md-sys-color-surface-container, #f8fafc);\n border-radius: 8px;\n font-size: 9.5px;\n color: var(--md-sys-color-on-surface-variant, #64748b);\n }\n /* ── Trigger chip row (empty state) ──────────────── */\n .empty .trigger-row {\n margin: 12px 0 4px;\n padding: 10px 12px;\n background: var(--md-sys-color-surface-container-low, #f8fafc);\n border: 1px solid var(--md-sys-color-outline-variant, #eef2f6);\n border-radius: 10px;\n }\n .empty .trigger-row-label {\n font-size: 10px;\n color: var(--md-sys-color-on-surface-variant, #64748b);\n margin-bottom: 6px;\n text-align: center;\n letter-spacing: 0.02em;\n }\n .empty .trigger-chips {\n display: flex;\n gap: 6px;\n justify-content: center;\n flex-wrap: wrap;\n }\n .empty .trigger-chip {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 5px 10px;\n background: var(--md-sys-color-surface, #ffffff);\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n border-radius: 18px;\n cursor: pointer;\n font-size: 11.5px;\n color: var(--md-sys-color-on-surface, #0f172a);\n transition:\n border-color 0.15s,\n background 0.15s,\n color 0.15s;\n }\n .empty .trigger-chip:hover {\n border-color: var(--md-sys-color-primary, #2563eb);\n background: var(--md-sys-color-primary-container, #dbeafe);\n color: var(--md-sys-color-on-primary-container, #1e3a8a);\n }\n .empty .trigger-chip .trigger-char {\n font-family:\n 'SF Mono',\n Menlo,\n Monaco,\n Consolas,\n monospace;\n font-size: 13px;\n font-weight: 600;\n color: var(--md-sys-color-primary, #2563eb);\n min-width: 12px;\n text-align: center;\n }\n .empty .trigger-chip:hover .trigger-char {\n color: var(--md-sys-color-on-primary-container, #1e3a8a);\n }\n .empty .trigger-chip .trigger-desc {\n font-weight: 500;\n }\n\n /* ── Message label (sender hint) ──────────────────── */\n .msg-label {\n font-size: 9.5px;\n color: var(--md-sys-color-outline, #94a3b8);\n letter-spacing: 0.05em;\n font-weight: 500;\n text-transform: uppercase;\n margin: 4px 0 1px 4px;\n align-self: flex-start;\n }\n .msg-label:first-child {\n margin-top: 0;\n }\n\n /* ── Message wrapper (메시지 + hover 액션) ────────── */\n .msg-wrap {\n display: flex;\n flex-direction: column;\n width: fit-content;\n max-width: min(92%, 420px);\n /* margin 0 — spacing 은 .messages gap 단일 source */\n }\n .msg-wrap.user {\n align-self: flex-end;\n align-items: flex-end;\n }\n .msg-wrap.assistant {\n align-self: flex-start;\n align-items: flex-start;\n }\n .msg-wrap.system {\n align-self: center;\n align-items: center;\n max-width: 100%;\n }\n\n /* ── Message bubble ───────────────────────────────── */\n .msg {\n display: inline-block;\n width: fit-content;\n max-width: 100%;\n padding: 5px 9px;\n border-radius: 10px;\n white-space: normal;\n overflow-wrap: anywhere;\n font-size: 11px;\n line-height: 1.45;\n }\n\n .msg.user {\n align-self: flex-end;\n background: var(--md-sys-color-primary, #1e293b);\n color: var(--md-sys-color-on-primary, #f8fafc);\n border-radius: 12px 12px 3px 12px;\n /* margin-top 0 — .messages gap 으로만 spacing */\n }\n\n /* 그룹챗 — 다른 참여자의 메시지. 내 메시지(우측 primary)와 구분: 좌측 정렬 + 보조 색. */\n .msg-wrap.user.other {\n align-self: flex-start;\n align-items: flex-start;\n }\n .msg.user.other {\n align-self: flex-start;\n background: var(--md-sys-color-secondary-container, #e2e8f0);\n color: var(--md-sys-color-on-secondary-container, #0f172a);\n border-radius: 12px 12px 12px 3px;\n }\n /* 발신자 이름 라벨 — AI 라벨(대문자)과 달리 사람 이름은 그대로. */\n .msg-label.sender {\n text-transform: none;\n letter-spacing: 0;\n color: var(--md-sys-color-primary, #475569);\n }\n\n .msg.assistant {\n align-self: flex-start;\n background: var(--md-sys-color-surface-container, #f8fafc);\n color: var(--md-sys-color-on-surface, #0f172a);\n border: 1px solid var(--md-sys-color-outline-variant, #eef2f6);\n border-radius: 12px 12px 12px 3px;\n }\n\n /* ── Markdown 렌더 (AI 응답) ──────────────────────── */\n .md-body {\n display: block;\n }\n .md-body > :first-child {\n margin-top: 0;\n }\n .md-body > :last-child {\n margin-bottom: 0;\n }\n .md-body p {\n margin: 0 0 4px;\n line-height: 1.45;\n }\n .md-body ul,\n .md-body ol {\n margin: 2px 0 4px;\n padding-left: 16px;\n }\n .md-body li {\n margin: 0;\n line-height: 1.45;\n }\n .md-body li > p {\n margin: 0;\n }\n .md-body strong {\n font-weight: 600;\n }\n .md-body em {\n font-style: italic;\n }\n /* '#token' mention — popup 의 match-badge 톤과 맞춤. 채팅 버블에서 컴포넌트\n 참조임을 시각적으로 구분 (사용자/AI 메시지 양쪽). */\n .md-body .mention {\n display: inline-block;\n padding: 0 5px;\n border-radius: 4px;\n background: var(--md-sys-color-primary-container, rgba(37, 99, 235, 0.12));\n color: var(--md-sys-color-on-primary-container, #1e40af);\n font-weight: 500;\n white-space: nowrap;\n cursor: pointer;\n transition: background 0.12s;\n }\n .md-body .mention:hover {\n background: var(--md-sys-color-primary, rgba(37, 99, 235, 0.25));\n color: var(--md-sys-color-on-primary, #ffffff);\n }\n .msg.user .md-body .mention {\n background: rgba(255, 255, 255, 0.22);\n color: inherit;\n }\n .msg.user .md-body .mention:hover {\n background: rgba(255, 255, 255, 0.40);\n }\n .md-body code {\n font-family:\n 'SF Mono',\n Menlo,\n Monaco,\n Consolas,\n monospace;\n font-size: 10px;\n padding: 1px 4px;\n background: var(--md-sys-color-surface-container-high, rgba(15, 23, 42, 0.06));\n border-radius: 3px;\n }\n .md-body pre {\n margin: 5px 0;\n padding: 6px 8px;\n background: var(--md-sys-color-surface-container-high, rgba(15, 23, 42, 0.06));\n border-radius: 5px;\n overflow-x: auto;\n font-size: 10px;\n line-height: 1.5;\n }\n /* user 메시지(어두운 배경) 위에서는 code/pre 의 contrast 보정 */\n .msg.user .md-body code,\n .msg.user .md-body pre {\n background: rgba(255, 255, 255, 0.15);\n }\n .msg.user .md-body a {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n }\n .msg.user .md-body blockquote {\n border-left-color: rgba(255, 255, 255, 0.3);\n color: rgba(255, 255, 255, 0.85);\n }\n .msg.user .md-body hr {\n border-top-color: rgba(255, 255, 255, 0.2);\n }\n .md-body pre code {\n padding: 0;\n background: transparent;\n font-size: inherit;\n }\n .md-body a {\n color: var(--md-sys-color-primary, #1e293b);\n text-decoration: underline;\n text-underline-offset: 2px;\n }\n .md-body a:hover {\n text-decoration: none;\n }\n .md-body h1,\n .md-body h2,\n .md-body h3,\n .md-body h4 {\n margin: 4px 0 2px;\n font-weight: 600;\n line-height: 1.35;\n }\n .md-body h1 { font-size: 13px; }\n .md-body h2 { font-size: 12px; }\n .md-body h3,\n .md-body h4 { font-size: 11.5px; }\n .md-body blockquote {\n margin: 4px 0;\n padding: 1px 8px;\n border-left: 2px solid var(--md-sys-color-outline-variant, #cbd5e1);\n color: var(--md-sys-color-on-surface-variant, #64748b);\n }\n .md-body hr {\n border: 0;\n border-top: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n margin: 6px 0;\n }\n .md-body table {\n border-collapse: collapse;\n margin: 4px 0;\n font-size: 10px;\n }\n .md-body th,\n .md-body td {\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n padding: 2px 6px;\n }\n .md-body th {\n background: var(--md-sys-color-surface-container, #f8fafc);\n font-weight: 600;\n }\n\n .msg.system {\n align-self: center;\n background: transparent;\n color: var(--md-sys-color-outline, #94a3b8);\n font-size: 10px;\n font-style: italic;\n max-width: 100%;\n padding: 2px 8px;\n margin: 0;\n border: 0;\n }\n\n .msg.pending {\n opacity: 0.5;\n }\n .typing-dots {\n display: inline-flex;\n gap: 4px;\n align-items: center;\n vertical-align: middle;\n }\n .typing-dots > span {\n display: inline-block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentColor;\n opacity: 0.4;\n animation: pulse 1.2s ease-in-out infinite;\n }\n .typing-dots > span:nth-child(2) { animation-delay: 0.2s; }\n .typing-dots > span:nth-child(3) { animation-delay: 0.4s; }\n @keyframes pulse {\n 0%, 100% { opacity: 0.2; transform: scale(0.85); }\n 50% { opacity: 0.7; transform: scale(1.1); }\n }\n\n /* ── Patch indicator + 인라인 되돌리기 ─────────────── */\n .summary {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-top: 4px;\n padding: 3px 4px 3px 8px;\n background: var(--md-sys-color-surface-container-high, #f1f5f9);\n border-radius: 6px;\n font-size: 10px;\n color: var(--md-sys-color-on-surface-variant, #475569);\n line-height: 1;\n }\n .summary md-icon {\n --md-icon-size: 13px;\n color: var(--md-sys-color-tertiary, #10b981);\n }\n .summary.reverted {\n opacity: 0.6;\n }\n .summary.reverted md-icon {\n color: var(--md-sys-color-outline, #94a3b8);\n }\n .summary .summary-text {\n letter-spacing: 0.01em;\n }\n .summary .revert-btn {\n height: 20px;\n padding: 0 8px;\n background: transparent;\n border: 0;\n border-radius: 4px;\n cursor: pointer;\n font-family: inherit;\n letter-spacing: inherit;\n font-size: 10px;\n color: var(--md-sys-color-on-surface-variant, #475569);\n line-height: 1;\n transition: background 0.12s, color 0.12s;\n width: auto;\n }\n .summary .revert-btn:hover:not(:disabled) {\n background: var(--md-sys-color-surface, #ffffff);\n color: var(--md-sys-color-on-surface, #0f172a);\n }\n .summary.reverted .revert-btn,\n .summary .revert-btn:disabled {\n display: none;\n }\n\n /* ── Follow-up question ───────────────────────────── */\n .followup {\n font-size: 10.5px;\n color: var(--md-sys-color-on-secondary-container, #92400e);\n background: var(--md-sys-color-secondary-container, #fffbeb);\n border-left: 2px solid var(--md-sys-color-secondary, #f59e0b);\n padding: 5px 8px;\n border-radius: 0 5px 5px 0;\n margin-top: 4px;\n line-height: 1.45;\n }\n\n /* ── Tool usages fold-able 박스 — AI 도구 호출 trace ───── */\n .tool-usages {\n margin-top: 4px;\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n border-radius: 6px;\n background: var(--md-sys-color-surface-container-low, #f8fafc);\n overflow: hidden;\n font-size: 10.5px;\n }\n .tool-usages-header {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 5px 8px;\n background: transparent;\n border: 0;\n cursor: pointer;\n color: var(--md-sys-color-on-surface-variant, #475569);\n font-family: inherit;\n font-size: inherit;\n letter-spacing: inherit;\n text-align: left;\n }\n .tool-usages-header:hover {\n background: var(--md-sys-color-surface-container, #f1f5f9);\n }\n .tool-usages-icon,\n .tool-usages-chevron {\n --md-icon-size: 14px;\n flex-shrink: 0;\n }\n .tool-usages-summary {\n flex: 1;\n font-weight: 500;\n }\n .tool-usages-counts {\n color: var(--md-sys-color-outline, #94a3b8);\n font-weight: 400;\n font-size: 10px;\n }\n .tool-usages-list {\n list-style: none;\n margin: 0;\n padding: 0 8px 8px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n .tool-usage-item {\n border-left: 2px solid var(--md-sys-color-outline-variant, #cbd5e1);\n padding: 4px 8px 4px 8px;\n background: var(--md-sys-color-surface, #ffffff);\n border-radius: 0 4px 4px 0;\n }\n .tool-usage-item.kind-read {\n border-left-color: var(--md-sys-color-tertiary, #10b981);\n }\n .tool-usage-item.kind-write {\n border-left-color: var(--md-sys-color-primary, #1e293b);\n }\n .tool-usage-item.kind-unknown {\n border-left-color: var(--md-sys-color-error, #b91c1c);\n }\n .tool-usage-head {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 3px;\n }\n .tool-usage-step {\n color: var(--md-sys-color-outline, #94a3b8);\n font-size: 10px;\n }\n .tool-usage-name {\n font-weight: 600;\n color: var(--md-sys-color-on-surface, #0f172a);\n font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace;\n font-size: 10.5px;\n }\n .tool-usage-kind {\n font-size: 9.5px;\n padding: 1px 5px;\n border-radius: 3px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n font-weight: 600;\n }\n .tool-usage-kind.kind-read {\n color: var(--md-sys-color-on-tertiary, #ffffff);\n background: var(--md-sys-color-tertiary, #10b981);\n }\n .tool-usage-kind.kind-write {\n color: var(--md-sys-color-on-primary, #ffffff);\n background: var(--md-sys-color-primary, #1e293b);\n }\n .tool-usage-kind.kind-unknown {\n color: var(--md-sys-color-on-error, #ffffff);\n background: var(--md-sys-color-error, #b91c1c);\n }\n .tool-usage-args,\n .tool-usage-result {\n margin: 2px 0 0;\n padding: 4px 6px;\n background: var(--md-sys-color-surface-container-high, rgba(15, 23, 42, 0.04));\n border-radius: 3px;\n font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace;\n font-size: 10px;\n line-height: 1.4;\n white-space: pre-wrap;\n overflow-x: auto;\n color: var(--md-sys-color-on-surface, #0f172a);\n max-height: 180px;\n overflow-y: auto;\n }\n .tool-usage-args {\n border-left: 2px solid var(--md-sys-color-outline-variant, #cbd5e1);\n }\n .tool-usage-result {\n border-left: 2px solid var(--md-sys-color-outline, #94a3b8);\n }\n\n /* ── Hover actions (assistant 메시지만, Material icon button) ── */\n .msg-actions {\n display: flex;\n gap: 2px;\n margin-top: 2px;\n padding: 0;\n opacity: 0;\n transition: opacity 0.15s;\n pointer-events: none;\n }\n .msg-wrap:hover .msg-actions,\n .msg-wrap:focus-within .msg-actions {\n opacity: 1;\n pointer-events: auto;\n }\n .msg-actions .msg-action {\n width: 22px;\n height: 22px;\n padding: 0;\n background: transparent;\n border: 0;\n border-radius: 5px;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--md-sys-color-outline, #94a3b8);\n transition:\n background 0.12s,\n color 0.12s;\n }\n .msg-actions .msg-action:hover:not(:disabled) {\n background: var(--md-sys-color-surface-container-high, #e2e8f0);\n color: var(--md-sys-color-on-surface, #0f172a);\n }\n .msg-actions .msg-action:disabled {\n opacity: 0.4;\n cursor: default;\n }\n .msg-actions .msg-action.confirmed {\n color: var(--md-sys-color-tertiary, #10b981);\n }\n .msg-actions .msg-action md-icon {\n --md-icon-size: 14px;\n }\n\n /* ── Error banner ─────────────────────────────────── */\n .error {\n color: var(--md-sys-color-on-error-container, #b91c1c);\n padding: 7px 11px;\n background: var(--md-sys-color-error-container, #fef2f2);\n border: 1px solid var(--md-sys-color-error, #fee2e2);\n border-radius: 8px;\n margin: 0 16px 8px;\n font-size: 10.5px;\n line-height: 1.5;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .error .error-text {\n flex: 1;\n }\n .error .retry-btn {\n flex-shrink: 0;\n padding: 3px 8px;\n font-size: 10px;\n font-family: inherit;\n letter-spacing: inherit;\n background: var(--md-sys-color-error, #b91c1c);\n color: var(--md-sys-color-on-error, #ffffff);\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n transition: opacity 0.15s;\n width: auto;\n height: auto;\n }\n .error .retry-btn:hover {\n opacity: 0.88;\n }\n\n /* ── Composer (actions + input 통합 영역) ──────────── */\n .composer {\n border-top: 1px solid var(--md-sys-color-outline-variant, #f1f5f9);\n background: var(--md-sys-color-surface, #ffffff);\n display: flex;\n flex-direction: column;\n }\n\n /* ── Mini action row (메시지 시작 후) ──────────────── */\n .actions-row {\n display: flex;\n gap: 6px;\n padding: 6px 14px 4px;\n flex-wrap: wrap;\n align-items: center;\n }\n /* Material 3 assist-chip 톤 — height 24, font label-small */\n .action {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 5px;\n height: 24px;\n padding: 0 10px;\n font-size: 11px;\n font-family: inherit;\n letter-spacing: inherit;\n line-height: 1;\n color: var(--md-sys-color-on-surface-variant, #475569);\n background: transparent;\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n border-radius: 8px;\n cursor: pointer;\n transition:\n background 0.15s,\n border-color 0.15s,\n color 0.15s;\n width: auto;\n vertical-align: middle;\n }\n .action:hover {\n background: var(--md-sys-color-surface-container, #f8fafc);\n border-color: var(--md-sys-color-outline, #cbd5e1);\n color: var(--md-sys-color-on-surface, #0f172a);\n }\n .action:active {\n transform: scale(0.97);\n }\n .action md-icon {\n --md-icon-size: 14px;\n opacity: 0.7;\n }\n\n /* ── Inline examples (action row 의 \"예시\" 토글 시) ──── */\n .inline-examples {\n padding: 4px 16px 0;\n display: flex;\n flex-direction: column;\n gap: 3px;\n }\n .inline-examples .ex-row {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n }\n .inline-examples .ex-row .ex-label {\n font-size: 9px;\n color: var(--md-sys-color-outline, #94a3b8);\n letter-spacing: 0.05em;\n text-transform: uppercase;\n margin-right: 2px;\n align-self: center;\n font-weight: 600;\n }\n .inline-examples .ex {\n all: unset;\n cursor: pointer;\n padding: 2px 8px;\n font-size: 10px;\n color: var(--md-sys-color-on-surface, #0f172a);\n background: var(--md-sys-color-surface-container, #f8fafc);\n border: 1px solid var(--md-sys-color-outline-variant, #eef2f6);\n border-radius: 10px;\n transition: background 0.15s;\n }\n .inline-examples .ex:hover {\n background: var(--md-sys-color-surface-container-high, #f1f5f9);\n }\n\n /* ── Input area ───────────────────────────────────── */\n /* border-top 은 .composer 가 담당 (actions-row 가 같은 wrapper 안) */\n .input-row {\n display: flex;\n gap: 8px;\n padding: 6px 14px 10px;\n background: transparent;\n align-items: flex-end;\n }\n /* 메시지 시작 전 — actions-row 가 없을 때는 input-row 가 직접 border-top */\n .composer.no-actions .input-row {\n padding-top: 10px;\n }\n\n /* mention popup — textarea wrapper 가 relative 컨텍스트, popup 이 textarea\n 와 정확히 같은 폭/좌표 (좌우 끝 정렬). send 버튼 영역은 제외. */\n .textarea-wrap {\n flex: 1;\n position: relative;\n display: flex;\n min-width: 0;\n }\n .textarea-wrap textarea {\n flex: 1;\n width: 100%;\n }\n .mention-popup {\n bottom: calc(100% + 4px);\n left: 0;\n right: 0;\n }\n\n textarea {\n flex: 1;\n padding: 8px 12px;\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n border-radius: 12px;\n font: inherit;\n letter-spacing: inherit;\n resize: none;\n min-height: 36px;\n max-height: 140px;\n outline: none;\n transition:\n border-color 0.2s,\n box-shadow 0.2s;\n background: var(--md-sys-color-surface, #ffffff);\n color: var(--md-sys-color-on-surface, #0f172a);\n }\n textarea::placeholder {\n color: var(--md-sys-color-outline, #94a3b8);\n }\n textarea:focus {\n border-color: var(--md-sys-color-outline, #cbd5e1);\n box-shadow: 0 0 0 3px var(--md-sys-color-primary-container, rgba(15, 23, 42, 0.08));\n }\n textarea:disabled {\n background: var(--md-sys-color-surface-container, #f8fafc);\n color: var(--md-sys-color-outline, #94a3b8);\n cursor: not-allowed;\n }\n\n /* ── Send button — ChatGPT 스타일 원형 ↑ (.input-row 안의 button 만) ─── */\n .input-row > button {\n width: 32px;\n height: 32px;\n padding: 0;\n background: var(--md-sys-color-primary, #1e293b);\n color: var(--md-sys-color-on-primary, #ffffff);\n border: 0;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background 0.15s,\n transform 0.1s,\n opacity 0.15s;\n flex-shrink: 0;\n align-self: flex-end;\n margin-bottom: 2px;\n }\n .input-row > button:disabled {\n background: var(--md-sys-color-surface-container-highest, #e2e8f0);\n color: var(--md-sys-color-outline, #94a3b8);\n cursor: not-allowed;\n }\n .input-row > button:hover:not(:disabled) {\n opacity: 0.88;\n }\n .input-row > button:active:not(:disabled) {\n transform: scale(0.94);\n }\n .input-row > button md-icon {\n --md-icon-size: 18px;\n }\n .input-row > button .spinner {\n width: 12px;\n height: 12px;\n border: 1.8px solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: spin 0.7s linear infinite;\n }\n @keyframes spin {\n to {\n transform: rotate(360deg);\n }\n }\n `\n ]\n\n connectedCallback() {\n super.connectedCallback()\n // 첫 마운트 시 sessionId 가 이미 set 돼있어도 loadHistory 는 첫 render 후 updated()\n // 가 처리 — 단일 진입점으로 일관 (이전엔 connectedCallback 과 updated 양쪽이 호출 →\n // 동시 fetch race 발생).\n }\n\n updated(changed: Map<string, any>) {\n if (changed.has('sessionId')) {\n // sessionId 가 바뀌면 (set / unset / 다른 보드 전환) 이전 세션의 모든 클라이언트\n // 컨텍스트 정리 — lines / pickedMentions / errorMessage / input 모두 새지 않도록.\n // 보드 단위 격리 (ChatSession 1:1 boardId) 가 깨지는 회귀 방지.\n this.lines = []\n this.pickedMentions.clear()\n this.pickedUserMentions.clear()\n this.cachedUserCandidates = []\n this.errorMessage = undefined\n this.input = ''\n this.participants = []\n this._seenMessageIds.clear() // 세션 전환 — 에코 dedup 컨텍스트 리셋\n if (this.sessionId) {\n this.loadHistory()\n this._startPresence() // C4: roster 로드 + heartbeat 시작\n this._startMessageSubscription() // C4: 메시지 실시간 방송 구독\n } else {\n this._stopPresence()\n this._stopMessageSubscription()\n }\n }\n // 자동 스크롤\n const ms = this.renderRoot.querySelector('.messages') as HTMLElement | null\n if (ms) ms.scrollTop = ms.scrollHeight\n }\n\n disconnectedCallback() {\n this._stopPresence()\n this._stopMessageSubscription()\n super.disconnectedCallback()\n }\n\n // ── C4 presence/roster ─────────────────────────────────────────────────────\n private _startPresence() {\n this._stopPresence()\n this._loadParticipants()\n this._touchPresence() // 즉시 1회 — 내 online 즉시 반영\n this._heartbeatTimer = setInterval(() => {\n this._touchPresence()\n this._loadParticipants()\n this._presenceTick++ // online 판정 재평가(시간 경과)\n }, PRESENCE_HEARTBEAT_MS)\n }\n\n private _stopPresence() {\n if (this._heartbeatTimer) {\n clearInterval(this._heartbeatTimer)\n this._heartbeatTimer = undefined\n }\n }\n\n // ── C4 그룹챗 — 메시지 실시간 방송 구독 ─────────────────────────────────────\n private _startMessageSubscription() {\n this._stopMessageSubscription()\n if (!this.sessionId) return\n try {\n this._messageSub = client\n .subscribe({ query: CHAT_MESSAGE_SUBSCRIPTION, variables: { sessionId: this.sessionId } })\n .subscribe({\n next: ({ data }: any) => this._onRemoteMessage(data?.chatMessageActivity),\n error: () => {\n /* 구독 에러는 채팅 본 기능 무영향 — 히스토리 쿼리로 폴백 */\n }\n })\n } catch {\n /* 구독 미지원 환경 — 조용히 스킵 */\n }\n }\n\n private _stopMessageSubscription() {\n if (this._messageSub) {\n try {\n this._messageSub.unsubscribe()\n } catch {\n /* 이미 닫힘 */\n }\n this._messageSub = undefined\n }\n }\n\n /** 서버 ChatMessage → ChatLine. */\n private _msgToLine(m: any): ChatLine {\n return remoteMessageToLine(m) as ChatLine\n }\n\n /**\n * 원격(방송) 메시지 수신 — 에코·중복은 id 로 차단, 신규는 transcript 에 append.\n * 자기 발신 메시지의 id 는 전송 reconcile 에서 이미 _seenMessageIds 에 등록됨.\n */\n private _onRemoteMessage(m: any) {\n const next = receiveRemoteMessage(this.lines as any, this._seenMessageIds, m)\n if (!next) return\n this._seenMessageIds = next.seen\n this.lines = next.lines as ChatLine[]\n }\n\n /**\n * 전송한 optimistic 라인을 서버 id 로 reconcile (index 비의존 — _localId 로 탐색).\n * 에코가 응답보다 먼저 도착해 이미 append 됐으면 optimistic 중복 라인을 제거한다.\n */\n private _reconcileSentLine(localId: string, serverId: string | undefined, patch: Partial<ChatLine>) {\n const next = reconcileSentLine(this.lines as any, this._seenMessageIds, localId, serverId, patch as any)\n this._seenMessageIds = next.seen\n this.lines = next.lines as ChatLine[]\n }\n\n private async _loadParticipants() {\n if (!this.sessionId) return\n try {\n const result = await client.query({\n query: CHAT_PARTICIPANTS_QUERY,\n variables: { sessionId: this.sessionId },\n fetchPolicy: 'network-only'\n })\n this.participants = result.data?.chatSessionParticipants ?? []\n } catch {\n /* roster 로드 실패는 채팅 본 기능 무영향 — 조용히 무시 */\n }\n }\n\n private async _touchPresence() {\n if (!this.sessionId) return\n try {\n await client.mutate({ mutation: TOUCH_PRESENCE_MUTATION, variables: { sessionId: this.sessionId } })\n } catch {\n /* heartbeat 실패 무시 */\n }\n }\n\n /** lastSeenAt 이 TTL 이내면 online. (_presenceTick 의존으로 주기 재평가.) */\n private _isOnline(lastSeenAt?: string): boolean {\n void this._presenceTick\n if (!lastSeenAt) return false\n const t = new Date(lastSeenAt).getTime()\n if (!Number.isFinite(t)) return false\n const age = Date.now() - t\n return age >= 0 ? age <= PRESENCE_TTL_MS : true\n }\n\n private async loadHistory() {\n if (!this.sessionId) return\n try {\n const result = await client.query({\n query: CHAT_MESSAGES_QUERY,\n variables: { sessionId: this.sessionId, limit: 200, offset: 0 },\n fetchPolicy: 'network-only'\n })\n const msgs = result.data?.chatMessages ?? []\n this.lines = msgs.map((m: any) => this._msgToLine(m))\n // 로드된 메시지 id 를 seen 에 등록 — 이후 방송 에코 dedup 기준.\n this._seenMessageIds = new Set<string>(msgs.map((m: any) => m.id).filter(Boolean))\n } catch (e: any) {\n this.errorMessage = `${i18next.t('board-ai.text.failed-to-load-history')}: ${e.message ?? e}`\n }\n }\n\n render() {\n const hasMessages = this.lines.length > 0\n return html`\n ${this.toastMessage\n ? html`<div class=\"toast show\">${this.toastMessage}</div>`\n : nothing}\n ${this.renderSessionTabs()}\n ${this.renderRoster()}\n <div class=\"messages\" @click=${this.onBubbleClick}>\n ${!hasMessages ? this.renderEmpty() : nothing}\n ${(() => {\n const myId = this._myUserId\n return this.lines.map((line, idx) => {\n const prev = this.lines[idx - 1]\n const showLabel = line.role === 'assistant' && (!prev || prev.role !== 'assistant')\n // 그룹챗: 다른 참여자의 user 메시지는 좌측 + 보낸이 이름 표시(연속 동일 발신자는 1회만).\n const isOther = line.role === 'user' && !!line.senderId && line.senderId !== myId\n const prevSameSender = !!prev && prev.role === 'user' && prev.senderId === line.senderId\n const showSenderLabel = isOther && !prevSameSender\n const content = (line.content ?? '').trim()\n const isMarkdown = line.role !== 'system' && content.length > 0\n const showActions = line.role !== 'system' && !line.pending && content.length > 0\n return html`\n ${showLabel\n ? html`<div class=\"msg-label\">${i18next.t('board-ai.label.ai')}</div>`\n : showSenderLabel\n ? html`<div class=\"msg-label sender\">${line.senderName || i18next.t('board-ai.label.participant')}</div>`\n : nothing}\n <div class=\"msg-wrap ${line.role} ${isOther ? 'other' : ''}\">\n <div class=\"msg ${line.role} ${isOther ? 'other' : ''} ${line.pending ? 'pending' : ''}\">\n ${line.pending\n ? html`<span class=\"typing-dots\"><span></span><span></span><span></span></span>`\n : isMarkdown\n ? html`<div class=\"md-body\">${unsafeHTML(renderMarkdown(content))}</div>`\n : content}\n ${line.patchId\n ? html`\n <div class=\"summary ${this.revertedPatchIds.has(line.patchId) ? 'reverted' : ''}\">\n <md-icon>check_circle</md-icon>\n <span class=\"summary-text\">\n ${this.revertedPatchIds.has(line.patchId)\n ? i18next.t('board-ai.text.reverted')\n : i18next.t('board-ai.text.applied-changes')}\n </span>\n <button\n class=\"revert-btn\"\n ?disabled=${this.revertedPatchIds.has(line.patchId)}\n @click=${() => line.patchId && this.onRevertClick(line.patchId)}>\n ${i18next.t('board-ai.button.revert')}\n </button>\n </div>\n `\n : nothing}\n ${line.followUp\n ? html`<div class=\"followup\">${unsafeHTML(renderMarkdown(line.followUp.trim()))}</div>`\n : nothing}\n ${line.toolUsages && line.toolUsages.length > 0\n ? this.renderToolUsages(idx, line)\n : nothing}\n </div>\n ${showActions ? this.renderMessageActions(idx, line, content) : nothing}\n </div>\n `\n })\n })()}\n </div>\n ${this.errorMessage\n ? html`\n <div class=\"error\">\n <span class=\"error-text\">${this.errorMessage}</span>\n ${this.lastFailedInput\n ? html`\n <button class=\"retry-btn\" @click=${this.retryLastSend}>\n ${i18next.t('board-ai.button.retry')}\n </button>\n `\n : nothing}\n </div>\n `\n : nothing}\n <div class=\"composer ${hasMessages ? '' : 'no-actions'}\">\n ${hasMessages\n ? html`\n <div class=\"actions-row\">\n <button\n class=\"action\"\n @click=${() => (this.examplesOpen = !this.examplesOpen)}\n title=${i18next.t('board-ai.text.show-examples-tooltip')}>\n <md-icon>${this.examplesOpen ? 'close' : 'lightbulb'}</md-icon>\n ${this.examplesOpen\n ? i18next.t('board-ai.button.close')\n : i18next.t('board-ai.button.examples')}\n </button>\n </div>\n ${this.examplesOpen ? this.renderInlineExamples() : nothing}\n `\n : nothing}\n <div class=\"input-row\">\n <div class=\"textarea-wrap\">\n <textarea\n .value=${this.input}\n @input=${(e: Event) => this.onTextareaInput(e)}\n @keydown=${(e: KeyboardEvent) => this.onKeyDown(e)}\n @blur=${() => {\n // 포커스를 popup item 으로 옮기는 click 보다 blur 가 먼저 — popup item 의\n // mousedown.preventDefault() 가 처리하지만 안전망으로 짧은 지연 후 닫기.\n setTimeout(() => this.closeMentionPopup(), 150)\n }}\n ?disabled=${this.busy}\n rows=\"1\"\n placeholder=${this.placeholder ?? i18next.t('board-ai.text.input-placeholder')}></textarea>\n ${this.mentionOpen\n ? html`\n <ox-mention-popup\n class=\"mention-popup\"\n .results=${this.mentionResults}\n .activeIndex=${this.mentionActiveIndex}\n .overflow=${this.mentionOverflow}\n @mention-active-change=${(e: CustomEvent) => {\n // 마우스 hover — keyboard nav 와 동일하게 select + centerTo\n const idx = e.detail?.index\n if (typeof idx === 'number' && idx >= 0 && idx < this.mentionResults.length) {\n this.mentionActiveIndex = idx\n this.previewActiveCandidate()\n }\n }}\n @mention-select=${(e: CustomEvent) =>\n this.applyMention(e.detail.candidate)}></ox-mention-popup>\n `\n : nothing}\n </div>\n <button\n ?disabled=${this.busy || !this.input.trim()}\n @click=${this.send}\n title=${i18next.t('board-ai.text.send-tooltip')}>\n ${this.busy\n ? html`<span class=\"spinner\"></span>`\n : html`<md-icon>arrow_upward</md-icon>`}\n </button>\n </div>\n </div>\n `\n }\n\n private onKeyDown(e: KeyboardEvent) {\n // Mention popup 활성 시 — 화살표/Enter/Tab/Esc 가로채서 popup 네비게이션\n if (this.mentionOpen) {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n if (this.mentionResults.length > 0) {\n this.mentionActiveIndex =\n (this.mentionActiveIndex + 1) % this.mentionResults.length\n this.previewActiveCandidate()\n }\n return\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n if (this.mentionResults.length > 0) {\n this.mentionActiveIndex =\n (this.mentionActiveIndex - 1 + this.mentionResults.length) %\n this.mentionResults.length\n this.previewActiveCandidate()\n }\n return\n }\n if (e.key === 'Enter' || e.key === 'Tab') {\n if (this.mentionResults.length > 0) {\n e.preventDefault()\n this.applyMention(this.mentionResults[this.mentionActiveIndex].candidate)\n return\n }\n // 매칭 0개면 popup 닫고 일반 동작 fall through\n this.closeMentionPopup()\n }\n if (e.key === 'Escape') {\n e.preventDefault()\n this.closeMentionPopup()\n return\n }\n }\n if (e.key === 'Enter' && !e.shiftKey && !this.busy) {\n e.preventDefault()\n this.send()\n }\n }\n\n /** input 이벤트 — value 즉시 반영 + popup 갱신은 150ms 디바운스 (F-Finder 와 동일).\n * big board 에서 매 keystroke filter 비용 절감 + 한국어 IME 조합 중 깜빡임 완화. */\n private onTextareaInput(e: Event) {\n const ta = e.target as HTMLTextAreaElement\n this.input = ta.value\n if (this.inputDebounceTimer) clearTimeout(this.inputDebounceTimer)\n this.inputDebounceTimer = setTimeout(() => {\n this.refreshMentionPopup(ta)\n }, 150)\n }\n\n /** 현재 cursor 위치를 보고 mention popup 활성/비활성 + 후보 갱신.\n * `#` (컴포넌트) / `@` (카탈로그) / `/` (slash 템플릿) 한 번에 하나만 활성. */\n private refreshMentionPopup(ta: HTMLTextAreaElement) {\n const cursor = ta.selectionStart ?? ta.value.length\n\n const triggers: Array<'#' | '@' | '/'> = ['#', '@', '/']\n let chosen: { hashPos: number; query: string; trigger: '#' | '@' | '/' } | null =\n null\n for (const t of triggers) {\n const ctx = extractMentionContext(ta.value, cursor, t)\n if (!ctx) continue\n if (!chosen || ctx.hashPos > chosen.hashPos) {\n chosen = { ...ctx, trigger: t }\n }\n }\n\n if (!chosen) {\n if (this.mentionOpen) this.closeMentionPopup()\n return\n }\n\n const wasOpen = this.mentionOpen\n const cap = 50\n let results: FilterResult[]\n if (chosen.trigger === '#' && this.searchProvider && chosen.query) {\n // 호스트의 SceneSearchEngine 위임 — F-Finder 와 동일 검색 룰. cap 만 chat 측에서 적용.\n results = this.searchProvider(chosen.query).slice(0, cap)\n } else if (chosen.trigger === '@') {\n // `@` = 도메인 사용자 멘션. host 가 userProvider set 했으면 query → users 비동기 fetch.\n // userProvider 미설정 / 결과 없음 → 빈 popup (멀티유저 미사용 환경).\n // async 라 즉시 반환은 빈 결과; await 후 다시 set 하는 패턴.\n results = this.cachedUserCandidates\n ? filterMentionCandidates(this.cachedUserCandidates, chosen.query, cap)\n : []\n // 비동기 갱신 — 결과 도착하면 popup 다시 갱신\n this.refreshUserCandidatesAsync(chosen.query, chosen.hashPos)\n } else {\n let all: MentionCandidate[]\n if (chosen.trigger === '#') {\n // 빈 query 의 # 또는 searchProvider 미설정 — 로컬 enumeration 으로 전체 후보 표시\n const liveBoard = this.boardProvider ? this.boardProvider() : this.currentBoard\n all = buildMentionCandidates(liveBoard?.components ?? [])\n } else {\n // `/` = 카탈로그 + slash 템플릿 통합 (Notion `/` 패턴). 둘 다 \"삽입 가능 항목\".\n all = [...this.cachedCatalogCandidates, ...this.cachedSlashCandidates]\n }\n results = filterMentionCandidates(all, chosen.query, cap)\n }\n // overflow 계산 — 전체 매칭 중 cap 으로 잘려나간 수 (popup 'N more' 표기)\n // (cap 잘라서 반환하므로, 전체 카운트는 자유 검색 시 알기 어려움.\n // 엄격한 overflow 추적 대신 results.length === cap 이면 추정 표시)\n const overflow = results.length === cap ? 0 : 0\n this.mentionResults = results\n this.mentionOverflow = overflow\n this.mentionActiveIndex = 0\n this.mentionHashPos = chosen.hashPos\n this.mentionTrigger = chosen.trigger\n this.mentionOpen = true\n\n // 첫 open 시 — 사용자 selection 스냅샷 (취소 시 원복용)\n if (!wasOpen && chosen.trigger === '#') {\n this.originalSelectedRefids = (this.selectedRefids ?? []).slice()\n }\n // 매칭된 후보 outline 만 갱신 — 카메라/선택 변경은 commit (Enter/click) 시점에만.\n this.highlightMatchedCandidates()\n }\n\n /** `@` query 에 대한 userProvider 비동기 호출 — 응답 도착 시 popup 결과 갱신.\n * race 방지: token 으로 최신 호출 식별 (늦게 도착한 응답 무시). */\n private async refreshUserCandidatesAsync(query: string, hashPos: number) {\n if (!this.userProvider) return\n const token = ++this.userFetchToken\n try {\n const users = await Promise.resolve(this.userProvider(query))\n if (token !== this.userFetchToken) return // 더 최신 호출이 있음\n // popup 이 닫혔거나 trigger / hashPos 가 바뀌면 무시\n if (!this.mentionOpen || this.mentionTrigger !== '@' || this.mentionHashPos !== hashPos) {\n return\n }\n this.cachedUserCandidates = buildUserCandidates(users)\n this.mentionResults = filterMentionCandidates(\n this.cachedUserCandidates,\n query,\n 50\n )\n this.mentionActiveIndex = 0\n } catch (e) {\n // 조용히 무시 — 사용자 입력 흐름 방해 X. 디버그 시 console 확인 가능.\n console.warn('[board-ai-chat] userProvider failed:', e)\n }\n }\n\n /** popup 후보들 outline highlight — 카메라/선택 변경 없음.\n * 타이핑 직후 (refreshMentionPopup) 에서 호출 — 사용자가 active 를 명시 이동하지\n * 않은 상태이므로 임의의 첫 항목을 선택하거나 카메라 이동시키지 않음.\n * # trigger 일 때만 활성. */\n private highlightMatchedCandidates() {\n if (this.mentionTrigger !== '#') return\n const allRefids: number[] = []\n for (const r of this.mentionResults) {\n if (typeof r.candidate.refid === 'number') allRefids.push(r.candidate.refid)\n }\n this.dispatchActions([{ action: 'highlightComponents', refids: allRefids }])\n }\n\n /** active 후보 live preview — 사용자가 ↑↓ 키 / popup 마우스 hover 로 명시적으로\n * active 를 옮긴 시점에 호출. F-Finder 동급 3-action: highlight + select + centerTo.\n * centerTo 는 150ms 디바운스 — 빠른 nav 시 카메라 애니메이션 race 방지.\n * # trigger 일 때만 활성. */\n private previewActiveCandidate() {\n if (this.mentionTrigger !== '#') return\n const allRefids: number[] = []\n for (const r of this.mentionResults) {\n if (typeof r.candidate.refid === 'number') allRefids.push(r.candidate.refid)\n }\n const active = this.mentionResults[this.mentionActiveIndex]\n const activeRefid =\n active && typeof active.candidate.refid === 'number'\n ? active.candidate.refid\n : undefined\n\n const actions: any[] = [{ action: 'highlightComponents', refids: allRefids }]\n if (typeof activeRefid === 'number') {\n actions.push({ action: 'selectComponents', refids: [activeRefid] })\n }\n this.dispatchActions(actions)\n\n if (this.centerDebounceTimer) clearTimeout(this.centerDebounceTimer)\n if (typeof activeRefid === 'number') {\n this.centerDebounceTimer = setTimeout(() => {\n this.dispatchActions([\n { action: 'centerToComponent', refid: activeRefid, animated: true }\n ])\n }, 150)\n }\n }\n\n private dispatchActions(actions: any[]) {\n this.dispatchEvent(\n new CustomEvent('board-action-execute', {\n detail: { actions, sessionId: this.sessionId },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /** 메시지 버블의 `<span class=\"mention\">#token</span>` 클릭 — 컴포넌트로 select + centerTo.\n * resolve 룰은 pure helper `resolveMentionToken` 에 위임 (단위 테스트 가능). */\n private onBubbleClick = (e: Event) => {\n const target = e.target as HTMLElement | null\n if (!target?.classList?.contains('mention')) return\n e.preventDefault()\n const text = (target.textContent || '').trim()\n const token = text.startsWith('#') ? text.slice(1) : text\n if (!token) return\n\n const refid = resolveMentionToken(token, {\n persistedRefid: target.getAttribute('data-refid'),\n searchProvider: this.searchProvider,\n board: this.boardProvider ? this.boardProvider() : this.currentBoard\n })\n\n if (typeof refid === 'number') {\n this.dispatchActions([\n { action: 'selectComponents', refids: [refid] },\n { action: 'centerToComponent', refid, animated: true }\n ])\n } else {\n this.showToast(\n i18next.t('board-ai.text.mention-not-found', {\n token,\n defaultValue: `#${token} 을(를) 찾을 수 없습니다`\n })\n )\n }\n }\n\n /** popup 닫기. commit=false (취소) 면 # 의 경우:\n * - highlight 정리 (highlightComponents [])\n * - 원래 selection 으로 원복\n * commit=true (확정) 면 현재 preview 상태 유지 + highlight 만 정리. */\n private closeMentionPopup(commit = false) {\n if (!this.mentionOpen) return\n const wasHashTrigger = this.mentionTrigger === '#'\n this.mentionOpen = false\n this.mentionResults = []\n this.mentionOverflow = 0\n this.mentionActiveIndex = 0\n this.mentionHashPos = -1\n\n if (wasHashTrigger) {\n // popup 닫힘 → highlight 항상 정리 (확정/취소 모두)\n const cleanupActions: any[] = [{ action: 'highlightComponents', refids: [] }]\n if (!commit) {\n // 취소 — 원래 selection 으로 원복\n cleanupActions.push({\n action: 'selectComponents',\n refids: this.originalSelectedRefids\n })\n }\n this.dispatchActions(cleanupActions)\n }\n }\n\n /** 후보 1개를 textarea 에 적용 + popup 닫고 cursor 재배치. */\n private applyMention(candidate: MentionCandidate) {\n const ta = this.renderRoot.querySelector('textarea') as HTMLTextAreaElement | null\n if (!ta || this.mentionHashPos < 0) {\n this.closeMentionPopup()\n return\n }\n const cursor = ta.selectionStart ?? ta.value.length\n const r = applyMentionSelection(\n ta.value,\n cursor,\n this.mentionHashPos,\n candidate,\n this.mentionTrigger\n )\n this.input = r.text\n // # trigger + refid 보유 후보 — commit 시점에만 select + centerTo (사용자 의도 확정 순간).\n // 동시에 picks 로 기록: textarea 에는 깨끗한 #label, server 에는 정확한 refid 전달.\n if (\n this.mentionTrigger === '#' &&\n typeof candidate.refid === 'number'\n ) {\n this.pickedMentions.set(candidate.label, candidate.refid)\n this.dispatchActions([\n { action: 'selectComponents', refids: [candidate.refid] },\n { action: 'centerToComponent', refid: candidate.refid, animated: true }\n ])\n } else if (\n this.mentionTrigger === '@' &&\n typeof candidate.userId === 'string' &&\n candidate.userId.length > 0\n ) {\n // @user 멘션 — userId 박제. 보드 액션은 발사 안 함 (사용자는 컴포넌트 아님).\n this.pickedUserMentions.set(candidate.label, candidate.userId)\n }\n this.closeMentionPopup(true) // commit — preview selection 유지, highlight 만 정리\n requestAnimationFrame(() => {\n const ta2 = this.renderRoot.querySelector('textarea') as HTMLTextAreaElement | null\n ta2?.focus()\n ta2?.setSelectionRange(r.cursorPos, r.cursorPos)\n })\n }\n\n // ── Empty state with onboarding guide ──────────────────────────\n\n /** 예시 그룹 — i18n 키. localize mixin 이 언어 변경 시 자동 재렌더. */\n private static readonly EXAMPLE_GROUPS: Array<{\n labelKey: string\n itemKeys: string[]\n }> = [\n {\n labelKey: 'board-ai.label.create',\n itemKeys: [\n 'board-ai.example.create-monitoring-dashboard',\n 'board-ai.example.create-welcome-screen'\n ]\n },\n {\n labelKey: 'board-ai.label.edit',\n itemKeys: [\n 'board-ai.example.edit-align-distribute',\n 'board-ai.example.edit-resize-board'\n ]\n },\n {\n labelKey: 'board-ai.label.style',\n itemKeys: [\n 'board-ai.example.style-dark-mode',\n 'board-ai.example.style-rounded-shadow'\n ]\n }\n ]\n\n /** 보드의 세션 탭 — 다중 세션 활성화 시 패널 상단. host 가 sessions 비워두면 표시 X.\n * 레이블은 session.name 우선, 없으면 fallback \"세션 N\". */\n private renderSessionTabs() {\n if (!this.sessions || this.sessions.length === 0) return nothing\n return html`\n <div class=\"session-tabs\" role=\"tablist\">\n ${this.sessions.map((s, i) => {\n const isActive = s.id === this.sessionId\n const fallback = i18next.t('board-ai.text.session-tab-label', {\n n: i + 1,\n defaultValue: `세션 ${i + 1}`\n })\n const label = s.name?.trim() || fallback\n const tooltip = s.createdAt\n ? `${label} · ${new Date(s.createdAt).toLocaleString()}`\n : label\n return html`\n <button\n class=\"session-tab ${isActive ? 'active' : ''}\"\n role=\"tab\"\n aria-selected=${isActive ? 'true' : 'false'}\n title=${tooltip}\n @click=${() => this._onSessionTabClick(s.id)}>\n ${label}\n </button>\n `\n })}\n <button\n class=\"session-tab-new\"\n title=${i18next.t('board-ai.button.new-chat', {\n defaultValue: '새 대화'\n })}\n @click=${this._onSessionCreateClick}>\n <md-icon>add</md-icon>\n <span>${i18next.t('board-ai.button.new-chat', { defaultValue: '새 대화' })}</span>\n </button>\n </div>\n `\n }\n\n /** C4 roster — 세션 참여자 + online 표시. 참여자 2명 미만이면 숨김(혼자면 의미 없음). */\n private renderRoster() {\n if (!this.sessionId || this.participants.length < 2) return nothing\n const online = this.participants.filter(p => this._isOnline(p.lastSeenAt)).length\n return html`\n <div class=\"roster\" role=\"group\" aria-label=\"participants\">\n <span class=\"roster-count\" title=${`${online} online / ${this.participants.length} total`}>\n <md-icon>group</md-icon>${online}/${this.participants.length}\n </span>\n ${this.participants.map(p => {\n const name = p.user?.name || p.user?.id || '?'\n const on = this._isOnline(p.lastSeenAt)\n return html`<span\n class=\"roster-chip ${on ? 'online' : 'offline'}\"\n title=${`${name} · ${p.role}${on ? ' · online' : ''}`}>\n <span class=\"roster-dot\"></span>${initials(name)}\n </span>`\n })}\n </div>\n `\n }\n\n private _onSessionTabClick(sessionId: string) {\n if (sessionId === this.sessionId) return\n this.dispatchEvent(\n new CustomEvent('session-switch', {\n detail: { sessionId },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _onSessionCreateClick = () => {\n this.dispatchEvent(\n new CustomEvent('session-create', {\n bubbles: true,\n composed: true\n })\n )\n }\n\n private renderEmpty() {\n return html`\n <div class=\"empty\">\n <div class=\"header\">\n <md-icon class=\"icon\">auto_awesome</md-icon>\n <div class=\"title\">${i18next.t('board-ai.text.empty-title')}</div>\n <div class=\"subtitle\">${i18next.t('board-ai.text.empty-subtitle')}</div>\n </div>\n\n <div class=\"trigger-row\">\n <div class=\"trigger-row-label\">${i18next.t('board-ai.text.triggers-heading')}</div>\n <div class=\"trigger-chips\">\n <button\n class=\"trigger-chip\"\n title=${i18next.t('board-ai.text.trigger-component-tooltip')}\n @click=${() => this.insertTrigger('#')}>\n <span class=\"trigger-char\">#</span>\n <span class=\"trigger-desc\">${i18next.t('board-ai.label.trigger-component')}</span>\n </button>\n <button\n class=\"trigger-chip\"\n title=${i18next.t('board-ai.text.trigger-user-tooltip')}\n @click=${() => this.insertTrigger('@')}>\n <span class=\"trigger-char\">@</span>\n <span class=\"trigger-desc\">${i18next.t('board-ai.label.trigger-user')}</span>\n </button>\n <button\n class=\"trigger-chip\"\n title=${i18next.t('board-ai.text.trigger-insert-tooltip')}\n @click=${() => this.insertTrigger('/')}>\n <span class=\"trigger-char\">/</span>\n <span class=\"trigger-desc\">${i18next.t('board-ai.label.trigger-insert')}</span>\n </button>\n </div>\n </div>\n\n ${OxBoardAIChat.EXAMPLE_GROUPS.map(\n group => html`\n <div class=\"group\">\n <div class=\"group-label\">${i18next.t(group.labelKey)}</div>\n ${group.itemKeys.map(key => {\n const text = i18next.t(key)\n return html`\n <button class=\"example\" @click=${() => this.useExample(text)}>\n ${text}<span class=\"arrow\">→</span>\n </button>\n `\n })}\n </div>\n `\n )}\n\n <div class=\"footer\">\n <span class=\"badge\">${i18next.t('board-ai.label.korean-supported')}</span>\n <span class=\"badge\">${i18next.t('board-ai.label.multi-command')}</span>\n <span class=\"badge\">${i18next.t('board-ai.label.review-able')}</span>\n <br />\n ${i18next.t('board-ai.text.empty-footer-notice')}\n </div>\n </div>\n `\n }\n\n /** trigger chip 클릭 — textarea 에 trigger 문자 삽입 + focus + popup 자동 활성. */\n private insertTrigger(trigger: '#' | '@' | '/') {\n // 입력 끝에 공백이 없으면 공백 추가 후 trigger — 자연스러운 위치\n const needsSpace = this.input.length > 0 && !/\\s$/.test(this.input)\n this.input = this.input + (needsSpace ? ' ' : '') + trigger\n requestAnimationFrame(() => {\n const ta = this.renderRoot.querySelector('textarea') as HTMLTextAreaElement | null\n if (!ta) return\n ta.focus()\n ta.setSelectionRange(ta.value.length, ta.value.length)\n // popup refresh — onTextareaInput 이 디바운스 후 호출되지만 직접 호출로 즉각 반영\n this.refreshMentionPopup(ta)\n })\n }\n\n /** 예시 클릭 — 입력창에 채우고 포커스 (사용자가 수정 후 전송 가능) */\n private useExample(text: string) {\n this.input = text\n this.examplesOpen = false\n requestAnimationFrame(() => {\n const ta = this.renderRoot.querySelector('textarea') as HTMLTextAreaElement | null\n ta?.focus()\n ta?.setSelectionRange(text.length, text.length)\n })\n }\n\n // ── Mini actions ───────────────────────────────────────────────\n\n private renderInlineExamples() {\n return html`\n <div class=\"inline-examples\">\n ${OxBoardAIChat.EXAMPLE_GROUPS.map(\n group => html`\n <div class=\"ex-row\">\n <span class=\"ex-label\">${i18next.t(group.labelKey)}</span>\n ${group.itemKeys.map(key => {\n const t = i18next.t(key)\n return html`<button class=\"ex\" @click=${() => this.useExample(t)}>${t}</button>`\n })}\n </div>\n `\n )}\n </div>\n `\n }\n\n // ── Retry (에러 발생 시) ───────────────────────────────────────\n\n private async retryLastSend() {\n if (!this.lastFailedInput) return\n this.input = this.lastFailedInput\n this.errorMessage = undefined\n this.lastFailedInput = undefined\n await this.send()\n }\n\n // ── Per-message hover action row ───────────────────────────────\n\n /**\n * 메시지별 hover 액션 — 텍스트 chip 으로 명확.\n * user : 액션 row 자체 X (자기 메시지에 시각 노이즈 X — 편집은 input 위 \"새 대화\" 흐름 또는 마우스 더블클릭)\n * assistant : 복사 + 재생성 + (patch 적용된 경우만) 되돌리기\n * system : 없음\n */\n private renderMessageActions(idx: number, line: ChatLine, content: string) {\n if (line.role !== 'assistant') return nothing\n\n const isCopied = this.copiedIdx === idx\n // revert 는 인라인 patch chip (메시지 안) 한 곳에만 — hover 액션에서 제외 (중복 방지).\n return html`\n <div class=\"msg-actions\">\n <button\n class=\"msg-action ${isCopied ? 'confirmed' : ''}\"\n title=${isCopied\n ? i18next.t('board-ai.text.copied')\n : i18next.t('board-ai.button.copy')}\n @click=${() => this.copyMessage(idx, content)}>\n <md-icon>${isCopied ? 'check' : 'content_copy'}</md-icon>\n </button>\n <button\n class=\"msg-action\"\n ?disabled=${this.busy}\n title=${i18next.t('board-ai.button.regenerate')}\n @click=${() => this.regenerateAssistant(idx)}>\n <md-icon>refresh</md-icon>\n </button>\n </div>\n `\n }\n\n /**\n * \"AI 가 이런 도구를 사용했습니다\" fold-able 박스.\n *\n * - 기본 닫힘 (toolUsagesOpen=false). 헤더 클릭 시 toggle.\n * - 펼쳤을 때 도구 별로 name + arguments + result + read/write 색상 배지.\n * - 디버그 / 신뢰도 향상용 — 사용자가 \"왜 이렇게 답했지\" 의문 가질 때 즉시 검증.\n */\n private renderToolUsages(idx: number, line: ChatLine) {\n const usages = line.toolUsages\n if (!usages || usages.length === 0) return nothing\n const open = !!line.toolUsagesOpen\n const readCount = usages.filter(u => u.kind === 'read').length\n const writeCount = usages.filter(u => u.kind === 'write').length\n const toolsLabel = i18next.t('board-ai.text.tools-used', { defaultValue: '도구 사용' })\n return html`\n <div class=\"tool-usages ${open ? 'open' : ''}\">\n <button\n class=\"tool-usages-header\"\n aria-expanded=${open}\n @click=${() => this.toggleToolUsages(idx)}>\n <md-icon class=\"tool-usages-icon\">${open ? 'expand_less' : 'build'}</md-icon>\n <span class=\"tool-usages-summary\">\n <strong>${usages.length}</strong> ${toolsLabel}\n <span class=\"tool-usages-counts\">· read ${readCount} · write ${writeCount}</span>\n </span>\n <md-icon class=\"tool-usages-chevron\">${open ? 'expand_less' : 'expand_more'}</md-icon>\n </button>\n ${open\n ? html`\n <ol class=\"tool-usages-list\">\n ${usages.map(\n (u, i) => html`\n <li class=\"tool-usage-item kind-${u.kind}\">\n <div class=\"tool-usage-head\">\n <span class=\"tool-usage-step\">${i + 1}.</span>\n <span class=\"tool-usage-name\">${u.name}</span>\n <span class=\"tool-usage-kind kind-${u.kind}\">${u.kind}</span>\n </div>\n ${u.arguments && Object.keys(u.arguments).length > 0\n ? html`<pre class=\"tool-usage-args\">${this.formatJson(u.arguments)}</pre>`\n : nothing}\n <pre class=\"tool-usage-result\">${this.formatJson(u.result)}</pre>\n </li>\n `\n )}\n </ol>\n `\n : nothing}\n </div>\n `\n }\n\n private toggleToolUsages(idx: number) {\n const next = [...this.lines]\n const line = next[idx]\n if (!line) return\n next[idx] = { ...line, toolUsagesOpen: !line.toolUsagesOpen }\n this.lines = next\n }\n\n private formatJson(value: any): string {\n try {\n return JSON.stringify(value, null, 2)\n } catch {\n return String(value)\n }\n }\n\n // ── Per-message actions ────────────────────────────────────────\n\n private async copyMessage(idx: number, content: string) {\n try {\n // 클립보드에는 marker 제거된 깨끗한 텍스트\n await navigator.clipboard.writeText(stripMentionRefids(content))\n this.copiedIdx = idx\n this.showToast(i18next.t('board-ai.text.copied'))\n setTimeout(() => {\n if (this.copiedIdx === idx) this.copiedIdx = undefined\n }, 1500)\n } catch {\n // clipboard 권한 없거나 비-secure context — 조용히 무시\n }\n }\n\n private showToast(message: string) {\n this.toastMessage = message\n setTimeout(() => {\n if (this.toastMessage === message) this.toastMessage = undefined\n }, 1500)\n }\n\n /**\n * 사용자 메시지 편집 — 해당 메시지 + 이후 모두 UI 에서 제거하고 입력창에 채움.\n * 서버 영속 history 는 그대로 유지 (다음 send 시 새 메시지로 추가).\n */\n private editUserMessage(idx: number, content: string) {\n // textarea 에는 marker 제거된 깨끗한 텍스트 — 사용자가 raw `{refid:N}` 보지 않게.\n // 다시 send 시 popup pickedMentions 기반으로 marker 재주입.\n const cleaned = stripMentionRefids(content)\n this.lines = this.lines.slice(0, idx)\n this.input = cleaned\n requestAnimationFrame(() => {\n const ta = this.renderRoot.querySelector('textarea') as HTMLTextAreaElement | null\n ta?.focus()\n ta?.setSelectionRange(cleaned.length, cleaned.length)\n })\n }\n\n /**\n * AI 응답 재생성 — 해당 assistant 메시지 + 이후 제거하고, 직전 user 메시지를 다시 send.\n */\n private async regenerateAssistant(idx: number) {\n if (this.busy) return\n // 직전 user 메시지 찾기\n let userIdx = idx - 1\n while (userIdx >= 0 && this.lines[userIdx].role !== 'user') userIdx--\n if (userIdx < 0) return\n // textarea 에는 marker 제거된 깨끗한 본문. send() 가 본문을 다시 line 으로 push 하는데,\n // pickedMentions 는 직전 send 에서 비워졌으므로 새 line 에는 marker 없음 — 클릭 시\n // runtime fallback resolver 가 처리. (영속된 원본 user 메시지의 marker 는 그대로 유지)\n const userContent = stripMentionRefids(this.lines[userIdx].content)\n // assistant 메시지 + 이후 제거\n this.lines = this.lines.slice(0, idx)\n this.input = userContent\n await this.send()\n }\n\n // ── Patch revert ───────────────────────────────────────────────\n\n private onRevertClick(patchId: string) {\n if (this.revertedPatchIds.has(patchId)) return\n this.revertedPatchIds = new Set([...this.revertedPatchIds, patchId])\n // 호스트 (board-modeller-page) 에게 revert 요청 — 호스트가 snapshot 복원 + 서버 mutation 호출\n this.dispatchEvent(\n new CustomEvent('board-edit-revert', {\n detail: { patchId },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private async send() {\n const text = this.input.trim()\n if (!text || this.busy) return\n\n // pickedMentions / pickedUserMentions 에서 현재 텍스트에 실제 #/@ token 으로\n // 등장하는 것만 추출 — 지운 토큰 제외. inline 마커로 content 에 주입.\n const mentions: Array<{ token: string; refid: number }> = []\n for (const [token, refid] of this.pickedMentions.entries()) {\n if (text.includes(`#${token}`)) mentions.push({ token, refid })\n }\n const userMentions: Array<{ token: string; userId: string }> = []\n for (const [token, userId] of this.pickedUserMentions.entries()) {\n if (text.includes(`@${token}`)) userMentions.push({ token, userId })\n }\n let enrichedContent = injectMentionRefids(text, mentions)\n enrichedContent = injectMentionUserIds(enrichedContent, userMentions)\n\n // optimistic 라인에 임시 핸들 — 방송 에코와의 reconcile 을 index 비의존으로.\n const userLocalId = `local-${++this._localSeq}-u`\n const asstLocalId = `local-${++this._localSeq}-a`\n\n this.lines = [\n ...this.lines,\n { role: 'user', content: enrichedContent, _localId: userLocalId }\n ]\n this.input = ''\n this.busy = true\n this.errorMessage = undefined\n\n // 가짜 pending 응답 표시 (UX 부드럽게)\n this.lines = [...this.lines, { role: 'assistant', content: '', pending: true, _localId: asstLocalId }]\n\n try {\n // LLM 으로 보낼 history — user/assistant 만 (system 은 백엔드가 자동 합류).\n // pending placeholder 와 system 은 제외 (index 비의존 — 방송 append 와 무관).\n const history = this.lines\n .filter(l => !l.pending && l._localId !== asstLocalId && l.role !== 'system')\n .map(l => ({ role: l.role, content: l.content }))\n\n // 라이브 보드 우선 — 호스트의 캔버스가 사용자 수작업 편집을 들고 있을 수 있음.\n // boardProvider 가 있으면 send 시점에 그것을 pull, 없으면 정적 currentBoard.\n const liveBoard = this.boardProvider ? this.boardProvider() : this.currentBoard\n\n // mentions 는 위에서 이미 계산해 user line 에 thread — 그대로 mutation 입력으로 재사용.\n const result = await client.mutate({\n mutation: BOARD_AI_CHAT_MUTATION,\n variables: {\n input: buildChatMutationInput({\n sessionId: this.sessionId,\n history,\n liveBoard,\n scopes: this.scopes,\n knownTypes: this.knownTypes,\n categories: this.categories,\n componentSchemas: this.componentSchemas,\n selectedRefids: this.selectedRefids ?? [],\n mentions\n })\n }\n })\n\n const out = result.data?.boardAIChat\n if (!out) throw new Error(i18next.t('board-ai.text.empty-response'))\n\n // 전송 성공 — picks 정리 (다음 메시지부터는 새 picks 만)\n this.pickedMentions.clear()\n this.pickedUserMentions.clear()\n\n // optimistic 라인을 서버 id 로 reconcile (방송 에코와 중복 방지, index 비의존).\n // 사용자 라인: enrich 된 본문 유지, id 만 부여. AI 라인: pending → 실제 응답.\n this._reconcileSentLine(userLocalId, out.userMessageId, {})\n this._reconcileSentLine(asstLocalId, out.assistantMessageId, {\n role: 'assistant',\n content: out.reply,\n patchSummary: out.patch?.summary,\n followUp: out.followUp || undefined,\n patchId: out.patchId || undefined,\n toolUsages: Array.isArray(out.toolUsages) ? out.toolUsages : undefined,\n pending: false\n })\n\n // 호스트로 patch 이벤트 전파\n if (out.patch) {\n this.dispatchEvent(\n new CustomEvent('board-edit-patch', {\n detail: {\n patch: out.patch,\n summary: out.patch.summary,\n confidence: out.patch.confidence,\n patchId: out.patchId,\n sessionId: out.sessionId\n },\n bubbles: true,\n composed: true\n })\n )\n }\n // 호스트로 ephemeral scene 조작 actions 전파 (selection / view / mode)\n if (Array.isArray(out.actions) && out.actions.length > 0) {\n this.dispatchEvent(\n new CustomEvent('board-action-execute', {\n detail: { actions: out.actions, sessionId: out.sessionId },\n bubbles: true,\n composed: true\n })\n )\n }\n if (out.followUp) {\n this.dispatchEvent(\n new CustomEvent('chat-followup', {\n detail: { question: out.followUp },\n bubbles: true,\n composed: true\n })\n )\n }\n } catch (e: any) {\n // pending(AI) placeholder 만 제거 — 사용자 라인은 보존(재시도). index 비의존.\n this.lines = this.lines.filter(l => l._localId !== asstLocalId)\n this.errorMessage = e?.message ?? String(e)\n this.lastFailedInput = text\n } finally {\n this.busy = false\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ox-board-ai-chat': OxBoardAIChat\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* board-ai-chat.ts 의 pure helper 회귀 방지.
|
|
3
|
+
*
|
|
4
|
+
* 핵심 보호:
|
|
5
|
+
* - GraphQL mutation input 에 selectedRefids 가 항상 포함되는지 — 누락이 사용자
|
|
6
|
+
* 보고된 "AI 가 선택된 컴포넌트를 모른다" 의 root cause 였다.
|
|
7
|
+
* - 식별자 정책 — refid (universal numeric) 가 selection 채널의 표준. id 는 데이터
|
|
8
|
+
* 바인딩 이름이며 unique 가 아니라 selection 에 부적합.
|
|
9
|
+
*/
|
|
10
|
+
import { buildChatMutationInput } from './chat-input-builder';
|
|
11
|
+
describe('buildChatMutationInput — mutation input 회귀 방지', () => {
|
|
12
|
+
test('selectedRefids 가 input 에 반드시 포함된다 (root cause regression)', () => {
|
|
13
|
+
const input = buildChatMutationInput({
|
|
14
|
+
sessionId: 'session-1',
|
|
15
|
+
history: [{ role: 'user', content: 'hi' }],
|
|
16
|
+
liveBoard: { width: 1000, height: 600, components: [] },
|
|
17
|
+
selectedRefids: [35]
|
|
18
|
+
});
|
|
19
|
+
expect(input).toHaveProperty('selectedRefids', [35]);
|
|
20
|
+
});
|
|
21
|
+
test('selectedRefids 빈 배열도 누락 없이 포함', () => {
|
|
22
|
+
const input = buildChatMutationInput({
|
|
23
|
+
history: [],
|
|
24
|
+
liveBoard: null,
|
|
25
|
+
selectedRefids: []
|
|
26
|
+
});
|
|
27
|
+
expect(input.selectedRefids).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
test('모든 expected 필드 존재', () => {
|
|
30
|
+
const input = buildChatMutationInput({
|
|
31
|
+
sessionId: 's1',
|
|
32
|
+
history: [{ role: 'user', content: 'hi' }],
|
|
33
|
+
liveBoard: { components: [] },
|
|
34
|
+
scopes: ['fmsim'],
|
|
35
|
+
knownTypes: ['rect'],
|
|
36
|
+
categories: ['equipment'],
|
|
37
|
+
componentSchemas: [{ type: 'rect' }],
|
|
38
|
+
selectedRefids: [1, 2]
|
|
39
|
+
});
|
|
40
|
+
expect(Object.keys(input).sort()).toEqual([
|
|
41
|
+
'categories',
|
|
42
|
+
'componentSchemas',
|
|
43
|
+
'currentBoard',
|
|
44
|
+
'knownTypes',
|
|
45
|
+
'messages',
|
|
46
|
+
'scopes',
|
|
47
|
+
'selectedRefids',
|
|
48
|
+
'sessionId'
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
test('sessionId 누락 → null 정규화', () => {
|
|
52
|
+
const input = buildChatMutationInput({
|
|
53
|
+
history: [],
|
|
54
|
+
liveBoard: null,
|
|
55
|
+
selectedRefids: []
|
|
56
|
+
});
|
|
57
|
+
expect(input.sessionId).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
test('liveBoard 누락 → null 정규화', () => {
|
|
60
|
+
const input = buildChatMutationInput({
|
|
61
|
+
history: [],
|
|
62
|
+
liveBoard: undefined,
|
|
63
|
+
selectedRefids: []
|
|
64
|
+
});
|
|
65
|
+
expect(input.currentBoard).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
test('messages 가 history 그대로', () => {
|
|
68
|
+
const history = [
|
|
69
|
+
{ role: 'user', content: 'msg1' },
|
|
70
|
+
{ role: 'assistant', content: 'reply1' }
|
|
71
|
+
];
|
|
72
|
+
const input = buildChatMutationInput({
|
|
73
|
+
history,
|
|
74
|
+
liveBoard: null,
|
|
75
|
+
selectedRefids: []
|
|
76
|
+
});
|
|
77
|
+
expect(input.messages).toBe(history);
|
|
78
|
+
});
|
|
79
|
+
test('liveBoard 가 그대로 currentBoard 로 매핑', () => {
|
|
80
|
+
const board = {
|
|
81
|
+
width: 1920,
|
|
82
|
+
height: 1080,
|
|
83
|
+
components: [{ refid: 1, type: 'rect' }]
|
|
84
|
+
};
|
|
85
|
+
const input = buildChatMutationInput({
|
|
86
|
+
history: [],
|
|
87
|
+
liveBoard: board,
|
|
88
|
+
selectedRefids: []
|
|
89
|
+
});
|
|
90
|
+
expect(input.currentBoard).toBe(board);
|
|
91
|
+
});
|
|
92
|
+
test('scopes / knownTypes / categories 누락 시 undefined', () => {
|
|
93
|
+
const input = buildChatMutationInput({
|
|
94
|
+
history: [],
|
|
95
|
+
liveBoard: null,
|
|
96
|
+
selectedRefids: []
|
|
97
|
+
});
|
|
98
|
+
expect(input.scopes).toBeUndefined();
|
|
99
|
+
expect(input.knownTypes).toBeUndefined();
|
|
100
|
+
expect(input.categories).toBeUndefined();
|
|
101
|
+
expect(input.componentSchemas).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
test('selectedRefids 다중 항목 (number 배열, stringify 없음)', () => {
|
|
104
|
+
const input = buildChatMutationInput({
|
|
105
|
+
history: [],
|
|
106
|
+
liveBoard: null,
|
|
107
|
+
selectedRefids: [35, 127, 4096]
|
|
108
|
+
});
|
|
109
|
+
expect(input.selectedRefids).toEqual([35, 127, 4096]);
|
|
110
|
+
});
|
|
111
|
+
test('mentions (popup picks) — 빈 배열은 input 에 안 들어감 (server 부담 X)', () => {
|
|
112
|
+
const input = buildChatMutationInput({
|
|
113
|
+
history: [],
|
|
114
|
+
liveBoard: null,
|
|
115
|
+
selectedRefids: [],
|
|
116
|
+
mentions: []
|
|
117
|
+
});
|
|
118
|
+
expect(input.mentions).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
test('mentions — 항목 있으면 input 에 그대로 통과', () => {
|
|
121
|
+
const input = buildChatMutationInput({
|
|
122
|
+
history: [{ role: 'user', content: '#경광등1 빨갛게' }],
|
|
123
|
+
liveBoard: null,
|
|
124
|
+
selectedRefids: [],
|
|
125
|
+
mentions: [{ token: '경광등1', refid: 35 }]
|
|
126
|
+
});
|
|
127
|
+
expect(input.mentions).toEqual([{ token: '경광등1', refid: 35 }]);
|
|
128
|
+
});
|
|
129
|
+
test('mentions 미지정 시 input 에 키 자체가 없음 (backwards compat)', () => {
|
|
130
|
+
const input = buildChatMutationInput({
|
|
131
|
+
history: [],
|
|
132
|
+
liveBoard: null,
|
|
133
|
+
selectedRefids: []
|
|
134
|
+
});
|
|
135
|
+
expect('mentions' in input).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
//# sourceMappingURL=board-ai-chat.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-ai-chat.test.js","sourceRoot":"","sources":["../../../client/components/board-ai-chat.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * board-ai-chat.ts 의 pure helper 회귀 방지.\n *\n * 핵심 보호:\n * - GraphQL mutation input 에 selectedRefids 가 항상 포함되는지 — 누락이 사용자\n * 보고된 \"AI 가 선택된 컴포넌트를 모른다\" 의 root cause 였다.\n * - 식별자 정책 — refid (universal numeric) 가 selection 채널의 표준. id 는 데이터\n * 바인딩 이름이며 unique 가 아니라 selection 에 부적합.\n */\nimport { buildChatMutationInput } from './chat-input-builder'\n\ndescribe('buildChatMutationInput — mutation input 회귀 방지', () => {\n test('selectedRefids 가 input 에 반드시 포함된다 (root cause regression)', () => {\n const input = buildChatMutationInput({\n sessionId: 'session-1',\n history: [{ role: 'user', content: 'hi' }],\n liveBoard: { width: 1000, height: 600, components: [] },\n selectedRefids: [35]\n })\n expect(input).toHaveProperty('selectedRefids', [35])\n })\n\n test('selectedRefids 빈 배열도 누락 없이 포함', () => {\n const input = buildChatMutationInput({\n history: [],\n liveBoard: null,\n selectedRefids: []\n })\n expect(input.selectedRefids).toEqual([])\n })\n\n test('모든 expected 필드 존재', () => {\n const input = buildChatMutationInput({\n sessionId: 's1',\n history: [{ role: 'user', content: 'hi' }],\n liveBoard: { components: [] },\n scopes: ['fmsim'],\n knownTypes: ['rect'],\n categories: ['equipment'],\n componentSchemas: [{ type: 'rect' }],\n selectedRefids: [1, 2]\n })\n expect(Object.keys(input).sort()).toEqual([\n 'categories',\n 'componentSchemas',\n 'currentBoard',\n 'knownTypes',\n 'messages',\n 'scopes',\n 'selectedRefids',\n 'sessionId'\n ])\n })\n\n test('sessionId 누락 → null 정규화', () => {\n const input = buildChatMutationInput({\n history: [],\n liveBoard: null,\n selectedRefids: []\n })\n expect(input.sessionId).toBeNull()\n })\n\n test('liveBoard 누락 → null 정규화', () => {\n const input = buildChatMutationInput({\n history: [],\n liveBoard: undefined,\n selectedRefids: []\n })\n expect(input.currentBoard).toBeNull()\n })\n\n test('messages 가 history 그대로', () => {\n const history = [\n { role: 'user' as const, content: 'msg1' },\n { role: 'assistant' as const, content: 'reply1' }\n ]\n const input = buildChatMutationInput({\n history,\n liveBoard: null,\n selectedRefids: []\n })\n expect(input.messages).toBe(history)\n })\n\n test('liveBoard 가 그대로 currentBoard 로 매핑', () => {\n const board = {\n width: 1920,\n height: 1080,\n components: [{ refid: 1, type: 'rect' }]\n }\n const input = buildChatMutationInput({\n history: [],\n liveBoard: board,\n selectedRefids: []\n })\n expect(input.currentBoard).toBe(board)\n })\n\n test('scopes / knownTypes / categories 누락 시 undefined', () => {\n const input = buildChatMutationInput({\n history: [],\n liveBoard: null,\n selectedRefids: []\n })\n expect(input.scopes).toBeUndefined()\n expect(input.knownTypes).toBeUndefined()\n expect(input.categories).toBeUndefined()\n expect(input.componentSchemas).toBeUndefined()\n })\n\n test('selectedRefids 다중 항목 (number 배열, stringify 없음)', () => {\n const input = buildChatMutationInput({\n history: [],\n liveBoard: null,\n selectedRefids: [35, 127, 4096]\n })\n expect(input.selectedRefids).toEqual([35, 127, 4096])\n })\n\n test('mentions (popup picks) — 빈 배열은 input 에 안 들어감 (server 부담 X)', () => {\n const input = buildChatMutationInput({\n history: [],\n liveBoard: null,\n selectedRefids: [],\n mentions: []\n })\n expect(input.mentions).toBeUndefined()\n })\n\n test('mentions — 항목 있으면 input 에 그대로 통과', () => {\n const input = buildChatMutationInput({\n history: [{ role: 'user', content: '#경광등1 빨갛게' }],\n liveBoard: null,\n selectedRefids: [],\n mentions: [{ token: '경광등1', refid: 35 }]\n })\n expect(input.mentions).toEqual([{ token: '경광등1', refid: 35 }])\n })\n\n test('mentions 미지정 시 input 에 키 자체가 없음 (backwards compat)', () => {\n const input = buildChatMutationInput({\n history: [],\n liveBoard: null,\n selectedRefids: []\n })\n expect('mentions' in input).toBe(false)\n })\n})\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** dedup 에 필요한 최소 라인 형태 (board-ai-chat 의 ChatLine 부분집합). */
|
|
2
|
+
export interface DedupLine {
|
|
3
|
+
id?: string;
|
|
4
|
+
_localId?: string;
|
|
5
|
+
role: string;
|
|
6
|
+
content: string;
|
|
7
|
+
senderId?: string;
|
|
8
|
+
senderName?: string;
|
|
9
|
+
[k: string]: any;
|
|
10
|
+
}
|
|
11
|
+
/** 서버 방송 메시지(필요 필드만). */
|
|
12
|
+
export interface RemoteMessage {
|
|
13
|
+
id?: string;
|
|
14
|
+
role: string;
|
|
15
|
+
content: string;
|
|
16
|
+
relatedPatchId?: string;
|
|
17
|
+
toolUsagesJson?: any;
|
|
18
|
+
creator?: {
|
|
19
|
+
id?: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
} | null;
|
|
22
|
+
}
|
|
23
|
+
/** 방송 메시지 → 라인. */
|
|
24
|
+
export declare function remoteMessageToLine(m: RemoteMessage): DedupLine;
|
|
25
|
+
/**
|
|
26
|
+
* 원격(방송) 메시지 수신 결과를 계산.
|
|
27
|
+
* - id 없음 / 이미 seen → 변화 없음(null).
|
|
28
|
+
* - 신규 → seen 에 추가 + 라인 append.
|
|
29
|
+
* 반환: 변경된 { lines, seen } (불변 — 새 배열/셋) 또는 null(무시).
|
|
30
|
+
*/
|
|
31
|
+
export declare function receiveRemoteMessage(lines: DedupLine[], seen: Set<string>, m: RemoteMessage | undefined | null): {
|
|
32
|
+
lines: DedupLine[];
|
|
33
|
+
seen: Set<string>;
|
|
34
|
+
} | null;
|
|
35
|
+
/**
|
|
36
|
+
* 전송한 optimistic 라인(_localId)을 서버 id 로 reconcile.
|
|
37
|
+
* - 에코가 먼저 도착해 serverId 가 이미 seen → optimistic 중복 라인 제거.
|
|
38
|
+
* - 아니면 serverId 부여(+ seen 등록) + patch 병합 + _localId 해제.
|
|
39
|
+
* - 해당 _localId 라인이 없으면 변화 없음.
|
|
40
|
+
* 반환: 새 { lines, seen } (불변).
|
|
41
|
+
*/
|
|
42
|
+
export declare function reconcileSentLine(lines: DedupLine[], seen: Set<string>, localId: string, serverId: string | undefined, patch: Partial<DedupLine>): {
|
|
43
|
+
lines: DedupLine[];
|
|
44
|
+
seen: Set<string>;
|
|
45
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 그룹챗 메시지 에코 dedup — 순수 로직.
|
|
3
|
+
*
|
|
4
|
+
* 실시간 방송은 자기 발신 메시지도 되돌려준다(에코). 또 pubsub publish 가 mutation 응답보다
|
|
5
|
+
* 먼저 도착할 수 있어(레이스) optimistic 라인과 방송 라인이 겹칠 수 있다. 이 모듈은 그 조정을
|
|
6
|
+
* **id 기반**으로 결정한다 (서버가 멘션 마커로 content 를 enrich 하므로 content 매칭은 불가).
|
|
7
|
+
*
|
|
8
|
+
* 컴포넌트 상태(this.lines / this._seenMessageIds)에서 분리 → 단위테스트 가능.
|
|
9
|
+
* (C2 의 patch-apply-gate.ts 와 동일한 "순수 게이트" 컨벤션.)
|
|
10
|
+
*/
|
|
11
|
+
/** 방송 메시지 → 라인. */
|
|
12
|
+
export function remoteMessageToLine(m) {
|
|
13
|
+
return {
|
|
14
|
+
id: m.id,
|
|
15
|
+
role: m.role,
|
|
16
|
+
content: m.content,
|
|
17
|
+
patchId: m.relatedPatchId ?? undefined,
|
|
18
|
+
toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,
|
|
19
|
+
senderId: m.creator?.id ?? undefined,
|
|
20
|
+
senderName: m.creator?.name ?? undefined
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 원격(방송) 메시지 수신 결과를 계산.
|
|
25
|
+
* - id 없음 / 이미 seen → 변화 없음(null).
|
|
26
|
+
* - 신규 → seen 에 추가 + 라인 append.
|
|
27
|
+
* 반환: 변경된 { lines, seen } (불변 — 새 배열/셋) 또는 null(무시).
|
|
28
|
+
*/
|
|
29
|
+
export function receiveRemoteMessage(lines, seen, m) {
|
|
30
|
+
if (!m || !m.id)
|
|
31
|
+
return null;
|
|
32
|
+
if (seen.has(m.id))
|
|
33
|
+
return null;
|
|
34
|
+
const nextSeen = new Set(seen);
|
|
35
|
+
nextSeen.add(m.id);
|
|
36
|
+
return { lines: [...lines, remoteMessageToLine(m)], seen: nextSeen };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 전송한 optimistic 라인(_localId)을 서버 id 로 reconcile.
|
|
40
|
+
* - 에코가 먼저 도착해 serverId 가 이미 seen → optimistic 중복 라인 제거.
|
|
41
|
+
* - 아니면 serverId 부여(+ seen 등록) + patch 병합 + _localId 해제.
|
|
42
|
+
* - 해당 _localId 라인이 없으면 변화 없음.
|
|
43
|
+
* 반환: 새 { lines, seen } (불변).
|
|
44
|
+
*/
|
|
45
|
+
export function reconcileSentLine(lines, seen, localId, serverId, patch) {
|
|
46
|
+
const idx = lines.findIndex(l => l._localId === localId);
|
|
47
|
+
if (idx < 0)
|
|
48
|
+
return { lines, seen };
|
|
49
|
+
const nextLines = [...lines];
|
|
50
|
+
if (serverId && seen.has(serverId)) {
|
|
51
|
+
nextLines.splice(idx, 1);
|
|
52
|
+
return { lines: nextLines, seen };
|
|
53
|
+
}
|
|
54
|
+
const nextSeen = serverId ? new Set(seen).add(serverId) : seen;
|
|
55
|
+
nextLines[idx] = { ...nextLines[idx], ...patch, id: serverId ?? nextLines[idx].id, _localId: undefined };
|
|
56
|
+
return { lines: nextLines, seen: nextSeen };
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=chat-echo-dedup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-echo-dedup.js","sourceRoot":"","sources":["../../../client/components/chat-echo-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAuBH,mBAAmB;AACnB,MAAM,UAAU,mBAAmB,CAAC,CAAgB;IAClD,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,OAAO,EAAE,CAAC,CAAC,cAAc,IAAI,SAAS;QACtC,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;QAC1E,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,IAAI,SAAS;QACpC,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,SAAS;KACzC,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAkB,EAClB,IAAiB,EACjB,CAAmC;IAEnC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;QAAE,OAAO,IAAI,CAAA;IAC5B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAA;IAC9B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAClB,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;AACtE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAkB,EAClB,IAAiB,EACjB,OAAe,EACf,QAA4B,EAC5B,KAAyB;IAEzB,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAA;IACxD,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAEnC,MAAM,SAAS,GAAG,CAAC,GAAG,KAAK,CAAC,CAAA;IAC5B,IAAI,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACxB,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAA;IACnC,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC9D,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,QAAQ,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAA;IACxG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;AAC7C,CAAC","sourcesContent":["/*\n * 그룹챗 메시지 에코 dedup — 순수 로직.\n *\n * 실시간 방송은 자기 발신 메시지도 되돌려준다(에코). 또 pubsub publish 가 mutation 응답보다\n * 먼저 도착할 수 있어(레이스) optimistic 라인과 방송 라인이 겹칠 수 있다. 이 모듈은 그 조정을\n * **id 기반**으로 결정한다 (서버가 멘션 마커로 content 를 enrich 하므로 content 매칭은 불가).\n *\n * 컴포넌트 상태(this.lines / this._seenMessageIds)에서 분리 → 단위테스트 가능.\n * (C2 의 patch-apply-gate.ts 와 동일한 \"순수 게이트\" 컨벤션.)\n */\n\n/** dedup 에 필요한 최소 라인 형태 (board-ai-chat 의 ChatLine 부분집합). */\nexport interface DedupLine {\n id?: string\n _localId?: string\n role: string\n content: string\n senderId?: string\n senderName?: string\n [k: string]: any\n}\n\n/** 서버 방송 메시지(필요 필드만). */\nexport interface RemoteMessage {\n id?: string\n role: string\n content: string\n relatedPatchId?: string\n toolUsagesJson?: any\n creator?: { id?: string; name?: string } | null\n}\n\n/** 방송 메시지 → 라인. */\nexport function remoteMessageToLine(m: RemoteMessage): DedupLine {\n return {\n id: m.id,\n role: m.role,\n content: m.content,\n patchId: m.relatedPatchId ?? undefined,\n toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,\n senderId: m.creator?.id ?? undefined,\n senderName: m.creator?.name ?? undefined\n }\n}\n\n/**\n * 원격(방송) 메시지 수신 결과를 계산.\n * - id 없음 / 이미 seen → 변화 없음(null).\n * - 신규 → seen 에 추가 + 라인 append.\n * 반환: 변경된 { lines, seen } (불변 — 새 배열/셋) 또는 null(무시).\n */\nexport function receiveRemoteMessage(\n lines: DedupLine[],\n seen: Set<string>,\n m: RemoteMessage | undefined | null\n): { lines: DedupLine[]; seen: Set<string> } | null {\n if (!m || !m.id) return null\n if (seen.has(m.id)) return null\n const nextSeen = new Set(seen)\n nextSeen.add(m.id)\n return { lines: [...lines, remoteMessageToLine(m)], seen: nextSeen }\n}\n\n/**\n * 전송한 optimistic 라인(_localId)을 서버 id 로 reconcile.\n * - 에코가 먼저 도착해 serverId 가 이미 seen → optimistic 중복 라인 제거.\n * - 아니면 serverId 부여(+ seen 등록) + patch 병합 + _localId 해제.\n * - 해당 _localId 라인이 없으면 변화 없음.\n * 반환: 새 { lines, seen } (불변).\n */\nexport function reconcileSentLine(\n lines: DedupLine[],\n seen: Set<string>,\n localId: string,\n serverId: string | undefined,\n patch: Partial<DedupLine>\n): { lines: DedupLine[]; seen: Set<string> } {\n const idx = lines.findIndex(l => l._localId === localId)\n if (idx < 0) return { lines, seen }\n\n const nextLines = [...lines]\n if (serverId && seen.has(serverId)) {\n nextLines.splice(idx, 1)\n return { lines: nextLines, seen }\n }\n const nextSeen = serverId ? new Set(seen).add(serverId) : seen\n nextLines[idx] = { ...nextLines[idx], ...patch, id: serverId ?? nextLines[idx].id, _localId: undefined }\n return { lines: nextLines, seen: nextSeen }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|