@movk/nuxt-docs 1.17.4 → 1.17.6
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/components/PageHeaderLinks.vue +3 -2
- package/nuxt.config.ts +4 -2
- package/package.json +5 -5
- package/providers/alibaba-puhuiti.ts +2 -4
- package/server/mcp/tools/get-example.ts +17 -4
- package/server/mcp/tools/get-page.ts +19 -74
- package/server/mcp/tools/list-examples.ts +7 -1
- package/server/mcp/tools/list-getting-started-guides.ts +7 -3
- package/server/mcp/tools/list-pages.ts +6 -0
- package/server/utils/extractSections.ts +62 -0
- package/utils/component-meta.ts +1 -1
|
@@ -10,6 +10,7 @@ const { ui } = useAppConfig()
|
|
|
10
10
|
const appBaseURL = useRuntimeConfig().app?.baseURL || '/'
|
|
11
11
|
|
|
12
12
|
const mdPath = computed(() => `${site.url}/raw${route.path}.md`)
|
|
13
|
+
const aiPrompt = computed(() => `I'm looking at this documentation: ${mdPath.value}\nHelp me understand how to use it. Be ready to explain concepts, give examples, or help debug based on it.`)
|
|
13
14
|
|
|
14
15
|
const items = [[
|
|
15
16
|
{
|
|
@@ -33,13 +34,13 @@ const items = [[
|
|
|
33
34
|
label: 'Open in ChatGPT',
|
|
34
35
|
icon: 'i-simple-icons-openai',
|
|
35
36
|
target: '_blank',
|
|
36
|
-
to: `https://chatgpt.com/?
|
|
37
|
+
to: `https://chatgpt.com/?prompt=${encodeURIComponent(aiPrompt.value)}`
|
|
37
38
|
},
|
|
38
39
|
{
|
|
39
40
|
label: 'Open in Claude',
|
|
40
41
|
icon: 'i-simple-icons-anthropic',
|
|
41
42
|
target: '_blank',
|
|
42
|
-
to: `https://claude.ai/new?q=${encodeURIComponent(
|
|
43
|
+
to: `https://claude.ai/new?q=${encodeURIComponent(aiPrompt.value)}`
|
|
43
44
|
}
|
|
44
45
|
], [
|
|
45
46
|
{
|
package/nuxt.config.ts
CHANGED
|
@@ -167,12 +167,14 @@ export default defineNuxtConfig({
|
|
|
167
167
|
]
|
|
168
168
|
},
|
|
169
169
|
|
|
170
|
+
// for users in China who may have trouble loading Google Fonts.
|
|
170
171
|
fonts: {
|
|
172
|
+
provider: 'alibaba-puhuiti',
|
|
171
173
|
providers: {
|
|
172
|
-
'alibaba-puhuiti': createAlibabaPuHuiTiProvider('https://cdn.mhaibaraai.cn/fonts')
|
|
174
|
+
'alibaba-puhuiti': createAlibabaPuHuiTiProvider('https://cdn.mhaibaraai.cn/fonts', 'Alibaba PuHuiTi')
|
|
173
175
|
},
|
|
174
176
|
families: [
|
|
175
|
-
{ name: 'Alibaba PuHuiTi',
|
|
177
|
+
{ name: 'Alibaba PuHuiTi', global: true }
|
|
176
178
|
]
|
|
177
179
|
},
|
|
178
180
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@movk/nuxt-docs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.17.
|
|
4
|
+
"version": "1.17.6",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Modern Nuxt 4 documentation theme with auto-generated component docs, AI chat assistant, MCP server, and complete developer experience optimization.",
|
|
7
7
|
"author": "YiXuan <mhaibaraai@gmail.com>",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"README.md"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@ai-sdk/gateway": "^3.0.
|
|
42
|
+
"@ai-sdk/gateway": "^3.0.93",
|
|
43
43
|
"@ai-sdk/mcp": "^1.0.35",
|
|
44
|
-
"@ai-sdk/vue": "^3.0.
|
|
44
|
+
"@ai-sdk/vue": "^3.0.152",
|
|
45
45
|
"@iconify-json/lucide": "^1.2.101",
|
|
46
46
|
"@iconify-json/simple-icons": "^1.2.77",
|
|
47
47
|
"@iconify-json/vscode-icons": "^1.2.45",
|
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
"@takumi-rs/wasm": "^0.73.1",
|
|
64
64
|
"@vueuse/core": "^14.2.1",
|
|
65
65
|
"@vueuse/nuxt": "^14.2.1",
|
|
66
|
-
"ai": "^6.0.
|
|
66
|
+
"ai": "^6.0.152",
|
|
67
67
|
"defu": "^6.1.7",
|
|
68
68
|
"exsolve": "^1.0.8",
|
|
69
69
|
"git-url-parse": "^16.1.0",
|
|
70
70
|
"motion-v": "^2.2.0",
|
|
71
71
|
"nuxt-component-meta": "^0.17.2",
|
|
72
72
|
"nuxt-llms": "^0.2.0",
|
|
73
|
-
"nuxt-og-image": "^6.3.
|
|
73
|
+
"nuxt-og-image": "^6.3.3",
|
|
74
74
|
"nuxt-site-config": "^4.0.7",
|
|
75
75
|
"ohash": "^2.0.11",
|
|
76
76
|
"pathe": "^2.0.3",
|
|
@@ -13,12 +13,10 @@ const WEIGHT_MAP: Record<string, string> = {
|
|
|
13
13
|
950: 'Black'
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export function createAlibabaPuHuiTiProvider(cdnBase: string) {
|
|
16
|
+
export function createAlibabaPuHuiTiProvider(cdnBase: string, name: string) {
|
|
19
17
|
return defineFontProvider('alibaba-puhuiti', () => ({
|
|
20
18
|
async resolveFont(fontFamily, options) {
|
|
21
|
-
if (fontFamily !==
|
|
19
|
+
if (fontFamily !== name) return undefined
|
|
22
20
|
|
|
23
21
|
const weights = options.weights?.length
|
|
24
22
|
? options.weights.filter(w => WEIGHT_MAP[w])
|
|
@@ -2,18 +2,31 @@ import { z } from 'zod'
|
|
|
2
2
|
|
|
3
3
|
export default defineMcpTool({
|
|
4
4
|
description: '检索特定的示例实现代码和详细信息',
|
|
5
|
+
annotations: {
|
|
6
|
+
readOnlyHint: true,
|
|
7
|
+
destructiveHint: false,
|
|
8
|
+
idempotentHint: true,
|
|
9
|
+
openWorldHint: false
|
|
10
|
+
},
|
|
5
11
|
inputSchema: {
|
|
6
12
|
exampleName: z.string().describe('示例名称(PascalCase)')
|
|
7
13
|
},
|
|
14
|
+
inputExamples: [
|
|
15
|
+
{ exampleName: 'ButtonBasic' },
|
|
16
|
+
{ exampleName: 'ModalOverlay' }
|
|
17
|
+
],
|
|
8
18
|
cache: '30m',
|
|
9
19
|
async handler({ exampleName }) {
|
|
10
20
|
try {
|
|
11
21
|
const result = await $fetch<{ code: string }>(`/api/component-example/${exampleName}.json`)
|
|
12
|
-
return
|
|
13
|
-
|
|
22
|
+
return result.code
|
|
23
|
+
} catch (error: unknown) {
|
|
24
|
+
const err = error as { statusCode?: number, response?: { status?: number } }
|
|
25
|
+
const status = err?.statusCode ?? err?.response?.status
|
|
26
|
+
if (status === 404) {
|
|
27
|
+
throw createError({ statusCode: 404, message: `示例 '${exampleName}' 未找到。使用 list_examples 工具查看所有可用示例。` })
|
|
14
28
|
}
|
|
15
|
-
|
|
16
|
-
throw createError({ statusCode: 404, message: `示例 '${exampleName}' 未找到。使用 list_examples 工具查看所有可用示例。` })
|
|
29
|
+
throw error
|
|
17
30
|
}
|
|
18
31
|
}
|
|
19
32
|
})
|
|
@@ -2,88 +2,33 @@ import { z } from 'zod'
|
|
|
2
2
|
|
|
3
3
|
export default defineMcpTool({
|
|
4
4
|
description: `检索特定文档页面的完整内容和详细信息,使用 \`sections\` 参数仅获取特定的 h2 部分以减少响应大小。`,
|
|
5
|
+
annotations: {
|
|
6
|
+
readOnlyHint: true,
|
|
7
|
+
destructiveHint: false,
|
|
8
|
+
idempotentHint: true,
|
|
9
|
+
openWorldHint: false
|
|
10
|
+
},
|
|
5
11
|
inputSchema: {
|
|
6
12
|
path: z.string().describe('从 list-pages 获取或用户提供的页面路径(例如 /docs/getting-started/installation)'),
|
|
7
13
|
sections: z.array(z.string()).optional().describe('要返回的特定 h2 部分标题(例如 ["Usage","API"])。如果省略,则返回完整文档。')
|
|
8
14
|
},
|
|
15
|
+
inputExamples: [
|
|
16
|
+
{ path: '/docs/components/button', sections: ['Usage', 'API'] },
|
|
17
|
+
{ path: '/docs/getting-started/installation' }
|
|
18
|
+
],
|
|
9
19
|
cache: '30m',
|
|
10
20
|
handler: async ({ path, sections }) => {
|
|
21
|
+
let content
|
|
11
22
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// If sections are specified, extract only those sections
|
|
17
|
-
if (sections && sections.length > 0) {
|
|
18
|
-
content = extractSections(fullContent, sections)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
content: [{ type: 'text', text: JSON.stringify(content, null, 2) }]
|
|
23
|
-
}
|
|
24
|
-
} catch (error) {
|
|
25
|
-
throw createError({ statusCode: 500, message: `获取页面失败: ${error}` })
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Extract specific sections from markdown content based on h2 headings
|
|
32
|
-
*/
|
|
33
|
-
function extractSections(markdown: string, sectionTitles: string[]): string {
|
|
34
|
-
const lines = markdown.split('\n')
|
|
35
|
-
const result: string[] = []
|
|
36
|
-
|
|
37
|
-
// Normalize section titles for matching
|
|
38
|
-
const normalizedTitles = sectionTitles.map(t => t.toLowerCase().trim())
|
|
39
|
-
|
|
40
|
-
// Always include title (h1) and description (first blockquote)
|
|
41
|
-
let inHeader = true
|
|
42
|
-
for (const line of lines) {
|
|
43
|
-
if (inHeader) {
|
|
44
|
-
result.push(line)
|
|
45
|
-
// Stop after the description blockquote
|
|
46
|
-
if (line.startsWith('>') && result.length > 1) {
|
|
47
|
-
result.push('')
|
|
48
|
-
inHeader = false
|
|
49
|
-
}
|
|
50
|
-
continue
|
|
23
|
+
content = await $fetch<string>(`/raw${path}.md`)
|
|
24
|
+
} catch {
|
|
25
|
+
throw createError({ statusCode: 404, message: `获取页面失败: ${path}` })
|
|
51
26
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// Find and extract requested sections
|
|
56
|
-
let currentSection: string | null = null
|
|
57
|
-
let sectionContent: string[] = []
|
|
58
|
-
|
|
59
|
-
for (let i = 0; i < lines.length; i++) {
|
|
60
|
-
const line = lines[i]
|
|
61
|
-
if (!line) continue
|
|
62
|
-
|
|
63
|
-
// Check for h2 heading
|
|
64
|
-
if (line.startsWith('## ')) {
|
|
65
|
-
// Save previous section if it was requested
|
|
66
|
-
if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
|
|
67
|
-
result.push(...sectionContent)
|
|
68
|
-
result.push('')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Start new section
|
|
72
|
-
currentSection = line.replace('## ', '').trim()
|
|
73
|
-
sectionContent = [line]
|
|
74
|
-
continue
|
|
27
|
+
// If sections are specified, extract only those sections
|
|
28
|
+
if (sections && sections.length > 0) {
|
|
29
|
+
content = extractSections(content, sections)
|
|
75
30
|
}
|
|
76
31
|
|
|
77
|
-
|
|
78
|
-
if (currentSection) {
|
|
79
|
-
sectionContent.push(line)
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Don't forget the last section
|
|
84
|
-
if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
|
|
85
|
-
result.push(...sectionContent)
|
|
32
|
+
return content
|
|
86
33
|
}
|
|
87
|
-
|
|
88
|
-
return result.join('\n').trim()
|
|
89
|
-
}
|
|
34
|
+
})
|
|
@@ -3,8 +3,14 @@ import { listComponentExamples } from '#component-example/nitro'
|
|
|
3
3
|
|
|
4
4
|
export default defineMcpTool({
|
|
5
5
|
description: '列出所有可用的示例和代码演示',
|
|
6
|
+
annotations: {
|
|
7
|
+
readOnlyHint: true,
|
|
8
|
+
destructiveHint: false,
|
|
9
|
+
idempotentHint: true,
|
|
10
|
+
openWorldHint: false
|
|
11
|
+
},
|
|
6
12
|
cache: '1h',
|
|
7
13
|
async handler() {
|
|
8
|
-
return
|
|
14
|
+
return listComponentExamples()
|
|
9
15
|
}
|
|
10
16
|
})
|
|
@@ -3,6 +3,12 @@ import { inferSiteURL } from '../../../utils/meta'
|
|
|
3
3
|
|
|
4
4
|
export default defineMcpTool({
|
|
5
5
|
description: '列出所有入门指南和安装说明',
|
|
6
|
+
annotations: {
|
|
7
|
+
readOnlyHint: true,
|
|
8
|
+
destructiveHint: false,
|
|
9
|
+
idempotentHint: true,
|
|
10
|
+
openWorldHint: false
|
|
11
|
+
},
|
|
6
12
|
cache: '30m',
|
|
7
13
|
async handler() {
|
|
8
14
|
const event = useEvent()
|
|
@@ -14,14 +20,12 @@ export default defineMcpTool({
|
|
|
14
20
|
.select('id', 'title', 'description', 'path', 'navigation')
|
|
15
21
|
.all()
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
return pages.map(page => ({
|
|
18
24
|
title: page.title,
|
|
19
25
|
description: page.description,
|
|
20
26
|
path: page.path,
|
|
21
27
|
url: `${siteUrl}${page.path}`,
|
|
22
28
|
navigation: page.navigation
|
|
23
29
|
})).sort((a, b) => a.path.localeCompare(b.path))
|
|
24
|
-
|
|
25
|
-
return result
|
|
26
30
|
}
|
|
27
31
|
})
|
|
@@ -2,6 +2,12 @@ import { queryCollection } from '@nuxt/content/server'
|
|
|
2
2
|
|
|
3
3
|
export default defineMcpTool({
|
|
4
4
|
description: `列出所有可用的文档页面及其分类和基本信息。`,
|
|
5
|
+
annotations: {
|
|
6
|
+
readOnlyHint: true,
|
|
7
|
+
destructiveHint: false,
|
|
8
|
+
idempotentHint: true,
|
|
9
|
+
openWorldHint: false
|
|
10
|
+
},
|
|
5
11
|
cache: '1h',
|
|
6
12
|
async handler() {
|
|
7
13
|
const event = useEvent()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract specific h2 sections from markdown content.
|
|
3
|
+
* Always includes the title (h1) and description blockquote.
|
|
4
|
+
*/
|
|
5
|
+
export function extractSections(markdown: string, sectionTitles: string[]): string {
|
|
6
|
+
const lines = markdown.split('\n')
|
|
7
|
+
const result: string[] = []
|
|
8
|
+
|
|
9
|
+
// Normalize section titles for matching
|
|
10
|
+
const normalizedTitles = sectionTitles.map(t => t.toLowerCase().trim())
|
|
11
|
+
|
|
12
|
+
// Always include title (h1) and description (first blockquote)
|
|
13
|
+
let inHeader = true
|
|
14
|
+
for (const line of lines) {
|
|
15
|
+
if (inHeader) {
|
|
16
|
+
if (line.startsWith('## ')) {
|
|
17
|
+
inHeader = false
|
|
18
|
+
break
|
|
19
|
+
}
|
|
20
|
+
result.push(line)
|
|
21
|
+
// Stop after the description blockquote
|
|
22
|
+
if (line.startsWith('>') && result.length > 1) {
|
|
23
|
+
result.push('')
|
|
24
|
+
inHeader = false
|
|
25
|
+
}
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
28
|
+
break
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Find and extract requested sections
|
|
32
|
+
let currentSection: string | null = null
|
|
33
|
+
let sectionContent: string[] = []
|
|
34
|
+
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
// Check for h2 heading
|
|
37
|
+
if (line.startsWith('## ')) {
|
|
38
|
+
// Save previous section if it was requested
|
|
39
|
+
if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
|
|
40
|
+
result.push(...sectionContent)
|
|
41
|
+
result.push('')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Start new section
|
|
45
|
+
currentSection = line.replace('## ', '').trim()
|
|
46
|
+
sectionContent = [line]
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add line to current section
|
|
51
|
+
if (currentSection) {
|
|
52
|
+
sectionContent.push(line)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Don't forget the last section
|
|
57
|
+
if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
|
|
58
|
+
result.push(...sectionContent)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result.join('\n').trim()
|
|
62
|
+
}
|
package/utils/component-meta.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function createComponentMetaExcludeFilters(
|
|
|
52
52
|
resolve('../app/components/content/ComponentProps.vue'),
|
|
53
53
|
resolve('../app/components/content/ComponentSlots.vue'),
|
|
54
54
|
resolve('../app/components/content/PageLastCommit.vue'),
|
|
55
|
-
resolve('./
|
|
55
|
+
resolve('./components/prose/Mermaid.vue')
|
|
56
56
|
]
|
|
57
57
|
|
|
58
58
|
const userComponentPaths = [
|