@farming-labs/nuxt-theme 0.0.2-beta.20 → 0.0.2-beta.22

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.
@@ -18,7 +18,7 @@ const props = withDefaults(
18
18
  position: "bottom-right",
19
19
  floatingStyle: "panel",
20
20
  triggerComponent: null,
21
- }
21
+ },
22
22
  );
23
23
 
24
24
  const mounted = ref(false);
@@ -30,7 +30,9 @@ const fmListEl = ref<HTMLElement | null>(null);
30
30
  const fmInputEl = ref<HTMLTextAreaElement | null>(null);
31
31
  const messagesEndEl = ref<HTMLElement | null>(null);
32
32
 
33
- onMounted(() => { mounted.value = true; });
33
+ onMounted(() => {
34
+ mounted.value = true;
35
+ });
34
36
 
35
37
  const isFullModal = computed(() => props.floatingStyle === "full-modal");
36
38
  const isModal = computed(() => props.floatingStyle === "modal");
@@ -66,7 +68,7 @@ const containerStyle = computed(() => {
66
68
  const animation = computed(() =>
67
69
  props.floatingStyle === "modal"
68
70
  ? "fd-ai-float-center-in 200ms ease-out"
69
- : "fd-ai-float-in 200ms ease-out"
71
+ : "fd-ai-float-in 200ms ease-out",
70
72
  );
71
73
 
72
74
  watch(isOpen, (open) => {
@@ -86,14 +88,19 @@ watch(isOpen, (open) => {
86
88
  }
87
89
  });
88
90
 
89
- watch(() => messages.value.length, () => {
90
- if (isFullModal.value && fmListEl.value) {
91
- nextTick(() => fmListEl.value?.scrollTo({ top: fmListEl.value.scrollHeight, behavior: "smooth" }));
92
- }
93
- if (!isFullModal.value) {
94
- nextTick(() => messagesEndEl.value?.scrollIntoView({ behavior: "smooth" }));
95
- }
96
- });
91
+ watch(
92
+ () => messages.value.length,
93
+ () => {
94
+ if (isFullModal.value && fmListEl.value) {
95
+ nextTick(() =>
96
+ fmListEl.value?.scrollTo({ top: fmListEl.value.scrollHeight, behavior: "smooth" }),
97
+ );
98
+ }
99
+ if (!isFullModal.value) {
100
+ nextTick(() => messagesEndEl.value?.scrollIntoView({ behavior: "smooth" }));
101
+ }
102
+ },
103
+ );
97
104
 
98
105
  async function submitQuestion(question: string) {
99
106
  if (!question.trim() || isStreaming.value) return;
@@ -114,7 +121,10 @@ async function submitQuestion(question: string) {
114
121
 
115
122
  if (!res.ok) {
116
123
  const err = await res.json().catch(() => ({}));
117
- messages.value = [...newMessages, { role: "assistant", content: (err as any).error ?? "Something went wrong." }];
124
+ messages.value = [
125
+ ...newMessages,
126
+ { role: "assistant", content: (err as any).error ?? "Something went wrong." },
127
+ ];
118
128
  isStreaming.value = false;
119
129
  return;
120
130
  }
@@ -147,7 +157,10 @@ async function submitQuestion(question: string) {
147
157
  }
148
158
  messages.value = [...newMessages, { role: "assistant", content: assistantContent }];
149
159
  } catch {
150
- messages.value = [...newMessages, { role: "assistant", content: "Failed to connect. Please try again." }];
160
+ messages.value = [
161
+ ...newMessages,
162
+ { role: "assistant", content: "Failed to connect. Please try again." },
163
+ ];
151
164
  }
152
165
  isStreaming.value = false;
153
166
  }
@@ -172,265 +185,344 @@ function handleFmKeyDown(e: KeyboardEvent) {
172
185
  </script>
173
186
 
174
187
  <template>
175
- <!-- ═══ FULL-MODAL: overlay + messages (only when full-modal AND open) ═══ -->
176
- <Teleport to="body" v-if="mounted && isFullModal && isOpen">
177
- <div class="fd-ai-fm-overlay" @click.self="isOpen = false" @keydown="handleKeydown">
178
- <div class="fd-ai-fm-topbar">
179
- <button class="fd-ai-fm-close-btn" type="button" aria-label="Close" @click="isOpen = false">
180
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
181
- <path d="M18 6 6 18" /><path d="m6 6 12 12" />
182
- </svg>
183
- </button>
184
- </div>
185
- <div ref="fmListEl" class="fd-ai-fm-messages">
186
- <div class="fd-ai-fm-messages-inner">
187
- <div v-for="(msg, i) in messages" :key="i" class="fd-ai-fm-msg" :data-role="msg.role">
188
- <div class="fd-ai-fm-msg-label" :data-role="msg.role">
189
- {{ msg.role === "user" ? "you" : label }}
190
- </div>
191
- <div class="fd-ai-fm-msg-content">
192
- <template v-if="msg.content">
193
- <div v-html="renderMarkdown(msg.content)" />
194
- </template>
195
- <div v-else class="fd-ai-fm-thinking">
196
- <span class="fd-ai-fm-thinking-dot" />
197
- <span class="fd-ai-fm-thinking-dot" />
198
- <span class="fd-ai-fm-thinking-dot" />
199
- </div>
188
+ <!-- ═══ FULL-MODAL: overlay + messages (only when full-modal AND open) ═══ -->
189
+ <Teleport to="body" v-if="mounted && isFullModal && isOpen">
190
+ <div class="fd-ai-fm-overlay" @click.self="isOpen = false" @keydown="handleKeydown">
191
+ <div class="fd-ai-fm-topbar">
192
+ <button class="fd-ai-fm-close-btn" type="button" aria-label="Close" @click="isOpen = false">
193
+ <svg
194
+ width="16"
195
+ height="16"
196
+ viewBox="0 0 24 24"
197
+ fill="none"
198
+ stroke="currentColor"
199
+ stroke-width="2"
200
+ stroke-linecap="round"
201
+ stroke-linejoin="round"
202
+ >
203
+ <path d="M18 6 6 18" />
204
+ <path d="m6 6 12 12" />
205
+ </svg>
206
+ </button>
207
+ </div>
208
+ <div ref="fmListEl" class="fd-ai-fm-messages">
209
+ <div class="fd-ai-fm-messages-inner">
210
+ <div v-for="(msg, i) in messages" :key="i" class="fd-ai-fm-msg" :data-role="msg.role">
211
+ <div class="fd-ai-fm-msg-label" :data-role="msg.role">
212
+ {{ msg.role === "user" ? "you" : label }}
213
+ </div>
214
+ <div class="fd-ai-fm-msg-content">
215
+ <template v-if="msg.content">
216
+ <div v-html="renderMarkdown(msg.content)" />
217
+ </template>
218
+ <div v-else class="fd-ai-fm-thinking">
219
+ <span class="fd-ai-fm-thinking-dot" />
220
+ <span class="fd-ai-fm-thinking-dot" />
221
+ <span class="fd-ai-fm-thinking-dot" />
200
222
  </div>
201
223
  </div>
202
224
  </div>
203
225
  </div>
204
226
  </div>
205
- </Teleport>
227
+ </div>
228
+ </Teleport>
206
229
 
207
- <!-- ═══ FULL-MODAL: bottom input bar (only when full-modal) ═══ -->
208
- <Teleport to="body" v-if="mounted && isFullModal">
230
+ <!-- ═══ FULL-MODAL: bottom input bar (only when full-modal) ═══ -->
231
+ <Teleport to="body" v-if="mounted && isFullModal">
232
+ <div
233
+ class="fd-ai-fm-input-bar"
234
+ :class="isOpen ? 'fd-ai-fm-input-bar--open' : 'fd-ai-fm-input-bar--closed'"
235
+ :style="isOpen ? undefined : btnStyle"
236
+ >
209
237
  <div
210
- class="fd-ai-fm-input-bar"
211
- :class="isOpen ? 'fd-ai-fm-input-bar--open' : 'fd-ai-fm-input-bar--closed'"
212
- :style="isOpen ? undefined : btnStyle"
238
+ v-if="!isOpen && triggerComponent"
239
+ class="fd-ai-floating-trigger"
240
+ :style="btnStyle"
241
+ @click="isOpen = true"
213
242
  >
214
- <div
215
- v-if="!isOpen && triggerComponent"
216
- class="fd-ai-floating-trigger"
217
- :style="btnStyle"
218
- @click="isOpen = true"
243
+ <component :is="triggerComponent" />
244
+ </div>
245
+ <button
246
+ v-else-if="!isOpen"
247
+ class="fd-ai-fm-trigger-btn"
248
+ type="button"
249
+ :aria-label="`Ask ${label}`"
250
+ @click="isOpen = true"
251
+ >
252
+ <svg
253
+ width="16"
254
+ height="16"
255
+ viewBox="0 0 24 24"
256
+ fill="none"
257
+ stroke="currentColor"
258
+ stroke-width="2"
259
+ stroke-linecap="round"
260
+ stroke-linejoin="round"
219
261
  >
220
- <component :is="triggerComponent" />
262
+ <path
263
+ d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"
264
+ />
265
+ <path d="M20 3v4" />
266
+ <path d="M22 5h-4" />
267
+ </svg>
268
+ <span>Ask {{ label }}</span>
269
+ </button>
270
+
271
+ <div v-else class="fd-ai-fm-input-container">
272
+ <div class="fd-ai-fm-input-wrap">
273
+ <textarea
274
+ ref="fmInputEl"
275
+ v-model="aiInput"
276
+ class="fd-ai-fm-input"
277
+ :placeholder="isStreaming ? 'answering...' : `Ask ${label}`"
278
+ :disabled="isStreaming"
279
+ rows="1"
280
+ @keydown="handleFmKeyDown"
281
+ />
282
+ <button
283
+ v-if="isStreaming"
284
+ class="fd-ai-fm-send-btn"
285
+ type="button"
286
+ aria-label="Stop"
287
+ @click="isStreaming = false"
288
+ >
289
+ <span class="fd-ai-loading-dots">
290
+ <span class="fd-ai-loading-dot" />
291
+ <span class="fd-ai-loading-dot" />
292
+ <span class="fd-ai-loading-dot" />
293
+ </span>
294
+ </button>
295
+ <button
296
+ v-else
297
+ class="fd-ai-fm-send-btn"
298
+ type="button"
299
+ :data-active="canSend"
300
+ :disabled="!canSend"
301
+ aria-label="Send"
302
+ @click="submitQuestion(aiInput)"
303
+ >
304
+ <svg
305
+ width="16"
306
+ height="16"
307
+ viewBox="0 0 24 24"
308
+ fill="none"
309
+ stroke="currentColor"
310
+ stroke-width="2"
311
+ stroke-linecap="round"
312
+ stroke-linejoin="round"
313
+ >
314
+ <path d="m5 12 7-7 7 7" />
315
+ <path d="M12 19V5" />
316
+ </svg>
317
+ </button>
221
318
  </div>
222
- <button
223
- v-else-if="!isOpen"
224
- class="fd-ai-fm-trigger-btn"
225
- type="button"
226
- :aria-label="`Ask ${label}`"
227
- @click="isOpen = true"
228
- >
229
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
230
- <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" />
231
- <path d="M20 3v4" /><path d="M22 5h-4" />
232
- </svg>
233
- <span>Ask {{ label }}</span>
234
- </button>
235
319
 
236
- <div v-else class="fd-ai-fm-input-container">
237
- <div class="fd-ai-fm-input-wrap">
238
- <textarea
239
- ref="fmInputEl"
240
- v-model="aiInput"
241
- class="fd-ai-fm-input"
242
- :placeholder="isStreaming ? 'answering...' : `Ask ${label}`"
243
- :disabled="isStreaming"
244
- rows="1"
245
- @keydown="handleFmKeyDown"
246
- />
320
+ <div
321
+ v-if="showSuggestions && suggestedQuestions.length > 0"
322
+ class="fd-ai-fm-suggestions-area"
323
+ >
324
+ <div class="fd-ai-fm-suggestions-label">Try asking:</div>
325
+ <div class="fd-ai-fm-suggestions">
247
326
  <button
248
- v-if="isStreaming"
249
- class="fd-ai-fm-send-btn"
327
+ v-for="q in suggestedQuestions"
328
+ :key="q"
250
329
  type="button"
251
- aria-label="Stop"
252
- @click="isStreaming = false"
330
+ class="fd-ai-fm-suggestion"
331
+ @click="submitQuestion(q)"
253
332
  >
254
- <span class="fd-ai-loading-dots">
255
- <span class="fd-ai-loading-dot" />
256
- <span class="fd-ai-loading-dot" />
257
- <span class="fd-ai-loading-dot" />
258
- </span>
333
+ {{ q }}
259
334
  </button>
260
- <button
261
- v-else
262
- class="fd-ai-fm-send-btn"
263
- type="button"
264
- :data-active="canSend"
265
- :disabled="!canSend"
266
- aria-label="Send"
267
- @click="submitQuestion(aiInput)"
335
+ </div>
336
+ </div>
337
+
338
+ <div class="fd-ai-fm-footer-bar">
339
+ <button
340
+ v-if="messages.length > 0"
341
+ class="fd-ai-fm-clear-btn"
342
+ type="button"
343
+ :aria-disabled="isStreaming"
344
+ @click="clearChat"
345
+ >
346
+ <svg
347
+ width="12"
348
+ height="12"
349
+ viewBox="0 0 24 24"
350
+ fill="none"
351
+ stroke="currentColor"
352
+ stroke-width="2"
353
+ stroke-linecap="round"
354
+ stroke-linejoin="round"
268
355
  >
269
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
270
- <path d="m5 12 7-7 7 7" /><path d="M12 19V5" />
271
- </svg>
272
- </button>
356
+ <path d="M3 6h18" />
357
+ <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
358
+ <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
359
+ </svg>
360
+ <span>Clear</span>
361
+ </button>
362
+ <div v-else class="fd-ai-fm-footer-hint">
363
+ AI can be inaccurate, please verify the information.
273
364
  </div>
365
+ </div>
366
+ </div>
367
+ </div>
368
+ </Teleport>
369
+
370
+ <!-- ═══ PANEL/MODAL/POPOVER: backdrop (only for modal style when open) ═══ -->
371
+ <Teleport to="body" v-if="mounted && !isFullModal && isOpen && isModal">
372
+ <div class="fd-ai-overlay" @click="isOpen = false" />
373
+ </Teleport>
274
374
 
275
- <div v-if="showSuggestions && suggestedQuestions.length > 0" class="fd-ai-fm-suggestions-area">
276
- <div class="fd-ai-fm-suggestions-label">Try asking:</div>
277
- <div class="fd-ai-fm-suggestions">
375
+ <!-- ═══ PANEL/MODAL/POPOVER: dialog (only when NOT full-modal AND open) ═══ -->
376
+ <Teleport to="body" v-if="mounted && !isFullModal && isOpen">
377
+ <div
378
+ class="fd-ai-dialog"
379
+ :style="`${containerStyle};animation:${animation}`"
380
+ @click.stop
381
+ @keydown="handleKeydown"
382
+ >
383
+ <div class="fd-ai-header">
384
+ <svg
385
+ width="16"
386
+ height="16"
387
+ viewBox="0 0 24 24"
388
+ fill="none"
389
+ stroke="currentColor"
390
+ stroke-width="2"
391
+ stroke-linecap="round"
392
+ stroke-linejoin="round"
393
+ >
394
+ <path
395
+ d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"
396
+ />
397
+ </svg>
398
+ <span class="fd-ai-header-title">Ask {{ label }}</span>
399
+ <button type="button" class="fd-ai-close-btn" aria-label="Close" @click="isOpen = false">
400
+ <kbd class="fd-ai-esc">ESC</kbd>
401
+ </button>
402
+ </div>
403
+
404
+ <div class="fd-ai-messages">
405
+ <template v-if="messages.length === 0 && !isStreaming">
406
+ <div class="fd-ai-empty">
407
+ <div class="fd-ai-empty-title">Ask anything about the docs</div>
408
+ <div class="fd-ai-empty-desc">Get instant answers from the documentation.</div>
409
+ <div v-if="suggestedQuestions.length > 0" class="fd-ai-suggestions">
278
410
  <button
279
411
  v-for="q in suggestedQuestions"
280
412
  :key="q"
281
413
  type="button"
282
- class="fd-ai-fm-suggestion"
414
+ class="fd-ai-suggestion"
283
415
  @click="submitQuestion(q)"
284
416
  >
285
417
  {{ q }}
286
418
  </button>
287
419
  </div>
288
420
  </div>
289
-
290
- <div class="fd-ai-fm-footer-bar">
291
- <button
292
- v-if="messages.length > 0"
293
- class="fd-ai-fm-clear-btn"
294
- type="button"
295
- :aria-disabled="isStreaming"
296
- @click="clearChat"
297
- >
298
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
299
- <path d="M3 6h18" /><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" /><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
300
- </svg>
301
- <span>Clear</span>
302
- </button>
303
- <div v-else class="fd-ai-fm-footer-hint">
304
- AI can be inaccurate, please verify the information.
305
- </div>
306
- </div>
307
- </div>
308
- </div>
309
- </Teleport>
310
-
311
- <!-- ═══ PANEL/MODAL/POPOVER: backdrop (only for modal style when open) ═══ -->
312
- <Teleport to="body" v-if="mounted && !isFullModal && isOpen && isModal">
313
- <div class="fd-ai-overlay" @click="isOpen = false" />
314
- </Teleport>
315
-
316
- <!-- ═══ PANEL/MODAL/POPOVER: dialog (only when NOT full-modal AND open) ═══ -->
317
- <Teleport to="body" v-if="mounted && !isFullModal && isOpen">
318
- <div
319
- class="fd-ai-dialog"
320
- :style="`${containerStyle};animation:${animation}`"
321
- @click.stop
322
- @keydown="handleKeydown"
323
- >
324
- <div class="fd-ai-header">
325
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
326
- <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" />
327
- </svg>
328
- <span class="fd-ai-header-title">Ask {{ label }}</span>
329
- <button type="button" class="fd-ai-close-btn" aria-label="Close" @click="isOpen = false">
330
- <kbd class="fd-ai-esc">ESC</kbd>
331
- </button>
332
- </div>
333
-
334
- <div class="fd-ai-messages">
335
- <template v-if="messages.length === 0 && !isStreaming">
336
- <div class="fd-ai-empty">
337
- <div class="fd-ai-empty-title">Ask anything about the docs</div>
338
- <div class="fd-ai-empty-desc">Get instant answers from the documentation.</div>
339
- <div v-if="suggestedQuestions.length > 0" class="fd-ai-suggestions">
340
- <button
341
- v-for="q in suggestedQuestions"
342
- :key="q"
343
- type="button"
344
- class="fd-ai-suggestion"
345
- @click="submitQuestion(q)"
346
- >
347
- {{ q }}
348
- </button>
349
- </div>
421
+ </template>
422
+ <template v-for="(msg, i) in messages" :key="i">
423
+ <div class="fd-ai-msg" :data-role="msg.role">
424
+ <div class="fd-ai-msg-label">
425
+ {{ msg.role === "user" ? "You" : label }}
350
426
  </div>
351
- </template>
352
- <template v-for="(msg, i) in messages" :key="i">
353
- <div class="fd-ai-msg" :data-role="msg.role">
354
- <div class="fd-ai-msg-label">
355
- {{ msg.role === "user" ? "You" : label }}
356
- </div>
357
- <div v-if="msg.role === 'user'" class="fd-ai-bubble-user">{{ msg.content }}</div>
358
- <div v-else class="fd-ai-bubble-ai">
359
- <template v-if="msg.content">
360
- <div v-html="renderMarkdown(msg.content)" />
361
- </template>
362
- <span v-else class="fd-ai-loading">
363
- <span class="fd-ai-loading-text">{{ label }} is thinking</span>
364
- <span class="fd-ai-loading-dots">
365
- <span class="fd-ai-loading-dot" />
366
- <span class="fd-ai-loading-dot" />
367
- <span class="fd-ai-loading-dot" />
368
- </span>
427
+ <div v-if="msg.role === 'user'" class="fd-ai-bubble-user">{{ msg.content }}</div>
428
+ <div v-else class="fd-ai-bubble-ai">
429
+ <template v-if="msg.content">
430
+ <div v-html="renderMarkdown(msg.content)" />
431
+ </template>
432
+ <span v-else class="fd-ai-loading">
433
+ <span class="fd-ai-loading-text">{{ label }} is thinking</span>
434
+ <span class="fd-ai-loading-dots">
435
+ <span class="fd-ai-loading-dot" />
436
+ <span class="fd-ai-loading-dot" />
437
+ <span class="fd-ai-loading-dot" />
369
438
  </span>
370
- </div>
439
+ </span>
371
440
  </div>
372
- </template>
373
- <div ref="messagesEndEl" />
374
- </div>
375
-
376
- <div class="fd-ai-chat-footer">
377
- <div class="fd-ai-input-wrap">
378
- <input
379
- v-model="aiInput"
380
- type="text"
381
- class="fd-ai-input"
382
- :placeholder="isStreaming ? `${label} is answering...` : `Ask ${label}...`"
383
- :disabled="isStreaming"
384
- @keydown.enter.prevent="submitQuestion(aiInput)"
385
- />
386
- <button
387
- type="button"
388
- class="fd-ai-send-btn"
389
- :data-active="canSend"
390
- :disabled="!canSend"
391
- aria-label="Send"
392
- @click="submitQuestion(aiInput)"
393
- >
394
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
395
- <path d="m5 12 7-7 7 7" /><path d="M12 19V5" />
396
- </svg>
397
- </button>
398
441
  </div>
442
+ </template>
443
+ <div ref="messagesEndEl" />
444
+ </div>
445
+
446
+ <div class="fd-ai-chat-footer">
447
+ <div class="fd-ai-input-wrap">
448
+ <input
449
+ v-model="aiInput"
450
+ type="text"
451
+ class="fd-ai-input"
452
+ :placeholder="isStreaming ? `${label} is answering...` : `Ask ${label}...`"
453
+ :disabled="isStreaming"
454
+ @keydown.enter.prevent="submitQuestion(aiInput)"
455
+ />
399
456
  <button
400
- v-if="messages.length > 0 && !isStreaming"
401
457
  type="button"
402
- class="fd-ai-clear-btn"
403
- @click="clearChat"
458
+ class="fd-ai-send-btn"
459
+ :data-active="canSend"
460
+ :disabled="!canSend"
461
+ aria-label="Send"
462
+ @click="submitQuestion(aiInput)"
404
463
  >
405
- Clear conversation
464
+ <svg
465
+ width="16"
466
+ height="16"
467
+ viewBox="0 0 24 24"
468
+ fill="none"
469
+ stroke="currentColor"
470
+ stroke-width="2"
471
+ stroke-linecap="round"
472
+ stroke-linejoin="round"
473
+ >
474
+ <path d="m5 12 7-7 7 7" />
475
+ <path d="M12 19V5" />
476
+ </svg>
406
477
  </button>
407
478
  </div>
479
+ <button
480
+ v-if="messages.length > 0 && !isStreaming"
481
+ type="button"
482
+ class="fd-ai-clear-btn"
483
+ @click="clearChat"
484
+ >
485
+ Clear conversation
486
+ </button>
408
487
  </div>
409
- </Teleport>
488
+ </div>
489
+ </Teleport>
410
490
 
411
- <!-- ═══ PANEL/MODAL/POPOVER: icon trigger (only when NOT full-modal AND NOT open) ═══ -->
412
- <Teleport to="body" v-if="mounted && !isFullModal && !isOpen">
413
- <div
414
- v-if="triggerComponent"
415
- class="fd-ai-floating-trigger"
416
- :style="btnStyle"
417
- @click="isOpen = true"
491
+ <!-- ═══ PANEL/MODAL/POPOVER: icon trigger (only when NOT full-modal AND NOT open) ═══ -->
492
+ <Teleport to="body" v-if="mounted && !isFullModal && !isOpen">
493
+ <div
494
+ v-if="triggerComponent"
495
+ class="fd-ai-floating-trigger"
496
+ :style="btnStyle"
497
+ @click="isOpen = true"
498
+ >
499
+ <component :is="triggerComponent" />
500
+ </div>
501
+ <button
502
+ v-else
503
+ type="button"
504
+ class="fd-ai-floating-btn"
505
+ :style="btnStyle"
506
+ :aria-label="`Ask ${label}`"
507
+ @click="isOpen = true"
508
+ >
509
+ <svg
510
+ width="16"
511
+ height="16"
512
+ viewBox="0 0 24 24"
513
+ fill="none"
514
+ stroke="currentColor"
515
+ stroke-width="2"
516
+ stroke-linecap="round"
517
+ stroke-linejoin="round"
418
518
  >
419
- <component :is="triggerComponent" />
420
- </div>
421
- <button
422
- v-else
423
- type="button"
424
- class="fd-ai-floating-btn"
425
- :style="btnStyle"
426
- :aria-label="`Ask ${label}`"
427
- @click="isOpen = true"
428
- >
429
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
430
- <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" />
431
- <path d="M20 3v4" /><path d="M22 5h-4" />
432
- </svg>
433
- <span>Ask {{ label }}</span>
434
- </button>
435
- </Teleport>
519
+ <path
520
+ d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"
521
+ />
522
+ <path d="M20 3v4" />
523
+ <path d="M22 5h-4" />
524
+ </svg>
525
+ <span>Ask {{ label }}</span>
526
+ </button>
527
+ </Teleport>
436
528
  </template>