@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.
Files changed (31) hide show
  1. package/client/components/{board-ai-chat.ts → assistant-chat.ts} +589 -18
  2. package/client/components/chat-defaults.ts +24 -0
  3. package/client/components/chat-input-builder.ts +1 -1
  4. package/client/index.ts +2 -3
  5. package/dist-client/components/{board-ai-chat.d.ts → assistant-chat.d.ts} +39 -3
  6. package/dist-client/components/{board-ai-chat.js → assistant-chat.js} +597 -66
  7. package/dist-client/components/assistant-chat.js.map +1 -0
  8. package/dist-client/components/{board-ai-chat.test.js → assistant-chat.test.js} +1 -1
  9. package/dist-client/components/assistant-chat.test.js.map +1 -0
  10. package/dist-client/components/chat-defaults.d.ts +22 -0
  11. package/dist-client/components/chat-defaults.js +4 -0
  12. package/dist-client/components/chat-defaults.js.map +1 -1
  13. package/dist-client/components/chat-input-builder.js +1 -1
  14. package/dist-client/components/chat-input-builder.js.map +1 -1
  15. package/dist-client/index.d.ts +2 -3
  16. package/dist-client/index.js +2 -3
  17. package/dist-client/index.js.map +1 -1
  18. package/dist-client/tsconfig.tsbuildinfo +1 -1
  19. package/dist-server/service/assistant-chat-resolver.js +12 -0
  20. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  21. package/dist-server/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +4 -4
  23. package/server/service/assistant-chat-resolver.ts +13 -0
  24. package/test/assistant-request-context.test.ts +1 -1
  25. package/test/assistant-session-toolbar.test.ts +1 -1
  26. package/translations/en.json +6 -1
  27. package/translations/ko.json +6 -1
  28. package/dist-client/components/board-ai-chat.js.map +0 -1
  29. package/dist-client/components/board-ai-chat.test.js.map +0 -1
  30. /package/client/components/{board-ai-chat.test.ts → assistant-chat.test.ts} +0 -0
  31. /package/dist-client/components/{board-ai-chat.test.d.ts → assistant-chat.test.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
- var OxBoardAIChat_1;
1
+ var OxAssistantChat_1;
2
2
  import { __decorate, __metadata } from "tslib";
3
3
  /**
4
- * <ox-board-ai-chat> — AI 주도 보드 모델링 채팅 컴포넌트 (Lit).
4
+ * <ox-assistant-chat> — AI 도우미 채팅 컴포넌트 (Lit).
5
5
  *
6
6
  * 입력:
7
7
  * - sessionId: 영속 ChatSession 식별자 (없으면 ad-hoc 모드, 메시지 영속 안 됨)
@@ -44,6 +44,13 @@ import { buildMentionCandidates, buildCatalogCandidates, buildSlashCandidates, b
44
44
  */
45
45
  import { chatDefaults } from './chat-defaults.js';
46
46
  import { availableTriggers } from './chat-triggers.js';
47
+ function formatFileSize(bytes) {
48
+ if (!bytes || bytes < 1024)
49
+ return `${bytes || 0} B`;
50
+ if (bytes < 1024 * 1024)
51
+ return `${(bytes / 1024).toFixed(1)} KB`;
52
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
53
+ }
47
54
  const BOARD_AI_CHAT_MUTATION = gql `
48
55
  mutation BoardAIChat($input: BoardAIChatInput!) {
49
56
  boardAIChat(input: $input) {
@@ -154,7 +161,7 @@ function initials(name) {
154
161
  return (parts[0][0] + parts[1][0]).toUpperCase();
155
162
  return /[a-zA-Z]/.test(n) ? n.slice(0, 2).toUpperCase() : n.slice(0, 1);
156
163
  }
157
- let OxBoardAIChat = class OxBoardAIChat extends LitElement {
164
+ let OxAssistantChat = class OxAssistantChat extends LitElement {
158
165
  constructor() {
159
166
  super(...arguments);
160
167
  this.adoptingSession = false;
@@ -206,12 +213,18 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
206
213
  this.input = '';
207
214
  /** 되돌려진 patch id 들 — 한 번 revert 한 patch 는 버튼 비활성 */
208
215
  this.revertedPatchIds = new Set();
216
+ /** 첨부된 파일/이미지 목록 */
217
+ this.attachments = [];
218
+ /** 드래그 오버 상태 (시각 피드백용) */
219
+ this.dragOver = false;
209
220
  /** mini action — 인라인 예시 토글 */
210
221
  this.examplesOpen = false;
211
222
  /** 원본 JSON 을 펼친 단계(라인:단계) — 목록은 한 줄 요약으로 훑고 필요한 것만 펼친다. */
212
223
  this.openToolSteps = new Set();
213
224
  /** 이미 실행을 요청한 제안 키 — 같은 카드를 두 번 눌러 되돌릴 수 없는 명령이 두 번 나가지 않게. */
214
225
  this.sentProposals = new Set();
226
+ /** Proposal keys the host has reported staged. Only the host knows; see `acknowledgeProposalStaged`. */
227
+ this.stagedProposals = new Set();
215
228
  /** 안내를 닫았는가 — 한 번 닫으면 이 세션에서는 다시 띄우지 않는다(잔소리 금지). */
216
229
  this.foldNoticeDismissed = false;
217
230
  this.busy = false;
@@ -298,10 +311,121 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
298
311
  */
299
312
  this.startNewFromNotice = () => {
300
313
  this.foldNoticeDismissed = true;
301
- this.dispatchEvent(new CustomEvent('board-ai-start-new-session', { bubbles: true, composed: true }));
314
+ this.dispatchEvent(new CustomEvent('assistant-start-new-session', { bubbles: true, composed: true }));
302
315
  };
303
316
  }
304
- static { OxBoardAIChat_1 = this; }
317
+ static { OxAssistantChat_1 = this; }
318
+ async onAttachFiles(files) {
319
+ if (!files || files.length === 0)
320
+ return;
321
+ const list = Array.from(files);
322
+ const newAttachments = [];
323
+ for (const file of list) {
324
+ const id = 'att-' + Math.random().toString(36).substring(2, 9);
325
+ const url = URL.createObjectURL(file);
326
+ let base64;
327
+ try {
328
+ base64 = await this.readFileAsBase64(file);
329
+ }
330
+ catch (err) {
331
+ console.warn('Failed to read file as base64', err);
332
+ }
333
+ newAttachments.push({
334
+ id,
335
+ file,
336
+ name: file.name,
337
+ size: file.size,
338
+ type: file.type || 'application/octet-stream',
339
+ url,
340
+ base64
341
+ });
342
+ }
343
+ this.attachments = [...this.attachments, ...newAttachments];
344
+ }
345
+ readFileAsBase64(file) {
346
+ return new Promise((resolve, reject) => {
347
+ const reader = new FileReader();
348
+ reader.onload = () => resolve(reader.result);
349
+ reader.onerror = reject;
350
+ reader.readAsDataURL(file);
351
+ });
352
+ }
353
+ removeAttachment(id) {
354
+ const target = this.attachments.find(a => a.id === id);
355
+ if (target?.url) {
356
+ URL.revokeObjectURL(target.url);
357
+ }
358
+ this.attachments = this.attachments.filter(a => a.id !== id);
359
+ }
360
+ onComposerPaste(e) {
361
+ const items = e.clipboardData?.items;
362
+ if (!items || items.length === 0)
363
+ return;
364
+ const files = [];
365
+ for (let i = 0; i < items.length; i++) {
366
+ const item = items[i];
367
+ if (item.kind === 'file') {
368
+ const file = item.getAsFile();
369
+ if (file)
370
+ files.push(file);
371
+ }
372
+ }
373
+ if (files.length > 0) {
374
+ this.onAttachFiles(files);
375
+ }
376
+ }
377
+ onComposerDragOver(e) {
378
+ e.preventDefault();
379
+ e.stopPropagation();
380
+ if (!this.dragOver)
381
+ this.dragOver = true;
382
+ }
383
+ onComposerDragLeave(e) {
384
+ e.preventDefault();
385
+ e.stopPropagation();
386
+ const currentTarget = e.currentTarget;
387
+ const relatedTarget = e.relatedTarget;
388
+ if (!currentTarget.contains(relatedTarget)) {
389
+ this.dragOver = false;
390
+ }
391
+ }
392
+ onComposerDrop(e) {
393
+ e.preventDefault();
394
+ e.stopPropagation();
395
+ this.dragOver = false;
396
+ if (e.dataTransfer?.files?.length) {
397
+ this.onAttachFiles(e.dataTransfer.files);
398
+ }
399
+ }
400
+ /**
401
+ * The host reports that it put this candidate somewhere the person can look at it.
402
+ *
403
+ * Staging happens outside this component — a dock catches `assistant-proposal-preview`, hands
404
+ * the candidate to a viewer, and only that host knows whether it worked, or whether it has a
405
+ * viewer at all. So the card says nothing until the host calls this, and it then says it in the
406
+ * words the host registered (`chatDefaults.stagedNoticeKey`).
407
+ *
408
+ * Safe to call for a candidate the card never showed; the key simply never matches.
409
+ */
410
+ acknowledgeProposalStaged(proposal) {
411
+ /*
412
+ Silence has two meanings and they are not the same defect. A host that never calls this is
413
+ telling us it does not know, and drawing nothing is right. A host that calls it without
414
+ registering `stagedNoticeKey` meant to say something and left out the words -- staying quiet
415
+ about that reads to whoever wired it as "the badge just doesn't appear here".
416
+
417
+ The screen still shows nothing either way. There is no sentence to show, and inventing one is
418
+ what this whole correction removed.
419
+ */
420
+ if (!chatDefaults.stagedNoticeKey && !OxAssistantChat_1.warnedMissingStagedNotice) {
421
+ OxAssistantChat_1.warnedMissingStagedNotice = true;
422
+ console.warn('[assistant-chat] a host reported a proposal staged but registered no `stagedNoticeKey`, ' +
423
+ 'so the card can say nothing. Pass one to `registerChatDefaults` from the host.');
424
+ }
425
+ this.stagedProposals = new Set(this.stagedProposals).add(this.proposalKey(proposal));
426
+ }
427
+ /** Once per page: the miss is a wiring mistake, and repeating it per candidate buries it. */
428
+ static { this.warnedMissingStagedNotice = false; }
305
429
  /** 현재 로그인 사용자 email — 그룹챗에서 내 메시지 vs 타인 메시지 구분.
306
430
  * email 을 쓰는 이유: /auth/profile 이 항상 내려주는 로그인 식별자이고(비어 있지 않다),
307
431
  * 내부 관리키(User.id)를 클라이언트 판정에 의존하지 않기 위함. */
@@ -1092,6 +1216,63 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1092
1216
  .proposal.sent {
1093
1217
  border-left-color: var(--md-sys-color-outline, #94a3b8);
1094
1218
  }
1219
+ .proposal .staged-badge {
1220
+ display: inline-flex;
1221
+ align-items: center;
1222
+ gap: 3px;
1223
+ padding: 3px 8px;
1224
+ border-radius: 999px;
1225
+ background: rgba(15, 118, 110, 0.1);
1226
+ color: var(--md-sys-color-primary, #0f766e);
1227
+ font-size: 11px;
1228
+ font-weight: 600;
1229
+ flex-shrink: 0;
1230
+ }
1231
+ .proposal .staged-badge md-icon {
1232
+ --md-icon-size: 13px;
1233
+ color: inherit;
1234
+ }
1235
+
1236
+ .proposal-choices {
1237
+ display: flex;
1238
+ flex-wrap: wrap;
1239
+ gap: 6px;
1240
+ margin-top: 6px;
1241
+ padding: 6px 8px;
1242
+ background: var(--md-sys-color-surface-container-lowest, #ffffff);
1243
+ border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
1244
+ border-radius: 8px;
1245
+ }
1246
+ .proposal-choices .choice-chip {
1247
+ display: inline-flex;
1248
+ align-items: center;
1249
+ gap: 4px;
1250
+ padding: 4px 9px;
1251
+ border-radius: 999px;
1252
+ border: 1px solid var(--md-sys-color-outline-variant, #cbd5e1);
1253
+ background: var(--md-sys-color-surface-container-low, #f8fafc);
1254
+ color: var(--md-sys-color-on-surface, #1e293b);
1255
+ font-family: inherit;
1256
+ font-size: 11px;
1257
+ font-weight: 550;
1258
+ cursor: pointer;
1259
+ transition: all 0.14s ease-in-out;
1260
+ }
1261
+ .proposal-choices .choice-chip:hover {
1262
+ background: var(--md-sys-color-surface-container-high, #e2e8f0);
1263
+ border-color: var(--md-sys-color-primary, #0f766e);
1264
+ color: var(--md-sys-color-primary, #0f766e);
1265
+ }
1266
+ .proposal-choices .choice-chip.primary {
1267
+ background: var(--md-sys-color-primary-container, #ccfbf1);
1268
+ color: var(--md-sys-color-on-primary-container, #0f766e);
1269
+ border-color: var(--md-sys-color-primary, #0f766e);
1270
+ font-weight: 650;
1271
+ }
1272
+ .proposal-choices .choice-chip md-icon {
1273
+ --md-icon-size: 13px;
1274
+ color: inherit;
1275
+ }
1095
1276
 
1096
1277
  /* ── 접지 경고 — 근거에 없는 대상을 지목한 답 ───── */
1097
1278
  .grounding-warning {
@@ -1394,10 +1575,156 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1394
1575
 
1395
1576
  /* ── Composer (actions + input 통합 영역) ──────────── */
1396
1577
  .composer {
1578
+ position: relative;
1397
1579
  border-top: 1px solid var(--md-sys-color-outline-variant, #f1f5f9);
1398
1580
  background: var(--md-sys-color-surface, #ffffff);
1399
1581
  display: flex;
1400
1582
  flex-direction: column;
1583
+ transition: background 0.15s, border-color 0.15s;
1584
+ }
1585
+ .composer.drag-over {
1586
+ background: var(--md-sys-color-surface-container, #f1f5f9);
1587
+ border-top-color: var(--md-sys-color-primary, #0284c7);
1588
+ }
1589
+ .drag-drop-overlay {
1590
+ position: absolute;
1591
+ top: 0;
1592
+ left: 0;
1593
+ right: 0;
1594
+ bottom: 0;
1595
+ background: rgba(255, 255, 255, 0.94);
1596
+ backdrop-filter: blur(2px);
1597
+ z-index: 10;
1598
+ display: flex;
1599
+ flex-direction: column;
1600
+ align-items: center;
1601
+ justify-content: center;
1602
+ gap: 6px;
1603
+ border: 2px dashed var(--md-sys-color-primary, #0284c7);
1604
+ border-radius: 4px;
1605
+ color: var(--md-sys-color-primary, #0284c7);
1606
+ font-size: 13px;
1607
+ font-weight: 500;
1608
+ pointer-events: none;
1609
+ }
1610
+ .drag-drop-overlay md-icon {
1611
+ --md-icon-size: 28px;
1612
+ }
1613
+ .attachment-strip {
1614
+ display: flex;
1615
+ flex-wrap: wrap;
1616
+ gap: 6px;
1617
+ padding: 6px 14px 2px;
1618
+ max-height: 120px;
1619
+ overflow-y: auto;
1620
+ }
1621
+ .attachment-chip {
1622
+ display: flex;
1623
+ align-items: center;
1624
+ gap: 6px;
1625
+ background: var(--md-sys-color-surface-container, #f1f5f9);
1626
+ border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
1627
+ border-radius: 8px;
1628
+ padding: 3px 6px 3px 4px;
1629
+ max-width: 220px;
1630
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1631
+ }
1632
+ .attachment-thumb {
1633
+ width: 28px;
1634
+ height: 28px;
1635
+ object-fit: cover;
1636
+ border-radius: 4px;
1637
+ flex: none;
1638
+ }
1639
+ .attachment-icon {
1640
+ --md-icon-size: 20px;
1641
+ color: var(--md-sys-color-primary, #0284c7);
1642
+ flex: none;
1643
+ }
1644
+ .attachment-meta {
1645
+ display: flex;
1646
+ flex-direction: column;
1647
+ min-width: 0;
1648
+ flex: 1;
1649
+ }
1650
+ .attachment-name {
1651
+ font-size: 11px;
1652
+ font-weight: 500;
1653
+ color: var(--md-sys-color-on-surface, #0f172a);
1654
+ overflow: hidden;
1655
+ text-overflow: ellipsis;
1656
+ white-space: nowrap;
1657
+ }
1658
+ .attachment-size {
1659
+ font-size: 9px;
1660
+ color: var(--md-sys-color-outline, #64748b);
1661
+ }
1662
+ .attachment-remove {
1663
+ all: unset;
1664
+ display: flex;
1665
+ align-items: center;
1666
+ justify-content: center;
1667
+ width: 18px;
1668
+ height: 18px;
1669
+ border-radius: 50%;
1670
+ cursor: pointer;
1671
+ color: var(--md-sys-color-outline, #64748b);
1672
+ transition: background 0.15s, color 0.15s;
1673
+ flex: none;
1674
+ }
1675
+ .attachment-remove:hover {
1676
+ background: var(--md-sys-color-surface-container-highest, #cbd5e1);
1677
+ color: var(--md-sys-color-error, #ef4444);
1678
+ }
1679
+ .attachment-remove md-icon {
1680
+ --md-icon-size: 14px;
1681
+ }
1682
+ .msg-attachments {
1683
+ display: flex;
1684
+ flex-wrap: wrap;
1685
+ gap: 8px;
1686
+ margin-top: 8px;
1687
+ }
1688
+ .msg-attachment-img-wrap {
1689
+ border-radius: 8px;
1690
+ overflow: hidden;
1691
+ border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
1692
+ background: var(--md-sys-color-surface-container-lowest, #ffffff);
1693
+ max-width: 240px;
1694
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
1695
+ display: flex;
1696
+ flex-direction: column;
1697
+ }
1698
+ .msg-attachment-img-wrap img {
1699
+ max-width: 100%;
1700
+ max-height: 160px;
1701
+ object-fit: cover;
1702
+ display: block;
1703
+ }
1704
+ .msg-attachment-label {
1705
+ padding: 4px 8px;
1706
+ font-size: 10px;
1707
+ color: var(--md-sys-color-on-surface-variant, #64748b);
1708
+ background: var(--md-sys-color-surface-container, #f8fafc);
1709
+ overflow: hidden;
1710
+ text-overflow: ellipsis;
1711
+ white-space: nowrap;
1712
+ border-top: 1px solid var(--md-sys-color-outline-variant, #f1f5f9);
1713
+ }
1714
+ .msg-attachment-file-chip {
1715
+ display: flex;
1716
+ align-items: center;
1717
+ gap: 6px;
1718
+ padding: 6px 10px;
1719
+ border-radius: 8px;
1720
+ background: var(--md-sys-color-surface-container, #f8fafc);
1721
+ border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
1722
+ font-size: 11px;
1723
+ color: var(--md-sys-color-on-surface, #0f172a);
1724
+ }
1725
+ .msg-attachment-file-chip md-icon {
1726
+ --md-icon-size: 18px;
1727
+ color: var(--md-sys-color-primary, #0284c7);
1401
1728
  }
1402
1729
 
1403
1730
  /* ── Mini action row (메시지 시작 후) ──────────────── */
@@ -1824,6 +2151,25 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1824
2151
  >${content}</span
1825
2152
  >`
1826
2153
  : content}
