@nuraly/lumenui 0.3.9 → 0.6.0

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 (37) hide show
  1. package/dist/nuralyui.bundle.js +676 -668
  2. package/dist/nuralyui.bundle.js.gz +0 -0
  3. package/dist/src/components/canvas/bundle.js +452 -444
  4. package/dist/src/components/canvas/bundle.js.gz +0 -0
  5. package/dist/src/components/chatbot/bundle.js +189 -181
  6. package/dist/src/components/chatbot/bundle.js.gz +0 -0
  7. package/dist/src/components/chatbot/chatbot.component.d.ts +14 -1
  8. package/dist/src/components/chatbot/chatbot.component.js +165 -8
  9. package/dist/src/components/chatbot/chatbot.style.js +6 -9
  10. package/dist/src/components/chatbot/chatbot.types.d.ts +94 -0
  11. package/dist/src/components/chatbot/templates/artifact-panel.template.d.ts +2 -1
  12. package/dist/src/components/chatbot/templates/artifact-panel.template.js +17 -18
  13. package/dist/src/components/chatbot/templates/chatbot-main.template.d.ts +3 -1
  14. package/dist/src/components/chatbot/templates/chatbot-main.template.js +15 -10
  15. package/dist/src/components/chatbot/templates/file-upload-area.template.d.ts +5 -6
  16. package/dist/src/components/chatbot/templates/file-upload-area.template.js +6 -13
  17. package/dist/src/components/chatbot/templates/input-box.template.d.ts +2 -1
  18. package/dist/src/components/chatbot/templates/input-box.template.js +45 -39
  19. package/dist/src/components/chatbot/templates/message.template.d.ts +4 -4
  20. package/dist/src/components/chatbot/templates/message.template.js +17 -17
  21. package/dist/src/components/chatbot/templates/suggestion.template.d.ts +3 -9
  22. package/dist/src/components/chatbot/templates/suggestion.template.js +6 -13
  23. package/dist/src/components/chatbot/templates/thread-sidebar.template.d.ts +2 -1
  24. package/dist/src/components/chatbot/templates/thread-sidebar.template.js +19 -19
  25. package/dist/src/components/chatbot/templates/url-modal.template.d.ts +2 -3
  26. package/dist/src/components/chatbot/templates/url-modal.template.js +24 -28
  27. package/dist/src/components/icon/bundle.js +10 -10
  28. package/dist/src/components/icon/bundle.js.gz +0 -0
  29. package/dist/src/components/icon/icon-paths.js +1 -0
  30. package/dist/src/components/iconpicker/bundle.js +1 -1
  31. package/dist/src/components/iconpicker/bundle.js.gz +0 -0
  32. package/dist/src/components/panel/bundle.js +1 -1
  33. package/dist/src/components/panel/bundle.js.gz +0 -0
  34. package/dist/src/components/tabs/bundle.js +1 -1
  35. package/dist/src/components/tabs/bundle.js.gz +0 -0
  36. package/package.json +1 -1
  37. package/packages/common/dist/VERSIONS.md +1 -1
@@ -4,23 +4,16 @@
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
  import { html } from 'lit';
