@things-factory/ai-assistant 10.1.8 → 10.1.15

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 (58) hide show
  1. package/client/components/{board-ai-chat.ts → assistant-chat.ts} +597 -66
  2. package/client/components/assistant-session-toolbar.ts +70 -0
  3. package/client/components/chat-input-builder.ts +1 -1
  4. package/client/index.ts +6 -3
  5. package/client/utils/assistant-request-context.ts +21 -0
  6. package/client/utils/assistant-session-controller.ts +166 -0
  7. package/client/utils/assistant-session-transport.ts +34 -0
  8. package/dist-client/components/{board-ai-chat.d.ts → assistant-chat.d.ts} +30 -3
  9. package/dist-client/components/{board-ai-chat.js → assistant-chat.js} +616 -109
  10. package/dist-client/components/assistant-chat.js.map +1 -0
  11. package/dist-client/components/{board-ai-chat.test.js → assistant-chat.test.js} +1 -1
  12. package/dist-client/components/assistant-chat.test.js.map +1 -0
  13. package/dist-client/components/assistant-session-toolbar.d.ts +18 -0
  14. package/dist-client/components/assistant-session-toolbar.js +109 -0
  15. package/dist-client/components/assistant-session-toolbar.js.map +1 -0
  16. package/dist-client/components/chat-input-builder.js +1 -1
  17. package/dist-client/components/chat-input-builder.js.map +1 -1
  18. package/dist-client/index.d.ts +6 -3
  19. package/dist-client/index.js +6 -3
  20. package/dist-client/index.js.map +1 -1
  21. package/dist-client/tsconfig.tsbuildinfo +1 -1
  22. package/dist-client/utils/assistant-request-context.d.ts +10 -0
  23. package/dist-client/utils/assistant-request-context.js +20 -0
  24. package/dist-client/utils/assistant-request-context.js.map +1 -0
  25. package/dist-client/utils/assistant-session-controller.d.ts +49 -0
  26. package/dist-client/utils/assistant-session-controller.js +165 -0
  27. package/dist-client/utils/assistant-session-controller.js.map +1 -0
  28. package/dist-client/utils/assistant-session-transport.d.ts +3 -0
  29. package/dist-client/utils/assistant-session-transport.js +39 -0
  30. package/dist-client/utils/assistant-session-transport.js.map +1 -0
  31. package/dist-server/service/assistant-chat-resolver.js +20 -0
  32. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  33. package/dist-server/service/chat-session/chat-session.js +1 -1
  34. package/dist-server/service/chat-session/chat-session.js.map +1 -1
  35. package/dist-server/service/chat-session/session-anchor.d.ts +1 -1
  36. package/dist-server/service/chat-session/session-anchor.js +3 -1
  37. package/dist-server/service/chat-session/session-anchor.js.map +1 -1
  38. package/dist-server/service/chat-session/station-session-guard.d.ts +12 -0
  39. package/dist-server/service/chat-session/station-session-guard.js +20 -0
  40. package/dist-server/service/chat-session/station-session-guard.js.map +1 -0
  41. package/dist-server/tsconfig.tsbuildinfo +1 -1
  42. package/docs/session-controller.md +52 -0
  43. package/package.json +2 -2
  44. package/server/service/assistant-chat-resolver.ts +22 -0
  45. package/server/service/chat-session/chat-session.ts +1 -1
  46. package/server/service/chat-session/session-anchor.ts +3 -1
  47. package/server/service/chat-session/station-session-guard.ts +17 -0
  48. package/server/service/session-anchor.test.ts +11 -0
  49. package/server/service/station-session-guard.test.ts +24 -0
  50. package/test/assistant-request-context.test.ts +30 -0
  51. package/test/assistant-session-controller.test.ts +88 -0
  52. package/test/assistant-session-toolbar.test.ts +29 -0
  53. package/translations/en.json +12 -1
  54. package/translations/ko.json +12 -1
  55. package/dist-client/components/board-ai-chat.js.map +0 -1
  56. package/dist-client/components/board-ai-chat.test.js.map +0 -1
  57. /package/client/components/{board-ai-chat.test.ts → assistant-chat.test.ts} +0 -0
  58. /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 모드, 메시지 영속 안 됨)
@@ -16,6 +16,8 @@ import { __decorate, __metadata } from "tslib";
16
16
  * 모드 전환은 컨테이너 (워크스페이스) 의 책임. 이 컴포넌트는 자체로 풀 채팅 UX.
17
17
  */
18
18
  import '@material/web/icon/icon.js';
19
+ import './assistant-session-toolbar.js';
20
+ import { assistantAutoAskKey, assistantModelFingerprint } from '../utils/assistant-request-context.js';
19
21
  import { LitElement, css, html, nothing } from 'lit';
20
22
  import { customElement, property, state } from 'lit/decorators.js';