2154
+ ${line.attachments && line.attachments.length > 0
2155
+ ? html `
2156
+ <div class="msg-attachments">
2157
+ ${line.attachments.map(att => att.type?.startsWith('image/') || att.url?.startsWith('data:image/') || att.base64
2158
+ ? html `
2159
+ <div class="msg-attachment-img-wrap" title=${att.name}>
2160
+ <img src=${att.url || att.base64} alt=${att.name} />
2161
+ <span class="msg-attachment-label">${att.name}</span>
2162
+ </div>
2163
+ `
2164
+ : html `
2165
+ <div class="msg-attachment-file-chip" title=${att.name}>
2166
+ <md-icon>description</md-icon>
2167
+ <span>${att.name}</span>
2168
+ </div>
2169
+ `)}
2170
+ </div>
2171
+ `
2172
+ : nothing}
1827
2173
  ${line.patchId
1828
2174
  ? html `
1829
2175
  <div class="summary ${this.revertedPatchIds.has(line.patchId) ? 'reverted' : ''}">
@@ -1874,11 +2220,47 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1874
2220
  `
1875
2221
  : nothing}
1876
2222
  ${this.renderFoldNotice()}
1877
- <div class="composer ${hasMessages ? '' : 'no-actions'}">
1878
- ${hasMessages
2223
+ <div
2224
+ class="composer ${this.dragOver ? 'drag-over' : ''}"
2225
+ @dragover=${(e) => this.onComposerDragOver(e)}
2226
+ @dragleave=${(e) => this.onComposerDragLeave(e)}
2227
+ @drop=${(e) => this.onComposerDrop(e)}
2228
+ @paste=${(e) => this.onComposerPaste(e)}>
2229
+ ${this.dragOver
2230
+ ? html `
2231
+ <div class="drag-drop-overlay">
2232
+ <md-icon>cloud_upload</md-icon>
2233
+ <span>${i18next.t('ai-assistant.text.drop-files-here', { defaultValue: '여기에 이미지나 파일을 놓으세요' })}</span>
2234
+ </div>
2235
+ `
2236
+ : nothing}
2237
+ <div class="actions-row">
2238
+ <input
2239
+ id="chat-file-input"
2240
+ type="file"
2241
+ multiple
2242
+ accept="image/*,.json,.txt,.csv"
2243
+ style="display: none"
2244
+ @change=${(e) => {
2245
+ const input = e.target;
2246
+ if (input.files?.length) {
2247
+ this.onAttachFiles(input.files);
2248
+ input.value = '';
2249
+ }
2250
+ }}
2251
+ />
2252
+ <button
2253
+ type="button"
2254
+ class="action"
2255
+ @click=${() => this.renderRoot.querySelector('#chat-file-input')?.click()}
2256
+ title=${i18next.t('ai-assistant.tooltip.attach-file', { defaultValue: '이미지 또는 파일 첨부 (클립보드 붙여넣기·드래그앤드롭 지원)' })}>
2257
+ <md-icon>attach_file</md-icon>
2258
+ ${i18next.t('ai-assistant.button.attach', { defaultValue: '첨부' })}
2259
+ </button>
2260
+ ${hasMessages
1879
2261
  ? html `
1880
- <div class="actions-row">
1881
2262
  <button
2263
+ type="button"
1882
2264
  class="action"
1883
2265
  @click=${() => (this.examplesOpen = !this.examplesOpen)}
1884
2266
  title=${i18next.t('ai-assistant.text.show-examples-tooltip')}>
@@ -1887,8 +2269,32 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1887
2269
  ? i18next.t('ai-assistant.button.close')
1888
2270
  : i18next.t('ai-assistant.button.examples')}
1889
2271
  </button>
2272
+ `
2273
+ : nothing}
2274
+ </div>
2275
+ ${this.examplesOpen ? this.renderInlineExamples() : nothing}
2276
+ ${this.attachments.length > 0
2277
+ ? html `
2278
+ <div class="attachment-strip">
2279
+ ${this.attachments.map(att => html `
2280
+ <div class="attachment-chip" title=${att.name}>
2281
+ ${att.type.startsWith('image/')
2282
+ ? html `<img class="attachment-thumb" src=${att.url} alt=${att.name} />`
2283
+ : html `<md-icon class="attachment-icon">description</md-icon>`}
2284
+ <div class="attachment-meta">
2285
+ <span class="attachment-name">${att.name}</span>
2286
+ <span class="attachment-size">${formatFileSize(att.size)}</span>
2287
+ </div>
2288
+ <button
2289
+ type="button"
2290
+ class="attachment-remove"
2291
+ title=${i18next.t('ai-assistant.button.remove-attachment', { defaultValue: '첨부 삭제' })}
2292
+ @click=${() => this.removeAttachment(att.id)}>
2293
+ <md-icon>close</md-icon>
2294
+ </button>
2295
+ </div>
2296
+ `)}
1890
2297
  </div>
1891
- ${this.examplesOpen ? this.renderInlineExamples() : nothing}
1892
2298
  `
1893
2299
  : nothing}
1894
2300
  <div class="input-row">
@@ -1925,7 +2331,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1925
2331
  : nothing}