7
- import { msg } from '@lit/localize';
8
- /**
9
- * Renders file upload drag-and-drop area
10
- */
11
- export function renderFileUploadArea(data, handlers) {
7
+ export function renderFileUploadArea(data) {
12
8
  return html `
13
- <div
9
+ <div
14
10
  class="file-upload-area ${data.isDragging ? 'file-upload-area--dragging' : ''}"
15
11
  part="file-upload-area"
16
- @drop=${handlers.onDrop}
17
- @dragover=${handlers.onDragOver}
18
- @dragleave=${handlers.onDragLeave}
19
12
  >
20
- <div class="file-upload-area__content">
21
- <div class="file-upload-area__icon">📁</div>
22
- <div class="file-upload-area__text">
23
- ${msg('Drop files here to upload')}
13
+ <div class="file-upload-area__content" part="file-upload-area-content">
14
+ <nr-icon name="upload" size="xlarge" part="file-upload-area-icon"></nr-icon>
15
+ <div class="file-upload-area__text" part="file-upload-area-text">
16
+ ${data.label}
24
17
  </div>
25
18
  </div>
26
19
  </div>
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
  import { TemplateResult } from 'lit';
7
- import { ChatbotFile } from '../chatbot.types.js';
7
+ import { ChatbotFile, ChatbotI18n } from '../chatbot.types.js';
8
8
  import type { AudioRecordingState } from '../chatbot-audio.controller.js';
9
9
  import { DropdownItem } from '../../dropdown/dropdown.types.js';
10
10
  import { SelectOption } from '../../select/select.types.js';
@@ -41,6 +41,7 @@ export interface InputBoxTemplateData {
41
41
  showAudioButton: boolean;
42
42
  audioRecording: AudioRecordingState;
43
43
  audioMode: 'transcribe' | 'message';
44
+ i18n: ChatbotI18n;
44
45
  }
45
46
  /**
46
47
  * Renders the complete input box
@@ -6,13 +6,12 @@
6
6
  import { html, nothing } from 'lit';
7
7
  import { repeat } from 'lit/directives/repeat.js';
8
8
  import { styleMap } from 'lit/directives/style-map.js';
9
- import { msg } from '@lit/localize';
10
9
  /**
11
10
  * Renders thumbnail chips for uploaded files (image thumb for images,
12
11
  * extension badge for other file types). While a file is uploading, a
13
12
  * spinner overlay is shown on top of the thumbnail.
14
13
  */
15
- function renderContextTags(files, onRemove, onFileClick) {
14
+ function renderContextTags(files, onRemove, i18n, onFileClick) {
16
15
  const formatFileSize = (bytes) => {
17
16
  if (bytes === 0)
18
17
  return '0 Bytes';
@@ -46,6 +45,7 @@ function renderContextTags(files, onRemove, onFileClick) {
46
45
  <div
47
46
  slot="trigger"
48
47
  class="file-thumb ${f.isUploading ? 'file-thumb--uploading' : ''}"
48
+ part="file-thumb"
49
49
  role="button"
50
50
  tabindex="0"
51
51
  title="${f.name}"
@@ -54,24 +54,26 @@ function renderContextTags(files, onRemove, onFileClick) {
54
54
  ${isImage(f.mimeType) && (f.previewUrl || f.url) ? html `
55
55
  <img
56
56
  class="file-thumb__image"
57
+ part="file-thumb-image"
57
58
  src="${f.previewUrl || f.url}"
58
59
  alt="${f.name}"
59
60
  />
60
61
  ` : html `
61
- <div class="file-thumb__ext" data-ext="${getExtension(f.name, f.mimeType)}">
62
- <span class="file-thumb__ext-label">${getExtension(f.name, f.mimeType)}</span>
62
+ <div class="file-thumb__ext" part="file-thumb-ext" data-ext="${getExtension(f.name, f.mimeType)}">
63
+ <span class="file-thumb__ext-label" part="file-thumb-ext-label">${getExtension(f.name, f.mimeType)}</span>
63
64
  </div>
64
65
  `}
65
66
  ${f.isUploading ? html `
66
- <div class="file-thumb__spinner" aria-label="${msg('Uploading')}">
67
+ <div class="file-thumb__spinner" part="file-thumb-spinner" aria-label="${i18n.input.uploadingLabel}">
67
68
  <span class="file-thumb__spinner-ring"></span>
68
69
  </div>
69
70
  ` : ''}
70
71
  <button
71
72
  type="button"
72
73
  class="file-thumb__remove"
73
- aria-label="${msg('Remove file')}"
74
- title="${msg('Remove file')}"
74
+ part="file-thumb-remove"
75
+ aria-label="${i18n.input.removeFileLabel}"
76
+ title="${i18n.input.removeFileLabel}"
75
77
  @click=${(e) => { e.stopPropagation(); onRemove(f.id); }}
76
78
  >
77
79
  <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round">
@@ -97,7 +99,7 @@ function renderContextTags(files, onRemove, onFileClick) {
97
99
  <div class="file-preview-name" title="${f.name}">${f.name}</div>
98
100
  <div class="file-preview-details">
99
101
  <span>${formatFileSize(f.size)}</span>
100
- ${f.isUploading ? html `<span> · ${msg('Uploading…')}</span>` : ''}
102
+ ${f.isUploading ? html `<span> · ${i18n.input.uploadingProgress}</span>` : ''}
101
103
  </div>
102
104
  </div>
103
105
  </div>
@@ -120,17 +122,17 @@ function renderFileUploadButton(data, handlers) {
120
122
  ?disabled=${data.disabled}
121
123
  @nr-dropdown-item-click=${handlers.onFileDropdownClick}
122
124
  >
123
- <nr-button
125
+ <nr-button
124
126
  slot="trigger"
125
127
  part="file-button"
126
128
  type="default"
127
129
  size="small"
128
- .icon=${["upload"]}
130
+ .icon=${["paperclip"]}
129
131
  ?disabled=${data.disabled}
130
- aria-label="${msg('Attach files')}"
131
- title="${msg('Attach files')}"
132
+ aria-label="${data.i18n.input.attachFilesAriaLabel}"
133
+ title="${data.i18n.input.attachFilesAriaLabel}"
132
134
  >
133
- Attach
135
+ ${data.i18n.input.attachButton}
134
136
  </nr-button>
135
137
  </nr-dropdown>
136
138
  `;
@@ -148,12 +150,12 @@ function renderModuleSelector(data, handlers) {
148
150
  size="small"
149
151
  ?disabled=${data.disabled}
150
152
  searchable
151
- search-placeholder="${msg('Search modules...')}"
153
+ search-placeholder="${data.i18n.modules.moduleSearchPlaceholder}"
152
154
  use-custom-selected-display
153
155
  part="module-select"
154
156
  class="module-select"
155
157
  @nr-change=${handlers.onModuleChange}
156
- aria-label="${msg('Select modules')}"
158
+ aria-label="${data.i18n.modules.moduleSelectAriaLabel}"
157
159
  >
158
160
  <span slot="selected-display">
159
161
  ${data.renderModuleDisplay()}
@@ -174,10 +176,10 @@ function renderSendButton(data, handlers) {
174
176
  .iconRight=${data.isQueryRunning ? 'square' : 'arrow-up'}
175
177
  @click=${data.isQueryRunning ? handlers.onStop : handlers.onSend}
176
178
  @keydown=${handlers.onSendKeydown}
177
- aria-label="${data.isQueryRunning ? msg('Stop query') : msg('Send message')}"
178
- title="${data.isQueryRunning ? msg('Stop query') : msg('Send message')}"
179
+ aria-label="${data.isQueryRunning ? data.i18n.send.stopQueryLabel : data.i18n.send.sendMessageLabel}"
180
+ title="${data.isQueryRunning ? data.i18n.send.stopQueryLabel : data.i18n.send.sendMessageLabel}"
179
181
  >
180
- ${data.isQueryRunning ? msg('Stop') : msg('Send')}
182
+ ${data.isQueryRunning ? data.i18n.send.stopButton : data.i18n.send.sendButton}
181
183
  </nr-button>
182
184
  `;
183
185
  }
@@ -190,7 +192,7 @@ function renderSendButton(data, handlers) {
190
192
  function renderRecordingBar(data, handlers) {
191
193
  const { duration, bars } = data.audioRecording;
192
194
  const isTranscribe = data.audioMode === 'transcribe';
193
- const sendTitle = isTranscribe ? msg('Convert to text') : msg('Send as voice message');
195
+ const sendTitle = isTranscribe ? data.i18n.audio.convertToTextLabel : data.i18n.audio.sendAsVoiceMessageLabel;
194
196
  const sendIcon = isTranscribe
195
197
  ? html `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
196
198
  <rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>
@@ -200,12 +202,13 @@ function renderRecordingBar(data, handlers) {
200
202
  <path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/>
201
203
  </svg>`;
202
204
  return html `
203
- <div class="audio-recording-bar">
205
+ <div class="audio-recording-bar" part="audio-recording-bar">
204
206
  <button
205
207
  class="audio-rec-cancel"
206
- title="${msg('Cancel recording')}"
208
+ part="audio-cancel-button"
209
+ title="${data.i18n.audio.cancelRecordingLabel}"
207
210
  @click=${handlers.onAudioCancel}
208
- aria-label="${msg('Cancel recording')}"
211
+ aria-label="${data.i18n.audio.cancelRecordingLabel}"
209
212
  >
210
213
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
211
214
  <polyline points="3 6 5 6 21 6"/>
@@ -214,22 +217,23 @@ function renderRecordingBar(data, handlers) {
214
217
  </svg>
215
218
  </button>
216
219
 
217
- <div class="audio-rec-indicator">
218
- <span class="audio-rec-dot"></span>
219
- <div class="audio-rec-wave">
220
+ <div class="audio-rec-indicator" part="audio-indicator">
221
+ <span class="audio-rec-dot" part="audio-dot"></span>
222
+ <div class="audio-rec-wave" part="audio-wave">
220
223
  ${bars.map(v => html `
221
- <div class="audio-rec-bar" style=${styleMap({ height: `${Math.round(v * 24)}px` })}></div>
224
+ <div class="audio-rec-bar" part="audio-bar" style=${styleMap({ height: `${Math.round(v * 24)}px` })}></div>
222
225
  `)}
223
226
  </div>
224
- <span class="audio-rec-time">${duration}</span>
227
+ <span class="audio-rec-time" part="audio-time">${duration}</span>
225
228
  </div>
226
229
 
227
- <span class="audio-rec-mode-label">
228
- ${isTranscribe ? msg('Speech to text') : msg('Voice message')}
230
+ <span class="audio-rec-mode-label" part="audio-mode-label">
231
+ ${isTranscribe ? data.i18n.audio.speechToTextLabel : data.i18n.audio.voiceMessageLabel}
229
232
  </span>
230
233
 
231
234
  <button
232
235
  class="audio-rec-send ${isTranscribe ? 'audio-rec-send--transcribe' : ''}"
236
+ part="audio-send-button"
233
237
  title="${sendTitle}"
234
238
  @click=${handlers.onAudioSend}
235
239
  aria-label="${sendTitle}"
@@ -244,23 +248,24 @@ function renderRecordingBar(data, handlers) {
244
248
  */
245
249
  function renderActionButtons(data, handlers) {
246
250
  return html `
247
- <div class="action-buttons-row">
248
- <div class="action-buttons-left">
251
+ <div class="action-buttons-row" part="actions">
252
+ <div class="action-buttons-left" part="actions-left">
249
253
  ${data.enableFileUpload ? renderFileUploadButton(data, handlers) : nothing}
250
254
  ${data.enableModuleSelection && data.moduleOptions.length > 0
251
255
  ? renderModuleSelector(data, handlers)
252
256
  : nothing}
253
257
  </div>
254
258
 
255
- <div class="action-buttons-right">
259
+ <div class="action-buttons-right" part="actions-right">
256
260
  ${data.showAudioButton && !data.isQueryRunning ? html `
257
261
  <!-- Speech-to-text: mic + keyboard indicator -->
258
262
  <button
259
263
  class="audio-mic-btn"
260
- title="${msg('Record speech to text')}"
264
+ part="audio-mic-button audio-mic-transcribe"
265
+ title="${data.i18n.audio.recordSpeechLabel}"
261
266
  ?disabled=${data.disabled}
262
267
  @click=${() => { var _a; return (_a = handlers.onAudioStart) === null || _a === void 0 ? void 0 : _a.call(handlers, 'transcribe'); }}
263
- aria-label="${msg('Record speech to text')}"
268
+ aria-label="${data.i18n.audio.recordSpeechLabel}"
264
269
  >
265
270
  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
266
271
  <path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z"/>
@@ -275,10 +280,11 @@ function renderActionButtons(data, handlers) {
275
280
  <!-- Voice message: mic + waveform indicator -->
276
281
  <button
277
282
  class="audio-mic-btn"
278
- title="${msg('Send voice message')}"
283
+ part="audio-mic-button audio-mic-voice"
284
+ title="${data.i18n.audio.sendVoiceMessageLabel}"
279
285
  ?disabled=${data.disabled}
280
286
  @click=${() => { var _a; return (_a = handlers.onAudioStart) === null || _a === void 0 ? void 0 : _a.call(handlers, 'message'); }}
281
- aria-label="${msg('Send voice message')}"
287
+ aria-label="${data.i18n.audio.sendVoiceMessageLabel}"
282
288
  >
283
289
  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
284
290
  <path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z"/>
@@ -316,18 +322,18 @@ export function renderInputBox(data, handlers) {
316
322
  <div class="input-container" part="input-container">
317
323
  <!-- Context tags -->
318
324
  ${data.uploadedFiles.length > 0
319
- ? renderContextTags(data.uploadedFiles, handlers.onFileRemove, handlers.onFileClick)
325
+ ? renderContextTags(data.uploadedFiles, handlers.onFileRemove, data.i18n, handlers.onFileClick)
320
326
  : nothing}
321
327
 
322
328
  <!-- Input area -->
323
- <div class="input-row">
329
+ <div class="input-row" part="input-row">
324
330
  <div
325
331
  class="input-box__input"
326
332
  part="input"
327
333
  contenteditable="true"
328
334
  role="textbox"
329
335
  aria-multiline="true"
330
- aria-label="${msg('Chat input')}"
336
+ aria-label="${data.i18n.input.chatInputAriaLabel}"
331
337
  data-placeholder="${data.placeholder}"
332
338
  @input=${handlers.onInput}
333
339
  @keydown=${handlers.onKeydown}
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
  import { TemplateResult, nothing } from 'lit';
7
- import { ChatbotMessage, ChatbotLoadingType } from '../chatbot.types.js';
7
+ import { ChatbotMessage, ChatbotLoadingType, ChatbotI18n } from '../chatbot.types.js';
8
8
  export interface MessageTemplateHandlers {
9
9
  onRetry: (message: ChatbotMessage) => void;
10
10
  onRetryKeydown: (e: KeyboardEvent) => void;
@@ -15,7 +15,7 @@ export interface MessageTemplateHandlers {
15
15
  /**
16
16
  * Renders a single message
17
17
  */
18
- export declare function renderMessage(message: ChatbotMessage, handlers: MessageTemplateHandlers): TemplateResult;
18
+ export declare function renderMessage(message: ChatbotMessage, handlers: MessageTemplateHandlers, i18n: ChatbotI18n): TemplateResult;
19
19
  /**
20
20
  * Renders bot typing indicator
21
21
  */
@@ -23,9 +23,9 @@ export declare function renderBotTypingIndicator(isTyping: boolean, loadingIndic
23
23
  /**
24
24
  * Renders empty state
25
25
  */
26
- export declare function renderEmptyState(): TemplateResult;
26
+ export declare function renderEmptyState(i18n: ChatbotI18n): TemplateResult;
27
27
  /**
28
28
  * Renders messages container with all messages
29
29
  */
30
- export declare function renderMessages(messages: ChatbotMessage[], suggestions: TemplateResult | typeof nothing, typingIndicator: TemplateResult | typeof nothing, messageHandlers: MessageTemplateHandlers): TemplateResult;
30
+ export declare function renderMessages(messages: ChatbotMessage[], suggestions: TemplateResult | typeof nothing, typingIndicator: TemplateResult | typeof nothing, messageHandlers: MessageTemplateHandlers, i18n: ChatbotI18n): TemplateResult;
31
31
  //# sourceMappingURL=message.template.d.ts.map
@@ -6,7 +6,6 @@
6
6
  import { html, nothing } from 'lit';
7
7
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
8
8
  import { classMap } from 'lit/directives/class-map.js';
9
- import { msg } from '@lit/localize';
10
9
  import { ChatbotLoadingType } from '../chatbot.types.js';
11
10
  import { formatTimestamp } from '../utils/format.js';
12
11
  /**
@@ -61,7 +60,7 @@ function getFileExtension(name, mimeType) {
61
60
  /**
62
61
  * Renders a single message
63
62
  */
64
- export function renderMessage(message, handlers) {
63
+ export function renderMessage(message, handlers, i18n) {
65
64
  var _a, _b, _c, _d, _e, _f, _g, _h;
66
65
  const isError = (_a = message.text) === null || _a === void 0 ? void 0 : _a.includes('[ERROR_START]');
67
66
  const messageClasses = {
@@ -84,7 +83,7 @@ export function renderMessage(message, handlers) {
84
83
  : unsafeHTML(((_h = (_g = message.text) === null || _g === void 0 ? void 0 : _g.trim()) !== null && _h !== void 0 ? _h : '').replaceAll('\n', '<br>'))}
85
84
  </div>
86
85
  ${message.files && message.files.length > 0 ? html `
87
- <div class="message__attachments" part="message-attachments" role="list" aria-label="${msg('Attached files')}">
86
+ <div class="message__attachments" part="message-attachments" role="list" aria-label="${i18n.messages.attachedFilesLabel}">
88
87
  ${message.files.map((f) => html `
89
88
  <nr-dropdown
90
89
  trigger="hover"
@@ -145,10 +144,11 @@ export function renderMessage(message, handlers) {
145
144
  size="small"
146
145
  color="#9ca3af"
147
146
  class="message__copy"
147
+ part="message-copy"
148
148
  @click=${() => handlers.onCopy(message)}
149
149
  @keydown=${(e) => handlers.onCopyKeydown(e, message)}
150
- title="${msg('Copy message')}"
151
- aria-label="${msg('Copy message')}"
150
+ title="${i18n.messages.copyMessageLabel}"
151
+ aria-label="${i18n.messages.copyMessageLabel}"
152
152
  role="button"
153
153
  tabindex="0"
154
154
  ></nr-icon>
@@ -162,9 +162,9 @@ export function renderMessage(message, handlers) {
162
162
  part="retry-button"
163
163
  @click=${() => handlers.onRetry(message)}
164
164
  @keydown=${handlers.onRetryKeydown}
165
- aria-label="${msg('Retry message')}"
165
+ aria-label="${i18n.messages.retryMessageLabel}"
166
166
  >
167
- ${msg('Retry')}
167
+ ${i18n.messages.retryButton}
168
168
  </nr-button>`
169
169
  : nothing}
170
170
  </div>
@@ -178,18 +178,18 @@ export function renderBotTypingIndicator(isTyping, loadingIndicator, loadingText
178
178
  return nothing;
179
179
  const indicatorContent = loadingIndicator === ChatbotLoadingType.Dots
180
180
  ? html `
181
- <div class="dots">
181
+ <div class="dots" part="typing-dots">
182
182
  <span></span>
183
183
  <span></span>
184
184
  <span></span>
185
185
  </div>
186
186
  `
187
- : html `<div class="spinner"></div>`;
187
+ : html `<div class="spinner" part="typing-spinner"></div>`;
188
188
  return html `
189
189
  <div class="message bot loading" part="typing-indicator">
190
- <div class="message__content">
190
+ <div class="message__content" part="typing-content">
191
191
  ${indicatorContent}
192
- ${loadingText ? html `<span class="loading-text">${loadingText.split('').map((char, i) => html `<span class="loading-text__char" style="animation-delay:${i * 0.04}s">${char === ' ' ? '\u00A0' : char}</span>`)}</span>` : nothing}
192
+ ${loadingText ? html `<span class="loading-text" part="typing-text">${loadingText.split('').map((char, i) => html `<span class="loading-text__char" style="animation-delay:${i * 0.04}s">${char === ' ' ? '\u00A0' : char}</span>`)}</span>` : nothing}
193
193
  </div>
194
194
  </div>
195
195
  `;
@@ -197,12 +197,12 @@ export function renderBotTypingIndicator(isTyping, loadingIndicator, loadingText
197
197
  /**
198
198
  * Renders empty state
199
199
  */
200
- export function renderEmptyState() {
200
+ export function renderEmptyState(i18n) {
201
201
  return html `
202
202
  <div class="empty-state" part="empty-state">
203
203
  <slot name="empty-state">
204
- <div class="empty-state__content">
205
- ${msg('Start a conversation')}
204
+ <div class="empty-state__content" part="empty-state-content">
205
+ ${i18n.messages.startConversationLabel}
206
206
  </div>
207
207
  </slot>
208
208
  </div>
@@ -211,11 +211,11 @@ export function renderEmptyState() {
211
211
  /**
212
212
  * Renders messages container with all messages
213
213
  */
214
- export function renderMessages(messages, suggestions, typingIndicator, messageHandlers) {
214
+ export function renderMessages(messages, suggestions, typingIndicator, messageHandlers, i18n) {
215
215
  return html `
216
216
  <div class="messages" part="messages">
217
- ${messages.length === 0 ? renderEmptyState() : nothing}
218
- ${messages.map((message) => renderMessage(message, messageHandlers))}
217
+ ${messages.length === 0 ? renderEmptyState(i18n) : nothing}
218
+ ${messages.map((message) => renderMessage(message, messageHandlers, i18n))}
219
219
  ${suggestions}
220
220
  ${typingIndicator}
221
221
  </div>
@@ -4,17 +4,11 @@
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
  import { TemplateResult, nothing } from 'lit';
7
- import { ChatbotSuggestion } from '../chatbot.types.js';
7
+ import { ChatbotSuggestion, ChatbotI18n } from '../chatbot.types.js';
8
8
  export interface SuggestionTemplateHandlers {
9
9
  onClick: (suggestion: ChatbotSuggestion) => void;
10
10
  onKeydown: (e: KeyboardEvent) => void;
11
11
  }
12
- /**
13
- * Renders a single suggestion
14
- */
15
- export declare function renderSuggestion(suggestion: ChatbotSuggestion, handlers: SuggestionTemplateHandlers): TemplateResult;
16
- /**
17
- * Renders suggestions container
18
- */
19
- export declare function renderSuggestions(_chatStarted: boolean, suggestions: ChatbotSuggestion[], handlers: SuggestionTemplateHandlers): TemplateResult | typeof nothing;
12
+ export declare function renderSuggestion(suggestion: ChatbotSuggestion, handlers: SuggestionTemplateHandlers, i18n: ChatbotI18n): TemplateResult;
13
+ export declare function renderSuggestions(_chatStarted: boolean, suggestions: ChatbotSuggestion[], handlers: SuggestionTemplateHandlers, i18n: ChatbotI18n): TemplateResult | typeof nothing;
20
14
  //# sourceMappingURL=suggestion.template.d.ts.map
@@ -5,34 +5,27 @@
5
5
  */
6
6
  import { html, nothing } from 'lit';
7
7
  import { classMap } from 'lit/directives/class-map.js';
8
- import { msg } from '@lit/localize';
9
- /**
10
- * Renders a single suggestion
11
- */
12
- export function renderSuggestion(suggestion, handlers) {
8
+ export function renderSuggestion(suggestion, handlers, i18n) {
13
9
  return html `
14
- <div
15
- class="suggestion ${classMap({ 'suggestion--disabled': suggestion.enabled === false })}"
10
+ <div
11
+ class="suggestion ${classMap({ 'suggestion--disabled': suggestion.enabled === false })}"
16
12
  part="suggestion"
17
13
  role="button"
18
14
  tabindex="0"
19
15
  @click=${() => handlers.onClick(suggestion)}
20
16
  @keydown=${handlers.onKeydown}
21
17
  data-id="${suggestion.id}"
22
- aria-label="${msg('Select suggestion: ')}${suggestion.text}"
18
+ aria-label="${i18n.messages.suggestionPrefix}${suggestion.text}"
23
19
  >
24
20
  ${suggestion.text}
25
21
  </div>
26
22
  `;
27
23
  }
28
- /**
29
- * Renders suggestions container
30
- */
31
- export function renderSuggestions(_chatStarted, suggestions, handlers) {
24
+ export function renderSuggestions(_chatStarted, suggestions, handlers, i18n) {
32
25
  return suggestions.length > 0
33
26
  ? html `
34
27
  <div class="suggestion-container" part="suggestions">
35
- ${suggestions.map((suggestion) => renderSuggestion(suggestion, handlers))}
28
+ ${suggestions.map((suggestion) => renderSuggestion(suggestion, handlers, i18n))}
36
29
  </div>
37
30
  `
38
31
  : nothing;
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
  import { TemplateResult } from 'lit';
7
- import { ChatbotThread } from '../chatbot.types.js';
7
+ import { ChatbotThread, ChatbotI18n } from '../chatbot.types.js';
8
8
  export interface ThreadSidebarTemplateHandlers {
9
9
  onCreateNew: () => void;
10
10
  onSelectThread: (threadId: string) => void;
@@ -16,6 +16,7 @@ export interface ThreadSidebarTemplateData {
16
16
  threads: ChatbotThread[];
17
17
  activeThreadId?: string;
18
18
  editingThreadId?: string;
19
+ i18n: ChatbotI18n;
19
20
  }
20
21
  /**
21
22
  * Renders thread sidebar
@@ -6,7 +6,6 @@
6
6
  import { html, nothing } from 'lit';
7
7
  import { repeat } from 'lit/directives/repeat.js';
8
8
  import { classMap } from 'lit/directives/class-map.js';
9
- import { msg } from '@lit/localize';
10
9
  import { formatTimestamp } from '../utils/format.js';
11
10
  function renderThreadItem(thread, data, handlers) {
12
11
  const lastMessage = thread.messages.length > 0
@@ -27,6 +26,7 @@ function renderThreadItem(thread, data, handlers) {
27
26
  ${data.editingThreadId === thread.id && handlers.onRenameThread ? html `
28
27
  <input
29
28
  class="thread-item__rename-input"
29
+ part="thread-rename-input"
30
30
  type="text"
31
31
  .value=${thread.title || ''}
32
32
  @click=${(e) => e.stopPropagation()}
@@ -53,13 +53,13 @@ function renderThreadItem(thread, data, handlers) {
53
53
  }}
54
54
  />
55
55
  ` : html `
56
- <div class="thread-item__title">${thread.title || msg('New Chat')}</div>
56
+ <div class="thread-item__title" part="thread-title">${thread.title || data.i18n.threads.newChatTitle}</div>
57
57
  `}
58
- <div class="thread-item__actions">
58
+ <div class="thread-item__actions" part="thread-actions">
59
59
  ${handlers.onBookmarkThread && thread.bookmarked ? html `
60
60
  <button
61
61
  class="thread-item__action-btn thread-item__bookmark--active"
62
- title="${msg('Remove bookmark')}"
62
+ title="${data.i18n.threads.removeBookmarkLabel}"
63
63
  @click=${(e) => {
64
64
  e.stopPropagation();
65
65
  handlers.onBookmarkThread(thread.id);
@@ -94,17 +94,17 @@ function renderThreadItem(thread, data, handlers) {
94
94
  }
95
95
  }}
96
96
  .items=${[
97
- ...(handlers.onRenameThread ? [{ id: 'rename', label: msg('Rename') }] : []),
98
- ...(handlers.onBookmarkThread ? [{ id: 'bookmark', label: thread.bookmarked ? msg('Remove bookmark') : msg('Bookmark') }] : []),
99
- ...(handlers.onDeleteThread ? [{ id: 'delete', label: msg('Delete') }] : []),
97
+ ...(handlers.onRenameThread ? [{ id: 'rename', label: data.i18n.threads.renameLabel }] : []),
98
+ ...(handlers.onBookmarkThread ? [{ id: 'bookmark', label: thread.bookmarked ? data.i18n.threads.removeBookmarkLabel : data.i18n.threads.bookmarkLabel }] : []),
99
+ ...(handlers.onDeleteThread ? [{ id: 'delete', label: data.i18n.threads.deleteLabel }] : []),
100
100
  ]}
101
101
  >
102
102
  <button
103
103
  slot="trigger"
104
104
  class="thread-item__action-btn thread-item__menu"
105
- title="${msg('More options')}"
105
+ title="${data.i18n.threads.moreOptionsLabel}"
106
106
  part="thread-menu"
107
- aria-label="${msg('More options')}"
107
+ aria-label="${data.i18n.threads.moreOptionsLabel}"
108
108
  >
109
109
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="none"><circle cx="5" cy="12" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="19" cy="12" r="1.8"/></svg>
110
110
  </button>
@@ -112,10 +112,10 @@ function renderThreadItem(thread, data, handlers) {
112
112
  ` : ''}
113
113
  </div>
114
114
  </div>
115
- <div class="thread-item__preview">
115
+ <div class="thread-item__preview" part="thread-preview">
116
116
  ${previewText}
117
117
  </div>
118
- <div class="thread-item__timestamp">${formatTimestamp(thread.updatedAt)}</div>
118
+ <div class="thread-item__timestamp" part="thread-timestamp">${formatTimestamp(thread.updatedAt)}</div>
119
119
  </div>
120
120
  `;
121
121
  }
@@ -127,27 +127,27 @@ export function renderThreadSidebar(data, handlers) {
127
127
  const regularThreads = data.threads.filter(t => !t.bookmarked);
128
128
  return html `
129
129
  <div class="thread-sidebar" part="thread-sidebar">
130
- <div class="thread-sidebar__header">
131
- <h3>${msg('Conversations')}</h3>
130
+ <div class="thread-sidebar__header" part="thread-sidebar-header">
131
+ <h3 part="thread-sidebar-title">${data.i18n.threads.conversationsTitle}</h3>
132
132
  </div>
133
133
 
134
- <div class="thread-list">
134
+ <div class="thread-list" part="thread-list">
135
135
  ${bookmarkedThreads.length > 0 ? html `
136
- <div class="thread-section" part="thread-section-bookmarks">
137
- <div class="thread-section__label">
136
+ <div class="thread-section" part="thread-section thread-section-bookmarks">
137
+ <div class="thread-section__label" part="thread-section-label">
138
138
  <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
139
- ${msg('Bookmarks')}
139
+ ${data.i18n.threads.bookmarksLabel}
140
140
  </div>
141
141
  ${repeat(bookmarkedThreads, t => t.id, t => renderThreadItem(t, data, handlers))}
142
142
  </div>
143
143
  ` : nothing}
144
144
  ${regularThreads.length > 0 || bookmarkedThreads.length === 0 ? html `
145
145
  ${bookmarkedThreads.length > 0 ? html `
146
- <div class="thread-section__label">${msg('All Conversations')}</div>
146
+ <div class="thread-section__label" part="thread-section-label">${data.i18n.threads.allConversationsLabel}</div>
147
147
  ` : nothing}
148
148
  ${repeat(regularThreads, t => t.id, t => renderThreadItem(t, data, handlers))}
149
149
  ${regularThreads.length === 0 && bookmarkedThreads.length === 0 ? html `
150
- <p class="empty-msg">${msg('No conversations yet')}</p>
150
+ <p class="empty-msg" part="thread-empty">${data.i18n.threads.noConversationsLabel}</p>
151
151
  ` : nothing}
152
152
  ` : nothing}
153
153
  </div>
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
  import { TemplateResult } from 'lit';
7
+ import { ChatbotI18n } from '../chatbot.types.js';
7
8
  export interface UrlModalTemplateHandlers {
8
9
  onClose: () => void;
9
10
  onUrlInputChange: (e: Event) => void;
@@ -17,9 +18,7 @@ export interface UrlModalTemplateData {
17
18
  isLoading?: boolean;
18
19
  error?: string;
19
20
  selectedFileName?: string;
21
+ i18n: ChatbotI18n;
20
22
  }
21
- /**
22
- * Renders URL attachment modal
23
- */
24
23
  export declare function renderUrlModal(data: UrlModalTemplateData, handlers: UrlModalTemplateHandlers): TemplateResult;
25
24
  //# sourceMappingURL=url-modal.template.d.ts.map