21
23
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
@@ -42,6 +44,13 @@ import { buildMentionCandidates, buildCatalogCandidates, buildSlashCandidates, b
42
44
  */
43
45
  import { chatDefaults } from './chat-defaults.js';
44
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
+ }
45
54
  const BOARD_AI_CHAT_MUTATION = gql `
46
55
  mutation BoardAIChat($input: BoardAIChatInput!) {
47
56
  boardAIChat(input: $input) {
@@ -152,9 +161,10 @@ function initials(name) {
152
161
  return (parts[0][0] + parts[1][0]).toUpperCase();
153
162
  return /[a-zA-Z]/.test(n) ? n.slice(0, 2).toUpperCase() : n.slice(0, 1);
154
163
  }
155
- let OxBoardAIChat = class OxBoardAIChat extends LitElement {
164
+ let OxAssistantChat = class OxAssistantChat extends LitElement {
156
165
  constructor() {
157
166
  super(...arguments);
167
+ this.adoptingSession = false;
158
168
  /**
159
169
  * 보드의 모든 ChatSession 리스트 — 다중 세션 활성화 시 탭 UI 로 표시.
160
170
  *
@@ -203,6 +213,10 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
203
213
  this.input = '';
204
214
  /** 되돌려진 patch id 들 — 한 번 revert 한 patch 는 버튼 비활성 */
205
215
  this.revertedPatchIds = new Set();
216
+ /** 첨부된 파일/이미지 목록 */
217
+ this.attachments = [];
218
+ /** 드래그 오버 상태 (시각 피드백용) */
219
+ this.dragOver = false;
206
220
  /** mini action — 인라인 예시 토글 */
207
221
  this.examplesOpen = false;
208
222
  /** 원본 JSON 을 펼친 단계(라인:단계) — 목록은 한 줄 요약으로 훑고 필요한 것만 펼친다. */
@@ -282,6 +296,8 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
282
296
  }
283
297
  };
284
298
  this._onSessionCreateClick = () => {
299
+ if (this.busy || this.lines.length === 0)
300
+ return;
285
301
  this.dispatchEvent(new CustomEvent('session-create', {
286
302
  bubbles: true,
287
303
  composed: true
@@ -293,10 +309,92 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
293
309
  */
294
310
  this.startNewFromNotice = () => {
295
311
  this.foldNoticeDismissed = true;
296
- this.dispatchEvent(new CustomEvent('board-ai-start-new-session', { bubbles: true, composed: true }));
312
+ this.dispatchEvent(new CustomEvent('assistant-start-new-session', { bubbles: true, composed: true }));
297
313
  };
298
314
  }
299
- static { OxBoardAIChat_1 = this; }
315
+ static { OxAssistantChat_1 = this; }
316
+ async onAttachFiles(files) {
317
+ if (!files || files.length === 0)
318
+ return;
319
+ const list = Array.from(files);
320
+ const newAttachments = [];
321
+ for (const file of list) {
322
+ const id = 'att-' + Math.random().toString(36).substring(2, 9);
323
+ const url = URL.createObjectURL(file);
324
+ let base64;
325
+ try {
326
+ base64 = await this.readFileAsBase64(file);
327
+ }
328
+ catch (err) {
329
+ console.warn('Failed to read file as base64', err);
330
+ }
331
+ newAttachments.push({
332
+ id,
333
+ file,
334
+ name: file.name,
335
+ size: file.size,
336
+ type: file.type || 'application/octet-stream',
337
+ url,
338
+ base64
339
+ });
340
+ }
341
+ this.attachments = [...this.attachments, ...newAttachments];
342
+ }
343
+ readFileAsBase64(file) {
344
+ return new Promise((resolve, reject) => {
345
+ const reader = new FileReader();
346
+ reader.onload = () => resolve(reader.result);
347
+ reader.onerror = reject;
348
+ reader.readAsDataURL(file);
349
+ });
350
+ }
351
+ removeAttachment(id) {
352
+ const target = this.attachments.find(a => a.id === id);
353
+ if (target?.url) {
354
+ URL.revokeObjectURL(target.url);
355
+ }
356
+ this.attachments = this.attachments.filter(a => a.id !== id);
357
+ }
358
+ onComposerPaste(e) {
359
+ const items = e.clipboardData?.items;
360
+ if (!items || items.length === 0)
361
+ return;
362
+ const files = [];
363
+ for (let i = 0; i < items.length; i++) {
364
+ const item = items[i];
365
+ if (item.kind === 'file') {
366
+ const file = item.getAsFile();
367
+ if (file)
368
+ files.push(file);
369
+ }
370
+ }
371
+ if (files.length > 0) {
372
+ this.onAttachFiles(files);
373
+ }
374
+ }
375
+ onComposerDragOver(e) {
376
+ e.preventDefault();
377
+ e.stopPropagation();
378
+ if (!this.dragOver)
379
+ this.dragOver = true;
380
+ }
381
+ onComposerDragLeave(e) {
382
+ e.preventDefault();
383
+ e.stopPropagation();
384
+ const currentTarget = e.currentTarget;
385
+ const relatedTarget = e.relatedTarget;
386
+ if (!currentTarget.contains(relatedTarget)) {
387
+ this.dragOver = false;
388
+ }
389
+ }
390
+ onComposerDrop(e) {
391
+ e.preventDefault();
392
+ e.stopPropagation();
393
+ this.dragOver = false;
394
+ if (e.dataTransfer?.files?.length) {
395
+ this.onAttachFiles(e.dataTransfer.files);
396
+ }
397
+ }
300
398
  /** 현재 로그인 사용자 email — 그룹챗에서 내 메시지 vs 타인 메시지 구분.
301
399
  * email 을 쓰는 이유: /auth/profile 이 항상 내려주는 로그인 식별자이고(비어 있지 않다),
302
400
  * 내부 관리키(User.id)를 클라이언트 판정에 의존하지 않기 위함. */
@@ -1087,6 +1185,63 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1087
1185
  .proposal.sent {
1088
1186
  border-left-color: var(--md-sys-color-outline, #94a3b8);
1089
1187
  }
1188
+ .proposal .staged-badge {
1189
+ display: inline-flex;
1190
+ align-items: center;
1191
+ gap: 3px;
1192
+ padding: 3px 8px;
1193
+ border-radius: 999px;
1194
+ background: rgba(15, 118, 110, 0.1);
1195
+ color: var(--md-sys-color-primary, #0f766e);
1196
+ font-size: 11px;
1197
+ font-weight: 600;
1198
+ flex-shrink: 0;
1199
+ }
1200
+ .proposal .staged-badge md-icon {
1201
+ --md-icon-size: 13px;
1202
+ color: inherit;
1203
+ }
1204
+
1205
+ .proposal-choices {
1206
+ display: flex;
1207
+ flex-wrap: wrap;
1208
+ gap: 6px;
1209
+ margin-top: 6px;
1210
+ padding: 6px 8px;
1211
+ background: var(--md-sys-color-surface-container-lowest, #ffffff);
1212
+ border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
1213
+ border-radius: 8px;
1214
+ }
1215
+ .proposal-choices .choice-chip {
1216
+ display: inline-flex;
1217
+ align-items: center;
1218
+ gap: 4px;
1219
+ padding: 4px 9px;
1220
+ border-radius: 999px;
1221
+ border: 1px solid var(--md-sys-color-outline-variant, #cbd5e1);
1222
+ background: var(--md-sys-color-surface-container-low, #f8fafc);
1223
+ color: var(--md-sys-color-on-surface, #1e293b);
1224
+ font-family: inherit;
1225
+ font-size: 11px;
1226
+ font-weight: 550;
1227
+ cursor: pointer;
1228
+ transition: all 0.14s ease-in-out;
1229
+ }
1230
+ .proposal-choices .choice-chip:hover {
1231
+ background: var(--md-sys-color-surface-container-high, #e2e8f0);
1232
+ border-color: var(--md-sys-color-primary, #0f766e);
1233
+ color: var(--md-sys-color-primary, #0f766e);
1234
+ }
1235
+ .proposal-choices .choice-chip.primary {
1236
+ background: var(--md-sys-color-primary-container, #ccfbf1);
1237
+ color: var(--md-sys-color-on-primary-container, #0f766e);
1238
+ border-color: var(--md-sys-color-primary, #0f766e);
1239
+ font-weight: 650;
1240
+ }
1241
+ .proposal-choices .choice-chip md-icon {
1242
+ --md-icon-size: 13px;
1243
+ color: inherit;
1244
+ }
1090
1245
 
1091
1246
  /* ── 접지 경고 — 근거에 없는 대상을 지목한 답 ───── */
1092
1247
  .grounding-warning {
@@ -1389,10 +1544,156 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1389
1544
 
1390
1545
  /* ── Composer (actions + input 통합 영역) ──────────── */
1391
1546
  .composer {
1547
+ position: relative;
1392
1548
  border-top: 1px solid var(--md-sys-color-outline-variant, #f1f5f9);
1393
1549
  background: var(--md-sys-color-surface, #ffffff);
1394
1550
  display: flex;
1395
1551
  flex-direction: column;
1552
+ transition: background 0.15s, border-color 0.15s;
1553
+ }
1554
+ .composer.drag-over {
1555
+ background: var(--md-sys-color-surface-container, #f1f5f9);
1556
+ border-top-color: var(--md-sys-color-primary, #0284c7);
1557
+ }
1558
+ .drag-drop-overlay {
1559
+ position: absolute;
1560
+ top: 0;
1561
+ left: 0;
1562
+ right: 0;
1563
+ bottom: 0;
1564
+ background: rgba(255, 255, 255, 0.94);
1565
+ backdrop-filter: blur(2px);
1566
+ z-index: 10;
1567
+ display: flex;
1568
+ flex-direction: column;
1569
+ align-items: center;
1570
+ justify-content: center;
1571
+ gap: 6px;
1572
+ border: 2px dashed var(--md-sys-color-primary, #0284c7);
1573
+ border-radius: 4px;
1574
+ color: var(--md-sys-color-primary, #0284c7);
1575
+ font-size: 13px;
1576
+ font-weight: 500;
1577
+ pointer-events: none;
1578
+ }
1579
+ .drag-drop-overlay md-icon {
1580
+ --md-icon-size: 28px;
1581
+ }
1582
+ .attachment-strip {
1583
+ display: flex;
1584
+ flex-wrap: wrap;
1585
+ gap: 6px;
1586
+ padding: 6px 14px 2px;
1587
+ max-height: 120px;
1588
+ overflow-y: auto;
1589
+ }
1590
+ .attachment-chip {
1591
+ display: flex;
1592
+ align-items: center;
1593
+ gap: 6px;
1594
+ background: var(--md-sys-color-surface-container, #f1f5f9);
1595
+ border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
1596
+ border-radius: 8px;
1597
+ padding: 3px 6px 3px 4px;
1598
+ max-width: 220px;
1599
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1600
+ }
1601
+ .attachment-thumb {
1602
+ width: 28px;
1603
+ height: 28px;
1604
+ object-fit: cover;
1605
+ border-radius: 4px;
1606
+ flex: none;
1607
+ }
1608
+ .attachment-icon {
1609
+ --md-icon-size: 20px;
1610
+ color: var(--md-sys-color-primary, #0284c7);
1611
+ flex: none;
1612
+ }
1613
+ .attachment-meta {
1614
+ display: flex;
1615
+ flex-direction: column;
1616
+ min-width: 0;
1617
+ flex: 1;
1618
+ }
1619
+ .attachment-name {
1620
+ font-size: 11px;
1621
+ font-weight: 500;
1622
+ color: var(--md-sys-color-on-surface, #0f172a);
1623
+ overflow: hidden;
1624
+ text-overflow: ellipsis;
1625
+ white-space: nowrap;
1626
+ }
1627
+ .attachment-size {
1628
+ font-size: 9px;
1629
+ color: var(--md-sys-color-outline, #64748b);
1630
+ }
1631
+ .attachment-remove {
1632
+ all: unset;
1633
+ display: flex;
1634
+ align-items: center;
1635
+ justify-content: center;
1636
+ width: 18px;
1637
+ height: 18px;
1638
+ border-radius: 50%;
1639
+ cursor: pointer;
1640
+ color: var(--md-sys-color-outline, #64748b);
1641
+ transition: background 0.15s, color 0.15s;
1642
+ flex: none;
1643
+ }
1644
+ .attachment-remove:hover {
1645
+ background: var(--md-sys-color-surface-container-highest, #cbd5e1);
1646
+ color: var(--md-sys-color-error, #ef4444);
1647
+ }
1648
+ .attachment-remove md-icon {
1649
+ --md-icon-size: 14px;
1650
+ }
1651
+ .msg-attachments {
1652
+ display: flex;
1653
+ flex-wrap: wrap;
1654
+ gap: 8px;
1655
+ margin-top: 8px;
1656
+ }
1657
+ .msg-attachment-img-wrap {
1658
+ border-radius: 8px;
1659
+ overflow: hidden;
1660
+ border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
1661
+ background: var(--md-sys-color-surface-container-lowest, #ffffff);
1662
+ max-width: 240px;
1663
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
1664
+ display: flex;
1665
+ flex-direction: column;
1666
+ }
1667
+ .msg-attachment-img-wrap img {
1668
+ max-width: 100%;
1669
+ max-height: 160px;
1670
+ object-fit: cover;
1671
+ display: block;
1672
+ }
1673
+ .msg-attachment-label {
1674
+ padding: 4px 8px;
1675
+ font-size: 10px;
1676
+ color: var(--md-sys-color-on-surface-variant, #64748b);
1677
+ background: var(--md-sys-color-surface-container, #f8fafc);
1678
+ overflow: hidden;
1679
+ text-overflow: ellipsis;
1680
+ white-space: nowrap;
1681
+ border-top: 1px solid var(--md-sys-color-outline-variant, #f1f5f9);
1682
+ }
1683
+ .msg-attachment-file-chip {
1684
+ display: flex;
1685
+ align-items: center;
1686
+ gap: 6px;
1687
+ padding: 6px 10px;
1688
+ border-radius: 8px;
1689
+ background: var(--md-sys-color-surface-container, #f8fafc);
1690
+ border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
1691
+ font-size: 11px;
1692
+ color: var(--md-sys-color-on-surface, #0f172a);
1693
+ }
1694
+ .msg-attachment-file-chip md-icon {
1695
+ --md-icon-size: 18px;
1696
+ color: var(--md-sys-color-primary, #0284c7);
1396
1697
  }
1397
1698
 
1398
1699
  /* ── Mini action row (메시지 시작 후) ──────────────── */
@@ -1601,12 +1902,17 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1601
1902
  * sessionId 가 붙기 전에 보내면 대화에 남지 않으므로 세션이 준비될 때까지 기다린다.
1602
1903
  * 같은 문장을 두 번 보내지 않도록 보낸 값을 기억한다 — 재렌더마다 updated 가 다시 돌기 때문이다.
1603
1904
  */
1604
- if (this.autoAsk && this.autoAsk !== this._autoAsked && this.sessionId && !this.busy) {
1605
- this._autoAsked = this.autoAsk;
1905
+ if (this.autoAsk && assistantAutoAskKey(this.autoAsk, this.autoAskRequestId) !== this._autoAsked && (this.sessionId || this.prepareSession) && !this.busy) {
1906
+ this._autoAsked = assistantAutoAskKey(this.autoAsk, this.autoAskRequestId);
1606
1907
  this.input = this.autoAsk;
1607
1908
  this.send();
1608
1909
  }
1609
- if (changed.has('sessionId')) {
1910
+ if (changed.has('sessionId') && this.adoptingSession) {
1911
+ this.adoptingSession = false;
1912
+ this._startPresence();
1913
+ this._startMessageSubscription();
1914
+ }
1915
+ else if (changed.has('sessionId')) {
1610
1916
  // sessionId 가 바뀌면 (set / unset / 다른 보드 전환) 이전 세션의 모든 클라이언트
1611
1917
  // 컨텍스트 정리 — lines / pickedMentions / errorMessage / input 모두 새지 않도록.
1612
1918
  // 보드 단위 격리 (ChatSession 1:1 boardId) 가 깨지는 회귀 방지.
@@ -1814,6 +2120,25 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1814
2120
  >${content}</span
1815
2121
  >`
