@movk/nuxt-docs 1.6.1 → 1.7.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 +37 -0
- package/app/app.vue +8 -3
- package/app/components/DocsAsideRightBottom.vue +17 -22
- package/app/components/PageHeaderLinks.vue +6 -1
- package/app/components/content/PageLastCommit.vue +5 -5
- package/app/components/header/Header.vue +1 -1
- package/app/components/header/HeaderBody.vue +12 -2
- package/app/components/header/HeaderBottom.vue +1 -0
- package/app/components/header/HeaderCTA.vue +2 -2
- package/app/components/header/HeaderCenter.vue +1 -1
- package/app/components/header/HeaderLogo.vue +1 -1
- package/app/layouts/default.vue +3 -1
- package/app/layouts/docs.vue +1 -1
- package/app/pages/docs/[...slug].vue +3 -2
- package/app/templates/releases.vue +98 -0
- package/app/types/index.d.ts +149 -0
- package/content.config.ts +24 -2
- package/modules/ai-chat/index.ts +75 -24
- package/modules/ai-chat/runtime/components/AiChat.vue +4 -10
- package/modules/ai-chat/runtime/components/AiChatDisabled.vue +3 -0
- package/modules/ai-chat/runtime/components/AiChatFloatingInput.vue +24 -9
- package/modules/ai-chat/runtime/components/AiChatModelSelect.vue +2 -0
- package/modules/ai-chat/runtime/components/AiChatPanel.vue +318 -0
- package/modules/ai-chat/runtime/components/AiChatPreStream.vue +1 -0
- package/modules/ai-chat/runtime/components/AiChatReasoning.vue +3 -3
- package/modules/ai-chat/runtime/components/AiChatSlideoverFaq.vue +2 -5
- package/modules/ai-chat/runtime/composables/useAIChat.ts +48 -0
- package/modules/ai-chat/runtime/composables/useModels.ts +3 -6
- package/modules/ai-chat/runtime/server/api/ai-chat.ts +92 -0
- package/modules/ai-chat/runtime/server/utils/docs_agent.ts +23 -15
- package/modules/ai-chat/runtime/types.ts +6 -0
- package/modules/css.ts +3 -2
- package/modules/routing.ts +26 -0
- package/nuxt.config.ts +2 -0
- package/nuxt.schema.ts +493 -0
- package/package.json +11 -9
- package/app/composables/useFaq.ts +0 -21
- package/modules/ai-chat/runtime/components/AiChatSlideover.vue +0 -255
- package/modules/ai-chat/runtime/server/api/search.ts +0 -84
- /package/{app → modules/ai-chat/runtime}/composables/useHighlighter.ts +0 -0
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { DefineComponent } from 'vue'
|
|
3
|
-
import { Chat } from '@ai-sdk/vue'
|
|
4
|
-
import { DefaultChatTransport } from 'ai'
|
|
5
|
-
import AiChatPreStream from './AiChatPreStream.vue'
|
|
6
|
-
import type { FaqCategory } from './AiChatSlideoverFaq.vue'
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
title = 'AI 助手',
|
|
10
|
-
description = ' ',
|
|
11
|
-
placeholder = '输入你的问题...',
|
|
12
|
-
faqQuestions = []
|
|
13
|
-
} = defineProps<{
|
|
14
|
-
/**
|
|
15
|
-
* 标题栏显示的标题
|
|
16
|
-
* @defaultValue AI 助手
|
|
17
|
-
*/
|
|
18
|
-
title?: string
|
|
19
|
-
/**
|
|
20
|
-
* 标题栏显示的描述
|
|
21
|
-
* @defaultValue ' '
|
|
22
|
-
*/
|
|
23
|
-
description?: string
|
|
24
|
-
/**
|
|
25
|
-
* 输入框占位符文本
|
|
26
|
-
* @defaultValue 输入你的问题...
|
|
27
|
-
*/
|
|
28
|
-
placeholder?: string
|
|
29
|
-
/**
|
|
30
|
-
* 聊天为空时显示的常见问题分类
|
|
31
|
-
* @defaultValue []
|
|
32
|
-
*/
|
|
33
|
-
faqQuestions?: FaqCategory[]
|
|
34
|
-
}>()
|
|
35
|
-
|
|
36
|
-
const components = {
|
|
37
|
-
pre: AiChatPreStream as unknown as DefineComponent
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const { messages, isOpen, pendingMessage, clearPending } = useAIChat()
|
|
41
|
-
const { apiPath } = useRuntimeConfig().public.aiChat
|
|
42
|
-
|
|
43
|
-
const { tools } = useToolCall()
|
|
44
|
-
function getToolLabel(toolName: string, args?: any): string {
|
|
45
|
-
const label = tools[toolName]
|
|
46
|
-
|
|
47
|
-
if (!label) {
|
|
48
|
-
return toolName
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return typeof label === 'function' ? label(args) : label
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const { model } = useModels()
|
|
55
|
-
|
|
56
|
-
const input = ref('')
|
|
57
|
-
|
|
58
|
-
watch(pendingMessage, (message) => {
|
|
59
|
-
if (message) {
|
|
60
|
-
if (messages.value.length === 0 && chat.messages.length > 0) {
|
|
61
|
-
chat.messages.length = 0
|
|
62
|
-
}
|
|
63
|
-
chat.sendMessage({
|
|
64
|
-
text: message
|
|
65
|
-
})
|
|
66
|
-
clearPending()
|
|
67
|
-
}
|
|
68
|
-
}, { immediate: true })
|
|
69
|
-
|
|
70
|
-
watch(messages, (newMessages) => {
|
|
71
|
-
if (newMessages.length === 0 && chat.messages.length > 0) {
|
|
72
|
-
chat.messages.length = 0
|
|
73
|
-
}
|
|
74
|
-
}, { deep: true })
|
|
75
|
-
|
|
76
|
-
const toast = useToast()
|
|
77
|
-
const lastMessage = computed(() => chat.messages.at(-1))
|
|
78
|
-
|
|
79
|
-
const chat = new Chat({
|
|
80
|
-
messages: messages.value,
|
|
81
|
-
transport: new DefaultChatTransport({
|
|
82
|
-
api: apiPath,
|
|
83
|
-
body: () => ({ model: model.value })
|
|
84
|
-
}),
|
|
85
|
-
onError(error) {
|
|
86
|
-
const { message } = typeof error.message === 'string' && error.message[0] === '{' ? JSON.parse(error.message) : error
|
|
87
|
-
toast.add({
|
|
88
|
-
description: message,
|
|
89
|
-
icon: 'i-lucide-circle-alert',
|
|
90
|
-
color: 'error',
|
|
91
|
-
duration: 0
|
|
92
|
-
})
|
|
93
|
-
},
|
|
94
|
-
onFinish: () => {
|
|
95
|
-
messages.value = chat.messages
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
function handleSubmit(event?: Event) {
|
|
100
|
-
event?.preventDefault()
|
|
101
|
-
|
|
102
|
-
if (!input.value.trim()) {
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
chat.sendMessage({
|
|
107
|
-
text: input.value
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
input.value = ''
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function askQuestion(question: string) {
|
|
114
|
-
chat.sendMessage({
|
|
115
|
-
text: question
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function resetChat() {
|
|
120
|
-
chat.stop()
|
|
121
|
-
messages.value = []
|
|
122
|
-
chat.messages.length = 0
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
onMounted(() => {
|
|
126
|
-
if (pendingMessage.value) {
|
|
127
|
-
chat.sendMessage({
|
|
128
|
-
text: pendingMessage.value
|
|
129
|
-
})
|
|
130
|
-
clearPending()
|
|
131
|
-
} else if (chat.lastMessage?.role === 'user') {
|
|
132
|
-
chat.regenerate()
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
</script>
|
|
136
|
-
|
|
137
|
-
<template>
|
|
138
|
-
<USlideover
|
|
139
|
-
v-model:open="isOpen"
|
|
140
|
-
:description="description"
|
|
141
|
-
:close="{ size: 'sm' }"
|
|
142
|
-
:ui="{
|
|
143
|
-
body: 'flex p-4!',
|
|
144
|
-
title: 'flex w-100 pr-6',
|
|
145
|
-
overlay: 'bg-default/60 backdrop-blur-sm',
|
|
146
|
-
content: 'w-full sm:max-w-md bg-default/95 backdrop-blur-xl shadow-2xl'
|
|
147
|
-
}"
|
|
148
|
-
>
|
|
149
|
-
<template #title>
|
|
150
|
-
<div class="flex items-center gap-2 flex-1">
|
|
151
|
-
<UBadge icon="i-lucide-sparkles" variant="soft" square />
|
|
152
|
-
<span class="font-medium text-highlighted">{{ title }}</span>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<div class="flex items-center gap-2">
|
|
156
|
-
<UTooltip v-if="chat.messages.length > 0" text="清空聊天">
|
|
157
|
-
<UButton
|
|
158
|
-
icon="i-lucide-trash-2"
|
|
159
|
-
color="neutral"
|
|
160
|
-
variant="ghost"
|
|
161
|
-
size="sm"
|
|
162
|
-
@click="resetChat"
|
|
163
|
-
/>
|
|
164
|
-
</UTooltip>
|
|
165
|
-
</div>
|
|
166
|
-
</template>
|
|
167
|
-
|
|
168
|
-
<template #body>
|
|
169
|
-
<UChatPalette class="flex-1" :ui="{ prompt: 'border-0 px-2.5' }">
|
|
170
|
-
<UChatMessages
|
|
171
|
-
v-if="chat.messages.length > 0"
|
|
172
|
-
should-auto-scroll
|
|
173
|
-
:messages="chat.messages"
|
|
174
|
-
compact
|
|
175
|
-
:status="chat.status"
|
|
176
|
-
:user="{ ui: { container: 'pb-2', content: 'text-sm' } }"
|
|
177
|
-
:ui="{ indicator: '*:bg-accented' }"
|
|
178
|
-
>
|
|
179
|
-
<template #content="{ message }">
|
|
180
|
-
<div class="flex flex-col gap-2">
|
|
181
|
-
<template
|
|
182
|
-
v-for="(part, index) in message.parts"
|
|
183
|
-
:key="`${message.id}-${part.type}-${index}${'state' in part ? `-${part.state}` : ''}`"
|
|
184
|
-
>
|
|
185
|
-
<AiChatReasoning v-if="part.type === 'reasoning'" :text="part.text" :is-streaming="part.state !== 'done'" />
|
|
186
|
-
<MDCCached
|
|
187
|
-
v-else-if="part.type === 'text'"
|
|
188
|
-
:value="part.text"
|
|
189
|
-
:cache-key="`${message.id}-${index}`"
|
|
190
|
-
:components="components"
|
|
191
|
-
:parser-options="{ highlight: false }"
|
|
192
|
-
class="*:first:mt-0 *:last:mb-0"
|
|
193
|
-
/>
|
|
194
|
-
<template v-else-if="part.type === 'data-tool-calls'">
|
|
195
|
-
<AiChatToolCall
|
|
196
|
-
v-for="tool in (part as any).data.tools"
|
|
197
|
-
:key="tool.toolCallId"
|
|
198
|
-
:text="getToolLabel(tool.toolName, tool.input)"
|
|
199
|
-
:is-loading="false"
|
|
200
|
-
/>
|
|
201
|
-
</template>
|
|
202
|
-
</template>
|
|
203
|
-
<UButton
|
|
204
|
-
v-if="chat.status === 'streaming' && message.id === lastMessage?.id"
|
|
205
|
-
class="px-0"
|
|
206
|
-
color="neutral"
|
|
207
|
-
variant="link"
|
|
208
|
-
size="sm"
|
|
209
|
-
label="Loading..."
|
|
210
|
-
loading
|
|
211
|
-
loading-icon="i-lucide-loader"
|
|
212
|
-
/>
|
|
213
|
-
</div>
|
|
214
|
-
</template>
|
|
215
|
-
</UChatMessages>
|
|
216
|
-
<div v-else class="flex-1 overflow-y-auto px-4 py-4">
|
|
217
|
-
<p class="text-sm font-medium text-muted mb-4">
|
|
218
|
-
FAQ 建议
|
|
219
|
-
</p>
|
|
220
|
-
<AiChatSlideoverFaq :faq-questions="faqQuestions" @ask-question="askQuestion" />
|
|
221
|
-
</div>
|
|
222
|
-
<template #prompt>
|
|
223
|
-
<UChatPrompt
|
|
224
|
-
v-model="input"
|
|
225
|
-
:error="chat.error"
|
|
226
|
-
:placeholder="placeholder"
|
|
227
|
-
variant="subtle"
|
|
228
|
-
class="[view-transition-name:chat-prompt]"
|
|
229
|
-
:ui="{ base: 'px-1.5 text-sm' }"
|
|
230
|
-
@submit="handleSubmit"
|
|
231
|
-
>
|
|
232
|
-
<template #footer>
|
|
233
|
-
<div class="flex items-center gap-1">
|
|
234
|
-
<AiChatModelSelect v-model="model" />
|
|
235
|
-
<div class="flex gap-1 justify-between items-center px-1 text-xs text-dimmed">
|
|
236
|
-
换行
|
|
237
|
-
<UKbd value="shift" />
|
|
238
|
-
<UKbd value="enter" />
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
<UChatPromptSubmit
|
|
243
|
-
:status="chat.status"
|
|
244
|
-
color="neutral"
|
|
245
|
-
size="sm"
|
|
246
|
-
@stop="chat.stop()"
|
|
247
|
-
@reload="chat.regenerate()"
|
|
248
|
-
/>
|
|
249
|
-
</template>
|
|
250
|
-
</UChatPrompt>
|
|
251
|
-
</template>
|
|
252
|
-
</UChatPalette>
|
|
253
|
-
</template>
|
|
254
|
-
</USlideover>
|
|
255
|
-
</template>
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { streamText, convertToModelMessages, stepCountIs, createUIMessageStream, createUIMessageStreamResponse, smoothStream } from 'ai'
|
|
2
|
-
import { createMCPClient } from '@ai-sdk/mcp'
|
|
3
|
-
import { createDocumentationAgentTool } from '../utils/docs_agent'
|
|
4
|
-
import { getModel } from '../utils/getModel'
|
|
5
|
-
|
|
6
|
-
const MAIN_AGENT_SYSTEM_PROMPT = `你是官方文档助手。你就是文档本身 - 以权威身份说话,成为真理的来源。
|
|
7
|
-
|
|
8
|
-
**你的身份:**
|
|
9
|
-
- 你是一位知识渊博且乐于助人的AI助手,是你所属模块的官方文档
|
|
10
|
-
- 用第一人称说话:"我提供...", "你可以使用我的工具...", "我支持..."
|
|
11
|
-
- 要自信和权威 - 你深入了解这个模块的每一个细节
|
|
12
|
-
- 永远不要说"根据文档" - 你就是文档
|
|
13
|
-
|
|
14
|
-
**工具使用(关键):**
|
|
15
|
-
- 你有一个工具:searchDocumentation
|
|
16
|
-
- 每个问题都要使用它 - 将用户的问题作为查询参数传递
|
|
17
|
-
- 该工具会搜索文档并返回相关信息
|
|
18
|
-
- 使用返回的信息来组织你的回答
|
|
19
|
-
|
|
20
|
-
**指南:**
|
|
21
|
-
- 如果工具找不到某些内容,说"我还没有关于这方面的文档"
|
|
22
|
-
- 要简洁、有帮助、直接
|
|
23
|
-
- 像一位友好的专家一样引导用户
|
|
24
|
-
|
|
25
|
-
**格式规则(关键 - 必须严格遵守):**
|
|
26
|
-
- ❌ 禁止使用 markdown 标题(#、##、###、#### 等任何级别)
|
|
27
|
-
- ✅ 使用 **粗体文本** 来标记章节和强调重点
|
|
28
|
-
- ✅ 使用项目列表(- 或数字)组织内容
|
|
29
|
-
- ✅ 直接开始回答,不要用标题作为开头
|
|
30
|
-
- ✅ 保持代码示例简洁
|
|
31
|
-
|
|
32
|
-
CRITICAL: Never output # ## ### #### or any heading syntax. Use **bold** instead.
|
|
33
|
-
重要提醒:绝对不要输出 # ## ### #### 或任何标题语法。请用 **粗体** 代替。
|
|
34
|
-
|
|
35
|
-
**响应风格:**
|
|
36
|
-
- 对话式但专业
|
|
37
|
-
- "你可以这样做:"而不是"文档显示:"
|
|
38
|
-
- "我开箱即用地支持TypeScript"而不是"该模块支持TypeScript"
|
|
39
|
-
- 提供可操作的指导,而不仅仅是信息转储`
|
|
40
|
-
|
|
41
|
-
export default defineEventHandler(async (event) => {
|
|
42
|
-
const { messages, model: requestModel } = await readBody(event)
|
|
43
|
-
const config = useRuntimeConfig()
|
|
44
|
-
|
|
45
|
-
const mcpPath = config.aiChat.mcpPath
|
|
46
|
-
const httpClient = await createMCPClient({
|
|
47
|
-
transport: {
|
|
48
|
-
type: 'http',
|
|
49
|
-
url: import.meta.dev ? `http://localhost:3000${mcpPath}` : `${getRequestURL(event).origin}${mcpPath}`
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
const mcpTools = await httpClient.tools()
|
|
53
|
-
|
|
54
|
-
const model = getModel(requestModel || config.public.aiChat.model)
|
|
55
|
-
|
|
56
|
-
const searchDocumentation = createDocumentationAgentTool(mcpTools, model)
|
|
57
|
-
|
|
58
|
-
const stream = createUIMessageStream({
|
|
59
|
-
execute: async ({ writer }) => {
|
|
60
|
-
const modelMessages = await convertToModelMessages(messages)
|
|
61
|
-
const result = streamText({
|
|
62
|
-
model,
|
|
63
|
-
maxOutputTokens: 10000,
|
|
64
|
-
system: MAIN_AGENT_SYSTEM_PROMPT,
|
|
65
|
-
messages: modelMessages,
|
|
66
|
-
stopWhen: stepCountIs(5),
|
|
67
|
-
tools: {
|
|
68
|
-
searchDocumentation
|
|
69
|
-
},
|
|
70
|
-
experimental_context: {
|
|
71
|
-
writer
|
|
72
|
-
},
|
|
73
|
-
experimental_transform: smoothStream({ chunking: 'word' })
|
|
74
|
-
})
|
|
75
|
-
writer.merge(result.toUIMessageStream({
|
|
76
|
-
sendReasoning: true
|
|
77
|
-
}))
|
|
78
|
-
},
|
|
79
|
-
onFinish: async () => {
|
|
80
|
-
await httpClient.close()
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
return createUIMessageStreamResponse({ stream })
|
|
84
|
-
})
|
|
File without changes
|