@movk/nuxt-docs 1.8.0 → 1.9.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 +4 -0
- package/app/app.config.ts +1 -2
- package/app/components/content/Mermaid.vue +7 -2
- package/app/composables/useToolCall.ts +3 -1
- package/app/pages/docs/[...slug].vue +1 -1
- package/app/pages/index.vue +1 -1
- package/app/templates/releases.vue +1 -1
- package/content.config.ts +7 -8
- package/modules/ai-chat/index.ts +3 -3
- package/modules/ai-chat/runtime/server/utils/docs_agent.ts +1 -1
- package/modules/ai-chat/runtime/server/utils/getModel.ts +15 -9
- package/modules/ai-chat/runtime/server/utils/modelProviders.ts +34 -0
- package/modules/config.ts +17 -35
- package/modules/css.ts +3 -1
- package/nuxt.config.ts +3 -6
- package/package.json +12 -12
- package/server/api/component-example.get.ts +2 -2
- package/server/api/github/commits.get.ts +4 -4
- package/server/api/github/last-commit.get.ts +4 -4
- package/server/mcp/tools/get-example.ts +19 -0
- package/server/mcp/tools/get-page.ts +75 -6
- package/server/mcp/tools/list-examples.ts +14 -0
- package/server/mcp/tools/list-pages.ts +1 -1
- package/server/plugins/llms.ts +10 -15
- package/server/routes/raw/[...slug].md.get.ts +28 -8
- package/server/utils/transformMDC.ts +559 -0
- package/utils/component-meta.ts +97 -0
- /package/app/components/content/prose/{ProsePre.vue → ProsePre.global.vue} +0 -0
package/README.md
CHANGED
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
|
|
21
21
|
### 🤖 AI 增强体验
|
|
22
22
|
|
|
23
|
+
<div style="padding: 40px 0; display: flex; justify-content: center;">
|
|
24
|
+
<img src="https://docs.mhaibaraai.cn/ai/AiChat.png" alt="AiChat" width="400">
|
|
25
|
+
</div>
|
|
26
|
+
|
|
23
27
|
- **AI 聊天助手** - 内置智能文档助手,基于 Vercel AI SDK 支持多种 LLM 模型(Mistral、Qwen、OpenRouter)
|
|
24
28
|
- **MCP Server 支持** - 集成 Model Context Protocol 服务器,为 AI 助手提供结构化的文档访问能力
|
|
25
29
|
- **LLM 优化** - 通过 `nuxt-llms` 模块自动生成 `llms.txt` 和 `llms-full.txt`,为 AI 工具提供优化的文档索引
|
package/app/app.config.ts
CHANGED
|
@@ -81,10 +81,11 @@ const diagramRef = ref<HTMLElement | null>(null)
|
|
|
81
81
|
const isRendered = ref(false)
|
|
82
82
|
const hasError = ref(false)
|
|
83
83
|
const errorMessage = ref('')
|
|
84
|
+
const hasBeenVisible = ref(false)
|
|
84
85
|
|
|
85
86
|
const [isFullscreen, toggleFullscreen] = useToggle(false)
|
|
86
87
|
const { copy, copied } = useClipboard({ source: () => props.code })
|
|
87
|
-
const isVisible = useElementVisibility(containerRef)
|
|
88
|
+
const isVisible = useElementVisibility(containerRef, { threshold: 0.1 })
|
|
88
89
|
|
|
89
90
|
async function renderMermaid() {
|
|
90
91
|
if (!props.code || isRendered.value || !diagramRef.value) return
|
|
@@ -127,7 +128,11 @@ async function reRender() {
|
|
|
127
128
|
watch(
|
|
128
129
|
[isVisible, diagramRef],
|
|
129
130
|
([visible, el]) => {
|
|
130
|
-
|
|
131
|
+
// 记录曾经可见状态,避免快速滚动时错过渲染
|
|
132
|
+
if (visible) {
|
|
133
|
+
hasBeenVisible.value = true
|
|
134
|
+
}
|
|
135
|
+
if (hasBeenVisible.value && el && !isRendered.value) {
|
|
131
136
|
renderMermaid()
|
|
132
137
|
}
|
|
133
138
|
},
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export function useToolCall() {
|
|
2
2
|
const tools: Record<string, string | ((args: any) => string)> = {
|
|
3
3
|
'list-pages': '列出所有文档页面',
|
|
4
|
-
'get-page': (args: any) => `检索 ${args?.path || '页面'}
|
|
4
|
+
'get-page': (args: any) => `检索 ${args?.path || '页面'}`,
|
|
5
|
+
'list-examples': '列出所有示例',
|
|
6
|
+
'get-example': (args: any) => `获取示例:${args?.exampleName || '示例'}`
|
|
5
7
|
}
|
|
6
8
|
return {
|
|
7
9
|
tools
|
|
@@ -14,7 +14,7 @@ const { toc, github } = useAppConfig()
|
|
|
14
14
|
const { data: page } = await useAsyncData(`docs-${kebabCase(route.path)}`, () => queryCollection('docs').path(route.path).first())
|
|
15
15
|
|
|
16
16
|
if (!page.value) {
|
|
17
|
-
throw createError({
|
|
17
|
+
throw createError({ status: 404, statusText: 'Page not found', fatal: true })
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
package/app/pages/index.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const { data: page } = await useAsyncData('landing', () => queryCollection('landing').path('/').first())
|
|
3
3
|
if (!page.value) {
|
|
4
|
-
throw createError({
|
|
4
|
+
throw createError({ status: 404, statusText: 'Page not found', fatal: true })
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
const title = page.value.seo?.title || page.value.title
|
|
@@ -3,7 +3,7 @@ import type { ButtonProps } from '@nuxt/ui'
|
|
|
3
3
|
|
|
4
4
|
const { data: page } = await useAsyncData('releases', () => queryCollection('releases').first())
|
|
5
5
|
if (!page.value) {
|
|
6
|
-
throw createError({
|
|
6
|
+
throw createError({ status: 404, statusText: 'Page not found', fatal: true })
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const title = page.value.seo?.title || page.value.title
|
package/content.config.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { defineCollection, defineContentConfig } from '@nuxt/content'
|
|
3
3
|
import { useNuxt } from '@nuxt/kit'
|
|
4
|
-
import { asSeoCollection } from '@nuxtjs/seo/content'
|
|
5
4
|
import { joinURL } from 'ufo'
|
|
6
|
-
import { z } from 'zod
|
|
5
|
+
import { z } from 'zod'
|
|
7
6
|
|
|
8
7
|
const { options } = useNuxt()
|
|
9
8
|
const cwd = joinURL(options.rootDir, 'content')
|
|
@@ -38,14 +37,14 @@ const PageHero = z.object({
|
|
|
38
37
|
|
|
39
38
|
export default defineContentConfig({
|
|
40
39
|
collections: {
|
|
41
|
-
landing: defineCollection(
|
|
40
|
+
landing: defineCollection({
|
|
42
41
|
type: 'page',
|
|
43
42
|
source: {
|
|
44
43
|
cwd,
|
|
45
44
|
include: 'index.md'
|
|
46
45
|
}
|
|
47
|
-
})
|
|
48
|
-
docs: defineCollection(
|
|
46
|
+
}),
|
|
47
|
+
docs: defineCollection({
|
|
49
48
|
type: 'page',
|
|
50
49
|
source: {
|
|
51
50
|
cwd,
|
|
@@ -58,8 +57,8 @@ export default defineContentConfig({
|
|
|
58
57
|
title: z.string().optional()
|
|
59
58
|
})
|
|
60
59
|
})
|
|
61
|
-
})
|
|
62
|
-
releases: defineCollection(
|
|
60
|
+
}),
|
|
61
|
+
releases: defineCollection({
|
|
63
62
|
type: 'page',
|
|
64
63
|
source: {
|
|
65
64
|
cwd,
|
|
@@ -71,6 +70,6 @@ export default defineContentConfig({
|
|
|
71
70
|
releases: z.string(),
|
|
72
71
|
hero: PageHero
|
|
73
72
|
})
|
|
74
|
-
})
|
|
73
|
+
})
|
|
75
74
|
}
|
|
76
75
|
})
|
package/modules/ai-chat/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface AiChatModuleOptions {
|
|
|
22
22
|
*/
|
|
23
23
|
mcpPath?: string
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* 使用的 AI 模型
|
|
26
26
|
*/
|
|
27
27
|
model?: string
|
|
28
28
|
/**
|
|
@@ -46,7 +46,7 @@ export default defineNuxtModule<AiChatModuleOptions>({
|
|
|
46
46
|
models: []
|
|
47
47
|
},
|
|
48
48
|
setup(options, nuxt) {
|
|
49
|
-
const hasApiKey = !!(process.env.AI_GATEWAY_API_KEY || process.env.OPENROUTER_API_KEY)
|
|
49
|
+
const hasApiKey = !!(process.env.AI_GATEWAY_API_KEY || process.env.OPENROUTER_API_KEY || process.env.ZHIPU_API_KEY)
|
|
50
50
|
|
|
51
51
|
const { resolve } = createResolver(import.meta.url)
|
|
52
52
|
|
|
@@ -77,7 +77,7 @@ export default defineNuxtModule<AiChatModuleOptions>({
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (!hasApiKey) {
|
|
80
|
-
log.warn('[ai-chat] Module disabled: no
|
|
80
|
+
log.warn('[ai-chat] Module disabled: no API key found in environment variables.')
|
|
81
81
|
return
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import { createGateway } from '@ai-sdk/gateway'
|
|
2
2
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
|
|
3
|
+
import { modelProviderRegistry } from './modelProviders'
|
|
4
|
+
|
|
5
|
+
modelProviderRegistry.register('openrouter', ({ config, modelId }) => {
|
|
6
|
+
const openRouter = createOpenRouter({
|
|
7
|
+
apiKey: config.openRouterApiKey as string | undefined
|
|
8
|
+
})
|
|
9
|
+
return openRouter.chat(modelId)
|
|
10
|
+
})
|
|
3
11
|
|
|
4
12
|
/**
|
|
5
13
|
* 获取 AI 模型实例
|
|
6
|
-
*
|
|
7
|
-
* @returns AI SDK 模型实例
|
|
14
|
+
* 优先使用注册的提供商,否则回退到 AI Gateway
|
|
8
15
|
*/
|
|
9
16
|
export function getModel(modelId: string) {
|
|
10
17
|
const config = useRuntimeConfig()
|
|
18
|
+
const parsed = modelProviderRegistry.parseModelId(modelId)
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
return openRouter.chat(modelId.replace('openrouter/', ''))
|
|
20
|
+
if (parsed) {
|
|
21
|
+
const factory = modelProviderRegistry.get(parsed.prefix)
|
|
22
|
+
if (factory) {
|
|
23
|
+
return factory({ config, modelId: parsed.modelId })
|
|
24
|
+
}
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
// AI Gateway 模型(默认)
|
|
21
27
|
const gateway = createGateway({
|
|
22
28
|
apiKey: config.aiGatewayApiKey as string | undefined
|
|
23
29
|
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LanguageModel } from 'ai'
|
|
2
|
+
|
|
3
|
+
export interface ModelProviderContext {
|
|
4
|
+
config: ReturnType<typeof useRuntimeConfig>
|
|
5
|
+
modelId: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type ModelProviderFactory = (context: ModelProviderContext) => LanguageModel
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 模型提供商注册表
|
|
12
|
+
*/
|
|
13
|
+
class ModelProviderRegistry {
|
|
14
|
+
private providers = new Map<string, ModelProviderFactory>()
|
|
15
|
+
|
|
16
|
+
register(prefix: string, factory: ModelProviderFactory): void {
|
|
17
|
+
this.providers.set(prefix, factory)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get(prefix: string): ModelProviderFactory | undefined {
|
|
21
|
+
return this.providers.get(prefix)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
parseModelId(modelId: string): { prefix: string, modelId: string } | null {
|
|
25
|
+
const separatorIndex = modelId.indexOf('/')
|
|
26
|
+
if (separatorIndex === -1) return null
|
|
27
|
+
return {
|
|
28
|
+
prefix: modelId.slice(0, separatorIndex),
|
|
29
|
+
modelId: modelId.slice(separatorIndex + 1)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const modelProviderRegistry = new ModelProviderRegistry()
|
package/modules/config.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createResolver, defineNuxtModule } from '@nuxt/kit'
|
|
2
|
-
import { join } from 'pathe'
|
|
3
2
|
import { defu } from 'defu'
|
|
4
3
|
import { getGitBranch, getGitEnv, getLocalGitInfo } from '../utils/git'
|
|
5
4
|
import { getPackageJsonMetadata, inferSiteURL } from '../utils/meta'
|
|
5
|
+
import { createComponentMetaExcludeFilters } from '../utils/component-meta'
|
|
6
6
|
|
|
7
7
|
export default defineNuxtModule({
|
|
8
8
|
meta: {
|
|
@@ -10,32 +10,32 @@ export default defineNuxtModule({
|
|
|
10
10
|
},
|
|
11
11
|
async setup(_options, nuxt) {
|
|
12
12
|
const { resolve } = createResolver(import.meta.url)
|
|
13
|
+
|
|
14
|
+
nuxt.options.alias['#ai-chat'] = resolve('./ai-chat/runtime')
|
|
15
|
+
|
|
13
16
|
const dir = nuxt.options.rootDir
|
|
14
17
|
const url = inferSiteURL()
|
|
15
18
|
const meta = await getPackageJsonMetadata(dir)
|
|
16
19
|
const gitInfo = await getLocalGitInfo(dir) || getGitEnv()
|
|
17
|
-
const siteName =
|
|
20
|
+
const siteName = meta.name || gitInfo?.name || ''
|
|
21
|
+
|
|
22
|
+
nuxt.options.site = defu(nuxt.options.site, {
|
|
23
|
+
url,
|
|
24
|
+
name: siteName,
|
|
25
|
+
debug: false
|
|
26
|
+
})
|
|
18
27
|
|
|
19
28
|
nuxt.options.llms = defu(nuxt.options.llms, {
|
|
20
29
|
domain: url || 'https://example.com',
|
|
21
30
|
title: siteName,
|
|
22
31
|
description: meta.description || '',
|
|
32
|
+
contentRawMarkdown: false as const,
|
|
23
33
|
full: {
|
|
24
34
|
title: siteName,
|
|
25
35
|
description: meta.description || ''
|
|
26
36
|
}
|
|
27
37
|
})
|
|
28
38
|
|
|
29
|
-
nuxt.options.site = defu(nuxt.options.site, {
|
|
30
|
-
url,
|
|
31
|
-
name: siteName,
|
|
32
|
-
debug: false
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
nuxt.options.robots = defu(nuxt.options.robots, {
|
|
36
|
-
sitemap: url ? `${url.replace(/\/$/, '')}/sitemap.xml` : undefined
|
|
37
|
-
})
|
|
38
|
-
|
|
39
39
|
nuxt.options.appConfig.header = defu(nuxt.options.appConfig.header, {
|
|
40
40
|
title: siteName
|
|
41
41
|
})
|
|
@@ -59,34 +59,16 @@ export default defineNuxtModule({
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
const layerPath = resolve('..')
|
|
62
|
-
const allowedComponents = [
|
|
63
|
-
resolve('../app/components/content/CommitChangelog.vue'),
|
|
64
|
-
resolve('../app/components/content/ComponentEmits.vue'),
|
|
65
|
-
resolve('../app/components/content/ComponentExample.vue'),
|
|
66
|
-
resolve('../app/components/content/ComponentProps.vue'),
|
|
67
|
-
resolve('../app/components/content/ComponentSlots.vue'),
|
|
68
|
-
resolve('../app/components/content/PageLastCommit.vue'),
|
|
69
|
-
resolve('../app/components/content/Mermaid.vue'),
|
|
70
|
-
resolve('./ai-chat/runtime/components/AiChatToolCall.vue'),
|
|
71
|
-
resolve('./ai-chat/runtime/components/AiChatReasoning.vue'),
|
|
72
|
-
resolve('./ai-chat/runtime/components/AiChatSlideoverFaq.vue'),
|
|
73
|
-
resolve('./ai-chat/runtime/components/AiChatPreStream.vue')
|
|
74
|
-
]
|
|
75
|
-
const userComponentPaths = [
|
|
76
|
-
join(dir, 'app/components'),
|
|
77
|
-
join(dir, 'components'),
|
|
78
|
-
join(dir, 'docs/app/components'),
|
|
79
|
-
join(dir, 'templates/*/app/components')
|
|
80
|
-
]
|
|
81
62
|
|
|
82
63
|
// @ts-ignore - component-meta is not typed
|
|
83
64
|
nuxt.hook('component-meta:extend', (options: any) => {
|
|
65
|
+
const userInclude = (nuxt.options.componentMeta && typeof nuxt.options.componentMeta === 'object')
|
|
66
|
+
? nuxt.options.componentMeta.include || []
|
|
67
|
+
: []
|
|
68
|
+
|
|
84
69
|
options.exclude = [
|
|
85
70
|
...(options.exclude || []),
|
|
86
|
-
(
|
|
87
|
-
filePath.startsWith(layerPath) && !allowedComponents.includes(filePath),
|
|
88
|
-
({ filePath }: { filePath: string }) =>
|
|
89
|
-
userComponentPaths.some(path => filePath.startsWith(path))
|
|
71
|
+
...createComponentMetaExcludeFilters(resolve, dir, layerPath, userInclude)
|
|
90
72
|
]
|
|
91
73
|
})
|
|
92
74
|
}
|
package/modules/css.ts
CHANGED
package/nuxt.config.ts
CHANGED
|
@@ -12,10 +12,10 @@ export default defineNuxtConfig({
|
|
|
12
12
|
'@nuxt/image',
|
|
13
13
|
'@nuxt/a11y',
|
|
14
14
|
'@nuxtjs/mcp-toolkit',
|
|
15
|
-
'@nuxtjs/seo',
|
|
16
15
|
'@vueuse/nuxt',
|
|
17
16
|
'nuxt-component-meta',
|
|
18
17
|
'nuxt-llms',
|
|
18
|
+
'nuxt-og-image',
|
|
19
19
|
'motion-v/nuxt',
|
|
20
20
|
() => {
|
|
21
21
|
extendViteConfig((config) => {
|
|
@@ -94,8 +94,8 @@ export default defineNuxtConfig({
|
|
|
94
94
|
metaFields: {
|
|
95
95
|
type: false,
|
|
96
96
|
props: true,
|
|
97
|
-
slots: 'no-schema'
|
|
98
|
-
events: 'no-schema'
|
|
97
|
+
slots: 'no-schema',
|
|
98
|
+
events: 'no-schema',
|
|
99
99
|
exposed: false
|
|
100
100
|
},
|
|
101
101
|
exclude: [
|
|
@@ -134,8 +134,5 @@ export default defineNuxtConfig({
|
|
|
134
134
|
'Inter:400',
|
|
135
135
|
'Inter:700'
|
|
136
136
|
]
|
|
137
|
-
},
|
|
138
|
-
sitemap: {
|
|
139
|
-
zeroRuntime: true
|
|
140
137
|
}
|
|
141
138
|
})
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@movk/nuxt-docs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.9.0",
|
|
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>",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@ai-sdk/gateway": "^3.0.
|
|
32
|
-
"@ai-sdk/mcp": "^1.0.
|
|
33
|
-
"@ai-sdk/vue": "^3.0.
|
|
31
|
+
"@ai-sdk/gateway": "^3.0.22",
|
|
32
|
+
"@ai-sdk/mcp": "^1.0.13",
|
|
33
|
+
"@ai-sdk/vue": "^3.0.48",
|
|
34
34
|
"@iconify-json/lucide": "^1.2.86",
|
|
35
35
|
"@iconify-json/ph": "^1.2.2",
|
|
36
36
|
"@iconify-json/simple-icons": "^1.2.67",
|
|
@@ -40,36 +40,36 @@
|
|
|
40
40
|
"@nuxt/a11y": "^1.0.0-alpha.1",
|
|
41
41
|
"@nuxt/content": "^3.11.0",
|
|
42
42
|
"@nuxt/image": "^2.0.0",
|
|
43
|
-
"@nuxt/kit": "^4.
|
|
44
|
-
"@nuxt/ui": "^4.
|
|
43
|
+
"@nuxt/kit": "^4.3.0",
|
|
44
|
+
"@nuxt/ui": "^4.4.0",
|
|
45
45
|
"@nuxtjs/mcp-toolkit": "^0.6.2",
|
|
46
|
-
"@nuxtjs/seo": "^3.3.0",
|
|
47
46
|
"@octokit/rest": "^22.0.1",
|
|
48
47
|
"@openrouter/ai-sdk-provider": "^2.0.0",
|
|
49
48
|
"@vercel/analytics": "^1.6.1",
|
|
50
49
|
"@vercel/speed-insights": "^1.3.1",
|
|
51
50
|
"@vueuse/core": "^14.1.0",
|
|
52
51
|
"@vueuse/nuxt": "^14.1.0",
|
|
53
|
-
"ai": "^6.0.
|
|
52
|
+
"ai": "^6.0.48",
|
|
54
53
|
"defu": "^6.1.4",
|
|
55
|
-
"dompurify": "^3.
|
|
54
|
+
"dompurify": "^3.3.1",
|
|
56
55
|
"exsolve": "^1.0.8",
|
|
57
56
|
"git-url-parse": "^16.1.0",
|
|
58
57
|
"mermaid": "^11.12.2",
|
|
59
58
|
"motion-v": "^1.9.0",
|
|
60
|
-
"nuxt": "^4.
|
|
59
|
+
"nuxt": "^4.3.0",
|
|
61
60
|
"nuxt-component-meta": "^0.17.1",
|
|
62
61
|
"nuxt-llms": "^0.2.0",
|
|
62
|
+
"nuxt-og-image": "^5.1.13",
|
|
63
63
|
"ohash": "^2.0.11",
|
|
64
64
|
"pathe": "^2.0.3",
|
|
65
65
|
"pkg-types": "^2.3.0",
|
|
66
|
-
"prettier": "^3.8.
|
|
66
|
+
"prettier": "^3.8.1",
|
|
67
67
|
"scule": "^1.3.0",
|
|
68
68
|
"shiki": "^3.21.0",
|
|
69
69
|
"shiki-stream": "^0.1.4",
|
|
70
70
|
"shiki-transformer-color-highlight": "^1.0.0",
|
|
71
71
|
"tailwindcss": "^4.1.18",
|
|
72
72
|
"ufo": "^1.6.3",
|
|
73
|
-
"zod": "^4.3.
|
|
73
|
+
"zod": "^4.3.6"
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -10,8 +10,8 @@ export default defineEventHandler((event) => {
|
|
|
10
10
|
const component = components[pascalCase(componentName)]
|
|
11
11
|
if (!component) {
|
|
12
12
|
throw createError({
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
statusText: 'Example not found!',
|
|
14
|
+
status: 404
|
|
15
15
|
})
|
|
16
16
|
}
|
|
17
17
|
return component
|
|
@@ -10,8 +10,8 @@ export default defineCachedEventHandler(async (event) => {
|
|
|
10
10
|
|
|
11
11
|
if (!paths.length || !paths[0]) {
|
|
12
12
|
throw createError({
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
status: 400,
|
|
14
|
+
statusText: 'Path is required'
|
|
15
15
|
})
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -19,8 +19,8 @@ export default defineCachedEventHandler(async (event) => {
|
|
|
19
19
|
|
|
20
20
|
if (!github || typeof github === 'boolean') {
|
|
21
21
|
throw createError({
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
status: 500,
|
|
23
|
+
statusText: 'GitHub configuration is not available'
|
|
24
24
|
})
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -8,8 +8,8 @@ export default defineCachedEventHandler(async (event) => {
|
|
|
8
8
|
const { path } = getQuery(event) as { path: string }
|
|
9
9
|
if (!path) {
|
|
10
10
|
throw createError({
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
status: 400,
|
|
12
|
+
statusText: 'Path is required'
|
|
13
13
|
})
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -17,8 +17,8 @@ export default defineCachedEventHandler(async (event) => {
|
|
|
17
17
|
|
|
18
18
|
if (!github || typeof github === 'boolean') {
|
|
19
19
|
throw createError({
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
status: 500,
|
|
21
|
+
statusText: 'GitHub configuration is not available'
|
|
22
22
|
})
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export default defineMcpTool({
|
|
4
|
+
description: '检索特定的 UI 示例实现代码和详细信息',
|
|
5
|
+
inputSchema: {
|
|
6
|
+
exampleName: z.string().describe('示例名称(PascalCase)')
|
|
7
|
+
},
|
|
8
|
+
cache: '30m',
|
|
9
|
+
async handler({ exampleName }) {
|
|
10
|
+
try {
|
|
11
|
+
const result = await $fetch<{ code: string }>(`/api/component-example/${exampleName}.json`)
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: 'text' as const, text: result.code }]
|
|
14
|
+
}
|
|
15
|
+
} catch {
|
|
16
|
+
return errorResult(`示例 '${exampleName}' 未找到。使用 list_examples 工具查看所有可用示例。`)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
})
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { z } from 'zod
|
|
1
|
+
import { z } from 'zod'
|
|
2
2
|
import { queryCollection } from '@nuxt/content/server'
|
|
3
3
|
import { inferSiteURL } from '../../../utils/meta'
|
|
4
4
|
|
|
5
5
|
export default defineMcpTool({
|
|
6
|
-
description:
|
|
6
|
+
description: `检索特定文档页面的完整内容和详细信息,使用 \`sections\` 参数仅获取特定的 h2 部分以减少响应大小。'
|
|
7
7
|
|
|
8
8
|
何时使用:当你知道文档页面的确切路径时使用。常见用例:
|
|
9
9
|
- 用户请求特定页面:「显示入门指南」→ /docs/getting-started
|
|
@@ -15,10 +15,11 @@ export default defineMcpTool({
|
|
|
15
15
|
|
|
16
16
|
工作流程:此工具返回完整的页面内容,包括标题、描述和完整的 markdown。当你需要从特定文档页面提供详细答案或代码示例时使用。`,
|
|
17
17
|
inputSchema: {
|
|
18
|
-
path: z.string().describe('从 list-pages 获取或用户提供的页面路径(例如 /docs/getting-started/installation)')
|
|
18
|
+
path: z.string().describe('从 list-pages 获取或用户提供的页面路径(例如 /docs/getting-started/installation)'),
|
|
19
|
+
sections: z.array(z.string()).optional().describe('要返回的特定 h2 部分标题(例如 ["Usage","API"])。如果省略,则返回完整文档。')
|
|
19
20
|
},
|
|
20
|
-
cache: '
|
|
21
|
-
handler: async ({ path }) => {
|
|
21
|
+
cache: '30m',
|
|
22
|
+
handler: async ({ path, sections }) => {
|
|
22
23
|
const event = useEvent()
|
|
23
24
|
const siteUrl = import.meta.dev ? 'http://localhost:3000' : inferSiteURL()
|
|
24
25
|
|
|
@@ -35,10 +36,17 @@ export default defineMcpTool({
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
+
const fullContent = await $fetch<string>(`/raw${path}.md`, {
|
|
39
40
|
baseURL: siteUrl
|
|
40
41
|
})
|
|
41
42
|
|
|
43
|
+
let content = fullContent
|
|
44
|
+
|
|
45
|
+
// If sections are specified, extract only those sections
|
|
46
|
+
if (sections && sections.length > 0) {
|
|
47
|
+
content = extractSections(fullContent, sections)
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
const result = {
|
|
43
51
|
title: page.title,
|
|
44
52
|
path: page.path,
|
|
@@ -58,3 +66,64 @@ export default defineMcpTool({
|
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
68
|
})
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extract specific sections from markdown content based on h2 headings
|
|
72
|
+
*/
|
|
73
|
+
function extractSections(markdown: string, sectionTitles: string[]): string {
|
|
74
|
+
const lines = markdown.split('\n')
|
|
75
|
+
const result: string[] = []
|
|
76
|
+
|
|
77
|
+
// Normalize section titles for matching
|
|
78
|
+
const normalizedTitles = sectionTitles.map(t => t.toLowerCase().trim())
|
|
79
|
+
|
|
80
|
+
// Always include title (h1) and description (first blockquote)
|
|
81
|
+
let inHeader = true
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (inHeader) {
|
|
84
|
+
result.push(line)
|
|
85
|
+
// Stop after the description blockquote
|
|
86
|
+
if (line.startsWith('>') && result.length > 1) {
|
|
87
|
+
result.push('')
|
|
88
|
+
inHeader = false
|
|
89
|
+
}
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Find and extract requested sections
|
|
96
|
+
let currentSection: string | null = null
|
|
97
|
+
let sectionContent: string[] = []
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < lines.length; i++) {
|
|
100
|
+
const line = lines[i]
|
|
101
|
+
if (!line) continue
|
|
102
|
+
|
|
103
|
+
// Check for h2 heading
|
|
104
|
+
if (line.startsWith('## ')) {
|
|
105
|
+
// Save previous section if it was requested
|
|
106
|
+
if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
|
|
107
|
+
result.push(...sectionContent)
|
|
108
|
+
result.push('')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Start new section
|
|
112
|
+
currentSection = line.replace('## ', '').trim()
|
|
113
|
+
sectionContent = [line]
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Add line to current section
|
|
118
|
+
if (currentSection) {
|
|
119
|
+
sectionContent.push(line)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Don't forget the last section
|
|
124
|
+
if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
|
|
125
|
+
result.push(...sectionContent)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return result.join('\n').trim()
|
|
129
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// @ts-expect-error - no types available
|
|
2
|
+
import components from '#component-example/nitro'
|
|
3
|
+
|
|
4
|
+
export default defineMcpTool({
|
|
5
|
+
description: '列出所有可用的 UI 示例和代码演示',
|
|
6
|
+
cache: '1h',
|
|
7
|
+
handler() {
|
|
8
|
+
const examples = Object.entries<{ pascalName: string }>(components).map(([_key, value]) => {
|
|
9
|
+
return value.pascalName
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
return jsonResult(examples)
|
|
13
|
+
}
|
|
14
|
+
})
|
|
@@ -19,7 +19,7 @@ export default defineMcpTool({
|
|
|
19
19
|
- path:用于 get-page 的确切路径
|
|
20
20
|
- description:页面内容的简要摘要
|
|
21
21
|
- url:完整 URL 供参考`,
|
|
22
|
-
cache: '
|
|
22
|
+
cache: '30m',
|
|
23
23
|
handler: async () => {
|
|
24
24
|
const event = useEvent()
|
|
25
25
|
const siteUrl = import.meta.dev ? 'http://localhost:3000' : getRequestURL(event).origin
|