docus 5.4.4 → 5.5.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.
@@ -0,0 +1,105 @@
1
+ <script setup lang="ts">
2
+ import { AnimatePresence, motion } from 'motion-v'
3
+ import { useDocusI18n } from '../../../../app/composables/useDocusI18n'
4
+
5
+ const route = useRoute()
6
+ const appConfig = useAppConfig()
7
+ const { open, isOpen } = useAssistant()
8
+ const { t } = useDocusI18n()
9
+ const input = ref('')
10
+ const isVisible = ref(true)
11
+ const inputRef = ref<{ inputRef: HTMLInputElement } | null>(null)
12
+
13
+ const isDocsRoute = computed(() => route.meta.layout === 'docs')
14
+ const isFloatingInputEnabled = computed(() => appConfig.assistant?.floatingInput !== false)
15
+ const focusInputShortcut = computed(() => appConfig.assistant?.shortcuts?.focusInput || 'meta_i')
16
+ const placeholder = computed(() => t('assistant.placeholder'))
17
+
18
+ const shortcutDisplayKeys = computed(() => {
19
+ const shortcut = focusInputShortcut.value
20
+ const parts = shortcut.split('_')
21
+ return parts.map(part => part === 'meta' ? 'meta' : part.toUpperCase())
22
+ })
23
+
24
+ function handleSubmit() {
25
+ if (!input.value.trim()) return
26
+
27
+ const message = input.value
28
+ isVisible.value = false
29
+
30
+ setTimeout(() => {
31
+ open(message, true)
32
+ input.value = ''
33
+ isVisible.value = true
34
+ }, 200)
35
+ }
36
+
37
+ const shortcuts = computed(() => ({
38
+ [focusInputShortcut.value]: {
39
+ usingInput: true,
40
+ handler: () => {
41
+ if (!isDocsRoute.value || !isFloatingInputEnabled.value) return
42
+ inputRef.value?.inputRef?.focus()
43
+ },
44
+ },
45
+ escape: {
46
+ usingInput: true,
47
+ handler: () => {
48
+ inputRef.value?.inputRef?.blur()
49
+ },
50
+ },
51
+ }))
52
+
53
+ defineShortcuts(shortcuts)
54
+ </script>
55
+
56
+ <template>
57
+ <AnimatePresence>
58
+ <motion.div
59
+ v-if="isFloatingInputEnabled && isDocsRoute && isVisible && !isOpen"
60
+ key="floating-input"
61
+ :initial="{ y: 20, opacity: 0 }"
62
+ :animate="{ y: 0, opacity: 1 }"
63
+ :exit="{ y: 100, opacity: 0 }"
64
+ :transition="{ duration: 0.2, ease: 'easeOut' }"
65
+ class="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 px-4"
66
+ style="will-change: transform"
67
+ >
68
+ <form @submit.prevent="handleSubmit">
69
+ <UInput
70
+ ref="inputRef"
71
+ v-model="input"
72
+ :placeholder="placeholder"
73
+ size="lg"
74
+ maxlength="1000"
75
+ :ui="{
76
+ root: 'group w-72 focus-within:w-96 transition-all duration-300 ease-out hover:scale-105 focus-within:scale-105',
77
+ base: 'bg-default shadow-lg rounded-xl',
78
+ trailing: 'pe-2',
79
+ }"
80
+ @keydown.enter.exact.prevent="handleSubmit"
81
+ >
82
+ <template #trailing>
83
+ <div class="flex items-center gap-2">
84
+ <div class="hidden sm:flex group-focus-within:hidden items-center gap-1">
85
+ <UKbd
86
+ v-for="key in shortcutDisplayKeys"
87
+ :key="key"
88
+ :value="key"
89
+ />
90
+ </div>
91
+
92
+ <UButton
93
+ type="submit"
94
+ icon="i-lucide-arrow-up"
95
+ color="primary"
96
+ size="xs"
97
+ :disabled="!input.trim()"
98
+ />
99
+ </div>
100
+ </template>
101
+ </UInput>
102
+ </form>
103
+ </motion.div>
104
+ </AnimatePresence>
105
+ </template>
@@ -0,0 +1,164 @@
1
+ <script setup lang="ts">
2
+ import { motion } from 'motion-v'
3
+ import { useDocusI18n } from '../../../../app/composables/useDocusI18n'
4
+
5
+ interface ToolCall {
6
+ toolCallId: string
7
+ toolName: string
8
+ args: Record<string, unknown>
9
+ }
10
+
11
+ const props = defineProps<{
12
+ text?: string
13
+ toolCalls?: ToolCall[]
14
+ isLoading?: boolean
15
+ }>()
16
+
17
+ const { t } = useDocusI18n()
18
+
19
+ const messages = computed(() => [
20
+ t('assistant.loading.searching'),
21
+ t('assistant.loading.reading'),
22
+ t('assistant.loading.analyzing'),
23
+ t('assistant.loading.finding'),
24
+ ])
25
+
26
+ const finishedMessage = computed(() => t('assistant.loading.finished'))
27
+
28
+ const currentIndex = ref(0)
29
+ const targetText = computed(() => {
30
+ if (!props.isLoading) {
31
+ return finishedMessage.value
32
+ }
33
+ return props.text || messages.value[currentIndex.value]
34
+ })
35
+ const displayedText = ref(targetText.value)
36
+
37
+ const chars = 'abcdefghijklmnopqrstuvwxyz'
38
+
39
+ function scrambleText(from: string, to: string) {
40
+ const maxLength = Math.max(from.length, to.length)
41
+ let frame = 0
42
+ const totalFrames = 15
43
+
44
+ const animate = () => {
45
+ frame++
46
+ let result = ''
47
+
48
+ for (let i = 0; i < maxLength; i++) {
49
+ const progress = frame / totalFrames
50
+ const charProgress = progress * maxLength
51
+
52
+ if (i < charProgress - 2) {
53
+ result += to[i] || ''
54
+ }
55
+ else if (i < charProgress) {
56
+ result += chars[Math.floor(Math.random() * chars.length)]
57
+ }
58
+ else {
59
+ result += from[i] || ''
60
+ }
61
+ }
62
+
63
+ displayedText.value = result
64
+
65
+ if (frame < totalFrames) {
66
+ requestAnimationFrame(animate)
67
+ }
68
+ else {
69
+ displayedText.value = to
70
+ }
71
+ }
72
+
73
+ requestAnimationFrame(animate)
74
+ }
75
+
76
+ let textInterval: ReturnType<typeof setInterval> | null = null
77
+
78
+ watch(targetText, (newText, oldText) => {
79
+ if (newText !== oldText && newText && oldText) {
80
+ scrambleText(oldText, newText)
81
+ }
82
+ })
83
+
84
+ // Stop text rotation when loading finishes
85
+ watch(() => props.isLoading, (isLoading) => {
86
+ if (!isLoading && textInterval) {
87
+ clearInterval(textInterval)
88
+ textInterval = null
89
+ }
90
+ })
91
+
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ function getToolLabel(toolName: string, args: any) {
94
+ const path = args?.path || ''
95
+
96
+ if (toolName === 'list-pages') {
97
+ return t('assistant.toolListPages')
98
+ }
99
+
100
+ if (toolName === 'get-page') {
101
+ return `${t('assistant.toolReadPage')} ${path || '...'}`
102
+ }
103
+
104
+ return toolName
105
+ }
106
+
107
+ onMounted(() => {
108
+ // Text rotation only when loading
109
+ if (!props.text && props.isLoading) {
110
+ textInterval = setInterval(() => {
111
+ currentIndex.value = (currentIndex.value + 1) % messages.value.length
112
+ }, 3500)
113
+ }
114
+ })
115
+
116
+ onUnmounted(() => {
117
+ if (textInterval) clearInterval(textInterval)
118
+ })
119
+ </script>
120
+
121
+ <template>
122
+ <div class="flex flex-col gap-2">
123
+ <!-- Main loader with matrix and text -->
124
+ <div class="flex items-center text-xs text-muted overflow-hidden">
125
+ <motion.div
126
+ v-if="isLoading"
127
+ :initial="{ opacity: 1, width: 'auto' }"
128
+ :exit="{ opacity: 0, width: 0 }"
129
+ :transition="{ duration: 0.2 }"
130
+ class="shrink-0 mr-2"
131
+ >
132
+ <AssistantMatrix />
133
+ </motion.div>
134
+ <motion.span
135
+ :animate="{ x: 0 }"
136
+ :transition="{ duration: 0.2 }"
137
+ class="font-mono tracking-tight"
138
+ >
139
+ {{ displayedText }}
140
+ </motion.span>
141
+ </div>
142
+
143
+ <!-- Tool calls displayed below -->
144
+ <div
145
+ v-if="toolCalls?.length"
146
+ class="flex flex-col gap-1"
147
+ :class="isLoading ? 'pl-[22px]' : 'pl-0'"
148
+ >
149
+ <motion.div
150
+ v-for="tool in toolCalls"
151
+ :key="`${tool.toolCallId}-${JSON.stringify(tool.args)}`"
152
+ :initial="{ opacity: 0, x: -4 }"
153
+ :animate="{ opacity: 1, x: 0 }"
154
+ :transition="{ duration: 0.15 }"
155
+ class="flex items-center gap-1.5"
156
+ >
157
+ <span class="size-1 rounded-full bg-current opacity-40" />
158
+ <span class="text-[11px] text-dimmed truncate max-w-[200px]">
159
+ {{ getToolLabel(tool.toolName, tool.args) }}
160
+ </span>
161
+ </motion.div>
162
+ </div>
163
+ </div>
164
+ </template>
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ size?: number
4
+ dotSize?: number
5
+ gap?: number
6
+ }
7
+
8
+ const props = withDefaults(defineProps<Props>(), {
9
+ size: 4,
10
+ dotSize: 2,
11
+ gap: 2,
12
+ })
13
+
14
+ const totalDots = computed(() => props.size * props.size)
15
+ const activeDots = ref<Set<number>>(new Set())
16
+
17
+ // Patterns for 4x4 grid (indices 0-15)
18
+ // Grid layout:
19
+ // 0 1 2 3
20
+ // 4 5 6 7
21
+ // 8 9 10 11
22
+ // 12 13 14 15
23
+ const patterns = [
24
+ // Spiral inward
25
+ [[0], [1], [2], [3], [7], [11], [15], [14], [13], [12], [8], [4], [5], [6], [10], [9]],
26
+ // Wave horizontal
27
+ [[0, 4, 8, 12], [1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15]],
28
+ // Diamond pulse
29
+ [[5, 6, 9, 10], [1, 4, 7, 8, 11, 14], [0, 3, 12, 15], [1, 4, 7, 8, 11, 14], [5, 6, 9, 10]],
30
+ // Loading bar
31
+ [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]],
32
+ // Corners rotate
33
+ [[0], [3], [15], [12]],
34
+ // Cross pulse
35
+ [[5, 6, 9, 10], [1, 2, 4, 7, 8, 11, 13, 14], [0, 3, 12, 15]],
36
+ // Snake
37
+ [[0], [1], [2], [3], [7], [6], [5], [4], [8], [9], [10], [11], [15], [14], [13], [12]],
38
+ // Diagonal wave
39
+ [[0], [1, 4], [2, 5, 8], [3, 6, 9, 12], [7, 10, 13], [11, 14], [15]],
40
+ ]
41
+
42
+ let patternIndex = 0
43
+ let stepIndex = 0
44
+ let interval: ReturnType<typeof setInterval> | null = null
45
+
46
+ function nextStep() {
47
+ const pattern = patterns[patternIndex]
48
+ if (!pattern) return
49
+
50
+ activeDots.value = new Set(pattern[stepIndex])
51
+ stepIndex++
52
+
53
+ if (stepIndex >= pattern.length) {
54
+ stepIndex = 0
55
+ patternIndex = (patternIndex + 1) % patterns.length
56
+ }
57
+ }
58
+
59
+ const gridStyle = computed(() => ({
60
+ display: 'grid',
61
+ gridTemplateColumns: `repeat(${props.size}, 1fr)`,
62
+ gap: `${props.gap}px`,
63
+ width: `${props.size * props.dotSize + (props.size - 1) * props.gap}px`,
64
+ height: `${props.size * props.dotSize + (props.size - 1) * props.gap}px`,
65
+ }))
66
+
67
+ const dotStyle = computed(() => ({
68
+ width: `${props.dotSize}px`,
69
+ height: `${props.dotSize}px`,
70
+ }))
71
+
72
+ onMounted(() => {
73
+ interval = setInterval(nextStep, 120)
74
+ nextStep()
75
+ })
76
+
77
+ onUnmounted(() => {
78
+ if (interval) clearInterval(interval)
79
+ })
80
+ </script>
81
+
82
+ <template>
83
+ <div :style="gridStyle">
84
+ <span
85
+ v-for="i in totalDots"
86
+ :key="i"
87
+ class="rounded-[0.5px] bg-current transition-opacity duration-100"
88
+ :class="activeDots.has(i - 1) ? 'opacity-100' : 'opacity-20'"
89
+ :style="dotStyle"
90
+ />
91
+ </div>
92
+ </template>
@@ -0,0 +1,329 @@
1
+ <script setup lang="ts">
2
+ import { defineAsyncComponent } from 'vue'
3
+ import type { UIMessage } from 'ai'
4
+ import { Chat } from '@ai-sdk/vue'
5
+ import { DefaultChatTransport } from 'ai'
6
+ import { createReusableTemplate } from '@vueuse/core'
7
+ import { useDocusI18n } from '../../../../app/composables/useDocusI18n'
8
+
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
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()
17
+ const config = useRuntimeConfig()
18
+ const toast = useToast()
19
+ const { t } = useDocusI18n()
20
+ const input = ref('')
21
+
22
+ const displayTitle = computed(() => t('assistant.title'))
23
+ const displayPlaceholder = computed(() => t('assistant.placeholder'))
24
+
25
+ const chat = new Chat({
26
+ messages: messages.value,
27
+ transport: new DefaultChatTransport({
28
+ api: config.public.assistant.apiPath,
29
+ }),
30
+ onError: (error: Error) => {
31
+ const message = (() => {
32
+ try {
33
+ const parsed = JSON.parse(error.message)
34
+ return parsed?.message || error.message
35
+ }
36
+ catch {
37
+ return error.message
38
+ }
39
+ })()
40
+
41
+ toast.add({
42
+ description: message,
43
+ icon: 'i-lucide-alert-circle',
44
+ color: 'error',
45
+ duration: 0,
46
+ })
47
+ },
48
+ onFinish: () => {
49
+ messages.value = chat.messages
50
+ },
51
+ })
52
+
53
+ watch(pendingMessage, (message: string | undefined) => {
54
+ if (message) {
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 })
64
+
65
+ watch(messages, (newMessages: UIMessage[]) => {
66
+ if (newMessages.length === 0 && chat.messages.length > 0) {
67
+ chat.messages.length = 0
68
+ }
69
+ }, { deep: true })
70
+
71
+ const lastMessage = computed(() => chat.messages.at(-1))
72
+ const showThinking = computed(() =>
73
+ chat.status === 'streaming'
74
+ && lastMessage.value?.role === 'assistant'
75
+ && !lastMessage.value?.parts?.some((p: { type: string }) => p.type === 'text'),
76
+ )
77
+
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ function getMessageToolCalls(message: any) {
80
+ if (!message?.parts) return []
81
+ return message.parts
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
+ .filter((p: any) => p.type === 'data-tool-calls')
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ .flatMap((p: any) => p.data?.tools || [])
86
+ }
87
+
88
+ function handleSubmit(event?: Event) {
89
+ event?.preventDefault()
90
+
91
+ if (!input.value.trim()) {
92
+ return
93
+ }
94
+
95
+ chat.sendMessage({
96
+ text: input.value,
97
+ })
98
+
99
+ input.value = ''
100
+ }
101
+
102
+ function askQuestion(question: string) {
103
+ chat.sendMessage({
104
+ text: question,
105
+ })
106
+ }
107
+
108
+ function resetChat() {
109
+ chat.stop()
110
+ messages.value = []
111
+ chat.messages.length = 0
112
+ }
113
+
114
+ onMounted(() => {
115
+ if (pendingMessage.value) {
116
+ chat.sendMessage({
117
+ text: pendingMessage.value,
118
+ })
119
+ clearPending()
120
+ }
121
+ else if (chat.lastMessage?.role === 'user') {
122
+ chat.regenerate()
123
+ }
124
+ })
125
+ </script>
126
+
127
+ <template>
128
+ <DefineChatContent v-slot="{ showExpandButton = true }">
129
+ <div class="flex h-full flex-col">
130
+ <div class="flex h-16 shrink-0 items-center justify-between border-b border-default px-4">
131
+ <span class="font-medium text-highlighted">{{ displayTitle }}</span>
132
+ <div class="flex items-center gap-1">
133
+ <UTooltip
134
+ v-if="showExpandButton"
135
+ :text="isExpanded ? t('assistant.collapse') : t('assistant.expand')"
136
+ >
137
+ <UButton
138
+ :icon="isExpanded ? 'i-lucide-minimize-2' : 'i-lucide-maximize-2'"
139
+ color="neutral"
140
+ variant="ghost"
141
+ size="sm"
142
+ class="text-muted hover:text-highlighted"
143
+ @click="toggleExpanded"
144
+ />
145
+ </UTooltip>
146
+ <UTooltip
147
+ v-if="chat.messages.length > 0"
148
+ :text="t('assistant.clearChat')"
149
+ >
150
+ <UButton
151
+ icon="i-lucide-trash-2"
152
+ color="neutral"
153
+ variant="ghost"
154
+ size="sm"
155
+ class="text-muted hover:text-highlighted"
156
+ @click="resetChat"
157
+ />
158
+ </UTooltip>
159
+ <UTooltip :text="t('assistant.close')">
160
+ <UButton
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>
171
+
172
+ <div class="min-h-0 flex-1 overflow-y-auto">
173
+ <UChatMessages
174
+ v-if="chat.messages.length > 0"
175
+ :messages="chat.messages"
176
+ compact
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"
188
+ />
189
+ <template
190
+ v-for="(part, index) in message.parts"
191
+ :key="`${message.id}-${part.type}-${index}${'state' in part ? `-${part.state}` : ''}`"
192
+ >
193
+ <MDCCached
194
+ v-if="part.type === 'text' && part.text"
195
+ :value="part.text"
196
+ :cache-key="`${message.id}-${index}`"
197
+ :components="components"
198
+ :parser-options="{ highlight: false }"
199
+ class="*:first:mt-0 *:last:mb-0"
200
+ />
201
+ </template>
202
+ </div>
203
+ </template>
204
+ </UChatMessages>
205
+
206
+ <div
207
+ v-else
208
+ class="p-4"
209
+ >
210
+ <div
211
+ v-if="!faqQuestions?.length"
212
+ class="flex h-full flex-col items-center justify-center py-12 text-center"
213
+ >
214
+ <div class="mb-4 flex size-12 items-center justify-center rounded-full bg-primary/10">
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>
226
+ </div>
227
+
228
+ <template v-else>
229
+ <p class="mb-4 text-sm font-medium text-muted">
230
+ {{ t('assistant.faq') }}
231
+ </p>
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>
255
+ </div>
256
+ </div>
257
+
258
+ <div class="w-full shrink-0 p-3">
259
+ <UChatPrompt
260
+ v-model="input"
261
+ :rows="2"
262
+ :placeholder="displayPlaceholder"
263
+ maxlength="1000"
264
+ :ui="{
265
+ root: 'shadow-none!',
266
+ body: '*:p-0! *:rounded-none! *:text-sm!',
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()"
288
+ />
289
+ </template>
290
+ </UChatPrompt>
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>
297
+ </div>
298
+ </div>
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>
316
+
317
+ <USlideover
318
+ v-else
319
+ v-model:open="isOpen"
320
+ side="right"
321
+ :ui="{
322
+ content: 'ring-0 bg-default',
323
+ }"
324
+ >
325
+ <template #content>
326
+ <ReuseChatContent :show-expand-button="false" />
327
+ </template>
328
+ </USlideover>
329
+ </template>