1816
2122
  : content}
2123
+ ${line.attachments && line.attachments.length > 0
2124
+ ? html `
2125
+ <div class="msg-attachments">
2126
+ ${line.attachments.map(att => att.type?.startsWith('image/') || att.url?.startsWith('data:image/') || att.base64
2127
+ ? html `
2128
+ <div class="msg-attachment-img-wrap" title=${att.name}>
2129
+ <img src=${att.url || att.base64} alt=${att.name} />
2130
+ <span class="msg-attachment-label">${att.name}</span>
2131
+ </div>
2132
+ `
2133
+ : html `
2134
+ <div class="msg-attachment-file-chip" title=${att.name}>
2135
+ <md-icon>description</md-icon>
2136
+ <span>${att.name}</span>
2137
+ </div>
2138
+ `)}
2139
+ </div>
2140
+ `
2141
+ : nothing}
1817
2142
  ${line.patchId
1818
2143
  ? html `
1819
2144
  <div class="summary ${this.revertedPatchIds.has(line.patchId) ? 'reverted' : ''}">
@@ -1864,11 +2189,47 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1864
2189
  `
1865
2190
  : nothing}
1866
2191
  ${this.renderFoldNotice()}
1867
- <div class="composer ${hasMessages ? '' : 'no-actions'}">
1868
- ${hasMessages
2192
+ <div
2193
+ class="composer ${this.dragOver ? 'drag-over' : ''}"
2194
+ @dragover=${(e) => this.onComposerDragOver(e)}
2195
+ @dragleave=${(e) => this.onComposerDragLeave(e)}
2196
+ @drop=${(e) => this.onComposerDrop(e)}
2197
+ @paste=${(e) => this.onComposerPaste(e)}>
2198
+ ${this.dragOver
2199
+ ? html `
2200
+ <div class="drag-drop-overlay">
2201
+ <md-icon>cloud_upload</md-icon>
2202
+ <span>${i18next.t('ai-assistant.text.drop-files-here', { defaultValue: '여기에 이미지나 파일을 놓으세요' })}</span>
2203
+ </div>
2204
+ `
2205
+ : nothing}
2206
+ <div class="actions-row">
2207
+ <input
2208
+ id="chat-file-input"
2209
+ type="file"
2210
+ multiple
2211
+ accept="image/*,.json,.txt,.csv"
2212
+ style="display: none"
2213
+ @change=${(e) => {
2214
+ const input = e.target;
2215
+ if (input.files?.length) {
2216
+ this.onAttachFiles(input.files);
2217
+ input.value = '';
2218
+ }
2219
+ }}
2220
+ />
2221
+ <button
2222
+ type="button"
2223
+ class="action"
2224
+ @click=${() => this.renderRoot.querySelector('#chat-file-input')?.click()}
2225
+ title=${i18next.t('ai-assistant.tooltip.attach-file', { defaultValue: '이미지 또는 파일 첨부 (클립보드 붙여넣기·드래그앤드롭 지원)' })}>
2226
+ <md-icon>attach_file</md-icon>
2227
+ ${i18next.t('ai-assistant.button.attach', { defaultValue: '첨부' })}
2228
+ </button>
2229
+ ${hasMessages
1869
2230
  ? html `
1870
- <div class="actions-row">
1871
2231
  <button
2232
+ type="button"
1872
2233
  class="action"
1873
2234
  @click=${() => (this.examplesOpen = !this.examplesOpen)}
1874
2235
  title=${i18next.t('ai-assistant.text.show-examples-tooltip')}>
@@ -1877,8 +2238,32 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1877
2238
  ? i18next.t('ai-assistant.button.close')
1878
2239
  : i18next.t('ai-assistant.button.examples')}
1879
2240
  </button>
2241
+ `
2242
+ : nothing}
2243
+ </div>
2244
+ ${this.examplesOpen ? this.renderInlineExamples() : nothing}
2245
+ ${this.attachments.length > 0
2246
+ ? html `
2247
+ <div class="attachment-strip">
2248
+ ${this.attachments.map(att => html `
2249
+ <div class="attachment-chip" title=${att.name}>
2250
+ ${att.type.startsWith('image/')
2251
+ ? html `<img class="attachment-thumb" src=${att.url} alt=${att.name} />`
2252
+ : html `<md-icon class="attachment-icon">description</md-icon>`}
2253
+ <div class="attachment-meta">
2254
+ <span class="attachment-name">${att.name}</span>
2255
+ <span class="attachment-size">${formatFileSize(att.size)}</span>
2256
+ </div>
2257
+ <button
2258
+ type="button"
2259
+ class="attachment-remove"
2260
+ title=${i18next.t('ai-assistant.button.remove-attachment', { defaultValue: '첨부 삭제' })}
2261
+ @click=${() => this.removeAttachment(att.id)}>
2262
+ <md-icon>close</md-icon>
2263
+ </button>
2264
+ </div>
2265
+ `)}
1880
2266
  </div>
1881
- ${this.examplesOpen ? this.renderInlineExamples() : nothing}
1882
2267
  `
1883
2268
  : nothing}
1884
2269
  <div class="input-row">
@@ -1915,7 +2300,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1915
2300
  : nothing}
