@things-factory/ai-assistant 10.1.6

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 (164) hide show
  1. package/client/components/board-ai-chat.test.ts +149 -0
  2. package/client/components/board-ai-chat.ts +3228 -0
  3. package/client/components/chat-defaults.ts +27 -0
  4. package/client/components/chat-echo-dedup.test.ts +230 -0
  5. package/client/components/chat-echo-dedup.ts +155 -0
  6. package/client/components/chat-input-builder.ts +69 -0
  7. package/client/components/markdown.test.ts +518 -0
  8. package/client/components/markdown.ts +508 -0
  9. package/client/components/mention-popup-helpers.ts +433 -0
  10. package/client/components/mention-popup.test.ts +588 -0
  11. package/client/components/mention-popup.ts +238 -0
  12. package/client/index.ts +14 -0
  13. package/client/tsconfig.json +13 -0
  14. package/client/utils/board-edit-patch.ts +374 -0
  15. package/dist-client/components/board-ai-chat.d.ts +423 -0
  16. package/dist-client/components/board-ai-chat.js +3034 -0
  17. package/dist-client/components/board-ai-chat.js.map +1 -0
  18. package/dist-client/components/board-ai-chat.test.d.ts +1 -0
  19. package/dist-client/components/board-ai-chat.test.js +138 -0
  20. package/dist-client/components/board-ai-chat.test.js.map +1 -0
  21. package/dist-client/components/chat-defaults.d.ts +8 -0
  22. package/dist-client/components/chat-defaults.js +22 -0
  23. package/dist-client/components/chat-defaults.js.map +1 -0
  24. package/dist-client/components/chat-echo-dedup.d.ts +54 -0
  25. package/dist-client/components/chat-echo-dedup.js +115 -0
  26. package/dist-client/components/chat-echo-dedup.js.map +1 -0
  27. package/dist-client/components/chat-echo-dedup.test.d.ts +1 -0
  28. package/dist-client/components/chat-echo-dedup.test.js +203 -0
  29. package/dist-client/components/chat-echo-dedup.test.js.map +1 -0
  30. package/dist-client/components/chat-input-builder.d.ts +55 -0
  31. package/dist-client/components/chat-input-builder.js +32 -0
  32. package/dist-client/components/chat-input-builder.js.map +1 -0
  33. package/dist-client/components/markdown.d.ts +60 -0
  34. package/dist-client/components/markdown.js +422 -0
  35. package/dist-client/components/markdown.js.map +1 -0
  36. package/dist-client/components/markdown.test.d.ts +1 -0
  37. package/dist-client/components/markdown.test.js +418 -0
  38. package/dist-client/components/markdown.test.js.map +1 -0
  39. package/dist-client/components/mention-popup-helpers.d.ts +165 -0
  40. package/dist-client/components/mention-popup-helpers.js +330 -0
  41. package/dist-client/components/mention-popup-helpers.js.map +1 -0
  42. package/dist-client/components/mention-popup.d.ts +35 -0
  43. package/dist-client/components/mention-popup.js +223 -0
  44. package/dist-client/components/mention-popup.js.map +1 -0
  45. package/dist-client/components/mention-popup.test.d.ts +1 -0
  46. package/dist-client/components/mention-popup.test.js +474 -0
  47. package/dist-client/components/mention-popup.test.js.map +1 -0
  48. package/dist-client/index.d.ts +14 -0
  49. package/dist-client/index.js +15 -0
  50. package/dist-client/index.js.map +1 -0
  51. package/dist-client/tsconfig.tsbuildinfo +1 -0
  52. package/dist-client/utils/board-edit-patch.d.ts +157 -0
  53. package/dist-client/utils/board-edit-patch.js +284 -0
  54. package/dist-client/utils/board-edit-patch.js.map +1 -0
  55. package/dist-server/index.d.ts +28 -0
  56. package/dist-server/index.js +32 -0
  57. package/dist-server/index.js.map +1 -0
  58. package/dist-server/service/chat-message/chat-message-broadcast.d.ts +13 -0
  59. package/dist-server/service/chat-message/chat-message-broadcast.js +13 -0
  60. package/dist-server/service/chat-message/chat-message-broadcast.js.map +1 -0
  61. package/dist-server/service/chat-message/chat-message-publish.d.ts +6 -0
  62. package/dist-server/service/chat-message/chat-message-publish.js +27 -0
  63. package/dist-server/service/chat-message/chat-message-publish.js.map +1 -0
  64. package/dist-server/service/chat-message/chat-message-subscription.d.ts +8 -0
  65. package/dist-server/service/chat-message/chat-message-subscription.js +57 -0
  66. package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -0
  67. package/dist-server/service/chat-message/chat-message.d.ts +68 -0
  68. package/dist-server/service/chat-message/chat-message.js +172 -0
  69. package/dist-server/service/chat-message/chat-message.js.map +1 -0
  70. package/dist-server/service/chat-message/fold-history.d.ts +43 -0
  71. package/dist-server/service/chat-message/fold-history.js +33 -0
  72. package/dist-server/service/chat-message/fold-history.js.map +1 -0
  73. package/dist-server/service/chat-message/history-summary.d.ts +43 -0
  74. package/dist-server/service/chat-message/history-summary.js +77 -0
  75. package/dist-server/service/chat-message/history-summary.js.map +1 -0
  76. package/dist-server/service/chat-message/index.d.ts +5 -0
  77. package/dist-server/service/chat-message/index.js +12 -0
  78. package/dist-server/service/chat-message/index.js.map +1 -0
  79. package/dist-server/service/chat-message/llm-history.d.ts +46 -0
  80. package/dist-server/service/chat-message/llm-history.js +87 -0
  81. package/dist-server/service/chat-message/llm-history.js.map +1 -0
  82. package/dist-server/service/chat-session/chat-session.d.ts +60 -0
  83. package/dist-server/service/chat-session/chat-session.js +159 -0
  84. package/dist-server/service/chat-session/chat-session.js.map +1 -0
  85. package/dist-server/service/chat-session/index.d.ts +3 -0
  86. package/dist-server/service/chat-session/index.js +7 -0
  87. package/dist-server/service/chat-session/index.js.map +1 -0
  88. package/dist-server/service/chat-session/session-activity-broadcast.d.ts +30 -0
  89. package/dist-server/service/chat-session/session-activity-broadcast.js +34 -0
  90. package/dist-server/service/chat-session/session-activity-broadcast.js.map +1 -0
  91. package/dist-server/service/chat-session/session-activity-publish.d.ts +12 -0
  92. package/dist-server/service/chat-session/session-activity-publish.js +37 -0
  93. package/dist-server/service/chat-session/session-activity-publish.js.map +1 -0
  94. package/dist-server/service/chat-session/session-activity-subscription.d.ts +14 -0
  95. package/dist-server/service/chat-session/session-activity-subscription.js +87 -0
  96. package/dist-server/service/chat-session/session-activity-subscription.js.map +1 -0
  97. package/dist-server/service/chat-session/session-anchor.d.ts +26 -0
  98. package/dist-server/service/chat-session/session-anchor.js +100 -0
  99. package/dist-server/service/chat-session/session-anchor.js.map +1 -0
  100. package/dist-server/service/chat-session/session-inbox.d.ts +80 -0
  101. package/dist-server/service/chat-session/session-inbox.js +133 -0
  102. package/dist-server/service/chat-session/session-inbox.js.map +1 -0
  103. package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +49 -0
  104. package/dist-server/service/chat-session-participant/chat-session-participant.js +141 -0
  105. package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
  106. package/dist-server/service/chat-session-participant/index.d.ts +4 -0
  107. package/dist-server/service/chat-session-participant/index.js +10 -0
  108. package/dist-server/service/chat-session-participant/index.js.map +1 -0
  109. package/dist-server/service/chat-session-participant/invite-notification.d.ts +27 -0
  110. package/dist-server/service/chat-session-participant/invite-notification.js +30 -0
  111. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -0
  112. package/dist-server/service/chat-session-participant/presence.d.ts +4 -0
  113. package/dist-server/service/chat-session-participant/presence.js +22 -0
  114. package/dist-server/service/chat-session-participant/presence.js.map +1 -0
  115. package/dist-server/service/chat-session-resolver.d.ts +93 -0
  116. package/dist-server/service/chat-session-resolver.js +706 -0
  117. package/dist-server/service/chat-session-resolver.js.map +1 -0
  118. package/dist-server/service/index.d.ts +24 -0
  119. package/dist-server/service/index.js +39 -0
  120. package/dist-server/service/index.js.map +1 -0
  121. package/dist-server/service/subscribe-auth.d.ts +14 -0
  122. package/dist-server/service/subscribe-auth.js +33 -0
  123. package/dist-server/service/subscribe-auth.js.map +1 -0
  124. package/dist-server/service/types.d.ts +5 -0
  125. package/dist-server/service/types.js +12 -0
  126. package/dist-server/service/types.js.map +1 -0
  127. package/dist-server/tsconfig.tsbuildinfo +1 -0
  128. package/package.json +48 -0
  129. package/server/index.ts +28 -0
  130. package/server/service/chat-message/chat-message-broadcast.integration.test.ts +73 -0
  131. package/server/service/chat-message/chat-message-broadcast.test.ts +40 -0
  132. package/server/service/chat-message/chat-message-broadcast.ts +27 -0
  133. package/server/service/chat-message/chat-message-publish.ts +22 -0
  134. package/server/service/chat-message/chat-message-subscription.ts +45 -0
  135. package/server/service/chat-message/chat-message.ts +195 -0
  136. package/server/service/chat-message/fold-history.test.ts +98 -0
  137. package/server/service/chat-message/fold-history.ts +77 -0
  138. package/server/service/chat-message/history-summary.test.ts +127 -0
  139. package/server/service/chat-message/history-summary.ts +100 -0
  140. package/server/service/chat-message/index.ts +12 -0
  141. package/server/service/chat-message/llm-history.test.ts +194 -0
  142. package/server/service/chat-message/llm-history.ts +163 -0
  143. package/server/service/chat-session/chat-session.ts +193 -0
  144. package/server/service/chat-session/index.ts +5 -0
  145. package/server/service/chat-session/session-activity-broadcast.ts +57 -0
  146. package/server/service/chat-session/session-activity-publish.ts +44 -0
  147. package/server/service/chat-session/session-activity-subscription.ts +67 -0
  148. package/server/service/chat-session/session-anchor.ts +106 -0
  149. package/server/service/chat-session/session-inbox.test.ts +185 -0
  150. package/server/service/chat-session/session-inbox.ts +161 -0
  151. package/server/service/chat-session-participant/chat-session-participant.ts +135 -0
  152. package/server/service/chat-session-participant/index.ts +6 -0
  153. package/server/service/chat-session-participant/invite-notification.test.ts +30 -0
  154. package/server/service/chat-session-participant/invite-notification.ts +48 -0
  155. package/server/service/chat-session-participant/presence.test.ts +39 -0
  156. package/server/service/chat-session-participant/presence.ts +21 -0
  157. package/server/service/chat-session-resolver.ts +644 -0
  158. package/server/service/index.ts +37 -0
  159. package/server/service/session-anchor.test.ts +133 -0
  160. package/server/service/subscribe-auth.test.ts +57 -0
  161. package/server/service/subscribe-auth.ts +34 -0
  162. package/server/service/types.ts +15 -0
  163. package/things-factory.config.js +1 -0
  164. package/tsconfig.json +9 -0
