docus 5.11.0 → 5.12.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.
- package/app/app.config.ts +3 -0
- package/app/app.vue +22 -27
- package/app/components/app/AppSearch.vue +54 -0
- package/app/components/docs/DocsAsideRight.vue +1 -2
- package/app/components/docs/DocsAsideRightBottom.vue +2 -2
- package/app/components/docs/DocsPageHeaderLinks.vue +4 -4
- package/app/composables/useDocusShortcuts.ts +24 -0
- package/app/composables/useUIConfig.ts +1 -1
- package/app/error.vue +1 -10
- package/app/pages/[[lang]]/[...slug].vue +6 -5
- package/app/types/index.d.ts +22 -0
- package/i18n/locales/pl.json +2 -2
- package/modules/assistant/index.ts +9 -3
- package/modules/assistant/runtime/components/AssistantChat.vue +5 -2
- package/modules/assistant/runtime/components/AssistantComark.ts +9 -0
- package/modules/assistant/runtime/components/AssistantFloatingInput.vue +9 -10
- package/modules/assistant/runtime/components/AssistantIndicator.vue +116 -0
- package/modules/assistant/runtime/components/AssistantPanel.vue +268 -258
- package/modules/assistant/runtime/components/AssistantPreStream.vue +1 -1
- package/modules/assistant/runtime/composables/useAssistant.ts +34 -38
- package/modules/assistant/runtime/server/api/search.ts +22 -42
- package/modules/css.ts +8 -0
- package/nuxt.schema.ts +28 -0
- package/package.json +46 -28
- package/server/mcp/tools/get-page.ts +11 -5
- package/server/mcp/tools/list-pages.ts +5 -4
- package/server/routes/sitemap.xml.ts +7 -4
- package/server/utils/content.ts +4 -0
- package/modules/assistant/runtime/components/AssistantLoading.vue +0 -164
- package/modules/assistant/runtime/components/AssistantMatrix.vue +0 -92
|
@@ -1,42 +1,47 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import type { ToolUIPart, DynamicToolUIPart } from 'ai'
|
|
3
|
+
import { DefaultChatTransport, isToolUIPart, isReasoningUIPart, isTextUIPart, getToolName } from 'ai'
|
|
4
4
|
import { Chat } from '@ai-sdk/vue'
|
|
5
|
-
import {
|
|
6
|
-
import { createReusableTemplate } from '@vueuse/core'
|
|
5
|
+
import { isPartStreaming, isToolStreaming } from '@nuxt/ui/utils/ai'
|
|
7
6
|
import { useDocusI18n } from '../../../../app/composables/useDocusI18n'
|
|
7
|
+
import AssistantComark from './AssistantComark'
|
|
8
|
+
import AssistantIndicator from './AssistantIndicator.vue'
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
const components: Record<string, any> = {
|
|
11
|
-
pre: defineAsyncComponent(() => import('./AssistantPreStream.vue')),
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const [DefineChatContent, ReuseChatContent] = createReusableTemplate<{ showExpandButton?: boolean }>()
|
|
15
|
-
|
|
16
|
-
const { isOpen, isExpanded, isMobile, panelWidth, toggleExpanded, messages, pendingMessage, clearPending, faqQuestions } = useAssistant()
|
|
10
|
+
const { isOpen, isStudioExpanded, messages, faqQuestions } = useAssistant()
|
|
17
11
|
const config = useRuntimeConfig()
|
|
18
12
|
const toast = useToast()
|
|
19
13
|
const { t } = useDocusI18n()
|
|
20
14
|
const input = ref('')
|
|
21
15
|
|
|
16
|
+
const open = computed({
|
|
17
|
+
get: () => isOpen.value && !isStudioExpanded.value,
|
|
18
|
+
set: (value) => {
|
|
19
|
+
if (!isStudioExpanded.value) {
|
|
20
|
+
isOpen.value = value
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
22
25
|
const displayTitle = computed(() => t('assistant.title'))
|
|
23
26
|
const displayPlaceholder = computed(() => t('assistant.placeholder'))
|
|
24
27
|
|
|
28
|
+
let _skipSync = false
|
|
29
|
+
|
|
25
30
|
const chat = new Chat({
|
|
26
31
|
messages: messages.value,
|
|
27
32
|
transport: new DefaultChatTransport({
|
|
28
33
|
api: (config.app?.baseURL.replace(/\/$/, '') || '') + config.public.assistant.apiPath,
|
|
29
34
|
}),
|
|
30
35
|
onError: (error: Error) => {
|
|
31
|
-
|
|
36
|
+
let message = error.message
|
|
37
|
+
if (typeof message === 'string' && message[0] === '{') {
|
|
32
38
|
try {
|
|
33
|
-
|
|
34
|
-
return parsed?.message || error.message
|
|
39
|
+
message = JSON.parse(message).message || message
|
|
35
40
|
}
|
|
36
41
|
catch {
|
|
37
|
-
|
|
42
|
+
// keep original on malformed JSON
|
|
38
43
|
}
|
|
39
|
-
}
|
|
44
|
+
}
|
|
40
45
|
|
|
41
46
|
toast.add({
|
|
42
47
|
description: message,
|
|
@@ -46,284 +51,289 @@ const chat = new Chat({
|
|
|
46
51
|
})
|
|
47
52
|
},
|
|
48
53
|
onFinish: () => {
|
|
49
|
-
|
|
54
|
+
_skipSync = true
|
|
55
|
+
messages.value = [...chat.messages]
|
|
56
|
+
nextTick(() => {
|
|
57
|
+
_skipSync = false
|
|
58
|
+
})
|
|
50
59
|
},
|
|
51
60
|
})
|
|
52
61
|
|
|
53
|
-
watch(
|
|
54
|
-
if (
|
|
55
|
-
if (messages.value.length === 0 && chat.messages.length > 0) {
|
|
56
|
-
chat.messages.length = 0
|
|
57
|
-
}
|
|
58
|
-
chat.sendMessage({
|
|
59
|
-
text: message,
|
|
60
|
-
})
|
|
61
|
-
clearPending()
|
|
62
|
-
}
|
|
63
|
-
}, { immediate: true })
|
|
62
|
+
watch(messages, (newMessages) => {
|
|
63
|
+
if (_skipSync) return
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
chat.
|
|
65
|
+
chat.messages = newMessages
|
|
66
|
+
if (chat.lastMessage?.role === 'user' && chat.status !== 'streaming') {
|
|
67
|
+
chat.regenerate()
|
|
68
68
|
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const canClear = computed(() => messages.value.length > 0)
|
|
72
|
+
|
|
73
|
+
type ToolPart = ToolUIPart | DynamicToolUIPart
|
|
74
|
+
type ToolState = ToolPart['state']
|
|
75
|
+
|
|
76
|
+
function getToolMessage(state: ToolState, toolName: string, input: Record<string, string | undefined>) {
|
|
77
|
+
const searchVerb = state === 'output-available' ? 'Searched' : 'Searching'
|
|
78
|
+
const readVerb = state === 'output-available' ? 'Read' : 'Reading'
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
'list-pages': `${searchVerb} pages`,
|
|
82
|
+
'get-page': `${readVerb} ${input.path || '...'}`,
|
|
83
|
+
}[toolName] || `${searchVerb} ${toolName}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getToolText(part: ToolPart) {
|
|
87
|
+
return getToolMessage(part.state, getToolName(part), (part.input || {}) as Record<string, string | undefined>)
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
+
function getToolIcon(part: ToolPart): string {
|
|
91
|
+
const toolName = getToolName(part)
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
return {
|
|
94
|
+
'get-page': 'i-lucide-file-text',
|
|
95
|
+
}[toolName] || 'i-lucide-search'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getToolOutput(part: ToolPart): string | undefined {
|
|
99
|
+
if (part.state !== 'output-available' || !part.output) return undefined
|
|
100
|
+
|
|
101
|
+
const output = part.output as Record<string, unknown>
|
|
102
|
+
|
|
103
|
+
if (getToolName(part) === 'list-pages') {
|
|
104
|
+
const content = (output.content ?? output) as Array<{ text?: string }> | string
|
|
105
|
+
if (typeof content === 'string') return content
|
|
106
|
+
return content
|
|
107
|
+
?.map(c => c.text)
|
|
108
|
+
.filter(Boolean)
|
|
109
|
+
.join('\n') || undefined
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (getToolName(part) === 'get-page') {
|
|
113
|
+
const content = (output.content ?? output) as Array<{ text?: string }> | string
|
|
114
|
+
if (typeof content === 'string') {
|
|
115
|
+
return content.length > 500 ? `${content.slice(0, 500)}…` : content
|
|
116
|
+
}
|
|
117
|
+
const text = content?.map(c => c.text).filter(Boolean).join('\n') || ''
|
|
118
|
+
return text.length > 500 ? `${text.slice(0, 500)}…` : text || undefined
|
|
93
119
|
}
|
|
94
120
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
})
|
|
121
|
+
return JSON.stringify(output, null, 2).slice(0, 500)
|
|
122
|
+
}
|
|
98
123
|
|
|
124
|
+
function onSubmit() {
|
|
125
|
+
if (!input.value.trim()) return
|
|
126
|
+
|
|
127
|
+
chat.sendMessage({ text: input.value })
|
|
99
128
|
input.value = ''
|
|
100
129
|
}
|
|
101
130
|
|
|
102
131
|
function askQuestion(question: string) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
})
|
|
132
|
+
input.value = question
|
|
133
|
+
onSubmit()
|
|
106
134
|
}
|
|
107
135
|
|
|
108
|
-
function
|
|
109
|
-
chat.
|
|
136
|
+
function clearMessages() {
|
|
137
|
+
if (chat.status === 'streaming') {
|
|
138
|
+
chat.stop()
|
|
139
|
+
}
|
|
110
140
|
messages.value = []
|
|
111
|
-
chat.messages
|
|
141
|
+
chat.messages = []
|
|
112
142
|
}
|
|
113
143
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
else if (chat.lastMessage?.role === 'user') {
|
|
122
|
-
chat.regenerate()
|
|
123
|
-
}
|
|
144
|
+
defineShortcuts({
|
|
145
|
+
meta_i: {
|
|
146
|
+
handler: () => {
|
|
147
|
+
open.value = !open.value
|
|
148
|
+
},
|
|
149
|
+
usingInput: true,
|
|
150
|
+
},
|
|
124
151
|
})
|
|
125
152
|
</script>
|
|
126
153
|
|
|
127
154
|
<template>
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
<USidebar
|
|
156
|
+
v-model:open="open"
|
|
157
|
+
side="right"
|
|
158
|
+
:title="displayTitle"
|
|
159
|
+
rail
|
|
160
|
+
:style="{ '--sidebar-width': '24rem' }"
|
|
161
|
+
:ui="{ footer: 'p-0', actions: 'gap-0.5', container: '!left-auto' }"
|
|
162
|
+
>
|
|
163
|
+
<template #actions>
|
|
164
|
+
<UTooltip
|
|
165
|
+
v-if="canClear"
|
|
166
|
+
:text="t('assistant.clearChat')"
|
|
167
|
+
>
|
|
168
|
+
<UButton
|
|
169
|
+
icon="i-lucide-list-x"
|
|
170
|
+
color="neutral"
|
|
171
|
+
variant="ghost"
|
|
172
|
+
@click="clearMessages"
|
|
173
|
+
/>
|
|
174
|
+
</UTooltip>
|
|
175
|
+
</template>
|
|
176
|
+
|
|
177
|
+
<template #close>
|
|
178
|
+
<UTooltip
|
|
179
|
+
:text="t('assistant.close')"
|
|
180
|
+
:kbds="['meta', 'i']"
|
|
181
|
+
>
|
|
182
|
+
<UButton
|
|
183
|
+
icon="i-lucide-panel-right-close"
|
|
184
|
+
color="neutral"
|
|
185
|
+
variant="ghost"
|
|
186
|
+
aria-label="Close"
|
|
187
|
+
@click="open = false"
|
|
188
|
+
/>
|
|
189
|
+
</UTooltip>
|
|
190
|
+
</template>
|
|
191
|
+
|
|
192
|
+
<UTheme
|
|
193
|
+
:ui="{
|
|
194
|
+
prose: {
|
|
195
|
+
p: { base: 'my-2 text-sm/6' },
|
|
196
|
+
li: { base: 'my-0.5 text-sm/6' },
|
|
197
|
+
ul: { base: 'my-2' },
|
|
198
|
+
ol: { base: 'my-2' },
|
|
199
|
+
h1: { base: 'text-xl mb-4' },
|
|
200
|
+
h2: { base: 'text-lg mt-6 mb-3' },
|
|
201
|
+
h3: { base: 'text-base mt-4 mb-2' },
|
|
202
|
+
h4: { base: 'text-sm mt-3 mb-1.5' },
|
|
203
|
+
code: { base: 'text-xs' },
|
|
204
|
+
pre: { root: 'my-2', base: 'text-xs/5' },
|
|
205
|
+
table: { root: 'my-2' },
|
|
206
|
+
hr: { base: 'my-4' },
|
|
207
|
+
},
|
|
208
|
+
}"
|
|
209
|
+
>
|
|
210
|
+
<UChatMessages
|
|
211
|
+
v-if="chat.messages.length"
|
|
212
|
+
should-auto-scroll
|
|
213
|
+
:messages="chat.messages"
|
|
214
|
+
:status="chat.status"
|
|
215
|
+
compact
|
|
216
|
+
class="px-0 gap-2"
|
|
217
|
+
:user="{ ui: { container: 'max-w-full' } }"
|
|
218
|
+
>
|
|
219
|
+
<template #indicator>
|
|
220
|
+
<AssistantIndicator />
|
|
221
|
+
</template>
|
|
222
|
+
|
|
223
|
+
<template #content="{ message }">
|
|
224
|
+
<template
|
|
225
|
+
v-for="(part, index) in message.parts"
|
|
226
|
+
:key="`${message.id}-${part.type}-${index}`"
|
|
149
227
|
>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
icon="i-lucide-x"
|
|
162
|
-
color="neutral"
|
|
163
|
-
variant="ghost"
|
|
164
|
-
size="sm"
|
|
165
|
-
class="text-muted hover:text-highlighted"
|
|
166
|
-
@click="isOpen = false"
|
|
167
|
-
/>
|
|
168
|
-
</UTooltip>
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
228
|
+
<UChatReasoning
|
|
229
|
+
v-if="isReasoningUIPart(part)"
|
|
230
|
+
:text="part.text"
|
|
231
|
+
:streaming="isPartStreaming(part)"
|
|
232
|
+
icon="i-lucide-brain"
|
|
233
|
+
>
|
|
234
|
+
<AssistantComark
|
|
235
|
+
:markdown="part.text"
|
|
236
|
+
:streaming="isPartStreaming(part)"
|
|
237
|
+
/>
|
|
238
|
+
</UChatReasoning>
|
|
171
239
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
:status="chat.status"
|
|
178
|
-
:user="{ ui: { content: 'text-sm' } }"
|
|
179
|
-
:ui="{ indicator: '*:bg-accented', root: 'h-auto!' }"
|
|
180
|
-
class="px-4 py-4"
|
|
181
|
-
>
|
|
182
|
-
<template #content="{ message }">
|
|
183
|
-
<div class="flex flex-col gap-2">
|
|
184
|
-
<AssistantLoading
|
|
185
|
-
v-if="message.role === 'assistant' && (getMessageToolCalls(message).length > 0 || (showThinking && message.id === lastMessage?.id))"
|
|
186
|
-
:tool-calls="getMessageToolCalls(message)"
|
|
187
|
-
:is-loading="showThinking && message.id === lastMessage?.id"
|
|
240
|
+
<template v-else-if="isTextUIPart(part) && part.text.length > 0">
|
|
241
|
+
<AssistantComark
|
|
242
|
+
v-if="message.role === 'assistant'"
|
|
243
|
+
:markdown="part.text"
|
|
244
|
+
:streaming="isPartStreaming(part)"
|
|
188
245
|
/>
|
|
189
|
-
<
|
|
190
|
-
v-
|
|
191
|
-
|
|
246
|
+
<p
|
|
247
|
+
v-else-if="message.role === 'user'"
|
|
248
|
+
class="whitespace-pre-wrap text-sm/6"
|
|
192
249
|
>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
250
|
+
{{ part.text }}
|
|
251
|
+
</p>
|
|
252
|
+
</template>
|
|
253
|
+
|
|
254
|
+
<UChatTool
|
|
255
|
+
v-else-if="isToolUIPart(part)"
|
|
256
|
+
:text="getToolText(part)"
|
|
257
|
+
:icon="getToolIcon(part)"
|
|
258
|
+
:streaming="isToolStreaming(part)"
|
|
259
|
+
chevron="leading"
|
|
260
|
+
>
|
|
261
|
+
<pre
|
|
262
|
+
v-if="getToolOutput(part)"
|
|
263
|
+
class="text-xs text-dimmed whitespace-pre-wrap"
|
|
264
|
+
v-text="getToolOutput(part)"
|
|
265
|
+
/>
|
|
266
|
+
</UChatTool>
|
|
203
267
|
</template>
|
|
204
|
-
</
|
|
268
|
+
</template>
|
|
269
|
+
</UChatMessages>
|
|
205
270
|
|
|
271
|
+
<div v-else>
|
|
206
272
|
<div
|
|
207
|
-
v-
|
|
208
|
-
class="
|
|
273
|
+
v-if="!faqQuestions?.length"
|
|
274
|
+
class="flex h-full flex-col items-center justify-center py-12 text-center"
|
|
209
275
|
>
|
|
210
|
-
<div
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<UIcon
|
|
216
|
-
name="i-lucide-message-circle-question"
|
|
217
|
-
class="size-6 text-primary"
|
|
218
|
-
/>
|
|
219
|
-
</div>
|
|
220
|
-
<h3 class="mb-2 text-base font-medium text-highlighted">
|
|
221
|
-
{{ t('assistant.askMeAnything') }}
|
|
222
|
-
</h3>
|
|
223
|
-
<p class="max-w-xs text-sm text-muted">
|
|
224
|
-
{{ t('assistant.askMeAnythingDescription') }}
|
|
225
|
-
</p>
|
|
276
|
+
<div class="mb-4 flex size-12 items-center justify-center rounded-full bg-primary/10">
|
|
277
|
+
<UIcon
|
|
278
|
+
name="i-lucide-message-circle-question"
|
|
279
|
+
class="size-6 text-primary"
|
|
280
|
+
/>
|
|
226
281
|
</div>
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<div class="flex flex-col gap-5">
|
|
234
|
-
<div
|
|
235
|
-
v-for="category in faqQuestions"
|
|
236
|
-
:key="category.category"
|
|
237
|
-
class="flex flex-col gap-1.5"
|
|
238
|
-
>
|
|
239
|
-
<h4 class="text-xs font-medium uppercase tracking-wide text-dimmed">
|
|
240
|
-
{{ category.category }}
|
|
241
|
-
</h4>
|
|
242
|
-
<div class="flex flex-col">
|
|
243
|
-
<button
|
|
244
|
-
v-for="question in category.items"
|
|
245
|
-
:key="question"
|
|
246
|
-
class="py-1.5 text-left text-sm text-muted transition-colors hover:text-highlighted"
|
|
247
|
-
@click="askQuestion(question)"
|
|
248
|
-
>
|
|
249
|
-
{{ question }}
|
|
250
|
-
</button>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
253
|
-
</div>
|
|
254
|
-
</template>
|
|
282
|
+
<h3 class="mb-2 text-base font-medium text-highlighted">
|
|
283
|
+
{{ t('assistant.askMeAnything') }}
|
|
284
|
+
</h3>
|
|
285
|
+
<p class="max-w-xs text-sm text-muted">
|
|
286
|
+
{{ t('assistant.askMeAnythingDescription') }}
|
|
287
|
+
</p>
|
|
255
288
|
</div>
|
|
256
|
-
</div>
|
|
257
289
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
root: 'shadow-none!',
|
|
266
|
-
body: '*:p-0! *:rounded-none! *:text-base!',
|
|
267
|
-
}"
|
|
268
|
-
@submit="handleSubmit"
|
|
269
|
-
>
|
|
270
|
-
<template #footer>
|
|
271
|
-
<div class="flex items-center gap-1 text-xs text-muted">
|
|
272
|
-
<span>{{ t('assistant.lineBreak') }}</span>
|
|
273
|
-
<UKbd
|
|
274
|
-
size="sm"
|
|
275
|
-
value="shift"
|
|
276
|
-
/>
|
|
277
|
-
<UKbd
|
|
278
|
-
size="sm"
|
|
279
|
-
value="enter"
|
|
280
|
-
/>
|
|
281
|
-
</div>
|
|
282
|
-
<UChatPromptSubmit
|
|
283
|
-
class="ml-auto"
|
|
284
|
-
size="xs"
|
|
285
|
-
:status="chat.status"
|
|
286
|
-
@stop="chat.stop()"
|
|
287
|
-
@reload="chat.regenerate()"
|
|
290
|
+
<template v-else>
|
|
291
|
+
<div class="flex flex-col gap-6">
|
|
292
|
+
<UPageLinks
|
|
293
|
+
v-for="category in faqQuestions"
|
|
294
|
+
:key="category.category"
|
|
295
|
+
:title="category.category"
|
|
296
|
+
:links="category.items.map(item => ({ label: item, onClick: () => askQuestion(item) }))"
|
|
288
297
|
/>
|
|
289
|
-
</
|
|
290
|
-
</
|
|
291
|
-
<div class="mt-1 flex text-xs text-dimmed items-center justify-between">
|
|
292
|
-
<span>{{ t('assistant.chatCleared') }}</span>
|
|
293
|
-
<span>
|
|
294
|
-
{{ input.length }}/1000
|
|
295
|
-
</span>
|
|
296
|
-
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</template>
|
|
297
300
|
</div>
|
|
298
|
-
</
|
|
299
|
-
</DefineChatContent>
|
|
300
|
-
|
|
301
|
-
<aside
|
|
302
|
-
v-if="!isMobile"
|
|
303
|
-
class="left-auto! fixed top-0 z-50 h-dvh overflow-hidden border-l border-default bg-default/95 backdrop-blur-xl transition-[right,width] duration-200 ease-linear will-change-[right,width]"
|
|
304
|
-
:style="{
|
|
305
|
-
width: `${panelWidth}px`,
|
|
306
|
-
right: isOpen ? '0' : `-${panelWidth}px`,
|
|
307
|
-
}"
|
|
308
|
-
>
|
|
309
|
-
<div
|
|
310
|
-
class="h-full transition-[width] duration-200 ease-linear"
|
|
311
|
-
:style="{ width: `${panelWidth}px` }"
|
|
312
|
-
>
|
|
313
|
-
<ReuseChatContent :show-expand-button="true" />
|
|
314
|
-
</div>
|
|
315
|
-
</aside>
|
|
301
|
+
</UTheme>
|
|
316
302
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
303
|
+
<template #footer>
|
|
304
|
+
<UChatPrompt
|
|
305
|
+
v-model="input"
|
|
306
|
+
:error="chat.error"
|
|
307
|
+
:placeholder="displayPlaceholder"
|
|
308
|
+
variant="naked"
|
|
309
|
+
size="sm"
|
|
310
|
+
autofocus
|
|
311
|
+
:ui="{ base: 'px-0' }"
|
|
312
|
+
class="px-4"
|
|
313
|
+
@submit="onSubmit"
|
|
314
|
+
>
|
|
315
|
+
<template #footer>
|
|
316
|
+
<div class="flex items-center gap-1.5 text-xs text-dimmed">
|
|
317
|
+
<span>{{ t('assistant.lineBreak') }}</span>
|
|
318
|
+
<UKbd
|
|
319
|
+
size="sm"
|
|
320
|
+
value="shift"
|
|
321
|
+
/>
|
|
322
|
+
<UKbd
|
|
323
|
+
size="sm"
|
|
324
|
+
value="enter"
|
|
325
|
+
/>
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
<UChatPromptSubmit
|
|
329
|
+
size="sm"
|
|
330
|
+
:status="chat.status"
|
|
331
|
+
:disabled="chat.status === 'ready' && !input.trim()"
|
|
332
|
+
@stop="chat.stop()"
|
|
333
|
+
@reload="chat.regenerate()"
|
|
334
|
+
/>
|
|
335
|
+
</template>
|
|
336
|
+
</UChatPrompt>
|
|
327
337
|
</template>
|
|
328
|
-
</
|
|
338
|
+
</USidebar>
|
|
329
339
|
</template>
|