@things-factory/ai-assistant 10.1.13 → 10.1.16
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/client/components/{board-ai-chat.ts → assistant-chat.ts} +589 -18
- package/client/components/chat-defaults.ts +24 -0
- package/client/components/chat-input-builder.ts +1 -1
- package/client/index.ts +2 -3
- package/dist-client/components/{board-ai-chat.d.ts → assistant-chat.d.ts} +39 -3
- package/dist-client/components/{board-ai-chat.js → assistant-chat.js} +597 -66
- package/dist-client/components/assistant-chat.js.map +1 -0
- package/dist-client/components/{board-ai-chat.test.js → assistant-chat.test.js} +1 -1
- package/dist-client/components/assistant-chat.test.js.map +1 -0
- package/dist-client/components/chat-defaults.d.ts +22 -0
- package/dist-client/components/chat-defaults.js +4 -0
- package/dist-client/components/chat-defaults.js.map +1 -1
- package/dist-client/components/chat-input-builder.js +1 -1
- package/dist-client/components/chat-input-builder.js.map +1 -1
- package/dist-client/index.d.ts +2 -3
- package/dist-client/index.js +2 -3
- package/dist-client/index.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/assistant-chat-resolver.js +12 -0
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/server/service/assistant-chat-resolver.ts +13 -0
- package/test/assistant-request-context.test.ts +1 -1
- package/test/assistant-session-toolbar.test.ts +1 -1
- package/translations/en.json +6 -1
- package/translations/ko.json +6 -1
- package/dist-client/components/board-ai-chat.js.map +0 -1
- package/dist-client/components/board-ai-chat.test.js.map +0 -1
- /package/client/components/{board-ai-chat.test.ts → assistant-chat.test.ts} +0 -0
- /package/dist-client/components/{board-ai-chat.test.d.ts → assistant-chat.test.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/ai-assistant",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.16",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"things-factory": true,
|
|
6
6
|
"author": "heartyoh",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"@operato/i18n": "^10.0.0",
|
|
31
31
|
"@operato/styles": "^10.0.0",
|
|
32
32
|
"@things-factory/ai-client-base": "^10.1.8",
|
|
33
|
-
"@things-factory/auth-base": "^10.1.
|
|
33
|
+
"@things-factory/auth-base": "^10.1.16",
|
|
34
34
|
"@things-factory/env": "^10.1.3",
|
|
35
|
-
"@things-factory/shell": "^10.1.
|
|
35
|
+
"@things-factory/shell": "^10.1.16",
|
|
36
36
|
"dompurify": "^3.0.0",
|
|
37
37
|
"graphql-scalars": "^1.22.0",
|
|
38
38
|
"graphql-tag": "^2.12.6",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"copyfiles": "^2.4.1",
|
|
45
45
|
"rimraf": "^5.0.0"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "aff86bc162b42c1d933711f7bc85a073f1ddcd29"
|
|
48
48
|
}
|
|
@@ -274,6 +274,19 @@ export class AssistantChatResolver {
|
|
|
274
274
|
)
|
|
275
275
|
: input.messages.map(message => ({ role: message.role as any, content: message.content }))
|
|
276
276
|
|
|
277
|
+
if (input.hostContext?.image?.data && conversation.length > 0) {
|
|
278
|
+
const last = conversation[conversation.length - 1]
|
|
279
|
+
if (last.role === 'user' && typeof last.content === 'string') {
|
|
280
|
+
const raw = input.hostContext.image
|
|
281
|
+
const rawData = typeof raw.data === 'string' ? raw.data.replace(/^data:image\/[a-zA-Z]+;base64,/, '') : raw.data
|
|
282
|
+
const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)
|
|
283
|
+
last.content = [
|
|
284
|
+
{ type: 'text', text: last.content },
|
|
285
|
+
{ type: 'image', source: { kind: 'base64', data: buf, mediaType: raw.mediaType || 'image/png' } }
|
|
286
|
+
] as any
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
277
290
|
const result = await registryAssistantChat({
|
|
278
291
|
client,
|
|
279
292
|
messages: conversation as any,
|
|
@@ -21,7 +21,7 @@ test('recent tabs retain the selected older conversation without mutating the in
|
|
|
21
21
|
assert.deepEqual(assistantRecentSessions([], '', 5), [])
|
|
22
22
|
})
|
|
23
23
|
test('send captures current context before optimistic input and attaches request base to edit events', () => {
|
|
24
|
-
const chat = readFileSync(new URL('../client/components/
|
|
24
|
+
const chat = readFileSync(new URL('../client/components/assistant-chat.ts', import.meta.url), 'utf8')
|
|
25
25
|
const send = chat.slice(chat.indexOf('private async send()'))
|
|
26
26
|
assert.ok(send.indexOf('this.contextProvider?.()') < send.indexOf('// optimistic'))
|
|
27
27
|
assert.match(send, /modelFingerprint: assistantModelFingerprint\(liveBoard\)/)
|
|
@@ -19,7 +19,7 @@ test('busy/error/temporary states and accessible controls are explicit', () => {
|
|
|
19
19
|
assert.match(toolbar, /role="status"/)
|
|
20
20
|
})
|
|
21
21
|
test('legacy Board chat forwards shared controls through existing host events', () => {
|
|
22
|
-
const chat = read('../client/components/
|
|
22
|
+
const chat = read('../client/components/assistant-chat.ts')
|
|
23
23
|
assert.match(chat, /import '\.\/assistant-session-toolbar\.js'/)
|
|
24
24
|
assert.match(chat, /<ox-assistant-session-toolbar/)
|
|
25
25
|
assert.match(chat, /event\.stopPropagation\(\); this\._onSessionTabClick/)
|
package/translations/en.json
CHANGED
|
@@ -61,5 +61,10 @@
|
|
|
61
61
|
"ai-assistant.button.run-proposal": "run",
|
|
62
62
|
"ai-assistant.text.proposal-sent": "execution requested",
|
|
63
63
|
"ai-assistant.text.history-folded": "this conversation grew long — the earlier {count} messages were left out. a new conversation is better for a different topic.",
|
|
64
|
-
"ai-assistant.text.history-folded-summarized": "this conversation grew long — the earlier {count} messages were folded into a summary. a new conversation is better for a different topic."
|
|
64
|
+
"ai-assistant.text.history-folded-summarized": "this conversation grew long — the earlier {count} messages were folded into a summary. a new conversation is better for a different topic.",
|
|
65
|
+
"ai-assistant.button.attach": "Attach",
|
|
66
|
+
"ai-assistant.tooltip.attach-file": "Attach images or files (drag & drop or paste supported)",
|
|
67
|
+
"ai-assistant.text.drop-files-here": "Drop images or files here",
|
|
68
|
+
"ai-assistant.button.remove-attachment": "Remove attachment",
|
|
69
|
+
"ai-assistant.session.all": "All History"
|
|
65
70
|
}
|
package/translations/ko.json
CHANGED
|
@@ -61,5 +61,10 @@
|
|
|
61
61
|
"ai-assistant.button.edit": "편집",
|
|
62
62
|
"ai-assistant.text.copied": "복사됨",
|
|
63
63
|
"ai-assistant.text.mention-not-found": "#{token} 을(를) 찾을 수 없습니다",
|
|
64
|
-
"ai-assistant.text.session-tab-label": "세션 {n}"
|
|
64
|
+
"ai-assistant.text.session-tab-label": "세션 {n}",
|
|
65
|
+
"ai-assistant.button.attach": "첨부",
|
|
66
|
+
"ai-assistant.tooltip.attach-file": "이미지 또는 파일 첨부 (클립보드 붙여넣기·드래그앤드롭 지원)",
|
|
67
|
+
"ai-assistant.text.drop-files-here": "여기에 이미지나 파일을 놓으세요",
|
|
68
|
+
"ai-assistant.button.remove-attachment": "첨부 삭제",
|
|
69
|
+
"ai-assistant.session.all": "전체 이력"
|
|
65
70
|
}
|
|
@@ -1 +0,0 @@
|
|
|
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'\nimport './assistant-session-toolbar.js'\nimport { assistantAutoAskKey, assistantModelFingerprint, type AssistantRequestContext } from '../utils/assistant-request-context.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 { buildAssistantChatInput, 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'\n/*\n * 기본 카탈로그·슬래시 목록은 **호스트가 넣는다** (2026-09-10).\n *\n * 여기서 `./default-catalog-entries.js` 를 직접 import 하고 있었다. 그 두 파일은 보드 저작의\n * 목록이고, 그래서 이 대화면이 보드 편집기 없이는 설 수 없었다 — 이 컴포넌트가 중립 패키지로\n * 오지 못한 유일한 이유가 그 두 줄이었다.\n *\n * 등록하는 쪽이 주인이라는 규약을 따른다(`registerToolCategory` 와 같은 모양): `board-ai` 를\n * import 하면 그 패키지가 자기 기본값을 넣고, 안 하면 비어 있다. 보드가 없는 제품에 보드 목록이\n * 뜨지 않는 것이 맞다.\n */\nimport { chatDefaults } from './chat-defaults.js'\nimport { availableTriggers, type TriggerChip } from './chat-triggers.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 /** 보낸 사용자 email/name — 그룹챗 발신자 표시 + 자기 여부 판정.\n * **email 로 판정한다**: 로그인 식별자라 비어 있지 않고, 시스템 내부 관리키(User.id)를\n * 클라이언트 판정에 끌어들이지 않는다(관리키 노출이 계약처럼 굳는 것을 피한다). */\n senderEmail?: 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<{\n name: string\n arguments: any\n result: any\n kind: 'read' | 'write' | 'unknown'\n /** 몇 번째 LLM 턴 — 단계로 읽기 위한 값. */\n iter?: number\n /** 이 호출이 어떻게 끝났는가 — 배지. */\n outcome?: 'ok' | 'rejected' | 'queued' | 'proposed' | 'folded' | 'error'\n }>\n /** UI 상태: tool usage 박스 펼침 여부 (assistant 메시지마다 독립). */\n toolUsagesOpen?: boolean\n /** UI 상태: 접힌 시스템 기록을 펼쳤는가(곁줄이 대화를 가리지 않게 기본은 3줄 접기). */\n systemOpen?: boolean\n /**\n * 조치 제안 — AI 가 **실행하지 않고** 제안한 것들. 사용자가 버튼을 눌러야 실행된다.\n *\n * 영속하지 않는다(이번 응답에만 붙는다): 승인은 **지금** 하는 판단이고, 되돌릴 수 없는 조치를\n * 지난 맥락으로 다시 권하는 것은 위험하다. 다시 필요하면 다시 물어보면 된다.\n */\n proposals?: any[]\n /**\n * 접지 경고 — 이 답이 언급했으나 모델이 받은 근거(프롬프트·보드 문맥·이력·도구 결과)에 없던 식별자.\n * 있으면 그 대상은 만들어 낸 것일 수 있다는 주의를 답 아래에 표시한다(답 자체는 그대로 보여준다).\n */\n groundingWarnings?: string[]\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 proposals\n groundingWarnings\n historyFolded\n }\n }\n`\n\n/*\n * The neutral door. Selected by `chatEndpoint`, and it asks for fewer fields because there is\n * less to ask for: no patch, no scene actions, no patch id — this surface has no document.\n *\n * `offeredTools` has no counterpart on the board side. It is what the server actually put in\n * front of the model this turn, so a surface that granted a category and sees it missing knows\n * the registration never happened, instead of reading a vague answer as a refusal.\n */\nconst ASSISTANT_CHAT_MUTATION = gql`\n mutation AssistantChat($input: AssistantChatInput!) {\n assistantChat(input: $input) {\n reply\n clientId\n sessionId\n userMessageId\n assistantMessageId\n toolUsages\n offeredTools\n proposals\n groundingWarnings\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 groundingWarningsJson\n createdAt\n creator {\n name\n email\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 groundingWarningsJson\n createdAt\n creator {\n name\n email\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 @property({ attribute: false }) prepareSession?: (adopt: (id: string) => void) => Promise<string | undefined>\n private adoptingSession = false\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 /**\n * 이 대화면이 쓸 수 있는 **외부 도구 카테고리** 화이트리스트(registerToolCategory 이름).\n * 미지정이면 등록된 전부(현행 동작) — 보드 모델러는 지정하지 않는다.\n * 성격이 다른 대화면(예: 트윈 운영 도크)이 서로의 능력을 갖지 않게 하는 경계.\n */\n @property({ type: Array, attribute: false })\n toolCategories?: string[]\n\n /**\n * 이 대화면이 무엇을 보고 있는지 — 서버 도구가 `ctx.state.host` 로 읽는다(예: `{ spaceId }`).\n * 없으면 도구가 대상을 몰라 모델이 사용자에게 식별자를 되묻는다.\n */\n @property({ type: Object, attribute: false })\n hostContext?: any\n\n /** 코어 보드 편집 도구 사용 여부. 기본 true. 보드를 고칠 이유가 없는 면에서 false. */\n @property({ type: Boolean, attribute: 'board-tools' })\n boardTools = true\n\n /*\n * Which server operation this surface talks to. A closed set of two, named after the operations\n * themselves so a reader can grep either way.\n *\n * boardAIChat board authoring — lives in @things-factory/board-ai, carries the board model,\n * returns patches and scene actions\n * assistantChat a surface with no document of its own — lives here, runs registered tools only\n *\n * Default stays `boardAIChat`: every existing consumer is a board surface, and a default that\n * changed under them would move their conversation to a door with no board tools behind it.\n *\n * A host that has no board must pass `assistantChat` **and** `systemPrompt`. Until it does, the\n * chat calls a mutation that lives in a package it may not even load — which is how\n * operato-figure got `Cannot query field \"boardAIChat\" on type \"Mutation\"` (2026-09-11).\n */\n @property({ type: String, attribute: 'chat-endpoint' })\n chatEndpoint: 'boardAIChat' | 'assistantChat' = 'boardAIChat'\n\n /**\n * Who the assistant is on this surface, in the host's words. Required by `assistantChat`; the\n * board conversation carries its own persona on the server and ignores this.\n */\n @property({ type: String, attribute: 'system-prompt' })\n systemPrompt?: string\n\n /**\n * 라이브 상태를 다루는 대화면인가 — true 면 **첫 턴에 도구 호출을 강제**한다.\n *\n * 접지 가드는 지어낸 식별자를 잡지만 식별자 없는 상황 서술(\"바쁘게 움직이고 있다\")은 잡지 못한다.\n * 그건 도구를 부르지 않고 답한 경우이므로, 조회를 먼저 하게 만들어 원천을 막는다.\n */\n @property({ type: Boolean, attribute: 'require-grounding-tools' })\n requireGroundingTools = false\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 /**\n * 밖에서 질문을 넣어 **자동으로 보내게** 하는 진입점.\n *\n * 오류 토스트의 \"AI에게 묻기\" 처럼, 사용자가 타이핑하지 않고 곧바로 답을 받아야 하는 경로가 있다.\n * 값이 들어오면 그 문장을 입력으로 삼아 한 번 전송하고 비운다(같은 질문이 반복 전송되지 않게).\n */\n @property({ attribute: false })\n autoAsk?: string\n @property() autoAskRequestId?: string\n @property({ attribute: false }) contextProvider?: () => AssistantRequestContext\n\n /**\n * 빈 화면의 제목·부제 — **호스트가 자기 말로 준다.**\n *\n * ── 왜 열었나 (2026-09-11) ────────────────────────────────────────────────\n * 이 값이 `board-ai.text.empty-title` 로 고정돼 있었다. 그래서 figure 저작면에서 도크를 열면\n * 도형을 편집하는 사람에게 **「보드를 만들라」고 가르쳤다** — 「Monitoring dashboard with 3\n * gauges」 같은 예시까지 함께.\n *\n * 빈 화면은 그 화면이 무엇인지 가르치는 자리다. 거기서 틀리면 처음 여는 사람이 이 도구를\n * 오해한다. 도구 쪽(`toolCategories`·`boardTools`)은 이미 중립이었고 **글만 안 열려 있었다.**\n *\n * ⚠ **키가 아니라 번역된 글을 받는다.** 호스트마다 자기 접두 아래 키를 두므로(figure 는\n * `figure.text.…`), 키를 받으면 이 컴포넌트가 남의 접두를 알아야 한다.\n *\n * ⚠ 안 주면 **지금까지의 보드 문구**가 나온다 — 거동을 바꾸지 않으려고 그렇게 뒀다. 즉 보드가\n * 아닌 호스트가 이 값을 잊으면 틀린 글이 조용히 뜬다. 기본값을 중립으로 바꾸는 것은\n * board-ui·twin 이 자기 글을 넘기게 하는 별 건이다(그때 이 주석을 지운다).\n */\n @property({ type: Object, attribute: false }) intro?: { title: string; subtitle: string }\n\n /**\n * 빈 화면의 예시 — 호스트가 자기 것을 준다. 안 주면 보드 예시가 나온다(위 ⚠ 와 같은 이유).\n *\n * 예시는 **그 도구로 실제로 할 수 있는 말**이어야 한다. 할 수 없는 것을 예시로 두면 빈 화면이\n * 거짓을 가르친다.\n */\n @property({ type: Array, attribute: false }) examples?: Array<{ label: string; items: string[] }>\n\n /** autoAsk 를 이미 보냈는지 — 재렌더마다 다시 보내지 않기 위한 표식. */\n private _autoAsked?: string\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 /** 원본 JSON 을 펼친 단계(라인:단계) — 목록은 한 줄 요약으로 훑고 필요한 것만 펼친다. */\n @state()\n private openToolSteps = new Set<string>()\n\n /** 이미 실행을 요청한 제안 키 — 같은 카드를 두 번 눌러 되돌릴 수 없는 명령이 두 번 나가지 않게. */\n @state()\n private sentProposals = new Set<string>()\n\n /**\n * 이 대화가 길어져 앞부분이 프롬프트에서 접혔다는 **사실**(서버 보고). 주제 이탈을 판정한 것이\n * 아니다 — 판정하면 오탐이 생기고, 오탐이 생기면 사용자는 안내 자체를 무시한다.\n * 사실만 알리고 \"새 대화로 시작할지\" 는 사용자가 정한다.\n */\n @state()\n private historyFolded?: { omitted: number; summarized: boolean }\n\n /** 안내를 닫았는가 — 한 번 닫으면 이 세션에서는 다시 띄우지 않는다(잔소리 금지). */\n @state()\n private foldNoticeDismissed = false\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 /** 현재 로그인 사용자 email — 그룹챗에서 내 메시지 vs 타인 메시지 구분.\n * email 을 쓰는 이유: /auth/profile 이 항상 내려주는 로그인 식별자이고(비어 있지 않다),\n * 내부 관리키(User.id)를 클라이언트 판정에 의존하지 않기 위함. */\n private get _myEmail(): string | undefined {\n return (auth.credential as any)?.email\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(chatDefaults.catalogEntries)\n\n /** Default slash template 후보 캐시 — chat panel mount 시 1회 빌드 */\n private cachedSlashCandidates: MentionCandidate[] = buildSlashCandidates(chatDefaults.slashTemplates)\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 넣었을 때 실제로 그랬다). 세 줄로 접고 눌러서 펼친다. */\n .msg.system .sys-text {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 3;\n overflow: hidden;\n white-space: pre-wrap;\n cursor: pointer;\n }\n .msg.system.open .sys-text {\n display: block;\n -webkit-line-clamp: unset;\n overflow: visible;\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 /* ── \"대화가 길어졌습니다\" 안내 — 조용한 한 줄, 강조하지 않는다 ───── */\n .fold-notice {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0 0 4px;\n padding: 5px 8px;\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n border-radius: 8px;\n background: var(--md-sys-color-surface-container-low, #f8fafc);\n color: var(--md-sys-color-on-surface-variant, #475569);\n font-size: 10.5px;\n line-height: 1.45;\n }\n .fold-notice md-icon {\n --md-icon-size: 14px;\n flex-shrink: 0;\n }\n .fold-notice .fn-text {\n flex: 1;\n min-width: 0;\n }\n /* 새 대화 — 권유이므로 강조색 글자만(채운 버튼은 지시처럼 읽힌다). */\n .fold-notice .fn-new {\n flex-shrink: 0;\n border: 0;\n background: none;\n color: var(--md-sys-color-primary, #0f766e);\n font-family: inherit;\n font-size: 10.5px;\n font-weight: 650;\n cursor: pointer;\n padding: 2px 4px;\n border-radius: 5px;\n white-space: nowrap;\n }\n .fold-notice .fn-new:hover {\n background: var(--md-sys-color-surface-container-high, #e2e8f0);\n }\n .fold-notice .fn-close {\n flex-shrink: 0;\n border: 0;\n background: none;\n color: var(--md-sys-color-outline, #94a3b8);\n cursor: pointer;\n padding: 0;\n display: inline-flex;\n }\n .fold-notice .fn-close md-icon {\n --md-icon-size: 13px;\n }\n\n /* ── 조치 제안 — AI 는 제안까지, 실행은 사용자 ───── */\n .proposals {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 6px;\n }\n .proposal {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px 6px 7px;\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n /* 왼쪽 강조선 — 되돌릴 수 없는 행동임을 조용히 구분한다(색만으로 소리치지 않는다). */\n border-left: 3px solid var(--md-sys-color-primary, #0f766e);\n border-radius: 8px;\n background: var(--md-sys-color-surface-container-low, #f8fafc);\n }\n .proposal md-icon {\n --md-icon-size: 16px;\n color: var(--md-sys-color-primary, #0f766e);\n flex-shrink: 0;\n }\n .proposal .pbody {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n flex: 1;\n }\n .proposal .plabel {\n font-size: 11.5px;\n font-weight: 650;\n color: var(--md-sys-color-on-surface, #0f172a);\n overflow-wrap: anywhere;\n }\n .proposal .preason {\n font-size: 10.5px;\n color: var(--md-sys-color-on-surface-variant, #475569);\n overflow-wrap: anywhere;\n }\n /* 실행 버튼 — 이 대화의 유일한 \"되돌릴 수 없는\" 행동이므로 채운 버튼 하나로 분명하게. */\n .proposal .run {\n flex-shrink: 0;\n padding: 4px 12px;\n border: 0;\n border-radius: 999px;\n background: var(--md-sys-color-primary, #0f766e);\n color: var(--md-sys-color-on-primary, #ffffff);\n font-family: inherit;\n font-size: 11px;\n font-weight: 650;\n letter-spacing: 0.01em;\n cursor: pointer;\n transition: filter 0.12s;\n }\n .proposal .run:hover:not(:disabled) {\n filter: brightness(1.08);\n }\n .proposal .run:disabled {\n background: var(--md-sys-color-surface-container-high, #e2e8f0);\n color: var(--md-sys-color-on-surface-variant, #475569);\n cursor: default;\n }\n .proposal.sent {\n border-left-color: var(--md-sys-color-outline, #94a3b8);\n }\n\n /* ── 접지 경고 — 근거에 없는 대상을 지목한 답 ───── */\n .grounding-warning {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n margin-top: 4px;\n padding: 5px 8px;\n border-radius: 6px;\n border: 1px solid var(--md-sys-color-error, #dc2626);\n background: var(--md-sys-color-error-container, #fef2f2);\n color: var(--md-sys-color-on-error-container, #7f1d1d);\n font-size: 10.5px;\n line-height: 1.45;\n }\n .grounding-warning md-icon {\n --md-icon-size: 14px;\n flex-shrink: 0;\n }\n .grounding-warning .ids {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-weight: 600;\n word-break: break-all;\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 /* 판단 파이프라인 — 훑어 읽는 목록. 배지로 결과를 구분하고 원본은 눌러서 본다. */\n .tool-usages.has-problem {\n border-color: var(--md-sys-color-error, #dc2626);\n }\n .tool-usages-counts .tu-flag {\n font-weight: 650;\n }\n .tool-usages-counts .tu-flag.bad {\n color: var(--md-sys-color-error, #dc2626);\n }\n .tu-turn {\n list-style: none;\n margin: 4px 0 2px;\n padding: 0 8px;\n font-size: 9.5px;\n font-weight: 650;\n letter-spacing: 0.04em;\n color: var(--md-sys-color-outline, #94a3b8);\n text-transform: uppercase;\n }\n .tool-usage-head {\n cursor: pointer;\n align-items: center;\n }\n .tool-usage-item.outcome-rejected,\n .tool-usage-item.outcome-error {\n border-left-color: var(--md-sys-color-error, #dc2626);\n }\n .tool-usage-item.outcome-proposed {\n border-left-color: var(--md-sys-color-primary, #0f766e);\n }\n .tool-usage-item.outcome-folded {\n opacity: 0.7;\n }\n .tu-badge {\n flex-shrink: 0;\n padding: 0 5px;\n border-radius: 999px;\n font-size: 9px;\n font-weight: 700;\n line-height: 15px;\n background: var(--md-sys-color-surface-container-high, #e2e8f0);\n color: var(--md-sys-color-on-surface-variant, #475569);\n }\n .tu-badge.rejected,\n .tu-badge.error {\n background: var(--md-sys-color-error-container, #fef2f2);\n color: var(--md-sys-color-error, #dc2626);\n }\n .tu-badge.proposed {\n background: var(--md-sys-color-primary, #0f766e);\n color: var(--md-sys-color-on-primary, #fff);\n }\n /* 한 줄 요약 — 넘치면 자른다. 목록은 훑는 곳이고 읽는 곳이 아니다. */\n .tu-line {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--md-sys-color-on-surface-variant, #475569);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 9.5px;\n }\n .tu-more {\n --md-icon-size: 13px;\n flex-shrink: 0;\n color: var(--md-sys-color-outline, #94a3b8);\n }\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 /*\n * 밖에서 넣어 준 질문을 자동 전송한다(오류 토스트의 \"AI에게 묻기\" 등).\n *\n * sessionId 가 붙기 전에 보내면 대화에 남지 않으므로 세션이 준비될 때까지 기다린다.\n * 같은 문장을 두 번 보내지 않도록 보낸 값을 기억한다 — 재렌더마다 updated 가 다시 돌기 때문이다.\n */\n if (this.autoAsk && assistantAutoAskKey(this.autoAsk, this.autoAskRequestId) !== this._autoAsked && (this.sessionId || this.prepareSession) && !this.busy) {\n this._autoAsked = assistantAutoAskKey(this.autoAsk, this.autoAskRequestId)\n this.input = this.autoAsk\n this.send()\n }\n\n if (changed.has('sessionId') && this.adoptingSession) {\n this.adoptingSession = false\n this._startPresence()\n this._startMessageSubscription()\n } else 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 /* 서버 이력을 다시 불러오면 화면에 전부 보인다 → 접은 지점 해제(안 하면 화면과 에이전트 문맥이\n * 어긋난 채 남는다). 전송 후에는 해제하지 않는다 — 접힌 화면이 그대로이므로 계속 숨겨야 한다. */\n this._truncateAfterId = undefined\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('ai-assistant.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 myEmail = this._myEmail\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 * 판정은 email 비교. **내 email 을 모를 때는 '남'으로 단정하지 않는다** — 내 발화를 남의 것으로\n * 잘못 보여주는 쪽이 더 해롭다(로그인 정보가 아직 안 온 순간 등). 내 email 을 아는 상태에서\n * 발신자 email 이 다르거나 비어 있으면 다른 계정으로 본다. */\n const isOther = line.role === 'user' && !!myEmail && line.senderEmail !== myEmail\n const prevSameSender = !!prev && prev.role === 'user' && prev.senderEmail === line.senderEmail\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('ai-assistant.label.ai')}</div>`\n : showSenderLabel\n ? html`<div class=\"msg-label sender\">${line.senderName || i18next.t('ai-assistant.label.participant')}</div>`\n : nothing}\n <div class=\"msg-wrap ${line.role} ${isOther ? 'other' : ''}\">\n <div\n class=\"msg ${line.role} ${isOther ? 'other' : ''} ${line.pending ? 'pending' : ''} ${line.systemOpen\n ? 'open'\n : ''}\">\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 : line.role === 'system'\n ? html`<span\n class=\"sys-text\"\n title=${i18next.t('ai-assistant.text.toggle-system-note', { defaultValue: '눌러서 펼치기' })}\n @click=${() => this.toggleSystemNote(idx)}\n >${content}</span\n >`\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('ai-assistant.text.reverted')\n : i18next.t('ai-assistant.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('ai-assistant.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.proposals && line.proposals.length > 0 ? this.renderProposals(line) : nothing}\n ${line.groundingWarnings && line.groundingWarnings.length > 0\n ? this.renderGroundingWarning(line)\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('ai-assistant.button.retry')}\n </button>\n `\n : nothing}\n </div>\n `\n : nothing}\n ${this.renderFoldNotice()}\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('ai-assistant.text.show-examples-tooltip')}>\n <md-icon>${this.examplesOpen ? 'close' : 'lightbulb'}</md-icon>\n ${this.examplesOpen\n ? i18next.t('ai-assistant.button.close')\n : i18next.t('ai-assistant.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('ai-assistant.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('ai-assistant.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('ai-assistant.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 /**\n * 화면에 그릴 예시 — 호스트가 준 것이 있으면 그것, 없으면 보드 기본값(i18n 키를 여기서 푼다).\n *\n * 두 자리(빈 화면 · 접힌 줄)가 같은 값을 써야 한다. 전에는 둘이 각각 `EXAMPLE_GROUPS` 를 직접\n * 읽었고, 그러면 호스트 값을 한쪽만 반영하는 상태가 만들어질 수 있었다.\n */\n private get exampleGroups(): Array<{ label: string; items: string[] }> {\n if (this.examples?.length) return this.examples\n return OxBoardAIChat.EXAMPLE_GROUPS.map(group => ({\n label: i18next.t(group.labelKey),\n items: group.itemKeys.map(key => i18next.t(key))\n }))\n }\n\n /** 이 표면이 실제로 쓸 수 있는 트리거 — 판정은 `chat-triggers.ts` 가 하고 여기서는 사실만 읽는다. */\n private get triggerChips(): TriggerChip[] {\n return availableTriggers({\n hasMentionableThings: !!(this.boardProvider || this.currentBoard || this.knownTypes?.length),\n hasSession: !!this.sessionId,\n slashTemplateCount: chatDefaults.slashTemplates.length\n })\n }\n\n private static readonly EXAMPLE_GROUPS: Array<{\n labelKey: string\n itemKeys: string[]\n }> = [\n {\n labelKey: 'ai-assistant.label.create',\n itemKeys: [\n 'ai-assistant.example.create-monitoring-dashboard',\n 'ai-assistant.example.create-welcome-screen'\n ]\n },\n {\n labelKey: 'ai-assistant.label.edit',\n itemKeys: [\n 'ai-assistant.example.edit-align-distribute',\n 'ai-assistant.example.edit-resize-board'\n ]\n },\n {\n labelKey: 'ai-assistant.label.style',\n itemKeys: [\n 'ai-assistant.example.style-dark-mode',\n 'ai-assistant.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 <ox-assistant-session-toolbar style=\"padding:8px 12px\" .sessions=${this.sessions}\n .sessionId=${this.sessionId || ''} .disabled=${this.busy}\n @assistant-session-select=${(event: CustomEvent) => { event.stopPropagation(); this._onSessionTabClick(event.detail.sessionId) }}\n @assistant-session-reset=${(event: Event) => { event.stopPropagation(); this._onSessionCreateClick() }}\n ></ox-assistant-session-toolbar>\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 if (this.busy || this.lines.length === 0) return\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\">${this.intro?.title ?? i18next.t('ai-assistant.text.empty-title')}</div>\n <div class=\"subtitle\">${this.intro?.subtitle ?? i18next.t('ai-assistant.text.empty-subtitle')}</div>\n </div>\n\n ${this.triggerChips.length > 0\n ? html`\n <div class=\"trigger-row\">\n <div class=\"trigger-row-label\">${i18next.t('ai-assistant.text.triggers-heading')}</div>\n <div class=\"trigger-chips\">\n ${this.triggerChips.map(\n trigger => html`\n <button\n class=\"trigger-chip\"\n title=${i18next.t(trigger.tooltipKey)}\n @click=${() => this.insertTrigger(trigger.char)}>\n <span class=\"trigger-char\">${trigger.char}</span>\n <span class=\"trigger-desc\">${i18next.t(trigger.labelKey)}</span>\n </button>\n `\n )}\n </div>\n </div>\n `\n : ''}\n\n ${this.exampleGroups.map(\n group => html`\n <div class=\"group\">\n <div class=\"group-label\">${group.label}</div>\n ${group.items.map(\n text => 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('ai-assistant.label.korean-supported')}</span>\n <span class=\"badge\">${i18next.t('ai-assistant.label.multi-command')}</span>\n <span class=\"badge\">${i18next.t('ai-assistant.label.review-able')}</span>\n ${chatDefaults.footerNoticeKey\n ? html`<br />${i18next.t(chatDefaults.footerNoticeKey)}`\n : ''}\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 ${this.exampleGroups.map(\n group => html`\n <div class=\"ex-row\">\n <span class=\"ex-label\">${group.label}</span>\n ${group.items.map(t => html`<button class=\"ex\" @click=${() => this.useExample(t)}>${t}</button>`)}\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('ai-assistant.text.copied')\n : i18next.t('ai-assistant.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('ai-assistant.button.regenerate')}\n @click=${() => this.regenerateAssistant(idx)}>\n <md-icon>refresh</md-icon>\n </button>\n </div>\n `\n }\n\n /**\n * \"대화가 길어졌습니다\" 안내 — **사실만** 알리고 결정은 사용자에게 맡긴다.\n *\n * 주제가 바뀌었는지 판정하지 않는다: 판정하면 오탐이 생기고, 오탐이 생기면 사용자는 안내 자체를\n * 무시한다(접지 경고에서 배운 것과 같다). 근거는 \"앞부분이 프롬프트에서 접혔다\" 는 객관적 사실뿐이다.\n *\n * 왜 알려야 하는가: 한 대화에 주제가 여럿 섞이면 (1) 옛 맥락이 새 답을 오염시키고 (2) 요약이\n * 뭉개지고 (3) 목록의 대화 이름이 거짓말이 된다. 그걸 아는 사람은 사용자뿐이다.\n */\n private renderFoldNotice() {\n const folded = this.historyFolded\n if (!folded || this.foldNoticeDismissed) return nothing\n return html`\n <div class=\"fold-notice\">\n <md-icon>unfold_less</md-icon>\n <span class=\"fn-text\">\n ${folded.summarized\n ? i18next.t('ai-assistant.text.history-folded-summarized', {\n count: folded.omitted,\n defaultValue: '대화가 길어져 앞부분 {count}개가 요약으로 접혔습니다. 다른 주제라면 새 대화가 낫습니다.'\n })\n : i18next.t('ai-assistant.text.history-folded', {\n count: folded.omitted,\n defaultValue: '대화가 길어져 앞부분 {count}개가 생략되었습니다. 다른 주제라면 새 대화가 낫습니다.'\n })}\n </span>\n <button class=\"fn-new\" @click=${this.startNewFromNotice}>\n ${i18next.t('ai-assistant.button.new-chat')}\n </button>\n <button class=\"fn-close\" title=${i18next.t('ai-assistant.button.close')} @click=${() => (this.foldNoticeDismissed = true)}>\n <md-icon>close</md-icon>\n </button>\n </div>\n `\n }\n\n /**\n * 새 대화로 — **맥락을 옮기지 않는다.** 주제를 바꾸려고 새로 시작하는 것이므로, 옮기면 그 이유가\n * 사라진다(승격은 이어가려는 것이고 이건 끊으려는 것이다). 세션 생성은 호스트의 몫이다.\n */\n private startNewFromNotice = () => {\n this.foldNoticeDismissed = true\n this.dispatchEvent(new CustomEvent('board-ai-start-new-session', { bubbles: true, composed: true }))\n }\n\n /**\n * 조치 제안 카드 — AI 는 제안까지, **실행은 사용자**.\n *\n * 왜 카드인가: 되돌릴 수 없는 현장 명령을 문장 속 링크로 흘리면 실수로 눌린다. 무엇을·왜 하는지\n * 한 줄로 보이고 실행이 **분명한 한 번의 행동**이어야 한다.\n * 실행 결과는 호스트가 **대화에 기록**한다 — 누가 무엇을 실행했는지가 협의 이력에 남아야 한다.\n */\n private renderProposals(line: ChatLine) {\n const items = line.proposals ?? []\n if (items.length === 0) return nothing\n return html`\n <div class=\"proposals\">\n ${items.map(p => {\n const key = this.proposalKey(p)\n const sent = this.sentProposals.has(key)\n return html`\n <div class=\"proposal ${sent ? 'sent' : ''}\">\n <md-icon>bolt</md-icon>\n <span class=\"pbody\">\n <span class=\"plabel\">${p.label || p.command}</span>\n ${p.reason ? html`<span class=\"preason\">${p.reason}</span>` : nothing}\n </span>\n <button\n class=\"run\"\n ?disabled=${sent}\n @click=${() => this.executeProposal(p, key)}>\n ${sent ? i18next.t('ai-assistant.text.proposal-sent', { defaultValue: '실행 요청됨' })\n : i18next.t('ai-assistant.button.run-proposal', { defaultValue: '실행' })}\n </button>\n </div>\n `\n })}\n </div>\n `\n }\n\n /**\n * 제안 식별 키 — 같은 조치를 두 번 실행하지 않도록 **효과**(명령·대상·인자)로만 만든다.\n *\n * 인자는 **키 순서를 정렬**해 직렬화한다: 같은 인자를 다른 순서로 받았을 뿐인데 키가 달라지면\n * 한 카드를 실행한 뒤에도 다른 카드가 살아 있어 **같은 명령이 두 번 나간다**(실제로 그랬다).\n * 설명 문구(label·reason)는 키에 넣지 않는다 — 문구가 달라도 같은 조치다.\n */\n private proposalKey(p: any): string {\n const stable = (v: any): string => {\n if (v === null || typeof v !== 'object') return JSON.stringify(v ?? null)\n if (Array.isArray(v)) return `[${v.map(stable).join(',')}]`\n return `{${Object.keys(v)\n .sort()\n .map(k => `${JSON.stringify(k)}:${stable(v[k])}`)\n .join(',')}}`\n }\n try {\n return `${p?.command ?? ''}|${p?.instanceId ?? ''}|${stable(p?.args)}`\n } catch {\n return `${p?.command ?? ''}|${p?.instanceId ?? ''}`\n }\n }\n\n /**\n * 실행은 호스트가 한다 — 이 컴포넌트는 도메인(트윈 명령 채널)을 모른다.\n * 확인 대화·명령 전송·결과 기록은 호스트의 몫이고, 여기서는 중복 클릭만 막는다.\n */\n private executeProposal(p: any, key: string) {\n this.sentProposals = new Set(this.sentProposals).add(key)\n this.dispatchEvent(\n new CustomEvent('board-ai-proposal-execute', {\n detail: { proposal: p, sessionId: this.sessionId },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /**\n * 접지 경고 — 답이 언급했지만 **근거에 없던** 대상을 알린다.\n *\n * 왜 답을 지우지 않는가: 판정은 식별자 대조라 확정이 아니다(예: 사용자가 화면에서 본 이름을\n * 대화 밖에서 알고 말한 경우). 그래서 답은 그대로 보여주고, 검증되지 않았다는 사실만 붙인다 —\n * 사용자가 그 대상을 실제 작업의 근거로 삼기 전에 알아야 하는 정보다.\n */\n private renderGroundingWarning(line: ChatLine) {\n const ids = line.groundingWarnings ?? []\n if (ids.length === 0) return nothing\n return html`\n <div class=\"grounding-warning\" role=\"note\">\n <md-icon>report</md-icon>\n <span>\n ${i18next.t('ai-assistant.text.ungrounded-mention', {\n defaultValue: '확인되지 않은 대상을 언급했습니다 — 실제 데이터에 없습니다:'\n })}\n <span class=\"ids\">${ids.join(', ')}</span>\n </span>\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('ai-assistant.text.tools-used', { defaultValue: '도구 사용' })\n /* 눈에 걸려야 하는 것을 머리줄에 올린다 — 거절·접힘이 있으면 펼치기 전에 보인다.\n * (이번 사고들이 전부 그것이었다: 인자 빠진 호출이 거절됐고, 중복 제안이 접혔다.) */\n const problems = usages.filter(u => u.outcome === 'rejected' || u.outcome === 'error').length\n const folded = usages.filter(u => u.outcome === 'folded').length\n return html`\n <div class=\"tool-usages ${open ? 'open' : ''} ${problems ? 'has-problem' : ''}\">\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' : 'account_tree'}</md-icon>\n <span class=\"tool-usages-summary\">\n <strong>${usages.length}</strong> ${toolsLabel}\n <span class=\"tool-usages-counts\">\n · read ${readCount} · write ${writeCount}${problems\n ? html` · <span class=\"tu-flag bad\">${i18next.t('ai-assistant.text.tool-rejected-count', {\n count: problems,\n defaultValue: '거절 {count}'\n })}</span>`\n : nothing}${folded\n ? html` · <span class=\"tu-flag\">${i18next.t('ai-assistant.text.tool-folded-count', {\n count: folded,\n defaultValue: '접힘 {count}'\n })}</span>`\n : nothing}\n </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((u, i) => this.renderToolStep(idx, u, i, usages[i - 1]))}\n </ol>\n `\n : nothing}\n </div>\n `\n }\n\n /** 시스템 기록 펼치기/접기 — 곁줄이 길어도 대화가 가려지지 않게. */\n private toggleSystemNote(idx: number) {\n const next = [...this.lines]\n const line = next[idx]\n if (!line) return\n next[idx] = { ...line, systemOpen: !line.systemOpen }\n this.lines = next\n }\n\n /**\n * 판단 과정의 한 단계 — 턴 구분 · 도구 · 배지 · **한 줄 요약**.\n *\n * 원본 JSON 을 처음부터 펼쳐 두면 눈이 흐려져 정작 중요한 것(무엇이 거절됐는지, 무엇이 접혔는지)이\n * 안 보인다. 한 줄로 줄이고 원본은 항목별로 눌러서 본다.\n */\n private renderToolStep(lineIdx: number, u: any, i: number, prev: any) {\n const outcome = u.outcome ?? 'ok'\n const openKey = `${lineIdx}:${i}`\n const open = this.openToolSteps.has(openKey)\n /* 턴이 바뀌면 구분선 — \"한 턴 안에서 정정했는지, 다음 턴에서 했는지\" 가 진단의 절반이다. */\n const newTurn = typeof u.iter === 'number' && (!prev || prev.iter !== u.iter)\n return html`\n ${newTurn\n ? html`<li class=\"tu-turn\">\n ${i18next.t('ai-assistant.text.tool-turn', { n: (u.iter ?? 0) + 1, defaultValue: '{n}번째 판단' })}\n </li>`\n : nothing}\n <li class=\"tool-usage-item kind-${u.kind} outcome-${outcome}\">\n <div class=\"tool-usage-head\" @click=${() => this.toggleToolStep(openKey)}>\n <span class=\"tool-usage-step\">${i + 1}.</span>\n <span class=\"tool-usage-name\">${u.name}</span>\n <span class=\"tu-badge ${outcome}\">${this.outcomeLabel(outcome)}</span>\n <span class=\"tu-line\" title=${this.stepSummary(u)}>${this.stepSummary(u)}</span>\n <md-icon class=\"tu-more\">${open ? 'expand_less' : 'expand_more'}</md-icon>\n </div>\n ${open\n ? html`\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 `\n : nothing}\n </li>\n `\n }\n\n /** 배지 문구 — 코드가 아니라 사람 말로. */\n private outcomeLabel(outcome: string): string {\n const map: Record<string, [string, string]> = {\n ok: ['ai-assistant.text.outcome-ok', '조회'],\n rejected: ['ai-assistant.text.outcome-rejected', '거절'],\n queued: ['ai-assistant.text.outcome-queued', '적용 예정'],\n proposed: ['ai-assistant.text.outcome-proposed', '제안'],\n folded: ['ai-assistant.text.outcome-folded', '중복 접힘'],\n error: ['ai-assistant.text.outcome-error', '오류']\n }\n const [key, fallback] = map[outcome] ?? map.ok\n return i18next.t(key, { defaultValue: fallback })\n }\n\n /**\n * 결과 한 줄 — 무엇을 얻었는지/왜 막혔는지만. 목록에서 훑어 읽을 수 있어야 한다.\n * 거절이면 사유를, 조회면 대표적인 개수를, 제안이면 무엇을 제안했는지.\n */\n private stepSummary(u: any): string {\n const r = u?.result\n if (!r || typeof r !== 'object') return typeof r === 'string' ? r.slice(0, 80) : ''\n if (u.outcome === 'rejected' || u.outcome === 'error') {\n return String(r.errorCode || r.error || r.issues?.[0]?.message || '').slice(0, 80)\n }\n if (u.outcome === 'proposed' || u.outcome === 'folded') {\n return [r.command, r.args && Object.keys(r.args).length ? this.formatJson(r.args) : '']\n .filter(Boolean)\n .join(' ')\n .slice(0, 80)\n }\n /* 조회 결과 — 배열 길이가 있는 필드를 골라 \"무엇이 몇 개\" 로 요약한다(전체 덤프 금지). */\n const counts = Object.keys(r)\n .filter(k => Array.isArray(r[k]) && r[k].length > 0)\n .slice(0, 2)\n .map(k => `${k} ${r[k].length}`)\n if (counts.length) return counts.join(' · ')\n const keys = Object.keys(r).slice(0, 4)\n return keys.map(k => (typeof r[k] === 'object' ? k : `${k} ${r[k]}`)).join(' · ').slice(0, 80)\n }\n\n private toggleToolStep(key: string) {\n const next = new Set(this.openToolSteps)\n next.has(key) ? next.delete(key) : next.add(key)\n this.openToolSteps = next\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('ai-assistant.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 /** 편집으로 접은 지점 — 마지막으로 남긴 **서버 메시지 id**. 서버 이력은 유지되므로 이걸 함께\n * 보내야 접은 말이 에이전트 문맥에 되살아나지 않는다(서버 buildLlmHistory 가 그 뒤를 버린다). */\n private _truncateAfterId?: string\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 /* 남길 마지막 줄의 서버 id — 없으면(아직 영속 안 된 줄뿐) 지정하지 않는다(서버가 전부 유지). */\n this._truncateAfterId = this.lines\n .slice(0, idx)\n .reverse()\n .find(l => !!l.id)?.id\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 if (!this.sessionId && this.prepareSession) {\n this.busy = true\n try {\n await this.prepareSession(id => { this.adoptingSession = true; this.sessionId = id })\n await this.updateComplete\n } catch (error) {\n this.errorMessage = error instanceof Error ? error.message : String(error)\n this.busy = false\n return\n }\n this.busy = false\n }\n\n let requestContext: AssistantRequestContext\n let liveBoard: any\n let requestBase: any\n try {\n const provided = this.contextProvider?.() || {}\n requestContext = structuredClone({ systemPrompt: this.systemPrompt, hostContext: this.hostContext, ...provided })\n liveBoard = structuredClone(this.boardProvider ? this.boardProvider() : this.currentBoard)\n requestBase = { version: 1, sessionId: this.sessionId, hostContext: requestContext.hostContext,\n modelFingerprint: assistantModelFingerprint(liveBoard) }\n } catch (error) {\n this.errorMessage = error instanceof Error ? error.message : String(error)\n return\n }\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, senderEmail: this._myEmail }\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 // mentions 는 위에서 이미 계산해 user line 에 thread — 그대로 mutation 입력으로 재사용.\n const neutral = this.chatEndpoint === 'assistantChat'\n const result = await client.mutate({\n mutation: neutral ? ASSISTANT_CHAT_MUTATION : BOARD_AI_CHAT_MUTATION,\n variables: {\n input: neutral\n ? buildAssistantChatInput({\n sessionId: this.sessionId,\n history,\n systemPrompt: requestContext.systemPrompt!,\n truncateAfterMessageId: this._truncateAfterId,\n hostContext: requestContext.hostContext,\n toolCategories: this.toolCategories,\n requireGroundingTools: this.requireGroundingTools\n })\n : buildChatMutationInput({\n sessionId: this.sessionId,\n history,\n liveBoard,\n scopes: this.scopes,\n truncateAfterMessageId: this._truncateAfterId,\n hostContext: requestContext.hostContext,\n toolCategories: this.toolCategories,\n boardTools: this.boardTools,\n requireGroundingTools: this.requireGroundingTools,\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 = neutral ? result.data?.assistantChat : result.data?.boardAIChat\n if (!out) throw new Error(i18next.t('ai-assistant.text.empty-response'))\n\n /* 대화가 길어졌다는 서버 보고 — 닫지 않았다면 안내를 띄운다. */\n this.historyFolded = out.historyFolded ?? undefined\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 groundingWarnings: Array.isArray(out.groundingWarnings) ? out.groundingWarnings : undefined,\n proposals: Array.isArray(out.proposals) ? out.proposals : 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 requestBase,\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, requestBase },\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"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"board-ai-chat.test.js","sourceRoot":"","sources":["../../client/components/board-ai-chat.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAE7D,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC7D,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACrE,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,SAAS,EAAE,WAAW;YACtB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YAC1C,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE;YACvD,cAAc,EAAE,CAAC,EAAE,CAAC;SACrB,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,EAAE;SACnB,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC7B,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YAC1C,SAAS,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;YAC7B,MAAM,EAAE,CAAC,OAAO,CAAC;YACjB,UAAU,EAAE,CAAC,MAAM,CAAC;YACpB,UAAU,EAAE,CAAC,WAAW,CAAC;YACzB,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YACpC,cAAc,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;SACvB,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC;YACxC,YAAY;YACZ,kBAAkB;YAClB,cAAc;YACd,YAAY;YACZ,UAAU;YACV,QAAQ;YACR,gBAAgB;YAChB,WAAW;SACZ,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACnC,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,EAAE;SACnB,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACnC,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,SAAS;YACpB,cAAc,EAAE,EAAE;SACnB,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAClC,MAAM,OAAO,GAAG;YACd,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,MAAM,EAAE;YAC1C,EAAE,IAAI,EAAE,WAAoB,EAAE,OAAO,EAAE,QAAQ,EAAE;SAClD,CAAA;QACD,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO;YACP,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,EAAE;SACnB,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC7C,MAAM,KAAK,GAAG;YACZ,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SACzC,CAAA;QACD,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,KAAK;YAChB,cAAc,EAAE,EAAE;SACnB,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;QAC3D,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,EAAE;SACnB,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,CAAA;QACpC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,aAAa,EAAE,CAAA;QACxC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,aAAa,EAAE,CAAA;QACxC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,aAAa,EAAE,CAAA;IAChD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC;SAChC,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACtE,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,EAAE;YAClB,QAAQ,EAAE,EAAE;SACb,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,aAAa,EAAE,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC5C,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;YACjD,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,EAAE;YAClB,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;SACzC,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC9D,MAAM,KAAK,GAAG,sBAAsB,CAAC;YACnC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,EAAE;SACnB,CAAC,CAAA;QACF,MAAM,CAAC,UAAU,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACzC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","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"]}
|
|
File without changes
|
|
File without changes
|