@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.
- package/package.json +17 -17
- package/src/components/Breadcrumb.vue +4 -4
- package/src/components/DocsContent.vue +6 -17
- package/src/components/DocsLayout.vue +229 -151
- package/src/components/DocsPage.vue +43 -9
- package/src/components/FloatingAIChat.vue +326 -234
- package/src/components/SearchDialog.vue +9 -7
- package/src/components/TableOfContents.vue +34 -18
- package/src/components/ThemeToggle.vue +18 -2
- package/src/lib/renderMarkdown.js +21 -11
- package/src/themes/colorful.d.ts +3 -1
- package/styles/colorful.css +3 -1
- package/styles/docs.css +239 -69
- package/styles/pixel-border.css +3 -4
|
@@ -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(() => {
|
|
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(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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 = [
|
|
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 = [
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
</
|
|
227
|
+
</div>
|
|
228
|
+
</Teleport>
|
|
206
229
|
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
:style="
|
|
238
|
+
v-if="!isOpen && triggerComponent"
|
|
239
|
+
class="fd-ai-floating-trigger"
|
|
240
|
+
:style="btnStyle"
|
|
241
|
+
@click="isOpen = true"
|
|
213
242
|
>
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
<
|
|
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
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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-
|
|
249
|
-
|
|
327
|
+
v-for="q in suggestedQuestions"
|
|
328
|
+
:key="q"
|
|
250
329
|
type="button"
|
|
251
|
-
|
|
252
|
-
@click="
|
|
330
|
+
class="fd-ai-fm-suggestion"
|
|
331
|
+
@click="submitQuestion(q)"
|
|
253
332
|
>
|
|
254
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
</
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
<
|
|
361
|
-
|
|
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
|
-
</
|
|
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-
|
|
403
|
-
|
|
458
|
+
class="fd-ai-send-btn"
|
|
459
|
+
:data-active="canSend"
|
|
460
|
+
:disabled="!canSend"
|
|
461
|
+
aria-label="Send"
|
|
462
|
+
@click="submitQuestion(aiInput)"
|
|
404
463
|
>
|
|
405
|
-
|
|
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
|
-
</
|
|
488
|
+
</div>
|
|
489
|
+
</Teleport>
|
|
410
490
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
<
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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>
|