@movk/nuxt-docs 1.14.1 → 1.15.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/README.md +1 -1
- package/app/app.config.ts +5 -32
- package/app/app.vue +54 -32
- package/app/components/DocsAsideRightBottom.vue +1 -1
- package/app/components/OgImage/OgImageDocs.takumi.vue +90 -0
- package/app/components/PageHeaderLinks.vue +6 -16
- package/app/components/content/CommitChangelog.vue +1 -0
- package/app/components/content/Mermaid.vue +3 -1
- package/app/components/header/HeaderCTA.vue +1 -10
- package/app/components/theme-picker/ThemePicker.vue +22 -33
- package/app/composables/useTheme.ts +64 -84
- package/app/composables/useToolCall.ts +5 -8
- package/app/error.vue +1 -1
- package/app/pages/docs/[...slug].vue +6 -6
- package/app/plugins/theme.ts +39 -68
- package/app/templates/landing.vue +5 -2
- package/app/templates/releases.vue +5 -2
- package/app/types/index.d.ts +8 -57
- package/content.config.ts +1 -0
- package/modules/ai-chat/index.ts +16 -26
- package/modules/ai-chat/runtime/components/AiChat.vue +3 -3
- package/modules/ai-chat/runtime/components/AiChatFloatingInput.vue +8 -8
- package/modules/ai-chat/runtime/components/AiChatPanel.vue +216 -231
- package/modules/ai-chat/runtime/components/AiChatPreStream.vue +0 -14
- package/modules/ai-chat/runtime/composables/useAIChat.ts +25 -73
- package/modules/ai-chat/runtime/composables/useModels.ts +0 -19
- package/modules/ai-chat/runtime/server/api/ai-chat.ts +74 -48
- package/modules/ai-chat/runtime/server/utils/getModel.ts +1 -9
- package/modules/ai-chat/runtime/types.ts +5 -0
- package/nuxt.config.ts +42 -36
- package/nuxt.schema.ts +14 -99
- package/package.json +25 -29
- package/server/mcp/tools/get-page.ts +5 -47
- package/server/mcp/tools/list-getting-started-guides.ts +1 -3
- package/server/mcp/tools/list-pages.ts +9 -44
- package/utils/git.ts +26 -79
- package/app/components/OgImage/Nuxt.vue +0 -247
- package/app/composables/useAnalytics.ts +0 -7
- package/modules/ai-chat/runtime/components/AiChatReasoning.vue +0 -49
- package/modules/ai-chat/runtime/components/AiChatSlideoverFaq.vue +0 -38
- package/modules/ai-chat/runtime/components/AiChatToolCall.vue +0 -31
- package/modules/ai-chat/runtime/server/utils/docs_agent.ts +0 -54
package/modules/ai-chat/index.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { join } from 'pathe'
|
|
3
3
|
import {
|
|
4
|
-
addComponent,
|
|
5
|
-
addComponentsDir,
|
|
6
4
|
addImports,
|
|
7
5
|
addServerHandler,
|
|
6
|
+
addComponent,
|
|
8
7
|
createResolver,
|
|
9
8
|
defineNuxtModule,
|
|
10
9
|
logger
|
|
@@ -46,7 +45,7 @@ export default defineNuxtModule<AiChatModuleOptions>({
|
|
|
46
45
|
models: []
|
|
47
46
|
},
|
|
48
47
|
setup(options, nuxt) {
|
|
49
|
-
const hasApiKey = !!
|
|
48
|
+
const hasApiKey = !!process.env.AI_GATEWAY_API_KEY
|
|
50
49
|
|
|
51
50
|
const { resolve } = createResolver(import.meta.url)
|
|
52
51
|
|
|
@@ -64,17 +63,22 @@ export default defineNuxtModule<AiChatModuleOptions>({
|
|
|
64
63
|
}
|
|
65
64
|
])
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
const components = [
|
|
67
|
+
'AiChat',
|
|
68
|
+
'AiChatFloatingInput',
|
|
69
|
+
'AiChatModelSelect',
|
|
70
|
+
'AiChatPanel',
|
|
71
|
+
'AiChatPreStream'
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
components.forEach(name =>
|
|
73
75
|
addComponent({
|
|
74
|
-
name
|
|
75
|
-
filePath:
|
|
76
|
+
name,
|
|
77
|
+
filePath: hasApiKey
|
|
78
|
+
? resolve(`./runtime/components/${name}.vue`)
|
|
79
|
+
: resolve('./runtime/components/AiChatDisabled.vue')
|
|
76
80
|
})
|
|
77
|
-
|
|
81
|
+
)
|
|
78
82
|
|
|
79
83
|
if (!hasApiKey) {
|
|
80
84
|
log.warn('[movk-nuxt-docs] Ai Chat Module disabled: no API key found in environment variables.')
|
|
@@ -85,20 +89,6 @@ export default defineNuxtModule<AiChatModuleOptions>({
|
|
|
85
89
|
mcpPath: options.mcpPath!
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
addImports([
|
|
89
|
-
{
|
|
90
|
-
name: 'useHighlighter',
|
|
91
|
-
from: resolve('./runtime/composables/useHighlighter')
|
|
92
|
-
}
|
|
93
|
-
])
|
|
94
|
-
|
|
95
|
-
addImports([
|
|
96
|
-
{
|
|
97
|
-
name: 'useModels',
|
|
98
|
-
from: resolve('./runtime/composables/useModels')
|
|
99
|
-
}
|
|
100
|
-
])
|
|
101
|
-
|
|
102
92
|
/**
|
|
103
93
|
* 检查用户项目中是否存在自定义 handler
|
|
104
94
|
* '/api/ai-chat' -> 'api/ai-chat','/custom' -> 'custom'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const { aiChat } = useAppConfig()
|
|
3
|
-
const {
|
|
3
|
+
const { toggleChat } = useAIChat()
|
|
4
4
|
</script>
|
|
5
5
|
|
|
6
6
|
<template>
|
|
@@ -9,8 +9,8 @@ const { toggle } = useAIChat()
|
|
|
9
9
|
:icon="aiChat.icons.trigger"
|
|
10
10
|
variant="ghost"
|
|
11
11
|
class="rounded-full"
|
|
12
|
-
aria-label="
|
|
13
|
-
@click="
|
|
12
|
+
:aria-label="aiChat.texts.trigger"
|
|
13
|
+
@click="toggleChat"
|
|
14
14
|
/>
|
|
15
15
|
</UTooltip>
|
|
16
16
|
</template>
|
|
@@ -20,17 +20,17 @@ const shortcutDisplayKeys = computed(() => {
|
|
|
20
20
|
return parts.map(part => part === 'meta' ? 'meta' : part.toUpperCase())
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
function handleSubmit() {
|
|
23
|
+
async function handleSubmit() {
|
|
24
24
|
if (!input.value.trim()) return
|
|
25
25
|
|
|
26
26
|
const message = input.value
|
|
27
27
|
isVisible.value = false
|
|
28
28
|
|
|
29
|
-
sleep(200)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
await sleep(200)
|
|
30
|
+
|
|
31
|
+
open(message)
|
|
32
|
+
input.value = ''
|
|
33
|
+
isVisible.value = true
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const shortcuts = computed(() => ({
|
|
@@ -61,14 +61,14 @@ defineShortcuts(shortcuts)
|
|
|
61
61
|
:animate="{ y: 0, opacity: 1 }"
|
|
62
62
|
:exit="{ y: 100, opacity: 0 }"
|
|
63
63
|
:transition="{ duration: 0.2, ease: 'easeOut' }"
|
|
64
|
-
class="fixed inset-x-0 z-10 px-4 sm:px-80 bottom-[max(1.5rem,env(safe-area-inset-bottom))]"
|
|
64
|
+
class="fixed inset-x-0 z-10 px-4 sm:px-80 bottom-[max(1.5rem,env(safe-area-inset-bottom))] pointer-events-none"
|
|
65
65
|
style="will-change: transform"
|
|
66
66
|
>
|
|
67
67
|
<form
|
|
68
68
|
class="flex w-full justify-center"
|
|
69
69
|
@submit.prevent="handleSubmit"
|
|
70
70
|
>
|
|
71
|
-
<div class="w-full max-w-96">
|
|
71
|
+
<div class="w-full max-w-96 pointer-events-auto">
|
|
72
72
|
<UInput
|
|
73
73
|
ref="inputRef"
|
|
74
74
|
v-model="input"
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { DefineComponent } from 'vue'
|
|
3
|
-
import type {
|
|
3
|
+
import type { FaqCategory, FaqQuestions, ToolPart, ToolState } from '../types'
|
|
4
4
|
import { Chat } from '@ai-sdk/vue'
|
|
5
|
-
import { DefaultChatTransport } from 'ai'
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
5
|
+
import { DefaultChatTransport, getToolName, isReasoningUIPart, isTextUIPart, isToolUIPart } from 'ai'
|
|
6
|
+
import { computed } from 'vue'
|
|
7
|
+
import { isReasoningStreaming, isToolStreaming } from '@nuxt/ui/utils/ai'
|
|
8
8
|
import { useModels } from '../composables/useModels'
|
|
9
|
+
import { splitByCase, upperFirst } from 'scule'
|
|
10
|
+
import AiChatPreStream from './AiChatPreStream.vue'
|
|
11
|
+
import { useMemoize } from '@vueuse/core'
|
|
9
12
|
|
|
10
13
|
const components = {
|
|
11
14
|
pre: AiChatPreStream as unknown as DefineComponent
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const { isOpen, isExpanded, isMobile, panelWidth, toggleExpanded, messages, pendingMessage, clearPending, faqQuestions } = useAIChat()
|
|
17
|
+
const { isOpen, messages } = useAIChat()
|
|
18
|
+
const toast = useToast()
|
|
17
19
|
const config = useRuntimeConfig()
|
|
18
20
|
const { aiChat } = useAppConfig()
|
|
19
|
-
const toast = useToast()
|
|
20
21
|
const { model } = useModels()
|
|
21
22
|
|
|
23
|
+
const canClear = computed(() => messages.value.length > 0)
|
|
24
|
+
|
|
22
25
|
const input = ref('')
|
|
26
|
+
let _skipSync = false
|
|
23
27
|
|
|
24
28
|
const chat = new Chat({
|
|
25
29
|
messages: messages.value,
|
|
@@ -28,14 +32,14 @@ const chat = new Chat({
|
|
|
28
32
|
body: () => ({ model: model.value })
|
|
29
33
|
}),
|
|
30
34
|
onError: (error: Error) => {
|
|
31
|
-
|
|
35
|
+
let message = error.message
|
|
36
|
+
if (typeof message === 'string' && message[0] === '{') {
|
|
32
37
|
try {
|
|
33
|
-
|
|
34
|
-
return parsed?.message || error.message
|
|
38
|
+
message = JSON.parse(message).message || message
|
|
35
39
|
} catch {
|
|
36
|
-
|
|
40
|
+
// keep original message on malformed JSON
|
|
37
41
|
}
|
|
38
|
-
}
|
|
42
|
+
}
|
|
39
43
|
|
|
40
44
|
toast.add({
|
|
41
45
|
description: message,
|
|
@@ -45,263 +49,244 @@ const chat = new Chat({
|
|
|
45
49
|
})
|
|
46
50
|
},
|
|
47
51
|
onFinish: () => {
|
|
52
|
+
_skipSync = true
|
|
48
53
|
messages.value = chat.messages
|
|
54
|
+
nextTick(() => {
|
|
55
|
+
_skipSync = false
|
|
56
|
+
})
|
|
49
57
|
}
|
|
50
58
|
})
|
|
51
59
|
|
|
52
|
-
watch(
|
|
53
|
-
if (
|
|
54
|
-
if (messages.value.length === 0 && chat.messages.length > 0) {
|
|
55
|
-
chat.messages.length = 0
|
|
56
|
-
}
|
|
57
|
-
chat.sendMessage({
|
|
58
|
-
text: message
|
|
59
|
-
})
|
|
60
|
-
clearPending()
|
|
61
|
-
}
|
|
62
|
-
}, { immediate: true })
|
|
60
|
+
watch(messages, (newMessages) => {
|
|
61
|
+
if (_skipSync) return
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
chat.
|
|
63
|
+
chat.messages = newMessages
|
|
64
|
+
if (chat.lastMessage?.role === 'user') {
|
|
65
|
+
chat.regenerate()
|
|
67
66
|
}
|
|
68
|
-
}
|
|
67
|
+
})
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
function upperName(name: string) {
|
|
70
|
+
return splitByCase(name).map(p => upperFirst(p)).join('')
|
|
71
|
+
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
73
|
+
function getToolMessage(state: ToolState, toolName: string, input: Record<string, string | undefined>) {
|
|
74
|
+
const { toolMessage } = useToolCall(state, toolName, input)
|
|
75
|
+
const searchVerb = state === 'output-available' ? '已搜索' : '搜索中'
|
|
76
|
+
const readVerb = state === 'output-available' ? '已读取' : '读取中'
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
return {
|
|
79
|
+
'list-getting-started-guides': `${searchVerb} 入门指南`,
|
|
80
|
+
'list-pages': `${searchVerb} 所有文档页面`,
|
|
81
|
+
'list-examples': `${searchVerb} 所有示例`,
|
|
82
|
+
'get-page': `${readVerb} ${input.path || ''} 页面`,
|
|
83
|
+
'get-example': `${readVerb} ${upperName(input.exampleName || '')} 示例`,
|
|
84
|
+
...toolMessage
|
|
85
|
+
}[toolName] || `${searchVerb} ${toolName}`
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const getCachedToolMessage = useMemoize((state: ToolState, toolName: string, input: string) =>
|
|
89
|
+
getToolMessage(state, toolName, JSON.parse(input))
|
|
90
|
+
)
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
function getToolText(part: ToolPart) {
|
|
93
|
+
return getCachedToolMessage(part.state, getToolName(part), JSON.stringify(part.input || {}))
|
|
81
94
|
}
|
|
82
95
|
|
|
83
|
-
function
|
|
84
|
-
|
|
96
|
+
function getToolIcon(part: ToolPart): string {
|
|
97
|
+
const toolName = getToolName(part)
|
|
98
|
+
const { toolIcon } = useToolCall(part.state, toolName, part.input || {} as any)
|
|
99
|
+
|
|
100
|
+
const iconMap: Record<string, string> = {
|
|
101
|
+
'get-page': 'i-lucide-file-text',
|
|
102
|
+
'get-example': 'i-lucide-file-text',
|
|
103
|
+
...toolIcon
|
|
104
|
+
}
|
|
85
105
|
|
|
106
|
+
return iconMap[toolName] || 'i-lucide-search'
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function onSubmit() {
|
|
86
110
|
if (!input.value.trim()) {
|
|
87
111
|
return
|
|
88
112
|
}
|
|
89
113
|
|
|
90
|
-
chat.sendMessage({
|
|
91
|
-
text: input.value
|
|
92
|
-
})
|
|
114
|
+
chat.sendMessage({ text: input.value })
|
|
93
115
|
|
|
94
116
|
input.value = ''
|
|
95
117
|
}
|
|
96
118
|
|
|
97
119
|
function askQuestion(question: string) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
})
|
|
120
|
+
input.value = question
|
|
121
|
+
onSubmit()
|
|
101
122
|
}
|
|
102
123
|
|
|
103
|
-
function
|
|
104
|
-
chat.
|
|
124
|
+
function clearMessages() {
|
|
125
|
+
if (chat.status === 'streaming') {
|
|
126
|
+
chat.stop()
|
|
127
|
+
}
|
|
105
128
|
messages.value = []
|
|
106
|
-
chat.messages
|
|
129
|
+
chat.messages = []
|
|
107
130
|
}
|
|
108
131
|
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
function normalizeFaqQuestions(questions: FaqQuestions): FaqCategory[] {
|
|
133
|
+
if (!questions || (Array.isArray(questions) && questions.length === 0)) {
|
|
134
|
+
return []
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (typeof questions[0] === 'string') {
|
|
138
|
+
return [{
|
|
139
|
+
category: '问题',
|
|
140
|
+
items: questions as string[]
|
|
141
|
+
}]
|
|
117
142
|
}
|
|
143
|
+
|
|
144
|
+
return questions as FaqCategory[]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const faqQuestions = computed<FaqCategory[]>(() => {
|
|
148
|
+
const faqConfig = aiChat?.faqQuestions
|
|
149
|
+
if (!faqConfig) return []
|
|
150
|
+
|
|
151
|
+
return normalizeFaqQuestions(faqConfig)
|
|
118
152
|
})
|
|
119
153
|
</script>
|
|
120
154
|
|
|
121
155
|
<template>
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
variant="ghost"
|
|
154
|
-
size="sm"
|
|
155
|
-
class="text-muted hover:text-highlighted"
|
|
156
|
-
aria-label="Close Chat Panel"
|
|
157
|
-
@click="isOpen = false"
|
|
158
|
-
/>
|
|
159
|
-
</UTooltip>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
156
|
+
<USidebar
|
|
157
|
+
v-model:open="isOpen"
|
|
158
|
+
side="right"
|
|
159
|
+
:title="aiChat.texts.title"
|
|
160
|
+
rail
|
|
161
|
+
:style="{ '--sidebar-width': '24rem' }"
|
|
162
|
+
:ui="{ footer: 'p-0', actions: 'gap-0.5' }"
|
|
163
|
+
>
|
|
164
|
+
<template #actions>
|
|
165
|
+
<UTooltip v-if="canClear" :text="aiChat.texts.clearChat">
|
|
166
|
+
<UButton
|
|
167
|
+
:icon="aiChat.icons.clearChat"
|
|
168
|
+
color="neutral"
|
|
169
|
+
variant="ghost"
|
|
170
|
+
:aria-label="aiChat.texts.clearChat"
|
|
171
|
+
@click="clearMessages"
|
|
172
|
+
/>
|
|
173
|
+
</UTooltip>
|
|
174
|
+
</template>
|
|
175
|
+
|
|
176
|
+
<template #close>
|
|
177
|
+
<UTooltip :text="aiChat.texts.close">
|
|
178
|
+
<UButton
|
|
179
|
+
:icon="aiChat.icons.close"
|
|
180
|
+
color="neutral"
|
|
181
|
+
variant="ghost"
|
|
182
|
+
:aria-label="aiChat.texts.close"
|
|
183
|
+
@click="isOpen = false"
|
|
184
|
+
/>
|
|
185
|
+
</UTooltip>
|
|
186
|
+
</template>
|
|
162
187
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
:
|
|
168
|
-
|
|
169
|
-
:
|
|
170
|
-
:
|
|
171
|
-
:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
</template>
|
|
204
|
-
<UButton
|
|
205
|
-
v-if="chat.status === 'streaming' && message.id === lastMessage?.id"
|
|
206
|
-
class="px-0"
|
|
207
|
-
color="neutral"
|
|
208
|
-
variant="link"
|
|
209
|
-
size="sm"
|
|
210
|
-
:label="aiChat.texts.loading"
|
|
211
|
-
loading
|
|
212
|
-
:loading-icon="aiChat.icons.loading"
|
|
188
|
+
<UTheme
|
|
189
|
+
:ui="{
|
|
190
|
+
prose: {
|
|
191
|
+
p: { base: 'my-2 text-sm/6' },
|
|
192
|
+
li: { base: 'my-0.5 text-sm/6' },
|
|
193
|
+
ul: { base: 'my-2' },
|
|
194
|
+
ol: { base: 'my-2' },
|
|
195
|
+
h1: { base: 'text-xl mb-4' },
|
|
196
|
+
h2: { base: 'text-lg mt-6 mb-3' },
|
|
197
|
+
h3: { base: 'text-base mt-4 mb-2' },
|
|
198
|
+
h4: { base: 'text-sm mt-3 mb-1.5' },
|
|
199
|
+
code: { base: 'text-xs' },
|
|
200
|
+
pre: { root: 'my-2', base: 'text-xs/5' },
|
|
201
|
+
table: { root: 'my-2' },
|
|
202
|
+
hr: { base: 'my-4' }
|
|
203
|
+
}
|
|
204
|
+
}"
|
|
205
|
+
>
|
|
206
|
+
<UChatMessages
|
|
207
|
+
v-if="chat.messages.length"
|
|
208
|
+
should-auto-scroll
|
|
209
|
+
:messages="chat.messages"
|
|
210
|
+
:status="chat.status"
|
|
211
|
+
compact
|
|
212
|
+
class="px-0 gap-2"
|
|
213
|
+
:user="{ ui: { container: 'max-w-full' } }"
|
|
214
|
+
>
|
|
215
|
+
<template #content="{ message }">
|
|
216
|
+
<template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
|
|
217
|
+
<UChatReasoning
|
|
218
|
+
v-if="isReasoningUIPart(part)"
|
|
219
|
+
:text="part.text"
|
|
220
|
+
:streaming="isReasoningStreaming(message, index, chat)"
|
|
221
|
+
:icon="aiChat.icons.reasoning"
|
|
222
|
+
>
|
|
223
|
+
<MDCCached
|
|
224
|
+
:value="part.text"
|
|
225
|
+
:cache-key="`reasoning-${message.id}-${index}`"
|
|
226
|
+
:parser-options="{ highlight: false }"
|
|
227
|
+
class="*:first:mt-0 *:last:mb-0"
|
|
213
228
|
/>
|
|
214
|
-
</
|
|
229
|
+
</UChatReasoning>
|
|
230
|
+
<MDCCached
|
|
231
|
+
v-else-if="isTextUIPart(part) && part.text.length > 0"
|
|
232
|
+
:value="part.text"
|
|
233
|
+
:cache-key="`${message.id}-${index}`"
|
|
234
|
+
:components="components"
|
|
235
|
+
:parser-options="{ highlight: false }"
|
|
236
|
+
class="*:first:mt-0 *:last:mb-0"
|
|
237
|
+
/>
|
|
238
|
+
<UChatTool
|
|
239
|
+
v-else-if="isToolUIPart(part)"
|
|
240
|
+
:text="getToolText(part)"
|
|
241
|
+
:icon="getToolIcon(part)"
|
|
242
|
+
:streaming="isToolStreaming(part)"
|
|
243
|
+
/>
|
|
215
244
|
</template>
|
|
216
|
-
</
|
|
245
|
+
</template>
|
|
246
|
+
</UChatMessages>
|
|
217
247
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
</h3>
|
|
226
|
-
<p class="max-w-xs text-sm text-muted">
|
|
227
|
-
{{ aiChat.texts.askMeAnythingDescription }}
|
|
228
|
-
</p>
|
|
229
|
-
</div>
|
|
230
|
-
|
|
231
|
-
<template v-else>
|
|
232
|
-
<p class="mb-4 text-sm font-medium text-muted">
|
|
233
|
-
{{ aiChat.texts.faq }}
|
|
234
|
-
</p>
|
|
235
|
-
|
|
236
|
-
<AiChatSlideoverFaq :faq-questions="faqQuestions" @ask-question="askQuestion" />
|
|
237
|
-
</template>
|
|
238
|
-
</div>
|
|
248
|
+
<div v-else class="flex flex-col gap-6">
|
|
249
|
+
<UPageLinks
|
|
250
|
+
v-for="category in faqQuestions"
|
|
251
|
+
:key="category.category"
|
|
252
|
+
:title="category.category"
|
|
253
|
+
:links="category.items.map(item => ({ label: item, onClick: () => askQuestion(item) }))"
|
|
254
|
+
/>
|
|
239
255
|
</div>
|
|
256
|
+
</UTheme>
|
|
240
257
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
<div class="flex gap-1 justify-between items-center px-1 text-xs text-muted">
|
|
257
|
-
<span>{{ aiChat.texts.lineBreak }}</span>
|
|
258
|
-
<UKbd value="shift" />
|
|
259
|
-
<UKbd value="enter" />
|
|
260
|
-
</div>
|
|
261
|
-
</div>
|
|
258
|
+
<template #footer>
|
|
259
|
+
<UChatPrompt
|
|
260
|
+
v-model="input"
|
|
261
|
+
:error="chat.error"
|
|
262
|
+
:placeholder="aiChat.texts.placeholder"
|
|
263
|
+
variant="naked"
|
|
264
|
+
size="sm"
|
|
265
|
+
autofocus
|
|
266
|
+
:ui="{ base: 'px-0' }"
|
|
267
|
+
class="px-4"
|
|
268
|
+
@submit="onSubmit"
|
|
269
|
+
>
|
|
270
|
+
<template #footer>
|
|
271
|
+
<div class="flex items-center gap-x-1 text-xs text-muted">
|
|
272
|
+
<AiChatModelSelect v-model="model" />
|
|
262
273
|
|
|
263
|
-
<
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
/>
|
|
270
|
-
</template>
|
|
271
|
-
</UChatPrompt>
|
|
272
|
-
<div class="mt-1 flex text-xs text-dimmed items-center justify-between">
|
|
273
|
-
<span>刷新时聊天会被清除</span>
|
|
274
|
-
<span>
|
|
275
|
-
{{ input.length }}/1000
|
|
276
|
-
</span>
|
|
277
|
-
</div>
|
|
278
|
-
</div>
|
|
279
|
-
</div>
|
|
280
|
-
</DefineChatContent>
|
|
281
|
-
|
|
282
|
-
<aside
|
|
283
|
-
v-if="!isMobile"
|
|
284
|
-
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]"
|
|
285
|
-
:style="{
|
|
286
|
-
width: `${panelWidth}px`,
|
|
287
|
-
right: isOpen ? '0' : `-${panelWidth}px`
|
|
288
|
-
}"
|
|
289
|
-
>
|
|
290
|
-
<div class="h-full transition-[width] duration-200 ease-linear" :style="{ width: `${panelWidth}px` }">
|
|
291
|
-
<ReuseChatContent :show-expand-button="true" />
|
|
292
|
-
</div>
|
|
293
|
-
</aside>
|
|
274
|
+
<div class="flex gap-1 justify-between items-center px-1 text-xs text-muted">
|
|
275
|
+
<span>{{ aiChat.texts.lineBreak }}</span>
|
|
276
|
+
<UKbd value="shift" />
|
|
277
|
+
<UKbd value="enter" />
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
294
280
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
<ReuseChatContent :show-expand-button="false" />
|
|
281
|
+
<UChatPromptSubmit
|
|
282
|
+
class="ml-auto"
|
|
283
|
+
size="xs"
|
|
284
|
+
:status="chat.status"
|
|
285
|
+
@stop="chat.stop()"
|
|
286
|
+
@reload="chat.regenerate()"
|
|
287
|
+
/>
|
|
288
|
+
</template>
|
|
289
|
+
</UChatPrompt>
|
|
305
290
|
</template>
|
|
306
|
-
</
|
|
291
|
+
</USidebar>
|
|
307
292
|
</template>
|