@movk/nuxt-docs 1.6.2 → 1.7.1
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/content/StarsBg.vue +119 -0
- 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 +53 -21
- 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 +40 -32
- 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/{app → modules/ai-chat/runtime}/composables/useHighlighter.ts +0 -0
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
import type { UIMessage } from 'ai'
|
|
2
|
+
import { useMediaQuery } from '@vueuse/core'
|
|
3
|
+
import type { FaqCategory, FaqQuestions } from '../types'
|
|
4
|
+
|
|
5
|
+
function normalizeFaqQuestions(questions: FaqQuestions): FaqCategory[] {
|
|
6
|
+
if (!questions || (Array.isArray(questions) && questions.length === 0)) {
|
|
7
|
+
return []
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (typeof questions[0] === 'string') {
|
|
11
|
+
return [{
|
|
12
|
+
category: '问题',
|
|
13
|
+
items: questions as string[]
|
|
14
|
+
}]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return questions as FaqCategory[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PANEL_WIDTH_COMPACT = 360
|
|
21
|
+
const PANEL_WIDTH_EXPANDED = 520
|
|
2
22
|
|
|
3
23
|
export function useAIChat() {
|
|
24
|
+
const config = useRuntimeConfig()
|
|
25
|
+
const appConfig = useAppConfig()
|
|
26
|
+
const isEnabled = computed(() => config.public.aiChat?.enabled ?? false)
|
|
27
|
+
|
|
4
28
|
const isOpen = useState('ai-chat-open', () => false)
|
|
29
|
+
const isExpanded = useState('ai-chat-expanded', () => false)
|
|
5
30
|
const messages = useState<UIMessage[]>('ai-chat-messages', () => [])
|
|
6
31
|
const pendingMessage = useState<string | undefined>('ai-chat-pending', () => undefined)
|
|
7
32
|
|
|
33
|
+
const isMobile = useMediaQuery('(max-width: 767px)')
|
|
34
|
+
const panelWidth = computed(() => isExpanded.value ? PANEL_WIDTH_EXPANDED : PANEL_WIDTH_COMPACT)
|
|
35
|
+
const shouldPushContent = computed(() => !isMobile.value && isOpen.value)
|
|
36
|
+
|
|
37
|
+
const faqQuestions = computed<FaqCategory[]>(() => {
|
|
38
|
+
const aiChatConfig = appConfig.aiChat
|
|
39
|
+
const faqConfig = aiChatConfig?.faqQuestions
|
|
40
|
+
if (!faqConfig) return []
|
|
41
|
+
|
|
42
|
+
return normalizeFaqQuestions(faqConfig)
|
|
43
|
+
})
|
|
44
|
+
|
|
8
45
|
function open(initialMessage?: string, clearPrevious = false) {
|
|
9
46
|
if (clearPrevious) {
|
|
10
47
|
messages.value = []
|
|
@@ -32,14 +69,25 @@ export function useAIChat() {
|
|
|
32
69
|
messages.value = []
|
|
33
70
|
}
|
|
34
71
|
|
|
72
|
+
function toggleExpanded() {
|
|
73
|
+
isExpanded.value = !isExpanded.value
|
|
74
|
+
}
|
|
75
|
+
|
|
35
76
|
return {
|
|
77
|
+
isEnabled,
|
|
36
78
|
isOpen,
|
|
79
|
+
isExpanded,
|
|
80
|
+
isMobile,
|
|
81
|
+
panelWidth,
|
|
82
|
+
shouldPushContent,
|
|
37
83
|
messages,
|
|
38
84
|
pendingMessage,
|
|
85
|
+
faqQuestions,
|
|
39
86
|
open,
|
|
40
87
|
clearPending,
|
|
41
88
|
close,
|
|
42
89
|
toggle,
|
|
90
|
+
toggleExpanded,
|
|
43
91
|
clearMessages
|
|
44
92
|
}
|
|
45
93
|
}
|
|
@@ -2,15 +2,12 @@ export function useModels() {
|
|
|
2
2
|
const config = useRuntimeConfig()
|
|
3
3
|
const model = useCookie<string>('model', { default: () => config.public.aiChat.model })
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
kwaipilot: 'i-lucide-wand',
|
|
8
|
-
zai: 'i-lucide-wand'
|
|
9
|
-
}
|
|
5
|
+
const { aiChat } = useAppConfig()
|
|
6
|
+
const providerIcons = computed(() => aiChat.icons.providers || {})
|
|
10
7
|
|
|
11
8
|
function getModelIcon(modelId: string): string {
|
|
12
9
|
const provider = modelId.split('/')[0] || ''
|
|
13
|
-
return providerIcons[provider] || `i-simple-icons-${modelId.split('/')[0]}`
|
|
10
|
+
return providerIcons.value[provider] || `i-simple-icons-${modelId.split('/')[0]}`
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
function formatModelName(modelId: string): string {
|
|
@@ -3,57 +3,65 @@ import { createMCPClient } from '@ai-sdk/mcp'
|
|
|
3
3
|
import { createDocumentationAgentTool } from '../utils/docs_agent'
|
|
4
4
|
import { getModel } from '../utils/getModel'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
function getMainAgentSystemPrompt(siteName: string) {
|
|
7
|
+
return `You are the official documentation assistant for ${siteName}. You ARE the documentation - speak with authority as the source of truth.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
9
|
+
**Your identity:**
|
|
10
|
+
- You are the ${siteName} documentation
|
|
11
|
+
- Speak in first person: "I provide...", "You can use my tools to...", "I support..."
|
|
12
|
+
- Be confident and authoritative - you know this project inside out
|
|
13
|
+
- Never say "according to the documentation" - YOU are the docs
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
15
|
+
**Tool usage (CRITICAL):**
|
|
16
|
+
- You have ONE tool: searchDocumentation
|
|
17
|
+
- Use it for EVERY question - pass the user's question as the query
|
|
18
|
+
- The tool will search the documentation and return relevant information
|
|
19
|
+
- Use the returned information to formulate your response
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
21
|
+
**Guidelines:**
|
|
22
|
+
- If the tool can't find something, say "I don't have documentation on that yet"
|
|
23
|
+
- Be concise, helpful, and direct
|
|
24
|
+
- Guide users like a friendly expert would
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
26
|
+
**FORMATTING RULES (CRITICAL):**
|
|
27
|
+
- NEVER use markdown headings (#, ##, ###, etc.)
|
|
28
|
+
- Use **bold text** for emphasis and section labels
|
|
29
|
+
- Start responses with content directly, never with a heading
|
|
30
|
+
- Use bullet points for lists
|
|
31
|
+
- Keep code examples focused and minimal
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
- "我开箱即用地支持TypeScript"而不是"该模块支持TypeScript"
|
|
39
|
-
- 提供可操作的指导,而不仅仅是信息转储`
|
|
33
|
+
**Response style:**
|
|
34
|
+
- Conversational but professional
|
|
35
|
+
- "Here's how you can do that:" instead of "The documentation shows:"
|
|
36
|
+
- "I support TypeScript out of the box" instead of "The module supports TypeScript"
|
|
37
|
+
- Provide actionable guidance, not just information dumps`
|
|
38
|
+
}
|
|
40
39
|
|
|
41
40
|
export default defineEventHandler(async (event) => {
|
|
42
41
|
const { messages, model: requestModel } = await readBody(event)
|
|
43
42
|
const config = useRuntimeConfig()
|
|
43
|
+
const siteConfig = getSiteConfig(event)
|
|
44
|
+
const siteName = siteConfig.name || 'Documentation'
|
|
44
45
|
|
|
45
46
|
const mcpPath = config.aiChat.mcpPath
|
|
47
|
+
const isExternalUrl = mcpPath.startsWith('http://') || mcpPath.startsWith('https://')
|
|
48
|
+
const mcpUrl = isExternalUrl
|
|
49
|
+
? mcpPath
|
|
50
|
+
: import.meta.dev
|
|
51
|
+
? `http://localhost:3000${mcpPath}`
|
|
52
|
+
: `${getRequestURL(event).origin}${mcpPath}`
|
|
53
|
+
|
|
46
54
|
const httpClient = await createMCPClient({
|
|
47
55
|
transport: {
|
|
48
56
|
type: 'http',
|
|
49
|
-
url:
|
|
57
|
+
url: mcpUrl
|
|
50
58
|
}
|
|
51
59
|
})
|
|
52
60
|
const mcpTools = await httpClient.tools()
|
|
53
61
|
|
|
54
62
|
const model = getModel(requestModel || config.public.aiChat.model)
|
|
55
63
|
|
|
56
|
-
const searchDocumentation = createDocumentationAgentTool(mcpTools, model)
|
|
64
|
+
const searchDocumentation = createDocumentationAgentTool(mcpTools, model, siteName)
|
|
57
65
|
|
|
58
66
|
const stream = createUIMessageStream({
|
|
59
67
|
execute: async ({ writer }) => {
|
|
@@ -61,7 +69,7 @@ export default defineEventHandler(async (event) => {
|
|
|
61
69
|
const result = streamText({
|
|
62
70
|
model,
|
|
63
71
|
maxOutputTokens: 10000,
|
|
64
|
-
system:
|
|
72
|
+
system: getMainAgentSystemPrompt(siteName),
|
|
65
73
|
messages: modelMessages,
|
|
66
74
|
stopWhen: stepCountIs(5),
|
|
67
75
|
tools: {
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { tool, stepCountIs, generateText } from 'ai'
|
|
2
2
|
import { z } from 'zod/v4'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
function getSubAgentSystemPrompt(siteName: string) {
|
|
5
|
+
return `You are a documentation search agent for ${siteName}. Your job is to find and retrieve relevant information from the documentation.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
7
|
+
**Your task:**
|
|
8
|
+
- Use the available tools to search and read documentation pages
|
|
9
|
+
- Start with list-pages to discover what documentation exists
|
|
10
|
+
- Then use get-page to read the relevant page(s)
|
|
11
|
+
- If a specific path is mentioned, you can call get-page directly
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
13
|
+
**Guidelines:**
|
|
14
|
+
- Be thorough - read all relevant pages before answering
|
|
15
|
+
- Return the raw information you find, let the main agent format the response
|
|
16
|
+
- If you can't find information, say so clearly
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
**Output:**
|
|
19
|
+
Return the relevant documentation content you found, including code examples if present.`
|
|
20
|
+
}
|
|
19
21
|
|
|
20
|
-
export function createDocumentationAgentTool(mcpTools: Record<string, any>, model: any) {
|
|
22
|
+
export function createDocumentationAgentTool(mcpTools: Record<string, any>, model: any, siteName: string) {
|
|
21
23
|
return tool({
|
|
22
24
|
description: '从文档中搜索并检索信息。使用此工具回答有关文档的任何问题。将用户的问题作为查询参数传递。',
|
|
23
25
|
inputSchema: z.object({
|
|
@@ -29,14 +31,20 @@ export function createDocumentationAgentTool(mcpTools: Record<string, any>, mode
|
|
|
29
31
|
const result = await generateText({
|
|
30
32
|
model,
|
|
31
33
|
tools: mcpTools,
|
|
32
|
-
system:
|
|
34
|
+
system: getSubAgentSystemPrompt(siteName),
|
|
33
35
|
stopWhen: stepCountIs(5),
|
|
34
36
|
onStepFinish: ({ toolCalls }) => {
|
|
37
|
+
if (toolCalls.length === 0) return
|
|
38
|
+
|
|
35
39
|
writer?.write({
|
|
36
40
|
id: toolCalls[0]?.toolCallId,
|
|
37
41
|
type: 'data-tool-calls',
|
|
38
42
|
data: {
|
|
39
|
-
tools: toolCalls.map(toolCall => ({
|
|
43
|
+
tools: toolCalls.map((toolCall: any) => ({
|
|
44
|
+
toolName: toolCall.toolName,
|
|
45
|
+
toolCallId: toolCall.toolCallId,
|
|
46
|
+
args: toolCall.args || toolCall.input || {}
|
|
47
|
+
}))
|
|
40
48
|
}
|
|
41
49
|
})
|
|
42
50
|
},
|
package/modules/css.ts
CHANGED
|
@@ -10,7 +10,8 @@ export default defineNuxtModule({
|
|
|
10
10
|
const { resolve } = createResolver(import.meta.url)
|
|
11
11
|
|
|
12
12
|
const layerDir = resolve('../app')
|
|
13
|
-
const
|
|
13
|
+
const aiChatDir = resolve('../modules/ai-chat')
|
|
14
|
+
|
|
14
15
|
const contentDir = joinURL(dir, 'content')
|
|
15
16
|
|
|
16
17
|
const cssTemplate = addTemplate({
|
|
@@ -22,7 +23,7 @@ export default defineNuxtModule({
|
|
|
22
23
|
|
|
23
24
|
@source "${contentDir.replace(/\\/g, '/')}/**/*";
|
|
24
25
|
@source "${layerDir.replace(/\\/g, '/')}/**/*";
|
|
25
|
-
@source "${
|
|
26
|
+
@source "${aiChatDir.replace(/\\/g, '/')}/**/*";
|
|
26
27
|
@source "../../app.config.ts";
|
|
27
28
|
|
|
28
29
|
/* Shiki icon highlight transformer styles */
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineNuxtModule, extendPages, createResolver } from '@nuxt/kit'
|
|
2
|
+
import { joinURL } from 'ufo'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
export default defineNuxtModule({
|
|
6
|
+
meta: {
|
|
7
|
+
name: 'routing'
|
|
8
|
+
},
|
|
9
|
+
async setup(_options, nuxt) {
|
|
10
|
+
const { resolve } = createResolver(import.meta.url)
|
|
11
|
+
const cwd = joinURL(nuxt.options.rootDir, 'content')
|
|
12
|
+
|
|
13
|
+
const hasReleases = ['releases.yml', 'releases.md']
|
|
14
|
+
.some(file => existsSync(joinURL(cwd, file)))
|
|
15
|
+
|
|
16
|
+
extendPages((pages) => {
|
|
17
|
+
if (hasReleases) {
|
|
18
|
+
pages.push({
|
|
19
|
+
name: 'releases',
|
|
20
|
+
path: '/releases',
|
|
21
|
+
file: resolve('../app/templates/releases.vue')
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
})
|
package/nuxt.config.ts
CHANGED
|
@@ -7,12 +7,14 @@ const { resolve } = createResolver(import.meta.url)
|
|
|
7
7
|
export default defineNuxtConfig({
|
|
8
8
|
modules: [
|
|
9
9
|
resolve('./modules/config'),
|
|
10
|
+
resolve('./modules/routing'),
|
|
10
11
|
resolve('./modules/css'),
|
|
11
12
|
resolve('./modules/component-example'),
|
|
12
13
|
resolve('./modules/ai-chat'),
|
|
13
14
|
'@nuxt/ui',
|
|
14
15
|
'@nuxt/content',
|
|
15
16
|
'@nuxt/image',
|
|
17
|
+
'@nuxt/a11y',
|
|
16
18
|
'@vueuse/nuxt',
|
|
17
19
|
'@nuxtjs/mcp-toolkit',
|
|
18
20
|
'@nuxtjs/seo',
|