1926
2332
  </div>
1927
2333
  <button
1928
- ?disabled=${this.busy || !this.input.trim()}
2334
+ ?disabled=${this.busy || (!this.input.trim() && this.attachments.length === 0)}
1929
2335
  @click=${this.send}
1930
2336
  title=${i18next.t('ai-assistant.text.send-tooltip')}>
1931
2337
  ${this.busy
@@ -2200,7 +2606,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2200
2606
  get exampleGroups() {
2201
2607
  if (this.examples?.length)
2202
2608
  return this.examples;
2203
- return OxBoardAIChat_1.EXAMPLE_GROUPS.map(group => ({
2609
+ return OxAssistantChat_1.EXAMPLE_GROUPS.map(group => ({
2204
2610
  label: i18next.t(group.labelKey),
2205
2611
  items: group.itemKeys.map(key => i18next.t(key))
2206
2612
  }));
@@ -2461,13 +2867,26 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2461
2867
  ${items.map(p => {
2462
2868
  const key = this.proposalKey(p);
2463
2869
  const sent = this.sentProposals.has(key);
2870
+ /*
2871
+ Follow-up chips are the host's words, never this component's. The fallback that used
2872
+ to live here named a 3D modeller's operations in hardcoded Korean, and it reached the
2873
+ plant and twin chats too.
2874
+ */
2875
+ const rawChoices = Array.isArray(p.choices) ? p.choices : (chatDefaults.proposalChoices ?? []);
2464
2876
  return html `
2465
2877
  <div class="proposal ${sent ? 'sent' : ''}">
2466
- <md-icon>bolt</md-icon>
2878
+ <md-icon>${p.icon || (p.source ? 'view_in_ar' : 'bolt')}</md-icon>
2467
2879
  <span class="pbody">
2468
2880
  <span class="plabel">${p.label || p.command}</span>
2469
2881
  ${p.reason ? html `<span class="preason">${p.reason}</span>` : nothing}
2470
2882
  </span>
2883
+ ${ /*
2884
+ The run button and the follow-up chips answer different questions — "is there
2885
+ something to apply" and "what else could I ask for" — and they used to share one
2886
+ ternary on `rawChoices.length`. A proposal that carried chips lost its button, so
2887
+ the one action the card exists for became unreachable while the model kept
2888
+ telling people to press it.
2889
+ */''}
2471
2890
  <button
2472
2891
  class="run"
2473
2892
  ?disabled=${sent}
@@ -2475,12 +2894,63 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2475
2894
  ${sent ? i18next.t('ai-assistant.text.proposal-sent', { defaultValue: '실행 요청됨' })
2476
2895
  : i18next.t('ai-assistant.button.run-proposal', { defaultValue: '실행' })}
2477
2896
  </button>
2897
+ ${ /*
2898
+ Drawn only where the host has said it staged this candidate, in the host's own
2899
+ words. Not knowing is not a claim: with no word from the host, nothing is drawn.
2900
+ This badge used to read "there are chips" as "the model is in the 3D viewer" and
2901
+ say so, in a component that plant and twin also use.
2902
+ */''}
2903
+ ${this.stagedProposals.has(key) && chatDefaults.stagedNoticeKey ? html `
2904
+ <span class="staged-badge">
2905
+ <md-icon>check_circle</md-icon>
2906
+ <span>${i18next.t(chatDefaults.stagedNoticeKey)}</span>
2907
+ </span>
2908
+ ` : nothing}
2478
2909
  </div>
2910
+ ${rawChoices.length ? html `
2911
+ <div class="proposal-choices" role="group" aria-label="대화 선택지">
2912
+ ${rawChoices.map((c) => html `
2913
+ <button
2914
+ class="choice-chip ${c.primary ? 'primary' : ''}"
2915
+ title=${c.prompt || c.label}
2916
+ @click=${() => this.onChoiceClick(c, p, key)}>
2917
+ ${c.icon ? html `<md-icon>${c.icon}</md-icon>` : nothing}
2918
+ <span>${c.label}</span>
2919
+ </button>
2920
+ `)}
2921
+ </div>
2922
+ ` : nothing}
2479
2923
  `;
2480
2924
  })}
2481
2925
  </div>
2482
2926
  `;
2483
2927
  }
2928
+ onChoiceClick(choice, proposal, key) {
2929
+ if (choice.action === 'save') {
2930
+ this.dispatchEvent(new CustomEvent('assistant-choice', {
2931
+ detail: { action: 'save', choice, proposal, sessionId: this.sessionId },
2932
+ bubbles: true,
2933
+ composed: true
2934
+ }));
2935
+ this.executeProposal(proposal, key);
2936
+ return;
2937
+ }
2938
+ if (choice.action === 'prompt' && choice.prompt) {
2939
+ this.input = choice.prompt;
2940
+ this.dispatchEvent(new CustomEvent('assistant-choice', {
2941
+ detail: { action: 'prompt', choice, proposal, sessionId: this.sessionId },
2942
+ bubbles: true,
2943
+ composed: true
2944
+ }));
2945
+ void this.send();
2946
+ return;
2947
+ }
2948
+ this.dispatchEvent(new CustomEvent('assistant-choice', {
2949
+ detail: { action: choice.action || 'custom', choice, proposal, sessionId: this.sessionId },
2950
+ bubbles: true,
2951
+ composed: true
2952
+ }));
2953
+ }
2484
2954
  /**
2485
2955
  * 제안 식별 키 — 같은 조치를 두 번 실행하지 않도록 **효과**(명령·대상·인자)로만 만든다.
2486
2956
  *
@@ -2512,7 +2982,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2512
2982
  */
2513
2983
  executeProposal(p, key) {
2514
2984
  this.sentProposals = new Set(this.sentProposals).add(key);
2515
- this.dispatchEvent(new CustomEvent('board-ai-proposal-execute', {
2985
+ this.dispatchEvent(new CustomEvent('assistant-proposal-execute', {
2516
2986
  detail: { proposal: p, sessionId: this.sessionId },
2517
2987
  bubbles: true,
2518
2988
  composed: true
@@ -2777,8 +3247,15 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2777
3247
  }
2778
3248
  async send() {
2779
3249
  const text = this.input.trim();
2780
- if (!text || this.busy)
3250
+ const attachmentsToSend = [...this.attachments];
3251
+ if ((!text && attachmentsToSend.length === 0) || this.busy)
2781
3252
  return;
3253
+ this.attachments = [];
3254
+ const promptText = text || (attachmentsToSend.length > 0
3255
+ ? (attachmentsToSend[0].type.startsWith('image/')
3256
+ ? '첨부된 이미지를 참고하여 제안해 주세요.'
3257
+ : '첨부된 파일을 확인해 주세요.')
3258
+ : '');
2782
3259
  if (!this.sessionId && this.prepareSession) {
2783
3260
  this.busy = true;
2784
3261
  try {
@@ -2810,22 +3287,34 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2810
3287
  // 등장하는 것만 추출 — 지운 토큰 제외. inline 마커로 content 에 주입.
2811
3288
  const mentions = [];
2812
3289
  for (const [token, refid] of this.pickedMentions.entries()) {
2813
- if (text.includes(`#${token}`))
3290
+ if (promptText.includes(`#${token}`))
2814
3291
  mentions.push({ token, refid });
2815
3292
  }
2816
3293
  const userMentions = [];
2817
3294
  for (const [token, userId] of this.pickedUserMentions.entries()) {
2818
- if (text.includes(`@${token}`))
3295
+ if (promptText.includes(`@${token}`))
2819
3296
  userMentions.push({ token, userId });
2820
3297
  }
2821
- let enrichedContent = injectMentionRefids(text, mentions);
3298
+ let enrichedContent = injectMentionRefids(promptText, mentions);
2822
3299
  enrichedContent = injectMentionUserIds(enrichedContent, userMentions);
2823
3300
  // optimistic 라인에 임시 핸들 — 방송 에코와의 reconcile 을 index 비의존으로.
2824
3301
  const userLocalId = `local-${++this._localSeq}-u`;
2825
3302
  const asstLocalId = `local-${++this._localSeq}-a`;
2826
3303
  this.lines = [
2827
3304
  ...this.lines,
2828
- { role: 'user', content: enrichedContent, _localId: userLocalId, senderEmail: this._myEmail }
3305
+ {
3306
+ role: 'user',
3307
+ content: enrichedContent,
3308
+ attachments: attachmentsToSend.map(a => ({
3309
+ name: a.name,
3310
+ size: a.size,
3311
+ type: a.type,
3312
+ url: a.url,
3313
+ base64: a.base64
3314
+ })),
3315
+ _localId: userLocalId,
3316
+ senderEmail: this._myEmail
3317
+ }
2829
3318
  ];
2830
3319
  this.input = '';
2831
3320
  this.busy = true;
@@ -2833,6 +3322,24 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2833
3322
  // 가짜 pending 응답 표시 (UX 부드럽게)
2834
3323
  this.lines = [...this.lines, { role: 'assistant', content: '', pending: true, _localId: asstLocalId }];
2835
3324
  try {
3325
+ if (attachmentsToSend.length > 0) {
3326
+ requestContext.hostContext = {
3327
+ ...requestContext.hostContext,
3328
+ attachments: attachmentsToSend.map(a => ({
3329
+ name: a.name,
3330
+ size: a.size,
3331
+ mediaType: a.type,
3332
+ data: a.base64
3333
+ })),
3334
+ image: attachmentsToSend.find(a => a.type.startsWith('image/'))
3335
+ ? {
3336
+ name: attachmentsToSend.find(a => a.type.startsWith('image/')).name,
3337
+ mediaType: attachmentsToSend.find(a => a.type.startsWith('image/')).type,
3338
+ data: attachmentsToSend.find(a => a.type.startsWith('image/')).base64
3339
+ }
3340
+ : undefined
3341
+ };
3342
+ }
2836
3343
  // LLM 으로 보낼 history — user/assistant 만 (system 은 백엔드가 자동 합류).
2837
3344
  // pending placeholder 와 system 은 제외 (index 비의존 — 방송 append 와 무관).
2838
3345
  const history = this.lines
@@ -2895,6 +3402,18 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2895
3402
  proposals: Array.isArray(out.proposals) ? out.proposals : undefined,
2896
3403
  pending: false
2897
3404
  });
3405
+ // 제안 자동 프리뷰 전파 (3D 뷰어 자동 반영)
3406
+ if (Array.isArray(out.proposals) && out.proposals.length > 0) {
3407
+ for (const p of out.proposals) {
3408
+ if (p?.source || p?.autoStage) {
3409
+ this.dispatchEvent(new CustomEvent('assistant-proposal-preview', {
3410
+ detail: { proposal: p, sessionId: out.sessionId },
3411
+ bubbles: true,
3412
+ composed: true
3413
+ }));
3414
+ }
3415
+ }
3416
+ }
2898
3417
  // 호스트로 patch 이벤트 전파
2899
3418
  if (out.patch) {
2900
3419
  this.dispatchEvent(new CustomEvent('board-edit-patch', {
@@ -2940,177 +3459,189 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2940
3459
  __decorate([
2941
3460
  property({ type: String, attribute: 'session-id' }),
2942
3461
  __metadata("design:type", String)
2943
- ], OxBoardAIChat.prototype, "sessionId", void 0);
3462
+ ], OxAssistantChat.prototype, "sessionId", void 0);
2944
3463
  __decorate([
2945
3464
  property({ attribute: false }),
2946
3465
  __metadata("design:type", Function)
2947
- ], OxBoardAIChat.prototype, "prepareSession", void 0);
3466
+ ], OxAssistantChat.prototype, "prepareSession", void 0);
2948
3467
  __decorate([
2949
3468
  property({ type: Array, attribute: false }),
2950
3469
  __metadata("design:type", Array)
2951
- ], OxBoardAIChat.prototype, "sessions", void 0);
3470
+ ], OxAssistantChat.prototype, "sessions", void 0);
2952
3471
  __decorate([
2953
3472
  property({ type: Object }),
2954
3473
  __metadata("design:type", Object)
2955
- ], OxBoardAIChat.prototype, "currentBoard", void 0);
3474
+ ], OxAssistantChat.prototype, "currentBoard", void 0);
2956
3475
  __decorate([
2957
3476
  property({ attribute: false }),
2958
3477
  __metadata("design:type", Function)
2959
- ], OxBoardAIChat.prototype, "boardProvider", void 0);
3478
+ ], OxAssistantChat.prototype, "boardProvider", void 0);
2960
3479
  __decorate([
2961
3480
  property({ attribute: false }),
2962
3481
  __metadata("design:type", Function)
2963
- ], OxBoardAIChat.prototype, "searchProvider", void 0);
3482
+ ], OxAssistantChat.prototype, "searchProvider", void 0);
2964
3483
  __decorate([
2965
3484
  property({ attribute: false }),
2966
3485
  __metadata("design:type", Function)
2967
- ], OxBoardAIChat.prototype, "userProvider", void 0);
3486
+ ], OxAssistantChat.prototype, "userProvider", void 0);
2968
3487
  __decorate([
2969
3488
  property({ type: Array, attribute: false }),
2970
3489
  __metadata("design:type", Array)
2971
- ], OxBoardAIChat.prototype, "selectedRefids", void 0);
3490
+ ], OxAssistantChat.prototype, "selectedRefids", void 0);
2972
3491
  __decorate([
2973
3492
  property({ type: Array, attribute: false }),
2974
3493
  __metadata("design:type", Array)
2975
- ], OxBoardAIChat.prototype, "toolCategories", void 0);
3494
+ ], OxAssistantChat.prototype, "toolCategories", void 0);
2976
3495
  __decorate([
2977
3496
  property({ type: Object, attribute: false }),
2978
3497
  __metadata("design:type", Object)
2979
- ], OxBoardAIChat.prototype, "hostContext", void 0);
3498
+ ], OxAssistantChat.prototype, "hostContext", void 0);
2980
3499
  __decorate([
2981
3500
  property({ type: Boolean, attribute: 'board-tools' }),
2982
3501
  __metadata("design:type", Object)
2983
- ], OxBoardAIChat.prototype, "boardTools", void 0);
3502
+ ], OxAssistantChat.prototype, "boardTools", void 0);
2984
3503
  __decorate([
2985
3504
  property({ type: String, attribute: 'chat-endpoint' }),
2986
3505
  __metadata("design:type", String)
2987
- ], OxBoardAIChat.prototype, "chatEndpoint", void 0);
3506
+ ], OxAssistantChat.prototype, "chatEndpoint", void 0);
2988
3507
  __decorate([
2989
3508
  property({ type: String, attribute: 'system-prompt' }),
2990
3509
  __metadata("design:type", String)
2991
- ], OxBoardAIChat.prototype, "systemPrompt", void 0);
3510
+ ], OxAssistantChat.prototype, "systemPrompt", void 0);
2992
3511
  __decorate([
2993
3512
  property({ type: Boolean, attribute: 'require-grounding-tools' }),
2994
3513
  __metadata("design:type", Object)
2995
- ], OxBoardAIChat.prototype, "requireGroundingTools", void 0);
3514
+ ], OxAssistantChat.prototype, "requireGroundingTools", void 0);
2996
3515
  __decorate([
2997
3516
  property({ type: Array }),
2998
3517
  __metadata("design:type", Array)
2999
- ], OxBoardAIChat.prototype, "scopes", void 0);
3518
+ ], OxAssistantChat.prototype, "scopes", void 0);
3000
3519
  __decorate([
3001
3520
  property({ type: Array, attribute: 'known-types' }),
3002
3521
  __metadata("design:type", Array)
3003
- ], OxBoardAIChat.prototype, "knownTypes", void 0);
3522
+ ], OxAssistantChat.prototype, "knownTypes", void 0);
3004
3523
  __decorate([
3005
3524
  property({ type: Array }),
3006
3525
  __metadata("design:type", Array)
3007
- ], OxBoardAIChat.prototype, "categories", void 0);
3526
+ ], OxAssistantChat.prototype, "categories", void 0);
3008
3527
  __decorate([
3009
3528
  property({ type: Array, attribute: false }),
3010
3529
  __metadata("design:type", Array)
3011
- ], OxBoardAIChat.prototype, "componentSchemas", void 0);
3530
+ ], OxAssistantChat.prototype, "componentSchemas", void 0);
3012
3531
  __decorate([
3013
3532
  property({ type: String }),
3014
3533
  __metadata("design:type", Object)
3015
- ], OxBoardAIChat.prototype, "placeholder", void 0);
3534
+ ], OxAssistantChat.prototype, "placeholder", void 0);
3016
3535
  __decorate([
3017
3536
  state(),
3018
3537
  __metadata("design:type", Array)
3019
- ], OxBoardAIChat.prototype, "lines", void 0);
3538
+ ], OxAssistantChat.prototype, "lines", void 0);
3020
3539
  __decorate([
3021
3540
  state(),
3022
3541
  __metadata("design:type", Object)
3023
- ], OxBoardAIChat.prototype, "input", void 0);
3542
+ ], OxAssistantChat.prototype, "input", void 0);
3024
3543
  __decorate([
3025
3544
  property({ attribute: false }),
3026
3545
  __metadata("design:type", String)
3027
- ], OxBoardAIChat.prototype, "autoAsk", void 0);
3546
+ ], OxAssistantChat.prototype, "autoAsk", void 0);
3028
3547
  __decorate([
3029
3548
  property(),
3030
3549
  __metadata("design:type", String)
3031
- ], OxBoardAIChat.prototype, "autoAskRequestId", void 0);
3550
+ ], OxAssistantChat.prototype, "autoAskRequestId", void 0);
3032
3551
  __decorate([
3033
3552
  property({ attribute: false }),
3034
3553
  __metadata("design:type", Function)
3035
- ], OxBoardAIChat.prototype, "contextProvider", void 0);
3554
+ ], OxAssistantChat.prototype, "contextProvider", void 0);
3036
3555
  __decorate([
3037
3556
  property({ type: Object, attribute: false }),
3038
3557
  __metadata("design:type", Object)
3039
- ], OxBoardAIChat.prototype, "intro", void 0);
3558
+ ], OxAssistantChat.prototype, "intro", void 0);
3040
3559
  __decorate([
3041
3560
  property({ type: Array, attribute: false }),
3042
3561
  __metadata("design:type", Array)
3043
- ], OxBoardAIChat.prototype, "examples", void 0);
3562
+ ], OxAssistantChat.prototype, "examples", void 0);
3044
3563
  __decorate([
3045
3564
  state(),
3046
3565
  __metadata("design:type", String)
3047
- ], OxBoardAIChat.prototype, "lastFailedInput", void 0);
3566
+ ], OxAssistantChat.prototype, "lastFailedInput", void 0);
3567
+ __decorate([
3568
+ state(),
3569
+ __metadata("design:type", Object)
3570
+ ], OxAssistantChat.prototype, "revertedPatchIds", void 0);
3571
+ __decorate([
3572
+ state(),
3573
+ __metadata("design:type", Array)
3574
+ ], OxAssistantChat.prototype, "attachments", void 0);
3048
3575
  __decorate([
3049
3576
  state(),
3050
3577
  __metadata("design:type", Object)
3051
- ], OxBoardAIChat.prototype, "revertedPatchIds", void 0);
3578
+ ], OxAssistantChat.prototype, "dragOver", void 0);
3052
3579
  __decorate([
3053
3580
  state(),
3054
3581
  __metadata("design:type", Object)
3055
- ], OxBoardAIChat.prototype, "examplesOpen", void 0);
3582
+ ], OxAssistantChat.prototype, "examplesOpen", void 0);
3056
3583
  __decorate([
3057
3584
  state(),
3058
3585
  __metadata("design:type", String)
3059
- ], OxBoardAIChat.prototype, "toastMessage", void 0);
3586
+ ], OxAssistantChat.prototype, "toastMessage", void 0);
3060
3587
  __decorate([
3061
3588
  state(),
3062
3589
  __metadata("design:type", Number)
3063
- ], OxBoardAIChat.prototype, "copiedIdx", void 0);
3590
+ ], OxAssistantChat.prototype, "copiedIdx", void 0);
3591
+ __decorate([
3592
+ state(),
3593
+ __metadata("design:type", Object)
3594
+ ], OxAssistantChat.prototype, "openToolSteps", void 0);
3064
3595
  __decorate([
3065
3596
  state(),
3066
3597
  __metadata("design:type", Object)
3067
- ], OxBoardAIChat.prototype, "openToolSteps", void 0);
3598
+ ], OxAssistantChat.prototype, "sentProposals", void 0);
3068
3599
  __decorate([
3069
3600
  state(),
3070
3601
  __metadata("design:type", Object)
3071
- ], OxBoardAIChat.prototype, "sentProposals", void 0);
3602
+ ], OxAssistantChat.prototype, "stagedProposals", void 0);
3072
3603
  __decorate([
3073
3604
  state(),
3074
3605
  __metadata("design:type", Object)
3075
- ], OxBoardAIChat.prototype, "historyFolded", void 0);
3606
+ ], OxAssistantChat.prototype, "historyFolded", void 0);
3076
3607
  __decorate([
3077
3608
  state(),
3078
3609
  __metadata("design:type", Object)
3079
- ], OxBoardAIChat.prototype, "foldNoticeDismissed", void 0);
3610
+ ], OxAssistantChat.prototype, "foldNoticeDismissed", void 0);
3080
3611
  __decorate([
3081
3612
  state(),
3082
3613
  __metadata("design:type", Object)
3083
- ], OxBoardAIChat.prototype, "busy", void 0);
3614
+ ], OxAssistantChat.prototype, "busy", void 0);
3084
3615
  __decorate([
3085
3616
  state(),
3086
3617
  __metadata("design:type", String)
3087
- ], OxBoardAIChat.prototype, "errorMessage", void 0);
3618
+ ], OxAssistantChat.prototype, "errorMessage", void 0);
3088
3619
  __decorate([
3089
3620
  state(),
3090
3621
  __metadata("design:type", Array)
3091
- ], OxBoardAIChat.prototype, "participants", void 0);
3622
+ ], OxAssistantChat.prototype, "participants", void 0);
3092
3623
  __decorate([
3093
3624
  state(),
3094
3625
  __metadata("design:type", Object)
3095
- ], OxBoardAIChat.prototype, "_presenceTick", void 0);
3626
+ ], OxAssistantChat.prototype, "_presenceTick", void 0);
3096
3627
  __decorate([
3097
3628
  state(),
3098
3629
  __metadata("design:type", Object)
3099
- ], OxBoardAIChat.prototype, "mentionOpen", void 0);
3630
+ ], OxAssistantChat.prototype, "mentionOpen", void 0);
3100
3631
  __decorate([
3101
3632
  state(),
3102
3633
  __metadata("design:type", Array)
3103
- ], OxBoardAIChat.prototype, "mentionResults", void 0);
3634
+ ], OxAssistantChat.prototype, "mentionResults", void 0);
3104
3635
  __decorate([
3105
3636
  state(),
3106
3637
  __metadata("design:type", Object)
3107
- ], OxBoardAIChat.prototype, "mentionActiveIndex", void 0);
3638
+ ], OxAssistantChat.prototype, "mentionActiveIndex", void 0);
3108
3639
  __decorate([
3109
3640
  state(),
3110
3641
  __metadata("design:type", Object)
3111
- ], OxBoardAIChat.prototype, "mentionOverflow", void 0);
3112
- OxBoardAIChat = OxBoardAIChat_1 = __decorate([
3113
- customElement('ox-board-ai-chat')
3114
- ], OxBoardAIChat);
3115
- export { OxBoardAIChat };
3116
- //# sourceMappingURL=board-ai-chat.js.map
3642
+ ], OxAssistantChat.prototype, "mentionOverflow", void 0);
3643
+ OxAssistantChat = OxAssistantChat_1 = __decorate([
3644
+ customElement('ox-assistant-chat')
3645
+ ], OxAssistantChat);
3646
+ export { OxAssistantChat };
3647
+ //# sourceMappingURL=assistant-chat.js.map