@@ -0,0 +1,223 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ /**
3
+ * <ox-mention-popup> — Mention 후보 리스트 popup overlay.
4
+ *
5
+ * board-ai-chat 의 textarea 위에 absolute 로 띄움. plain `<textarea>` + popup 분리 패턴
6
+ * (한국어 IME 함정 회피 — contenteditable 사용 안 함).
7
+ *
8
+ * 키보드 네비게이션은 호스트 (board-ai-chat) 가 textarea keydown 으로 처리하고
9
+ * activeIndex 만 동기화. 마우스 클릭은 popup 자체에서 처리.
10
+ *
11
+ * Pure helper (extractMentionContext / filterMentionCandidates 등) 는
12
+ * `mention-popup-helpers.ts` 에 분리 — 단위 테스트 가능.
13
+ */
14
+ import { LitElement, css, html } from 'lit';
15
+ import { customElement, property } from 'lit/decorators.js';
16
+ export { buildMentionCandidates, buildCatalogCandidates, buildSlashCandidates, buildUserCandidates, extractMentionContext, filterMentionCandidates, applyMentionSelection, resolveMentionToken } from './mention-popup-helpers.js';
17
+ /** match type 라벨 — things-scene SearchOverlay 의 MATCH_TYPE_LABELS 와 동일. */
18
+ const MATCH_TYPE_LABEL = {
19
+ id: 'ID',
20
+ name: 'Name',
21
+ tag: 'Tag',
22
+ type: 'Type',
23
+ class: 'Class',
24
+ text: 'Text',
25
+ data: 'Data',
26
+ property: 'Prop',
27
+ description: 'Desc'
28
+ };
29
+ let OxMentionPopup = class OxMentionPopup extends LitElement {
30
+ constructor() {
31
+ super(...arguments);
32
+ this.results = [];
33
+ this.activeIndex = 0;
34
+ /** filter cap 후 표시 안 된 추가 매칭 수 ("...and N more" 표기용) */
35
+ this.overflow = 0;
36
+ }
37
+ static { this.styles = css `
38
+ :host {
39
+ display: block;
40
+ position: absolute;
41
+ z-index: 1000;
42
+ background: var(--md-sys-color-surface, #ffffff);
43
+ border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
44
+ border-radius: 6px;
45
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.10);
46
+ max-height: 220px;
47
+ overflow-y: auto;
48
+ /* 폭은 호스트가 left/right 로 결정. */
49
+ }
50
+ .empty {
51
+ padding: 8px 10px;
52
+ color: var(--md-sys-color-outline, #94a3b8);
53
+ font-size: 11px;
54
+ }
55
+ /* dense 한 단일 행 — scene Finder 톤. label / hint / type-chip inline. */
56
+ .item {
57
+ display: flex;
58
+ align-items: center;
59
+ gap: 6px;
60
+ padding: 4px 8px;
61
+ cursor: pointer;
62
+ font-size: 11.5px;
63
+ line-height: 1.4;
64
+ color: var(--md-sys-color-on-surface, #0f172a);
65
+ border-bottom: 1px solid var(--md-sys-color-outline-variant, #f1f5f9);
66
+ }
67
+ .item:last-child {
68
+ border-bottom: 0;
69
+ }
70
+ .item:hover {
71
+ background: var(--md-sys-color-surface-container, #f8fafc);
72
+ }
73
+ .item.active {
74
+ background: var(--md-sys-color-primary-container, rgba(37, 99, 235, 0.10));
75
+ }
76
+ .match-badge {
77
+ flex-shrink: 0;
78
+ background: var(--md-sys-color-primary-container, rgba(37, 99, 235, 0.15));
79
+ color: var(--md-sys-color-on-primary-container, #1e40af);
80
+ padding: 0 5px;
81
+ border-radius: 3px;
82
+ font-size: 9px;
83
+ font-weight: 600;
84
+ letter-spacing: 0.04em;
85
+ min-width: 32px;
86
+ text-align: center;
87
+ }
88
+ /* badge 가 비어있을 때 (matchType=label) — 자리는 reserve 해서 모든 행 정렬 일치 */
89
+ .match-badge.invisible {
90
+ visibility: hidden;
91
+ }
92
+ .label {
93
+ flex: 1;
94
+ min-width: 0;
95
+ font-weight: 500;
96
+ overflow: hidden;
97
+ text-overflow: ellipsis;
98
+ white-space: nowrap;
99
+ color: var(--md-sys-color-on-surface, #0f172a);
100
+ }
101
+ .hint {
102
+ font-size: 10px;
103
+ color: var(--md-sys-color-outline, #94a3b8);
104
+ flex-shrink: 0;
105
+ white-space: nowrap;
106
+ max-width: 45%;
107
+ overflow: hidden;
108
+ text-overflow: ellipsis;
109
+ }
110
+ .type {
111
+ flex-shrink: 0;
112
+ background: var(--md-sys-color-surface-container, #f1f5f9);
113
+ padding: 0 5px;
114
+ border-radius: 3px;
115
+ font-size: 9.5px;
116
+ color: var(--md-sys-color-outline, #94a3b8);
117
+ letter-spacing: 0.02em;
118
+ }
119
+ /* 상태 footer — 'N matches' 카운터 + cap 초과 안내 */
120
+ .status {
121
+ padding: 3px 8px;
122
+ font-size: 9.5px;
123
+ color: var(--md-sys-color-outline, #94a3b8);
124
+ border-top: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
125
+ background: var(--md-sys-color-surface-container-low, #fafbfc);
126
+ display: flex;
127
+ justify-content: space-between;
128
+ gap: 8px;
129
+ }
130
+ .more {
131
+ color: var(--md-sys-color-outline, #94a3b8);
132
+ }
133
+ `; }
134
+ render() {
135
+ if (this.results.length === 0) {
136
+ return html `<div class="empty">매칭 없음</div>`;
137
+ }
138
+ const totalShown = this.results.length;
139
+ const totalAll = totalShown + this.overflow;
140
+ return html `
141
+ ${this.results.map((r, i) => {
142
+ // 행 layout:
143
+ // [matchType-badge] [type-chip] [label] [secondary hint when 의미 있을 때]
144
+ // - label = 항상 사람-친화적 1차 식별 (component=id, user=name, catalog=name 등)
145
+ // - secondary hint = matchValue 가 label 과 다른 경우 (예: tag/text 매칭) 또는
146
+ // user 의 email 같은 부가 정보. 없으면 생략.
147
+ const matchType = r.matchType;
148
+ const badgeText = matchType ? MATCH_TYPE_LABEL[matchType] ?? '' : '';
149
+ const label = r.candidate.label;
150
+ // matchValue 가 label 과 같으면 redundant 라 hint 안 함
151
+ const matchHint = r.matchValue && r.matchValue !== label ? r.matchValue : '';
152
+ // user 후보이고 hint 가 비어있으면 email 을 hint 로 (보조 식별)
153
+ const fallbackHint = !matchHint && r.candidate.type === 'user' && r.candidate.email !== label
154
+ ? r.candidate.email ?? ''
155
+ : '';
156
+ const hint = matchHint || fallbackHint;
157
+ return html `
158
+ <div
159
+ class="item ${i === this.activeIndex ? 'active' : ''}"
160
+ @mouseenter=${() => this._setActive(i)}
161
+ @mousedown=${(e) => this._select(e, r.candidate)}>
162
+ <span class="match-badge ${badgeText ? '' : 'invisible'}"
163
+ >${badgeText || '·'}</span
164
+ >
165
+ <span class="type">${r.candidate.type}</span>
166
+ <span class="label">${label}</span>
167
+ ${hint ? html `<span class="hint">${hint}</span>` : ''}
168
+ </div>
169
+ `;
170
+ })}
171
+ <div class="status">
172
+ <span>${totalShown} / ${totalAll} matches</span>
173
+ ${this.overflow > 0
174
+ ? html `<span class="more">+${this.overflow} more — narrow query</span>`
175
+ : ''}
176
+ </div>
177
+ `;
178
+ }
179
+ _select(e, candidate) {
180
+ e.preventDefault(); // textarea blur 방지
181
+ this.dispatchEvent(new CustomEvent('mention-select', {
182
+ detail: { candidate },
183
+ bubbles: true,
184
+ composed: true
185
+ }));
186
+ }
187
+ /** 마우스 hover 로 active 이동 — host 가 받아 keyboard nav 와 동일하게 처리. */
188
+ _setActive(index) {
189
+ if (index === this.activeIndex)
190
+ return;
191
+ this.dispatchEvent(new CustomEvent('mention-active-change', {
192
+ detail: { index },
193
+ bubbles: true,
194
+ composed: true
195
+ }));
196
+ }
197
+ /** activeIndex 가 바뀌면 해당 항목을 view 로 스크롤 — popup 의 overflow-y:auto
198
+ * 영역에서 키보드 네비 시 active 가 가려지지 않도록. */
199
+ updated(changed) {
200
+ if (changed.has('activeIndex') || changed.has('results')) {
201
+ const items = this.renderRoot.querySelectorAll('.item');
202
+ const active = items[this.activeIndex];
203
+ active?.scrollIntoView({ block: 'nearest' });
204
+ }
205
+ }
206
+ };
207
+ __decorate([
208
+ property({ type: Array }),
209
+ __metadata("design:type", Array)
210
+ ], OxMentionPopup.prototype, "results", void 0);
211
+ __decorate([
212
+ property({ type: Number }),
213
+ __metadata("design:type", Object)
214
+ ], OxMentionPopup.prototype, "activeIndex", void 0);
215
+ __decorate([
216
+ property({ type: Number }),
217
+ __metadata("design:type", Object)
218
+ ], OxMentionPopup.prototype, "overflow", void 0);
219
+ OxMentionPopup = __decorate([
220
+ customElement('ox-mention-popup')
221
+ ], OxMentionPopup);
222
+ export { OxMentionPopup };
223
+ //# sourceMappingURL=mention-popup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mention-popup.js","sourceRoot":"","sources":["../../client/components/mention-popup.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAO3D,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,EAIpB,MAAM,4BAA4B,CAAA;AAEnC,2EAA2E;AAC3E,MAAM,gBAAgB,GAA2B;IAC/C,EAAE,EAAE,IAAI;IACR,IAAI,EAAE,MAAM;IACZ,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,MAAM;IAChB,WAAW,EAAE,MAAM;CACpB,CAAA;AAGM,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QACsB,YAAO,GAAmB,EAAE,CAAA;QAC3B,gBAAW,GAAG,CAAC,CAAA;QAC3C,wDAAwD;QAC5B,aAAQ,GAAG,CAAC,CAAA;IAmL1C,CAAC;aAjLQ,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgGlB,AAhGY,CAgGZ;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,IAAI,CAAA,gCAAgC,CAAA;QAC7C,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAA;QACtC,MAAM,QAAQ,GAAG,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAA;QAC3C,OAAO,IAAI,CAAA;QACP,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC1B,YAAY;YACZ,wEAAwE;YACxE,sEAAsE;YACtE,oEAAoE;YACpE,mCAAmC;YACnC,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,CAAA;YAC7B,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YACpE,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAA;YAC/B,gDAAgD;YAChD,MAAM,SAAS,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAA;YAC5E,gDAAgD;YAChD,MAAM,YAAY,GAChB,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,KAAK,KAAK;gBACtE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE;gBACzB,CAAC,CAAC,EAAE,CAAA;YACR,MAAM,IAAI,GAAG,SAAS,IAAI,YAAY,CAAA;YACtC,OAAO,IAAI,CAAA;;0BAEO,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;0BACtC,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;yBACzB,CAAC,CAAa,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;uCACjC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW;iBAClD,SAAS,IAAI,GAAG;;iCAEA,CAAC,CAAC,SAAS,CAAC,IAAI;kCACf,KAAK;cACzB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA,sBAAsB,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE;;SAExD,CAAA;QACH,CAAC,CAAC;;gBAEQ,UAAU,MAAM,QAAQ;UAC9B,IAAI,CAAC,QAAQ,GAAG,CAAC;YACjB,CAAC,CAAC,IAAI,CAAA,uBAAuB,IAAI,CAAC,QAAQ,6BAA6B;YACvE,CAAC,CAAC,EAAE;;KAET,CAAA;IACH,CAAC;IAEO,OAAO,CAAC,CAAa,EAAE,SAA2B;QACxD,CAAC,CAAC,cAAc,EAAE,CAAA,CAAC,mBAAmB;QACtC,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,gBAAgB,EAAE;YAChC,MAAM,EAAE,EAAE,SAAS,EAAE;YACrB,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAA;IACH,CAAC;IAED,gEAAgE;IACxD,UAAU,CAAC,KAAa;QAC9B,IAAI,KAAK,KAAK,IAAI,CAAC,WAAW;YAAE,OAAM;QACtC,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,uBAAuB,EAAE;YACvC,MAAM,EAAE,EAAE,KAAK,EAAE;YACjB,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAA;IACH,CAAC;IAED;2CACuC;IAC7B,OAAO,CAAC,OAA6B;QAC7C,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACzD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;YACvD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAA4B,CAAA;YACjE,MAAM,EAAE,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;QAC9C,CAAC;IACH,CAAC;;AArL0B;IAA1B,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;+CAA6B;AAC3B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;mDAAgB;AAEf;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;gDAAa;AAJ7B,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CAuL1B","sourcesContent":["/**\n * <ox-mention-popup> — Mention 후보 리스트 popup overlay.\n *\n * board-ai-chat 의 textarea 위에 absolute 로 띄움. plain `<textarea>` + popup 분리 패턴\n * (한국어 IME 함정 회피 — contenteditable 사용 안 함).\n *\n * 키보드 네비게이션은 호스트 (board-ai-chat) 가 textarea keydown 으로 처리하고\n * activeIndex 만 동기화. 마우스 클릭은 popup 자체에서 처리.\n *\n * Pure helper (extractMentionContext / filterMentionCandidates 등) 는\n * `mention-popup-helpers.ts` 에 분리 — 단위 테스트 가능.\n */\nimport { LitElement, css, html } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport type { FilterResult, MentionCandidate } from './mention-popup-helpers.js'\n\nexport type {\n MentionCandidate,\n FilterResult\n} from './mention-popup-helpers.js'\nexport {\n buildMentionCandidates,\n buildCatalogCandidates,\n buildSlashCandidates,\n buildUserCandidates,\n extractMentionContext,\n filterMentionCandidates,\n applyMentionSelection,\n resolveMentionToken,\n type CatalogEntry,\n type SlashTemplate,\n type ResolveMentionTokenOptions\n} from './mention-popup-helpers.js'\n\n/** match type 라벨 — things-scene SearchOverlay 의 MATCH_TYPE_LABELS 와 동일. */\nconst MATCH_TYPE_LABEL: Record<string, string> = {\n id: 'ID',\n name: 'Name',\n tag: 'Tag',\n type: 'Type',\n class: 'Class',\n text: 'Text',\n data: 'Data',\n property: 'Prop',\n description: 'Desc'\n}\n\n@customElement('ox-mention-popup')\nexport class OxMentionPopup extends LitElement {\n @property({ type: Array }) results: FilterResult[] = []\n @property({ type: Number }) activeIndex = 0\n /** filter cap 후 표시 안 된 추가 매칭 수 (\"...and N more\" 표기용) */\n @property({ type: Number }) overflow = 0\n\n static styles = css`\n :host {\n display: block;\n position: absolute;\n z-index: 1000;\n background: var(--md-sys-color-surface, #ffffff);\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n border-radius: 6px;\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.10);\n max-height: 220px;\n overflow-y: auto;\n /* 폭은 호스트가 left/right 로 결정. */\n }\n .empty {\n padding: 8px 10px;\n color: var(--md-sys-color-outline, #94a3b8);\n font-size: 11px;\n }\n /* dense 한 단일 행 — scene Finder 톤. label / hint / type-chip inline. */\n .item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n cursor: pointer;\n font-size: 11.5px;\n line-height: 1.4;\n color: var(--md-sys-color-on-surface, #0f172a);\n border-bottom: 1px solid var(--md-sys-color-outline-variant, #f1f5f9);\n }\n .item:last-child {\n border-bottom: 0;\n }\n .item:hover {\n background: var(--md-sys-color-surface-container, #f8fafc);\n }\n .item.active {\n background: var(--md-sys-color-primary-container, rgba(37, 99, 235, 0.10));\n }\n .match-badge {\n flex-shrink: 0;\n background: var(--md-sys-color-primary-container, rgba(37, 99, 235, 0.15));\n color: var(--md-sys-color-on-primary-container, #1e40af);\n padding: 0 5px;\n border-radius: 3px;\n font-size: 9px;\n font-weight: 600;\n letter-spacing: 0.04em;\n min-width: 32px;\n text-align: center;\n }\n /* badge 가 비어있을 때 (matchType=label) — 자리는 reserve 해서 모든 행 정렬 일치 */\n .match-badge.invisible {\n visibility: hidden;\n }\n .label {\n flex: 1;\n min-width: 0;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--md-sys-color-on-surface, #0f172a);\n }\n .hint {\n font-size: 10px;\n color: var(--md-sys-color-outline, #94a3b8);\n flex-shrink: 0;\n white-space: nowrap;\n max-width: 45%;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .type {\n flex-shrink: 0;\n background: var(--md-sys-color-surface-container, #f1f5f9);\n padding: 0 5px;\n border-radius: 3px;\n font-size: 9.5px;\n color: var(--md-sys-color-outline, #94a3b8);\n letter-spacing: 0.02em;\n }\n /* 상태 footer — 'N matches' 카운터 + cap 초과 안내 */\n .status {\n padding: 3px 8px;\n font-size: 9.5px;\n color: var(--md-sys-color-outline, #94a3b8);\n border-top: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n background: var(--md-sys-color-surface-container-low, #fafbfc);\n display: flex;\n justify-content: space-between;\n gap: 8px;\n }\n .more {\n color: var(--md-sys-color-outline, #94a3b8);\n }\n `\n\n render() {\n if (this.results.length === 0) {\n return html`<div class=\"empty\">매칭 없음</div>`\n }\n const totalShown = this.results.length\n const totalAll = totalShown + this.overflow\n return html`\n ${this.results.map((r, i) => {\n // 행 layout:\n // [matchType-badge] [type-chip] [label] [secondary hint when 의미 있을 때]\n // - label = 항상 사람-친화적 1차 식별 (component=id, user=name, catalog=name 등)\n // - secondary hint = matchValue 가 label 과 다른 경우 (예: tag/text 매칭) 또는\n // user 의 email 같은 부가 정보. 없으면 생략.\n const matchType = r.matchType\n const badgeText = matchType ? MATCH_TYPE_LABEL[matchType] ?? '' : ''\n const label = r.candidate.label\n // matchValue 가 label 과 같으면 redundant 라 hint 안 함\n const matchHint = r.matchValue && r.matchValue !== label ? r.matchValue : ''\n // user 후보이고 hint 가 비어있으면 email 을 hint 로 (보조 식별)\n const fallbackHint =\n !matchHint && r.candidate.type === 'user' && r.candidate.email !== label\n ? r.candidate.email ?? ''\n : ''\n const hint = matchHint || fallbackHint\n return html`\n <div\n class=\"item ${i === this.activeIndex ? 'active' : ''}\"\n @mouseenter=${() => this._setActive(i)}\n @mousedown=${(e: MouseEvent) => this._select(e, r.candidate)}>\n <span class=\"match-badge ${badgeText ? '' : 'invisible'}\"\n >${badgeText || '·'}</span\n >\n <span class=\"type\">${r.candidate.type}</span>\n <span class=\"label\">${label}</span>\n ${hint ? html`<span class=\"hint\">${hint}</span>` : ''}\n </div>\n `\n })}\n <div class=\"status\">\n <span>${totalShown} / ${totalAll} matches</span>\n ${this.overflow > 0\n ? html`<span class=\"more\">+${this.overflow} more — narrow query</span>`\n : ''}\n </div>\n `\n }\n\n private _select(e: MouseEvent, candidate: MentionCandidate) {\n e.preventDefault() // textarea blur 방지\n this.dispatchEvent(\n new CustomEvent('mention-select', {\n detail: { candidate },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /** 마우스 hover 로 active 이동 — host 가 받아 keyboard nav 와 동일하게 처리. */\n private _setActive(index: number) {\n if (index === this.activeIndex) return\n this.dispatchEvent(\n new CustomEvent('mention-active-change', {\n detail: { index },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /** activeIndex 가 바뀌면 해당 항목을 view 로 스크롤 — popup 의 overflow-y:auto\n * 영역에서 키보드 네비 시 active 가 가려지지 않도록. */\n protected updated(changed: Map<string, unknown>) {\n if (changed.has('activeIndex') || changed.has('results')) {\n const items = this.renderRoot.querySelectorAll('.item')\n const active = items[this.activeIndex] as HTMLElement | undefined\n active?.scrollIntoView({ block: 'nearest' })\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ox-mention-popup': OxMentionPopup\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};