1916
2301
  </div>
1917
2302
  <button
1918
- ?disabled=${this.busy || !this.input.trim()}
2303
+ ?disabled=${this.busy || (!this.input.trim() && this.attachments.length === 0)}
1919
2304
  @click=${this.send}
1920
2305
  title=${i18next.t('ai-assistant.text.send-tooltip')}>
1921
2306
  ${this.busy
@@ -2190,7 +2575,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2190
2575
  get exampleGroups() {
2191
2576
  if (this.examples?.length)
2192
2577
  return this.examples;
2193
- return OxBoardAIChat_1.EXAMPLE_GROUPS.map(group => ({
2578
+ return OxAssistantChat_1.EXAMPLE_GROUPS.map(group => ({
2194
2579
  label: i18next.t(group.labelKey),
2195
2580
  items: group.itemKeys.map(key => i18next.t(key))
2196
2581
  }));
@@ -2232,38 +2617,11 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2232
2617
  if (!this.sessions || this.sessions.length === 0)
2233
2618
  return nothing;
2234
2619
  return html `
2235
- <div class="session-tabs" role="tablist">
2236
- ${this.sessions.map((s, i) => {
2237
- const isActive = s.id === this.sessionId;
2238
- const fallback = i18next.t('ai-assistant.text.session-tab-label', {
2239
- n: i + 1,
2240
- defaultValue: `세션 ${i + 1}`
2241
- });
2242
- const label = s.name?.trim() || fallback;
2243
- const tooltip = s.createdAt
2244
- ? `${label} · ${new Date(s.createdAt).toLocaleString()}`
2245
- : label;
2246
- return html `
2247
- <button
2248
- class="session-tab ${isActive ? 'active' : ''}"
2249
- role="tab"
2250
- aria-selected=${isActive ? 'true' : 'false'}
2251
- title=${tooltip}
2252
- @click=${() => this._onSessionTabClick(s.id)}>
2253
- ${label}
2254
- </button>
2255
- `;
2256
- })}
2257
- <button
2258
- class="session-tab-new"
2259
- title=${i18next.t('ai-assistant.button.new-chat', {
2260
- defaultValue: '새 대화'
2261
- })}
2262
- @click=${this._onSessionCreateClick}>
2263
- <md-icon>add</md-icon>
2264
- <span>${i18next.t('ai-assistant.button.new-chat', { defaultValue: '새 대화' })}</span>
2265
- </button>
2266
- </div>
2620
+ <ox-assistant-session-toolbar style="padding:8px 12px" .sessions=${this.sessions}
2621
+ .sessionId=${this.sessionId || ''} .disabled=${this.busy}
2622
+ @assistant-session-select=${(event) => { event.stopPropagation(); this._onSessionTabClick(event.detail.sessionId); }}
2623
+ @assistant-session-reset=${(event) => { event.stopPropagation(); this._onSessionCreateClick(); }}
2624
+ ></ox-assistant-session-toolbar>
2267
2625
  `;
2268
2626
  }
2269
2627
  /** C4 roster — 세션 참여자 + online 표시. 참여자 2명 미만이면 숨김(혼자면 의미 없음). */
@@ -2478,26 +2836,79 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2478
2836
  ${items.map(p => {
2479
2837
  const key = this.proposalKey(p);
2480
2838
  const sent = this.sentProposals.has(key);
2839
+ const rawChoices = Array.isArray(p.choices) ? p.choices : (p.source ? [
2840
+ { id: 'save', action: 'save', label: i18next.t('label.save-as-draft', { defaultValue: '초안 저장' }), icon: 'save', primary: true },
2841
+ { id: 'repropose', action: 'prompt', label: '다른 콘셉트 재제안', icon: 'refresh', prompt: '현재 제안과 완전히 다른 스타일과 콘셉트로 새로 제안해줘' },
2842
+ { id: 'slim', action: 'prompt', label: '비율 슬림 조정', icon: 'aspect_ratio', prompt: '전체적인 가로세로 비율을 좀 더 슬림하고 날렵하게 조정해줘' },
2843
+ { id: 'palette', action: 'prompt', label: '색상 테마 변경', icon: 'palette', prompt: '다른 팔레트 색상 테마로 부품 색상을 변경해줘' },
2844
+ { id: 'detail', action: 'prompt', label: '디테일 보강', icon: 'extension', prompt: '주요 디테일과 센서 부품을 좀 더 풍부하게 보강해줘' }
2845
+ ] : []);
2481
2846
  return html `
2482
2847
  <div class="proposal ${sent ? 'sent' : ''}">
2483
- <md-icon>bolt</md-icon>
2848
+ <md-icon>${p.icon || (p.source ? 'view_in_ar' : 'bolt')}</md-icon>
2484
2849
  <span class="pbody">
2485
2850
  <span class="plabel">${p.label || p.command}</span>
2486
2851
  ${p.reason ? html `<span class="preason">${p.reason}</span>` : nothing}
2487
2852
  </span>
2488
- <button
2489
- class="run"
2490
- ?disabled=${sent}
2491
- @click=${() => this.executeProposal(p, key)}>
2492
- ${sent ? i18next.t('ai-assistant.text.proposal-sent', { defaultValue: '실행 요청됨' })
2853
+ ${!rawChoices.length ? html `
2854
+ <button
2855
+ class="run"
2856
+ ?disabled=${sent}
2857
+ @click=${() => this.executeProposal(p, key)}>
2858
+ ${sent ? i18next.t('ai-assistant.text.proposal-sent', { defaultValue: '실행 요청됨' })
2493
2859
  : i18next.t('ai-assistant.button.run-proposal', { defaultValue: '실행' })}
2494
- </button>
2860
+ </button>
2861
+ ` : html `
2862
+ <span class="staged-badge" title="3D 뷰어에 모델이 표시되었습니다">
2863
+ <md-icon>check_circle</md-icon>
2864
+ <span>뷰어 반영됨</span>
2865
+ </span>
2866
+ `}
2495
2867
  </div>
2868
+ ${rawChoices.length ? html `
2869
+ <div class="proposal-choices" role="group" aria-label="대화 선택지">
2870
+ ${rawChoices.map((c) => html `
2871
+ <button
2872
+ class="choice-chip ${c.primary ? 'primary' : ''}"
2873
+ title=${c.prompt || c.label}
2874
+ @click=${() => this.onChoiceClick(c, p, key)}>
2875
+ ${c.icon ? html `<md-icon>${c.icon}</md-icon>` : nothing}
2876
+ <span>${c.label}</span>
2877
+ </button>
2878
+ `)}
2879
+ </div>
2880
+ ` : nothing}
2496
2881
  `;
2497
2882
  })}
2498
2883
  </div>
2499
2884
  `;
2500
2885
  }
2886
+ onChoiceClick(choice, proposal, key) {
2887
+ if (choice.action === 'save') {
2888
+ this.dispatchEvent(new CustomEvent('assistant-choice', {
2889
+ detail: { action: 'save', choice, proposal, sessionId: this.sessionId },
2890
+ bubbles: true,
2891
+ composed: true
2892
+ }));
2893
+ this.executeProposal(proposal, key);
2894
+ return;
2895
+ }
2896
+ if (choice.action === 'prompt' && choice.prompt) {
2897
+ this.input = choice.prompt;
2898
+ this.dispatchEvent(new CustomEvent('assistant-choice', {
2899
+ detail: { action: 'prompt', choice, proposal, sessionId: this.sessionId },
2900
+ bubbles: true,
2901
+ composed: true
2902
+ }));
2903
+ void this.send();
2904
+ return;
2905
+ }
2906
+ this.dispatchEvent(new CustomEvent('assistant-choice', {
2907
+ detail: { action: choice.action || 'custom', choice, proposal, sessionId: this.sessionId },
2908
+ bubbles: true,
2909
+ composed: true
2910
+ }));
2911
+ }
2501
2912
  /**
2502
2913
  * 제안 식별 키 — 같은 조치를 두 번 실행하지 않도록 **효과**(명령·대상·인자)로만 만든다.
2503
2914
  *
@@ -2529,7 +2940,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2529
2940
  */
2530
2941
  executeProposal(p, key) {
2531
2942
  this.sentProposals = new Set(this.sentProposals).add(key);
2532
- this.dispatchEvent(new CustomEvent('board-ai-proposal-execute', {
2943
+ this.dispatchEvent(new CustomEvent('assistant-proposal-execute', {
2533
2944
  detail: { proposal: p, sessionId: this.sessionId },
2534
2945
  bubbles: true,
2535
2946
  composed: true
@@ -2794,28 +3205,74 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2794
3205
  }
2795
3206
  async send() {
2796
3207
  const text = this.input.trim();
2797
- if (!text || this.busy)
3208
+ const attachmentsToSend = [...this.attachments];
3209
+ if ((!text && attachmentsToSend.length === 0) || this.busy)
2798
3210
  return;
3211
+ this.attachments = [];
3212
+ const promptText = text || (attachmentsToSend.length > 0
3213
+ ? (attachmentsToSend[0].type.startsWith('image/')
3214
+ ? '첨부된 이미지를 참고하여 제안해 주세요.'
3215
+ : '첨부된 파일을 확인해 주세요.')
3216
+ : '');
3217
+ if (!this.sessionId && this.prepareSession) {
3218
+ this.busy = true;
3219
+ try {
3220
+ await this.prepareSession(id => { this.adoptingSession = true; this.sessionId = id; });
3221
+ await this.updateComplete;
3222
+ }
3223
+ catch (error) {
3224
+ this.errorMessage = error instanceof Error ? error.message : String(error);
3225
+ this.busy = false;
3226
+ return;
3227
+ }
3228
+ this.busy = false;
3229
+ }
3230
+ let requestContext;
3231
+ let liveBoard;
3232
+ let requestBase;
3233
+ try {
3234
+ const provided = this.contextProvider?.() || {};
3235
+ requestContext = structuredClone({ systemPrompt: this.systemPrompt, hostContext: this.hostContext, ...provided });
3236
+ liveBoard = structuredClone(this.boardProvider ? this.boardProvider() : this.currentBoard);
3237
+ requestBase = { version: 1, sessionId: this.sessionId, hostContext: requestContext.hostContext,
3238
+ modelFingerprint: assistantModelFingerprint(liveBoard) };
3239
+ }
3240
+ catch (error) {
3241
+ this.errorMessage = error instanceof Error ? error.message : String(error);
3242
+ return;
3243
+ }
2799
3244
  // pickedMentions / pickedUserMentions 에서 현재 텍스트에 실제 #/@ token 으로
2800
3245
  // 등장하는 것만 추출 — 지운 토큰 제외. inline 마커로 content 에 주입.
2801
3246
  const mentions = [];
2802
3247
  for (const [token, refid] of this.pickedMentions.entries()) {
2803
- if (text.includes(`#${token}`))
3248
+ if (promptText.includes(`#${token}`))
2804
3249
  mentions.push({ token, refid });
2805
3250
  }
2806
3251
  const userMentions = [];
2807
3252
  for (const [token, userId] of this.pickedUserMentions.entries()) {
2808
- if (text.includes(`@${token}`))
3253
+ if (promptText.includes(`@${token}`))
2809
3254
  userMentions.push({ token, userId });
2810
3255
  }
2811
- let enrichedContent = injectMentionRefids(text, mentions);
3256
+ let enrichedContent = injectMentionRefids(promptText, mentions);
2812
3257
  enrichedContent = injectMentionUserIds(enrichedContent, userMentions);
2813
3258
  // optimistic 라인에 임시 핸들 — 방송 에코와의 reconcile 을 index 비의존으로.
2814
3259
  const userLocalId = `local-${++this._localSeq}-u`;
2815
3260
  const asstLocalId = `local-${++this._localSeq}-a`;
2816
3261
  this.lines = [
2817
3262
  ...this.lines,
2818
- { role: 'user', content: enrichedContent, _localId: userLocalId, senderEmail: this._myEmail }
3263
+ {
3264
+ role: 'user',
3265
+ content: enrichedContent,
3266
+ attachments: attachmentsToSend.map(a => ({
3267
+ name: a.name,
3268
+ size: a.size,
3269
+ type: a.type,
3270
+ url: a.url,
3271
+ base64: a.base64
3272
+ })),
3273
+ _localId: userLocalId,
3274
+ senderEmail: this._myEmail
3275
+ }
2819
3276
  ];
2820
3277
  this.input = '';
2821
3278
  this.busy = true;
@@ -2823,6 +3280,24 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2823
3280
  // 가짜 pending 응답 표시 (UX 부드럽게)
2824
3281
  this.lines = [...this.lines, { role: 'assistant', content: '', pending: true, _localId: asstLocalId }];
2825
3282
  try {
3283
+ if (attachmentsToSend.length > 0) {
3284
+ requestContext.hostContext = {
3285
+ ...requestContext.hostContext,
3286
+ attachments: attachmentsToSend.map(a => ({
3287
+ name: a.name,
3288
+ size: a.size,
3289
+ mediaType: a.type,
3290
+ data: a.base64
3291
+ })),
3292
+ image: attachmentsToSend.find(a => a.type.startsWith('image/'))
3293
+ ? {
3294
+ name: attachmentsToSend.find(a => a.type.startsWith('image/')).name,
3295
+ mediaType: attachmentsToSend.find(a => a.type.startsWith('image/')).type,
3296
+ data: attachmentsToSend.find(a => a.type.startsWith('image/')).base64
3297
+ }
3298
+ : undefined
3299
+ };
3300
+ }
2826
3301
  // LLM 으로 보낼 history — user/assistant 만 (system 은 백엔드가 자동 합류).
2827
3302
  // pending placeholder 와 system 은 제외 (index 비의존 — 방송 append 와 무관).
2828
3303
  const history = this.lines
@@ -2830,7 +3305,6 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2830
3305
  .map(l => ({ role: l.role, content: l.content }));
2831
3306
  // 라이브 보드 우선 — 호스트의 캔버스가 사용자 수작업 편집을 들고 있을 수 있음.
2832
3307
  // boardProvider 가 있으면 send 시점에 그것을 pull, 없으면 정적 currentBoard.
2833
- const liveBoard = this.boardProvider ? this.boardProvider() : this.currentBoard;
2834
3308
  // mentions 는 위에서 이미 계산해 user line 에 thread — 그대로 mutation 입력으로 재사용.
2835
3309
  const neutral = this.chatEndpoint === 'assistantChat';
2836
3310
  const result = await client.mutate({
@@ -2840,9 +3314,9 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2840
3314
  ? buildAssistantChatInput({
2841
3315
  sessionId: this.sessionId,
2842
3316
  history,
2843
- systemPrompt: this.systemPrompt,
3317
+ systemPrompt: requestContext.systemPrompt,
2844
3318
  truncateAfterMessageId: this._truncateAfterId,
2845
- hostContext: this.hostContext,
3319
+ hostContext: requestContext.hostContext,
2846
3320
  toolCategories: this.toolCategories,
2847
3321
  requireGroundingTools: this.requireGroundingTools
2848
3322
  })
@@ -2852,7 +3326,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2852
3326
  liveBoard,
2853
3327
  scopes: this.scopes,
2854
3328
  truncateAfterMessageId: this._truncateAfterId,
2855
- hostContext: this.hostContext,
3329
+ hostContext: requestContext.hostContext,
2856
3330
  toolCategories: this.toolCategories,
2857
3331
  boardTools: this.boardTools,
2858
3332
  requireGroundingTools: this.requireGroundingTools,
@@ -2886,11 +3360,24 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2886
3360
  proposals: Array.isArray(out.proposals) ? out.proposals : undefined,
2887
3361
  pending: false
2888
3362
  });
3363
+ // 제안 자동 프리뷰 전파 (3D 뷰어 자동 반영)
3364
+ if (Array.isArray(out.proposals) && out.proposals.length > 0) {
3365
+ for (const p of out.proposals) {
3366
+ if (p?.source || p?.autoStage) {
3367
+ this.dispatchEvent(new CustomEvent('assistant-proposal-preview', {
3368
+ detail: { proposal: p, sessionId: out.sessionId },
3369
+ bubbles: true,
3370
+ composed: true
3371
+ }));
3372
+ }
3373
+ }
3374
+ }
2889
3375
  // 호스트로 patch 이벤트 전파
2890
3376
  if (out.patch) {
2891
3377
  this.dispatchEvent(new CustomEvent('board-edit-patch', {
2892
3378
  detail: {
2893
3379
  patch: out.patch,
3380
+ requestBase,
2894
3381
  summary: out.patch.summary,
2895
3382
  confidence: out.patch.confidence,
2896
3383
  patchId: out.patchId,
@@ -2903,7 +3390,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2903
3390
  // 호스트로 ephemeral scene 조작 actions 전파 (selection / view / mode)
2904
3391
  if (Array.isArray(out.actions) && out.actions.length > 0) {
2905
3392
  this.dispatchEvent(new CustomEvent('board-action-execute', {
2906
- detail: { actions: out.actions, sessionId: out.sessionId },
3393
+ detail: { actions: out.actions, sessionId: out.sessionId, requestBase },
2907
3394
  bubbles: true,
2908
3395
  composed: true
2909
3396
  }));
@@ -2930,165 +3417,185 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2930
3417
  __decorate([
2931
3418
  property({ type: String, attribute: 'session-id' }),
2932
3419
  __metadata("design:type", String)
2933
- ], OxBoardAIChat.prototype, "sessionId", void 0);
3420
+ ], OxAssistantChat.prototype, "sessionId", void 0);
3421
+ __decorate([
3422
+ property({ attribute: false }),
3423
+ __metadata("design:type", Function)
3424
+ ], OxAssistantChat.prototype, "prepareSession", void 0);
2934
3425
  __decorate([
2935
3426
  property({ type: Array, attribute: false }),
2936
3427
  __metadata("design:type", Array)
2937
- ], OxBoardAIChat.prototype, "sessions", void 0);
3428
+ ], OxAssistantChat.prototype, "sessions", void 0);
2938
3429
  __decorate([
2939
3430
  property({ type: Object }),
2940
3431
  __metadata("design:type", Object)
2941
- ], OxBoardAIChat.prototype, "currentBoard", void 0);
3432
+ ], OxAssistantChat.prototype, "currentBoard", void 0);
2942
3433
  __decorate([
2943
3434
  property({ attribute: false }),
2944
3435
  __metadata("design:type", Function)
2945
- ], OxBoardAIChat.prototype, "boardProvider", void 0);
3436
+ ], OxAssistantChat.prototype, "boardProvider", void 0);
2946
3437
  __decorate([
2947
3438
  property({ attribute: false }),
2948
3439
  __metadata("design:type", Function)
2949
- ], OxBoardAIChat.prototype, "searchProvider", void 0);
3440
+ ], OxAssistantChat.prototype, "searchProvider", void 0);
2950
3441
  __decorate([
2951
3442
  property({ attribute: false }),
2952
3443
  __metadata("design:type", Function)
2953
- ], OxBoardAIChat.prototype, "userProvider", void 0);
3444
+ ], OxAssistantChat.prototype, "userProvider", void 0);
2954
3445
  __decorate([
2955
3446
  property({ type: Array, attribute: false }),
2956
3447
  __metadata("design:type", Array)
2957
- ], OxBoardAIChat.prototype, "selectedRefids", void 0);
3448
+ ], OxAssistantChat.prototype, "selectedRefids", void 0);
2958
3449
  __decorate([
2959
3450
  property({ type: Array, attribute: false }),
2960
3451
  __metadata("design:type", Array)
2961
- ], OxBoardAIChat.prototype, "toolCategories", void 0);
3452
+ ], OxAssistantChat.prototype, "toolCategories", void 0);
2962
3453
  __decorate([
2963
3454
  property({ type: Object, attribute: false }),
2964
3455
  __metadata("design:type", Object)
2965
- ], OxBoardAIChat.prototype, "hostContext", void 0);
3456
+ ], OxAssistantChat.prototype, "hostContext", void 0);
2966
3457
  __decorate([
2967
3458
  property({ type: Boolean, attribute: 'board-tools' }),
2968
3459
  __metadata("design:type", Object)
2969
- ], OxBoardAIChat.prototype, "boardTools", void 0);
3460
+ ], OxAssistantChat.prototype, "boardTools", void 0);
2970
3461
  __decorate([
2971
3462
  property({ type: String, attribute: 'chat-endpoint' }),
2972
3463
  __metadata("design:type", String)
2973
- ], OxBoardAIChat.prototype, "chatEndpoint", void 0);
3464
+ ], OxAssistantChat.prototype, "chatEndpoint", void 0);
2974
3465
  __decorate([
2975
3466
  property({ type: String, attribute: 'system-prompt' }),
2976
3467
  __metadata("design:type", String)
2977
- ], OxBoardAIChat.prototype, "systemPrompt", void 0);
3468
+ ], OxAssistantChat.prototype, "systemPrompt", void 0);
2978
3469
  __decorate([
2979
3470
  property({ type: Boolean, attribute: 'require-grounding-tools' }),
2980
3471
  __metadata("design:type", Object)
2981
- ], OxBoardAIChat.prototype, "requireGroundingTools", void 0);
3472
+ ], OxAssistantChat.prototype, "requireGroundingTools", void 0);
2982
3473
  __decorate([
2983
3474
  property({ type: Array }),
2984
3475
  __metadata("design:type", Array)
2985
- ], OxBoardAIChat.prototype, "scopes", void 0);
3476
+ ], OxAssistantChat.prototype, "scopes", void 0);
2986
3477
  __decorate([
2987
3478
  property({ type: Array, attribute: 'known-types' }),
2988
3479
  __metadata("design:type", Array)
2989
- ], OxBoardAIChat.prototype, "knownTypes", void 0);
3480
+ ], OxAssistantChat.prototype, "knownTypes", void 0);
2990
3481
  __decorate([
2991
3482
  property({ type: Array }),
2992
3483
  __metadata("design:type", Array)
2993
- ], OxBoardAIChat.prototype, "categories", void 0);
3484
+ ], OxAssistantChat.prototype, "categories", void 0);
2994
3485
  __decorate([
2995
3486
  property({ type: Array, attribute: false }),
2996
3487
  __metadata("design:type", Array)
2997
- ], OxBoardAIChat.prototype, "componentSchemas", void 0);
3488
+ ], OxAssistantChat.prototype, "componentSchemas", void 0);
2998
3489
  __decorate([
2999
3490
  property({ type: String }),
3000
3491
  __metadata("design:type", Object)
3001
- ], OxBoardAIChat.prototype, "placeholder", void 0);
3492
+ ], OxAssistantChat.prototype, "placeholder", void 0);
3002
3493
  __decorate([
3003
3494
  state(),
3004
3495
  __metadata("design:type", Array)
3005
- ], OxBoardAIChat.prototype, "lines", void 0);
3496
+ ], OxAssistantChat.prototype, "lines", void 0);
3006
3497
  __decorate([
3007
3498
  state(),
3008
3499
  __metadata("design:type", Object)
3009
- ], OxBoardAIChat.prototype, "input", void 0);
3500
+ ], OxAssistantChat.prototype, "input", void 0);
3010
3501
  __decorate([
3011
3502
  property({ attribute: false }),
3012
3503
  __metadata("design:type", String)
3013
- ], OxBoardAIChat.prototype, "autoAsk", void 0);
3504
+ ], OxAssistantChat.prototype, "autoAsk", void 0);
3505
+ __decorate([
3506
+ property(),
3507
+ __metadata("design:type", String)
3508
+ ], OxAssistantChat.prototype, "autoAskRequestId", void 0);
3509
+ __decorate([
3510
+ property({ attribute: false }),
3511
+ __metadata("design:type", Function)
3512
+ ], OxAssistantChat.prototype, "contextProvider", void 0);
3014
3513
  __decorate([
3015
3514
  property({ type: Object, attribute: false }),
3016
3515
  __metadata("design:type", Object)
3017
- ], OxBoardAIChat.prototype, "intro", void 0);
3516
+ ], OxAssistantChat.prototype, "intro", void 0);
3018
3517
  __decorate([
3019
3518
  property({ type: Array, attribute: false }),
3020
3519
  __metadata("design:type", Array)
3021
- ], OxBoardAIChat.prototype, "examples", void 0);
3520
+ ], OxAssistantChat.prototype, "examples", void 0);
3022
3521
  __decorate([
3023
3522
  state(),
3024
3523
  __metadata("design:type", String)
3025
- ], OxBoardAIChat.prototype, "lastFailedInput", void 0);
3524
+ ], OxAssistantChat.prototype, "lastFailedInput", void 0);
3525
+ __decorate([
3526
+ state(),
3527
+ __metadata("design:type", Object)
3528
+ ], OxAssistantChat.prototype, "revertedPatchIds", void 0);
3529
+ __decorate([
3530
+ state(),
3531
+ __metadata("design:type", Array)
3532
+ ], OxAssistantChat.prototype, "attachments", void 0);
3026
3533
  __decorate([
3027
3534
  state(),
3028
3535
  __metadata("design:type", Object)
3029
- ], OxBoardAIChat.prototype, "revertedPatchIds", void 0);
3536
+ ], OxAssistantChat.prototype, "dragOver", void 0);
3030
3537
  __decorate([
3031
3538
  state(),
3032
3539
  __metadata("design:type", Object)
3033
- ], OxBoardAIChat.prototype, "examplesOpen", void 0);
3540
+ ], OxAssistantChat.prototype, "examplesOpen", void 0);
3034
3541
  __decorate([
3035
3542
  state(),
3036
3543
  __metadata("design:type", String)
3037
- ], OxBoardAIChat.prototype, "toastMessage", void 0);
3544
+ ], OxAssistantChat.prototype, "toastMessage", void 0);
3038
3545
  __decorate([
3039
3546
  state(),
3040
3547
  __metadata("design:type", Number)
3041
- ], OxBoardAIChat.prototype, "copiedIdx", void 0);
3548
+ ], OxAssistantChat.prototype, "copiedIdx", void 0);
3042
3549
  __decorate([
3043
3550
  state(),
3044
3551
  __metadata("design:type", Object)
3045
- ], OxBoardAIChat.prototype, "openToolSteps", void 0);
3552
+ ], OxAssistantChat.prototype, "openToolSteps", void 0);
3046
3553
  __decorate([
3047
3554
  state(),
3048
3555
  __metadata("design:type", Object)
3049
- ], OxBoardAIChat.prototype, "sentProposals", void 0);
3556
+ ], OxAssistantChat.prototype, "sentProposals", void 0);
3050
3557
  __decorate([
3051
3558
  state(),
3052
3559
  __metadata("design:type", Object)
3053
- ], OxBoardAIChat.prototype, "historyFolded", void 0);
3560
+ ], OxAssistantChat.prototype, "historyFolded", void 0);
3054
3561
  __decorate([
3055
3562
  state(),
3056
3563
  __metadata("design:type", Object)
3057
- ], OxBoardAIChat.prototype, "foldNoticeDismissed", void 0);
3564
+ ], OxAssistantChat.prototype, "foldNoticeDismissed", void 0);
3058
3565
  __decorate([
3059
3566
  state(),
3060
3567
  __metadata("design:type", Object)
3061
- ], OxBoardAIChat.prototype, "busy", void 0);
3568
+ ], OxAssistantChat.prototype, "busy", void 0);
3062
3569
  __decorate([
3063
3570
  state(),
3064
3571
  __metadata("design:type", String)
3065
- ], OxBoardAIChat.prototype, "errorMessage", void 0);
3572
+ ], OxAssistantChat.prototype, "errorMessage", void 0);
3066
3573
  __decorate([
3067
3574
  state(),
3068
3575
  __metadata("design:type", Array)
3069
- ], OxBoardAIChat.prototype, "participants", void 0);
3576
+ ], OxAssistantChat.prototype, "participants", void 0);
3070
3577
  __decorate([
3071
3578
  state(),
3072
3579
  __metadata("design:type", Object)
3073
- ], OxBoardAIChat.prototype, "_presenceTick", void 0);
3580
+ ], OxAssistantChat.prototype, "_presenceTick", void 0);
3074
3581
  __decorate([
3075
3582
  state(),
3076
3583
  __metadata("design:type", Object)
3077
- ], OxBoardAIChat.prototype, "mentionOpen", void 0);
3584
+ ], OxAssistantChat.prototype, "mentionOpen", void 0);
3078
3585
  __decorate([
3079
3586
  state(),
3080
3587
  __metadata("design:type", Array)
3081
- ], OxBoardAIChat.prototype, "mentionResults", void 0);
3588
+ ], OxAssistantChat.prototype, "mentionResults", void 0);
3082
3589
  __decorate([
3083
3590
  state(),
3084
3591
  __metadata("design:type", Object)
3085
- ], OxBoardAIChat.prototype, "mentionActiveIndex", void 0);
3592
+ ], OxAssistantChat.prototype, "mentionActiveIndex", void 0);
3086
3593
  __decorate([
3087
3594
  state(),
3088
3595
  __metadata("design:type", Object)
3089
- ], OxBoardAIChat.prototype, "mentionOverflow", void 0);
3090
- OxBoardAIChat = OxBoardAIChat_1 = __decorate([
3091
- customElement('ox-board-ai-chat')
3092
- ], OxBoardAIChat);
3093
- export { OxBoardAIChat };
3094
- //# sourceMappingURL=board-ai-chat.js.map
3596
+ ], OxAssistantChat.prototype, "mentionOverflow", void 0);
3597
+ OxAssistantChat = OxAssistantChat_1 = __decorate([
3598
+ customElement('ox-assistant-chat')
3599
+ ], OxAssistantChat);
3600
+ export { OxAssistantChat };
3601
+ //# sourceMappingURL=assistant-chat.js.map