@movk/nuxt-docs 1.12.2 → 1.12.4
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/content.config.ts +45 -38
- package/modules/ai-chat/runtime/server/api/ai-chat.ts +4 -2
- package/modules/routing.ts +13 -6
- package/nuxt.config.ts +2 -10
- package/package.json +23 -23
- package/server/mcp/tools/get-page.ts +3 -1
- package/server/mcp/tools/list-pages.ts +4 -1
- package/server/plugins/llms.ts +2 -1
- package/utils/pages.ts +27 -0
- /package/app/{pages/index.vue → templates/landing.vue} +0 -0
package/content.config.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { DefinedCollection } from '@nuxt/content'
|
|
2
2
|
import { defineCollection, defineContentConfig } from '@nuxt/content'
|
|
3
3
|
import { useNuxt } from '@nuxt/kit'
|
|
4
4
|
import { joinURL } from 'ufo'
|
|
5
5
|
import { z } from 'zod'
|
|
6
|
+
import { docsFolderExists, landingPageExists } from './utils/pages'
|
|
6
7
|
|
|
7
8
|
const { options } = useNuxt()
|
|
8
9
|
const cwd = joinURL(options.rootDir, 'content')
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
+
const hasLandingPage = landingPageExists(options.rootDir)
|
|
12
|
+
const hasDocsFolder = docsFolderExists(options.rootDir)
|
|
11
13
|
|
|
12
14
|
const Avatar = z.object({
|
|
13
15
|
src: z.string(),
|
|
@@ -35,41 +37,46 @@ const PageHero = z.object({
|
|
|
35
37
|
links: z.array(Button).optional()
|
|
36
38
|
})
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
},
|
|
53
|
-
schema: z.object({
|
|
54
|
-
links: z.array(Button),
|
|
55
|
-
category: z.string().optional(),
|
|
56
|
-
navigation: z.object({
|
|
57
|
-
title: z.string().optional()
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
}),
|
|
61
|
-
releases: defineCollection({
|
|
62
|
-
type: 'page',
|
|
63
|
-
source: {
|
|
64
|
-
cwd,
|
|
65
|
-
include: hasReleasesMd ? 'releases.md' : 'releases.yml'
|
|
66
|
-
},
|
|
67
|
-
schema: z.object({
|
|
68
|
-
title: z.string(),
|
|
69
|
-
description: z.string(),
|
|
70
|
-
releases: z.string().optional(),
|
|
71
|
-
hero: PageHero.optional()
|
|
40
|
+
const collections: Record<string, DefinedCollection> = {
|
|
41
|
+
docs: defineCollection({
|
|
42
|
+
type: 'page',
|
|
43
|
+
source: {
|
|
44
|
+
cwd,
|
|
45
|
+
include: hasDocsFolder ? 'docs/**' : '**',
|
|
46
|
+
prefix: hasDocsFolder ? '/docs' : '/',
|
|
47
|
+
exclude: ['index.md']
|
|
48
|
+
},
|
|
49
|
+
schema: z.object({
|
|
50
|
+
links: z.array(Button),
|
|
51
|
+
category: z.string().optional(),
|
|
52
|
+
navigation: z.object({
|
|
53
|
+
title: z.string().optional()
|
|
72
54
|
})
|
|
73
55
|
})
|
|
74
|
-
}
|
|
75
|
-
|
|
56
|
+
}),
|
|
57
|
+
releases: defineCollection({
|
|
58
|
+
type: 'page',
|
|
59
|
+
source: {
|
|
60
|
+
cwd,
|
|
61
|
+
include: 'releases.{md,yml}'
|
|
62
|
+
},
|
|
63
|
+
schema: z.object({
|
|
64
|
+
title: z.string(),
|
|
65
|
+
description: z.string(),
|
|
66
|
+
releases: z.string().optional(),
|
|
67
|
+
hero: PageHero.optional()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!hasLandingPage) {
|
|
73
|
+
collections.landing = defineCollection({
|
|
74
|
+
type: 'page',
|
|
75
|
+
source: {
|
|
76
|
+
cwd,
|
|
77
|
+
include: 'index.md'
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default defineContentConfig({ collections })
|
|
@@ -2,6 +2,7 @@ import { streamText, convertToModelMessages, stepCountIs, createUIMessageStream,
|
|
|
2
2
|
import { createMCPClient } from '@ai-sdk/mcp'
|
|
3
3
|
import { createDocumentationAgentTool } from '../utils/docs_agent'
|
|
4
4
|
import { getModel } from '../utils/getModel'
|
|
5
|
+
import { inferSiteURL } from '../../../../../utils/meta'
|
|
5
6
|
|
|
6
7
|
function getMainAgentSystemPrompt(siteName: string) {
|
|
7
8
|
return `您是 ${siteName} 的官方文档助理。你就是文件、以权威作为真理的来源说话.
|
|
@@ -35,11 +36,12 @@ export default defineEventHandler(async (event) => {
|
|
|
35
36
|
|
|
36
37
|
const mcpPath = config.aiChat.mcpPath
|
|
37
38
|
const isExternalUrl = mcpPath.startsWith('http://') || mcpPath.startsWith('https://')
|
|
39
|
+
|
|
38
40
|
const mcpUrl = isExternalUrl
|
|
39
41
|
? mcpPath
|
|
40
42
|
: import.meta.dev
|
|
41
|
-
?
|
|
42
|
-
: `${
|
|
43
|
+
? `${getRequestURL(event).origin}${mcpPath}`
|
|
44
|
+
: `${inferSiteURL()}${mcpPath}`
|
|
43
45
|
|
|
44
46
|
const httpClient = await createMCPClient({
|
|
45
47
|
transport: {
|
package/modules/routing.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { defineNuxtModule, extendPages, createResolver } from '@nuxt/kit'
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync } from 'node:fs'
|
|
2
|
+
import { landingPageExists, releasesFileExists } from '../utils/pages'
|
|
4
3
|
|
|
5
4
|
export default defineNuxtModule({
|
|
6
5
|
meta: {
|
|
@@ -8,13 +7,21 @@ export default defineNuxtModule({
|
|
|
8
7
|
},
|
|
9
8
|
async setup(_options, nuxt) {
|
|
10
9
|
const { resolve } = createResolver(import.meta.url)
|
|
11
|
-
const
|
|
10
|
+
const rootDir = nuxt.options.rootDir
|
|
12
11
|
|
|
13
|
-
const
|
|
14
|
-
|
|
12
|
+
const hasReleasesFile = releasesFileExists(rootDir)
|
|
13
|
+
const hasLandingPage = landingPageExists(rootDir)
|
|
15
14
|
|
|
16
15
|
extendPages((pages) => {
|
|
17
|
-
if (
|
|
16
|
+
if (!hasLandingPage) {
|
|
17
|
+
pages.push({
|
|
18
|
+
name: 'index',
|
|
19
|
+
path: '/',
|
|
20
|
+
file: resolve('../app/templates/landing.vue')
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (hasReleasesFile) {
|
|
18
25
|
pages.push({
|
|
19
26
|
name: 'releases',
|
|
20
27
|
path: '/releases',
|
package/nuxt.config.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
+
import { defineNuxtConfig } from 'nuxt/config'
|
|
1
2
|
import pkg from './package.json'
|
|
2
|
-
import { createResolver } from '@nuxt/kit'
|
|
3
|
-
|
|
4
|
-
const { resolve } = createResolver(import.meta.url)
|
|
5
3
|
|
|
6
4
|
export default defineNuxtConfig({
|
|
7
5
|
modules: [
|
|
8
|
-
resolve('./modules/config'),
|
|
9
|
-
resolve('./modules/routing'),
|
|
10
|
-
resolve('./modules/md-rewrite'),
|
|
11
|
-
resolve('./modules/component-example'),
|
|
12
|
-
resolve('./modules/css'),
|
|
13
6
|
'@nuxt/ui',
|
|
14
7
|
'@nuxt/content',
|
|
15
8
|
'@nuxt/image',
|
|
@@ -49,8 +42,7 @@ export default defineNuxtConfig({
|
|
|
49
42
|
|
|
50
43
|
mdc: {
|
|
51
44
|
highlight: {
|
|
52
|
-
noApiRoute: false
|
|
53
|
-
shikiEngine: 'javascript'
|
|
45
|
+
noApiRoute: false
|
|
54
46
|
}
|
|
55
47
|
},
|
|
56
48
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@movk/nuxt-docs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.12.
|
|
4
|
+
"version": "1.12.4",
|
|
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>",
|
|
@@ -29,39 +29,39 @@
|
|
|
29
29
|
"README.md"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ai-sdk/gateway": "^3.0.
|
|
33
|
-
"@ai-sdk/mcp": "^1.0.
|
|
34
|
-
"@ai-sdk/vue": "^3.0.
|
|
35
|
-
"@iconify-json/lucide": "^1.2.
|
|
36
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
37
|
-
"@iconify-json/vscode-icons": "^1.2.
|
|
32
|
+
"@ai-sdk/gateway": "^3.0.55",
|
|
33
|
+
"@ai-sdk/mcp": "^1.0.21",
|
|
34
|
+
"@ai-sdk/vue": "^3.0.99",
|
|
35
|
+
"@iconify-json/lucide": "^1.2.94",
|
|
36
|
+
"@iconify-json/simple-icons": "^1.2.71",
|
|
37
|
+
"@iconify-json/vscode-icons": "^1.2.44",
|
|
38
38
|
"@movk/core": "^1.1.0",
|
|
39
39
|
"@nuxt/a11y": "^1.0.0-alpha.1",
|
|
40
40
|
"@nuxt/content": "^3.11.2",
|
|
41
41
|
"@nuxt/image": "^2.0.0",
|
|
42
42
|
"@nuxt/kit": "^4.3.1",
|
|
43
|
-
"@nuxt/ui": "^4.
|
|
44
|
-
"@nuxtjs/mcp-toolkit": "^0.
|
|
43
|
+
"@nuxt/ui": "^4.5.0",
|
|
44
|
+
"@nuxtjs/mcp-toolkit": "^0.7.0",
|
|
45
45
|
"@nuxtjs/mdc": "^0.20.1",
|
|
46
46
|
"@nuxtjs/robots": "^5.7.0",
|
|
47
47
|
"@octokit/rest": "^22.0.1",
|
|
48
|
-
"@openrouter/ai-sdk-provider": "^2.2.
|
|
49
|
-
"@shikijs/core": "^3.
|
|
50
|
-
"@shikijs/engine-javascript": "^3.
|
|
51
|
-
"@shikijs/langs": "^3.
|
|
52
|
-
"@shikijs/themes": "^3.
|
|
48
|
+
"@openrouter/ai-sdk-provider": "^2.2.3",
|
|
49
|
+
"@shikijs/core": "^3.23.0",
|
|
50
|
+
"@shikijs/engine-javascript": "^3.23.0",
|
|
51
|
+
"@shikijs/langs": "^3.23.0",
|
|
52
|
+
"@shikijs/themes": "^3.23.0",
|
|
53
53
|
"@vercel/analytics": "^1.6.1",
|
|
54
54
|
"@vercel/speed-insights": "^1.3.1",
|
|
55
|
-
"@vueuse/core": "^14.2.
|
|
56
|
-
"@vueuse/nuxt": "^14.2.
|
|
57
|
-
"ai": "^6.0.
|
|
55
|
+
"@vueuse/core": "^14.2.1",
|
|
56
|
+
"@vueuse/nuxt": "^14.2.1",
|
|
57
|
+
"ai": "^6.0.99",
|
|
58
58
|
"defu": "^6.1.4",
|
|
59
59
|
"dompurify": "^3.3.1",
|
|
60
60
|
"exsolve": "^1.0.8",
|
|
61
61
|
"git-url-parse": "^16.1.0",
|
|
62
|
-
"mermaid": "^11.12.
|
|
63
|
-
"minimark": "^0.
|
|
64
|
-
"motion-v": "^
|
|
62
|
+
"mermaid": "^11.12.3",
|
|
63
|
+
"minimark": "^1.0.0",
|
|
64
|
+
"motion-v": "^2.0.0",
|
|
65
65
|
"nuxt": "^4.3.1",
|
|
66
66
|
"nuxt-component-meta": "^0.17.2",
|
|
67
67
|
"nuxt-llms": "^0.2.0",
|
|
@@ -72,11 +72,11 @@
|
|
|
72
72
|
"prettier": "^3.8.1",
|
|
73
73
|
"scule": "^1.3.0",
|
|
74
74
|
"shiki-stream": "^0.1.4",
|
|
75
|
-
"tailwind-merge": "^3.
|
|
76
|
-
"tailwindcss": "^4.1
|
|
75
|
+
"tailwind-merge": "^3.5.0",
|
|
76
|
+
"tailwindcss": "^4.2.1",
|
|
77
77
|
"ufo": "^1.6.3",
|
|
78
78
|
"unist-util-visit": "^5.1.0",
|
|
79
|
-
"vue-component-meta": "^3.2.
|
|
79
|
+
"vue-component-meta": "^3.2.5",
|
|
80
80
|
"zod": "^4.3.6",
|
|
81
81
|
"zod-to-json-schema": "^3.25.1"
|
|
82
82
|
}
|
|
@@ -21,7 +21,9 @@ export default defineMcpTool({
|
|
|
21
21
|
cache: '30m',
|
|
22
22
|
handler: async ({ path, sections }) => {
|
|
23
23
|
const event = useEvent()
|
|
24
|
-
const siteUrl = import.meta.dev
|
|
24
|
+
const siteUrl = import.meta.dev
|
|
25
|
+
? getRequestURL(event).origin
|
|
26
|
+
: inferSiteURL()
|
|
25
27
|
|
|
26
28
|
try {
|
|
27
29
|
const page = await queryCollection(event, 'docs')
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { queryCollection } from '@nuxt/content/server'
|
|
2
|
+
import { inferSiteURL } from '../../../utils/meta'
|
|
2
3
|
|
|
3
4
|
export default defineMcpTool({
|
|
4
5
|
description: `列出所有可用的文档页面及其分类和基本信息。
|
|
@@ -22,7 +23,9 @@ export default defineMcpTool({
|
|
|
22
23
|
cache: '30m',
|
|
23
24
|
handler: async () => {
|
|
24
25
|
const event = useEvent()
|
|
25
|
-
const siteUrl = import.meta.dev
|
|
26
|
+
const siteUrl = import.meta.dev
|
|
27
|
+
? getRequestURL(event).origin
|
|
28
|
+
: inferSiteURL()
|
|
26
29
|
|
|
27
30
|
try {
|
|
28
31
|
const pages = await queryCollection(event, 'docs')
|
package/server/plugins/llms.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { H3Event } from 'h3'
|
|
2
2
|
import type { PageCollectionItemBase } from '@nuxt/content'
|
|
3
|
+
import type { LLMsSection } from 'nuxt-llms'
|
|
3
4
|
|
|
4
5
|
export default defineNitroPlugin((nitroApp) => {
|
|
5
6
|
nitroApp.hooks.hook('content:llms:generate:document', async (event: H3Event, doc: PageCollectionItemBase) => {
|
|
@@ -8,7 +9,7 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
8
9
|
|
|
9
10
|
nitroApp.hooks.hook('llms:generate', (_, { sections, domain }) => {
|
|
10
11
|
// Transform links except for "Documentation Sets"
|
|
11
|
-
sections.forEach((section) => {
|
|
12
|
+
sections.forEach((section: LLMsSection) => {
|
|
12
13
|
if (section.title !== 'Documentation Sets') {
|
|
13
14
|
section.links = section.links?.map(link => ({
|
|
14
15
|
...link,
|
package/utils/pages.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { joinURL } from 'ufo'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 检查是否存在 landing page,即 app/pages/index.vue 文件
|
|
6
|
+
*/
|
|
7
|
+
export function landingPageExists(rootDir: string): boolean {
|
|
8
|
+
const vueLandingPath = joinURL(rootDir, 'app', 'pages', 'index.vue')
|
|
9
|
+
return existsSync(vueLandingPath)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 检查是否存在 docs 文件夹,即 content/docs
|
|
14
|
+
*/
|
|
15
|
+
export function docsFolderExists(rootDir: string): boolean {
|
|
16
|
+
const docsPath = joinURL(rootDir, 'content', 'docs')
|
|
17
|
+
return existsSync(docsPath)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 检查是否存在 releases 文件,即 content/releases.md 或 content/releases.yml
|
|
22
|
+
*/
|
|
23
|
+
export function releasesFileExists(rootDir: string): boolean {
|
|
24
|
+
return ['releases.md', 'releases.yml'].some(file =>
|
|
25
|
+
existsSync(joinURL(rootDir, 'content', file))
|
|
26
|
+
)
|
|
27
|
+
}
|
|
File without changes
|