@movk/nuxt-docs 1.16.2 → 1.17.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 +18 -16
- package/app/app.config.ts +1 -1
- package/app/assets/css/main.css +1 -1
- package/app/components/DocsAsideRightBottom.vue +2 -2
- package/app/components/OgImage/OgImageDocs.takumi.vue +6 -10
- package/app/components/header/Header.vue +4 -4
- package/app/components/theme-picker/ThemePicker.vue +6 -6
- package/app/composables/useTheme.ts +6 -6
- package/app/pages/docs/[...slug].vue +8 -1
- package/app/plugins/theme.ts +1 -1
- package/modules/ai-chat/index.ts +1 -1
- package/modules/ai-chat/runtime/components/AiChat.vue +3 -3
- package/modules/ai-chat/runtime/components/AiChatFloatingInput.vue +2 -2
- package/modules/ai-chat/runtime/components/AiChatPanel.vue +32 -21
- package/modules/ai-chat/runtime/composables/useModels.ts +1 -1
- package/modules/ai-chat/runtime/server/api/ai-chat.ts +3 -2
- package/modules/module.ts +2 -11
- package/modules/skills/index.ts +144 -0
- package/modules/skills/runtime/server/routes/skills-files.ts +49 -0
- package/modules/skills/runtime/server/routes/skills-index.ts +8 -0
- package/nuxt.config.ts +9 -5
- package/nuxt.schema.ts +1 -1
- package/package.json +14 -13
- package/server/api/github/commits.json.get.ts +3 -3
- package/server/api/github/last-commit.json.get.ts +5 -5
- package/server/api/github/releases.json.get.ts +2 -2
- package/modules/runtime/public/noto-sans-sc-400-normal.woff2 +0 -0
- package/modules/runtime/public/noto-sans-sc-500-normal.woff2 +0 -0
- package/modules/runtime/public/noto-sans-sc-latin-400-normal.woff +0 -0
- package/modules/runtime/public/noto-sans-sc-latin-500-normal.woff2 +0 -0
- /package/modules/{runtime/components → components}/prose/Mermaid.vue +0 -0
- /package/modules/{runtime/components → components}/prose/Pre.vue +0 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://docs.mhaibaraai.cn/)
|
|
2
2
|
[](https://docs.mhaibaraai.cn/)
|
|
3
3
|
|
|
4
4
|
> 基于 Nuxt 4 的现代文档主题,集成组件自动化文档、AI 聊天助手、MCP Server 和完整的开发者体验优化
|
|
@@ -30,6 +30,23 @@
|
|
|
30
30
|
- **LLM 优化** - 通过 `nuxt-llms` 模块自动生成 `llms.txt` 和 `llms-full.txt`,为 AI 工具提供优化的文档索引
|
|
31
31
|
- **流式响应** - 支持 AI 响应流式输出和代码高亮,配合 `shiki-stream` 实现实时语法高亮渲染
|
|
32
32
|
|
|
33
|
+
### AI 助手 Skill
|
|
34
|
+
|
|
35
|
+
Agent Skills 是一种开放格式,允许 AI 代理(Claude Code、Cursor、Windsurf 等)自动发现并加载文档站的专属工作流。Movk Nuxt Docs 将 `skills/` 目录下的所有技能自动发布到 `/.well-known/skills/` 端点。
|
|
36
|
+
|
|
37
|
+
**内置技能:**
|
|
38
|
+
|
|
39
|
+
- `create-docs` - 为任意项目生成基于 Movk Nuxt Docs 的完整文档网站
|
|
40
|
+
- `review-docs` - 审查文档质量,检查清晰度、SEO 和技术正确性
|
|
41
|
+
|
|
42
|
+
**一键安装到 AI 工具:**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx skills add https://docs.mhaibaraai.cn
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
详见 [Agent Skills 文档](https://docs.mhaibaraai.cn/docs/getting-started/skills)。
|
|
49
|
+
|
|
33
50
|
### 🧩 自动化文档生成
|
|
34
51
|
|
|
35
52
|
- **组件元数据自动提取** - 基于 `nuxt-component-meta` 自动提取 Vue 组件的 Props、Slots、Emits 定义
|
|
@@ -79,21 +96,6 @@ pnpm dev
|
|
|
79
96
|
|
|
80
97
|
访问 `http://localhost:3000` 查看你的文档网站。
|
|
81
98
|
|
|
82
|
-
### AI 助手 Skill
|
|
83
|
-
|
|
84
|
-
为你的 AI 助手(Cursor、Claude Code 等)添加 Movk Nuxt Docs 专业知识,加速文档编写:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
npx skills add mhaibaraai/movk-nuxt-docs
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
此 Skill 为 AI 助手提供 Movk Nuxt Docs 专业知识,帮助你更高效地编写文档:
|
|
91
|
-
|
|
92
|
-
- 📝 MDC 组件用法和现成模板
|
|
93
|
-
- 🎨 中文文档写作规范和内容结构模式
|
|
94
|
-
- 🔧 nuxt.config.ts 和 app.config.ts 配置参考
|
|
95
|
-
- 📚 入门页、功能介绍页等常用页面模板
|
|
96
|
-
|
|
97
99
|
### 作为 Layer 使用
|
|
98
100
|
|
|
99
101
|
在现有 Nuxt 项目中使用 Movk Nuxt Docs 作为 layer:
|
package/app/app.config.ts
CHANGED
package/app/assets/css/main.css
CHANGED
|
@@ -13,9 +13,9 @@ const showExplainWithAi = computed(() => {
|
|
|
13
13
|
<template>
|
|
14
14
|
<UButton
|
|
15
15
|
v-if="showExplainWithAi"
|
|
16
|
-
:icon="aiChat.icons
|
|
16
|
+
:icon="aiChat.icons?.explain ?? ''"
|
|
17
17
|
target="_blank"
|
|
18
|
-
:label="aiChat.texts
|
|
18
|
+
:label="aiChat.texts?.explainWithAi ?? ''"
|
|
19
19
|
size="sm"
|
|
20
20
|
variant="ghost"
|
|
21
21
|
color="neutral"
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
2
|
+
defineProps<{
|
|
3
3
|
title?: string
|
|
4
4
|
description?: string
|
|
5
5
|
siteName?: string
|
|
6
|
-
}>()
|
|
7
|
-
title: '文档',
|
|
8
|
-
description: '使用 Nuxt Content 构建的文档站点',
|
|
9
|
-
siteName: 'Movk Nuxt Docs'
|
|
10
|
-
})
|
|
6
|
+
}>()
|
|
11
7
|
</script>
|
|
12
8
|
|
|
13
9
|
<template>
|
|
14
|
-
<div style="font-family: '
|
|
10
|
+
<div style="font-family: 'Alibaba PuHuiTi', sans-serif;" class="w-full h-full flex flex-col justify-center items-center relative p-10 lg:p-15 bg-white text-neutral-900 dark:bg-neutral-900 dark:text-neutral-50">
|
|
15
11
|
<div
|
|
16
12
|
class="absolute top-0 left-0 right-0 bottom-0"
|
|
17
13
|
:style="{
|
|
@@ -65,14 +61,14 @@ withDefaults(defineProps<{
|
|
|
65
61
|
fill="url(#nsLine1)"
|
|
66
62
|
/>
|
|
67
63
|
</svg>
|
|
68
|
-
<span class="text-[32px] font-
|
|
64
|
+
<span class="text-[32px] font-bold lg:text-[42px] text-green-500">
|
|
69
65
|
{{ siteName }}
|
|
70
66
|
</span>
|
|
71
67
|
</div>
|
|
72
68
|
|
|
73
69
|
<div class="items-center justify-center w-full">
|
|
74
70
|
<h1
|
|
75
|
-
class="text-[48px] lg:text-[72px] leading-tight text-pretty font-
|
|
71
|
+
class="text-[48px] lg:text-[72px] leading-tight text-pretty font-medium text-slate-800 max-w-175 lg:max-w-250"
|
|
76
72
|
style="display: block; line-clamp: 3; text-overflow: ellipsis; text-wrap: balance;"
|
|
77
73
|
>
|
|
78
74
|
{{ title }}
|
|
@@ -81,7 +77,7 @@ withDefaults(defineProps<{
|
|
|
81
77
|
|
|
82
78
|
<p
|
|
83
79
|
v-if="description"
|
|
84
|
-
class="text-slate-
|
|
80
|
+
class="text-slate-700 text-[24px] lg:text-[32px] font-normal opacity-70 max-w-162.5 lg:max-w-225 leading-normal"
|
|
85
81
|
>
|
|
86
82
|
{{ description }}
|
|
87
83
|
</p>
|
|
@@ -4,7 +4,7 @@ import type { ButtonProps } from '@nuxt/ui'
|
|
|
4
4
|
const route = useRoute()
|
|
5
5
|
const { header, github } = useAppConfig()
|
|
6
6
|
|
|
7
|
-
const links = computed<ButtonProps[]>(() => github && github.url
|
|
7
|
+
const links = computed<ButtonProps[]>(() => (github && github.url
|
|
8
8
|
? [
|
|
9
9
|
{
|
|
10
10
|
'icon': 'i-simple-icons-github',
|
|
@@ -12,13 +12,13 @@ const links = computed<ButtonProps[]>(() => github && github.url
|
|
|
12
12
|
'target': '_blank',
|
|
13
13
|
'aria-label': 'GitHub'
|
|
14
14
|
},
|
|
15
|
-
...header?.links || []
|
|
15
|
+
...(header?.links || [])
|
|
16
16
|
]
|
|
17
|
-
: header
|
|
17
|
+
: header?.links || []) as ButtonProps[])
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<template>
|
|
21
|
-
<UHeader :ui="{ left: 'min-w-0' }" class="flex flex-col" aria-label="Site Header">
|
|
21
|
+
<UHeader :ui="{ left: 'min-w-0', right: 'gap-0.5' }" class="flex flex-col" aria-label="Site Header">
|
|
22
22
|
<template #left>
|
|
23
23
|
<HeaderLogo />
|
|
24
24
|
</template>
|
|
@@ -49,7 +49,7 @@ const {
|
|
|
49
49
|
Primary
|
|
50
50
|
|
|
51
51
|
<UButton
|
|
52
|
-
to="/docs/getting-started/theme/css-variables#colors"
|
|
52
|
+
to="https://ui.nuxt.com/docs/getting-started/theme/css-variables#colors"
|
|
53
53
|
size="xs"
|
|
54
54
|
color="neutral"
|
|
55
55
|
variant="link"
|
|
@@ -86,7 +86,7 @@ const {
|
|
|
86
86
|
Neutral
|
|
87
87
|
|
|
88
88
|
<UButton
|
|
89
|
-
to="/docs/getting-started/theme/css-variables#text"
|
|
89
|
+
to="https://ui.nuxt.com/docs/getting-started/theme/css-variables#text"
|
|
90
90
|
size="xs"
|
|
91
91
|
color="neutral"
|
|
92
92
|
variant="link"
|
|
@@ -113,7 +113,7 @@ const {
|
|
|
113
113
|
Radius
|
|
114
114
|
|
|
115
115
|
<UButton
|
|
116
|
-
to="/docs/getting-started/theme/css-variables#radius"
|
|
116
|
+
to="https://ui.nuxt.com/docs/getting-started/theme/css-variables#radius"
|
|
117
117
|
size="xs"
|
|
118
118
|
color="neutral"
|
|
119
119
|
variant="link"
|
|
@@ -140,7 +140,7 @@ const {
|
|
|
140
140
|
Font
|
|
141
141
|
|
|
142
142
|
<UButton
|
|
143
|
-
to="/docs/getting-started/integrations/fonts"
|
|
143
|
+
to="https://ui.nuxt.com/docs/getting-started/integrations/fonts"
|
|
144
144
|
size="xs"
|
|
145
145
|
color="neutral"
|
|
146
146
|
variant="link"
|
|
@@ -168,7 +168,7 @@ const {
|
|
|
168
168
|
Icons
|
|
169
169
|
|
|
170
170
|
<UButton
|
|
171
|
-
to="/docs/getting-started/integrations/icons"
|
|
171
|
+
to="https://ui.nuxt.com/docs/getting-started/integrations/icons"
|
|
172
172
|
size="xs"
|
|
173
173
|
color="neutral"
|
|
174
174
|
variant="link"
|
|
@@ -196,7 +196,7 @@ const {
|
|
|
196
196
|
Color Mode
|
|
197
197
|
|
|
198
198
|
<UButton
|
|
199
|
-
to="/docs/getting-started/integrations/color-mode"
|
|
199
|
+
to="https://ui.nuxt.com/docs/getting-started/integrations/color-mode"
|
|
200
200
|
size="xs"
|
|
201
201
|
color="neutral"
|
|
202
202
|
variant="link"
|
|
@@ -9,7 +9,7 @@ export function useTheme() {
|
|
|
9
9
|
const site = useSiteConfig()
|
|
10
10
|
|
|
11
11
|
const radius = useLocalStorage(`${site.name}-ui-radius`, 0.25)
|
|
12
|
-
const font = useLocalStorage(`${site.name}-ui-font`, '
|
|
12
|
+
const font = useLocalStorage(`${site.name}-ui-font`, 'Alibaba PuHuiTi')
|
|
13
13
|
const _iconSet = useLocalStorage(`${site.name}-ui-icons`, 'lucide')
|
|
14
14
|
const blackAsPrimary = useLocalStorage(`${site.name}-ui-black-as-primary`, false)
|
|
15
15
|
|
|
@@ -38,7 +38,7 @@ export function useTheme() {
|
|
|
38
38
|
})
|
|
39
39
|
|
|
40
40
|
const radiuses = [0, 0.125, 0.25, 0.375, 0.5]
|
|
41
|
-
const fonts = ['Public Sans', 'DM Sans', 'Geist', 'Inter', 'Poppins', 'Outfit', 'Raleway']
|
|
41
|
+
const fonts = ['Alibaba PuHuiTi', 'Public Sans', 'DM Sans', 'Geist', 'Inter', 'Poppins', 'Outfit', 'Raleway']
|
|
42
42
|
|
|
43
43
|
const icons = [{
|
|
44
44
|
label: 'Lucide',
|
|
@@ -83,7 +83,7 @@ export function useTheme() {
|
|
|
83
83
|
|
|
84
84
|
const link = computed(() => {
|
|
85
85
|
const name = font.value
|
|
86
|
-
if (name === '
|
|
86
|
+
if (name === 'Alibaba PuHuiTi' || !fonts.includes(name)) return []
|
|
87
87
|
return [{
|
|
88
88
|
rel: 'stylesheet' as const,
|
|
89
89
|
href: `https://fonts.googleapis.com/css2?family=${encodeURIComponent(name)}:wght@400;500;600;700&display=swap`,
|
|
@@ -100,7 +100,7 @@ export function useTheme() {
|
|
|
100
100
|
const hasCSSChanges = computed(() => {
|
|
101
101
|
return radius.value !== 0.25
|
|
102
102
|
|| blackAsPrimary.value
|
|
103
|
-
|| font.value !== '
|
|
103
|
+
|| font.value !== 'Alibaba PuHuiTi'
|
|
104
104
|
})
|
|
105
105
|
|
|
106
106
|
const hasAppConfigChanges = computed(() => {
|
|
@@ -115,7 +115,7 @@ export function useTheme() {
|
|
|
115
115
|
'@import "@nuxt/ui";'
|
|
116
116
|
]
|
|
117
117
|
|
|
118
|
-
if (font.value !== '
|
|
118
|
+
if (font.value !== 'Alibaba PuHuiTi') {
|
|
119
119
|
lines.push('', '@theme {', ` --font-sans: '${font.value}', sans-serif;`, '}')
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -172,7 +172,7 @@ export function useTheme() {
|
|
|
172
172
|
window.localStorage.removeItem(`${site.name}-ui-neutral`)
|
|
173
173
|
|
|
174
174
|
radius.value = 0.25
|
|
175
|
-
font.value = '
|
|
175
|
+
font.value = 'Alibaba PuHuiTi'
|
|
176
176
|
_iconSet.value = 'lucide'
|
|
177
177
|
appConfig.ui.icons = themeIcons.lucide as any
|
|
178
178
|
blackAsPrimary.value = false
|
|
@@ -8,6 +8,7 @@ definePageMeta({
|
|
|
8
8
|
heroBackground: 'opacity-30'
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
+
const { isOpen } = useAIChat()
|
|
11
12
|
const route = useRoute()
|
|
12
13
|
const appConfig = useAppConfig()
|
|
13
14
|
const { toc, github } = appConfig
|
|
@@ -110,7 +111,13 @@ defineOgImage('Docs', {
|
|
|
110
111
|
</script>
|
|
111
112
|
|
|
112
113
|
<template>
|
|
113
|
-
<UPage
|
|
114
|
+
<UPage
|
|
115
|
+
v-if="page"
|
|
116
|
+
:ui="isOpen ? {
|
|
117
|
+
center: 'lg:col-span-10',
|
|
118
|
+
right: 'lg:col-span-0 hidden'
|
|
119
|
+
} : undefined"
|
|
120
|
+
>
|
|
114
121
|
<UPageHeader :title="title">
|
|
115
122
|
<template #headline>
|
|
116
123
|
<UBreadcrumb :items="breadcrumb" />
|
package/app/plugins/theme.ts
CHANGED
|
@@ -67,7 +67,7 @@ export default defineNuxtPlugin({
|
|
|
67
67
|
`if (localStorage.getItem('${site.name}-ui-font')) {`,
|
|
68
68
|
`var font = localStorage.getItem('${site.name}-ui-font');`,
|
|
69
69
|
`document.getElementById('${site.name}-ui-font').innerHTML = ':root { --font-sans: \\'' + font + '\\', sans-serif; }';`,
|
|
70
|
-
`if (font !== 'Public Sans') {`,
|
|
70
|
+
`if (font !== 'Alibaba PuHuiTi' && ['Alibaba PuHuiTi', 'Public Sans', 'DM Sans', 'Geist', 'Inter', 'Poppins', 'Outfit', 'Raleway'].includes(font)) {`,
|
|
71
71
|
`var lnk = document.createElement('link');`,
|
|
72
72
|
`lnk.rel = 'stylesheet';`,
|
|
73
73
|
`lnk.href = 'https://fonts.googleapis.com/css2?family=' + encodeURIComponent(font) + ':wght@400;500;600;700&display=swap';`,
|
package/modules/ai-chat/index.ts
CHANGED
|
@@ -4,12 +4,12 @@ const { toggleChat } = useAIChat()
|
|
|
4
4
|
</script>
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
|
-
<UTooltip :text="aiChat.texts
|
|
7
|
+
<UTooltip :text="aiChat.texts?.trigger ?? ''">
|
|
8
8
|
<UButton
|
|
9
|
-
:icon="aiChat.icons
|
|
9
|
+
:icon="aiChat.icons?.trigger ?? ''"
|
|
10
10
|
variant="ghost"
|
|
11
11
|
class="rounded-full"
|
|
12
|
-
:aria-label="aiChat.texts
|
|
12
|
+
:aria-label="aiChat.texts?.trigger ?? ''"
|
|
13
13
|
@click="toggleChat"
|
|
14
14
|
/>
|
|
15
15
|
</UTooltip>
|
|
@@ -12,7 +12,7 @@ const inputRef = ref<{ inputRef: HTMLInputElement } | null>(null)
|
|
|
12
12
|
|
|
13
13
|
const isDocsRoute = computed(() => route.meta.layout === 'docs')
|
|
14
14
|
const isFloatingInputEnabled = computed(() => aiChat.floatingInput !== false)
|
|
15
|
-
const focusInputShortcut = computed(() => aiChat.shortcuts
|
|
15
|
+
const focusInputShortcut = computed(() => aiChat.shortcuts?.focusInput ?? 'meta_i')
|
|
16
16
|
|
|
17
17
|
const shortcutDisplayKeys = computed(() => {
|
|
18
18
|
const shortcut = focusInputShortcut.value
|
|
@@ -72,7 +72,7 @@ defineShortcuts(shortcuts)
|
|
|
72
72
|
<UInput
|
|
73
73
|
ref="inputRef"
|
|
74
74
|
v-model="input"
|
|
75
|
-
:placeholder="aiChat.texts
|
|
75
|
+
:placeholder="aiChat.texts?.placeholder ?? ''"
|
|
76
76
|
size="lg"
|
|
77
77
|
maxlength="1000"
|
|
78
78
|
:ui="{
|
|
@@ -4,7 +4,7 @@ import type { FaqCategory, FaqQuestions, ToolPart, ToolState } from '../types'
|
|
|
4
4
|
import { Chat } from '@ai-sdk/vue'
|
|
5
5
|
import { DefaultChatTransport, getToolName, isReasoningUIPart, isTextUIPart, isToolUIPart } from 'ai'
|
|
6
6
|
import { computed } from 'vue'
|
|
7
|
-
import {
|
|
7
|
+
import { isPartStreaming, isToolStreaming } from '@nuxt/ui/utils/ai'
|
|
8
8
|
import { useModels } from '../composables/useModels'
|
|
9
9
|
import { splitByCase, upperFirst } from 'scule'
|
|
10
10
|
import AiChatPreStream from './AiChatPreStream.vue'
|
|
@@ -28,7 +28,7 @@ let _skipSync = false
|
|
|
28
28
|
const chat = new Chat({
|
|
29
29
|
messages: messages.value,
|
|
30
30
|
transport: new DefaultChatTransport({
|
|
31
|
-
api: config.public.aiChat.apiPath,
|
|
31
|
+
api: (config.app?.baseURL.replace(/\/$/, '') || '') + config.public.aiChat.apiPath,
|
|
32
32
|
body: () => ({ model: model.value })
|
|
33
33
|
}),
|
|
34
34
|
onError: (error: Error) => {
|
|
@@ -159,30 +159,30 @@ const faqQuestions = computed<FaqCategory[]>(() => {
|
|
|
159
159
|
<USidebar
|
|
160
160
|
v-model:open="isOpen"
|
|
161
161
|
side="right"
|
|
162
|
-
:title="aiChat.texts
|
|
162
|
+
:title="aiChat.texts?.title ?? ''"
|
|
163
163
|
rail
|
|
164
164
|
:style="{ '--sidebar-width': '24rem' }"
|
|
165
165
|
:ui="{ footer: 'p-0', actions: 'gap-0.5' }"
|
|
166
166
|
>
|
|
167
167
|
<template #actions>
|
|
168
|
-
<UTooltip v-if="canClear" :text="aiChat.texts
|
|
168
|
+
<UTooltip v-if="canClear" :text="aiChat.texts?.clearChat ?? ''">
|
|
169
169
|
<UButton
|
|
170
|
-
:icon="aiChat.icons
|
|
170
|
+
:icon="aiChat.icons?.clearChat ?? ''"
|
|
171
171
|
color="neutral"
|
|
172
172
|
variant="ghost"
|
|
173
|
-
:aria-label="aiChat.texts
|
|
173
|
+
:aria-label="aiChat.texts?.clearChat ?? ''"
|
|
174
174
|
@click="clearMessages"
|
|
175
175
|
/>
|
|
176
176
|
</UTooltip>
|
|
177
177
|
</template>
|
|
178
178
|
|
|
179
179
|
<template #close>
|
|
180
|
-
<UTooltip :text="aiChat.texts
|
|
180
|
+
<UTooltip :text="aiChat.texts?.close ?? ''">
|
|
181
181
|
<UButton
|
|
182
|
-
:icon="aiChat.icons
|
|
182
|
+
:icon="aiChat.icons?.close ?? ''"
|
|
183
183
|
color="neutral"
|
|
184
184
|
variant="ghost"
|
|
185
|
-
:aria-label="aiChat.texts
|
|
185
|
+
:aria-label="aiChat.texts?.close ?? ''"
|
|
186
186
|
@click="isOpen = false"
|
|
187
187
|
/>
|
|
188
188
|
</UTooltip>
|
|
@@ -215,13 +215,17 @@ const faqQuestions = computed<FaqCategory[]>(() => {
|
|
|
215
215
|
class="px-0 gap-2"
|
|
216
216
|
:user="{ ui: { container: 'max-w-full' } }"
|
|
217
217
|
>
|
|
218
|
+
<template #indicator>
|
|
219
|
+
<UChatTool icon="i-lucide-brain" text="Thinking..." streaming />
|
|
220
|
+
</template>
|
|
221
|
+
|
|
218
222
|
<template #content="{ message }">
|
|
219
223
|
<template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
|
|
220
224
|
<UChatReasoning
|
|
221
225
|
v-if="isReasoningUIPart(part)"
|
|
222
226
|
:text="part.text"
|
|
223
|
-
:streaming="
|
|
224
|
-
:icon="aiChat.icons
|
|
227
|
+
:streaming="isPartStreaming(part)"
|
|
228
|
+
:icon="aiChat.icons?.reasoning ?? ''"
|
|
225
229
|
>
|
|
226
230
|
<MDCCached
|
|
227
231
|
:value="part.text"
|
|
@@ -230,14 +234,21 @@ const faqQuestions = computed<FaqCategory[]>(() => {
|
|
|
230
234
|
class="*:first:mt-0 *:last:mb-0"
|
|
231
235
|
/>
|
|
232
236
|
</UChatReasoning>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
237
|
+
|
|
238
|
+
<template v-else-if="isTextUIPart(part) && part.text.length > 0">
|
|
239
|
+
<MDCCached
|
|
240
|
+
v-if="message.role === 'assistant'"
|
|
241
|
+
:value="part.text"
|
|
242
|
+
:cache-key="`${message.id}-${index}`"
|
|
243
|
+
:components="components"
|
|
244
|
+
:parser-options="{ highlight: false }"
|
|
245
|
+
class="*:first:mt-0 *:last:mb-0"
|
|
246
|
+
/>
|
|
247
|
+
<p v-else-if="message.role === 'user'" class="whitespace-pre-wrap text-sm/6">
|
|
248
|
+
{{ part.text }}
|
|
249
|
+
</p>
|
|
250
|
+
</template>
|
|
251
|
+
|
|
241
252
|
<UChatTool
|
|
242
253
|
v-else-if="isToolUIPart(part)"
|
|
243
254
|
:text="getToolText(part)"
|
|
@@ -262,7 +273,7 @@ const faqQuestions = computed<FaqCategory[]>(() => {
|
|
|
262
273
|
<UChatPrompt
|
|
263
274
|
v-model="input"
|
|
264
275
|
:error="chat.error"
|
|
265
|
-
:placeholder="aiChat.texts
|
|
276
|
+
:placeholder="aiChat.texts?.placeholder ?? ''"
|
|
266
277
|
variant="naked"
|
|
267
278
|
size="sm"
|
|
268
279
|
autofocus
|
|
@@ -275,7 +286,7 @@ const faqQuestions = computed<FaqCategory[]>(() => {
|
|
|
275
286
|
<AiChatModelSelect v-model="model" />
|
|
276
287
|
|
|
277
288
|
<div class="flex gap-1 justify-between items-center px-1 text-xs text-muted">
|
|
278
|
-
<span>{{ aiChat.texts
|
|
289
|
+
<span>{{ aiChat.texts?.lineBreak ?? '' }}</span>
|
|
279
290
|
<UKbd value="shift" />
|
|
280
291
|
<UKbd value="enter" />
|
|
281
292
|
</div>
|
|
@@ -3,7 +3,7 @@ export function useModels() {
|
|
|
3
3
|
const model = useCookie<string>('model', { default: () => config.public.aiChat.model })
|
|
4
4
|
|
|
5
5
|
const { aiChat } = useAppConfig()
|
|
6
|
-
const providerIcons = computed(() => aiChat.icons
|
|
6
|
+
const providerIcons = computed(() => (aiChat.icons?.providers ?? {}) as Record<string, string>)
|
|
7
7
|
|
|
8
8
|
function getModelIcon(modelId: string): string {
|
|
9
9
|
const provider = modelId.split('/')[0] || ''
|
|
@@ -23,7 +23,7 @@ function getMainAgentSystemPrompt(siteName: string) {
|
|
|
23
23
|
- 在适用时引用具体的组件名称、props 或 API。
|
|
24
24
|
- 如果问题不明确,请要求澄清而不是猜测。
|
|
25
25
|
- 当发现多个相关项目时,使用要点清楚地列出它们。
|
|
26
|
-
-
|
|
26
|
+
- 您最多需要 5 次工具调用才能找到答案,因此要有策略:从广泛开始,然后在需要时具体化。
|
|
27
27
|
- 以对话方式格式化回复,而不是文档章节形式`
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -42,6 +42,7 @@ export default defineEventHandler(async (event) => {
|
|
|
42
42
|
const siteConfig = getSiteConfig(event)
|
|
43
43
|
const siteName = siteConfig.name || 'Documentation'
|
|
44
44
|
|
|
45
|
+
const baseURL = config.app?.baseURL?.replace(/\/$/, '') || ''
|
|
45
46
|
const mcpPath = config.aiChat.mcpPath
|
|
46
47
|
const isExternalUrl = mcpPath.startsWith('http://') || mcpPath.startsWith('https://')
|
|
47
48
|
|
|
@@ -50,7 +51,7 @@ export default defineEventHandler(async (event) => {
|
|
|
50
51
|
try {
|
|
51
52
|
const mcpUrl = isExternalUrl
|
|
52
53
|
? mcpPath
|
|
53
|
-
: `${getRequestURL(event).origin}${mcpPath}`
|
|
54
|
+
: `${getRequestURL(event).origin}${baseURL}${mcpPath}`
|
|
54
55
|
|
|
55
56
|
httpClient = await createMCPClient({
|
|
56
57
|
transport: { type: 'http', url: mcpUrl }
|
package/modules/module.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface ModuleOptions {
|
|
|
22
22
|
mermaid?: boolean
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const log = logger.withTag('movk
|
|
25
|
+
const log = logger.withTag('@movk/nuxt-docs')
|
|
26
26
|
|
|
27
27
|
export default defineNuxtModule<ModuleOptions>({
|
|
28
28
|
meta: {
|
|
@@ -63,7 +63,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
63
63
|
|
|
64
64
|
if (mermaidAvailable) {
|
|
65
65
|
addComponentsDir({
|
|
66
|
-
path: resolve('./
|
|
66
|
+
path: resolve('./components/prose'),
|
|
67
67
|
pathPrefix: false,
|
|
68
68
|
prefix: 'Prose',
|
|
69
69
|
global: true
|
|
@@ -153,7 +153,6 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
153
153
|
})
|
|
154
154
|
|
|
155
155
|
const layerPath = resolve('..')
|
|
156
|
-
|
|
157
156
|
// @ts-ignore - component-meta is not typed
|
|
158
157
|
nuxt.hook('component-meta:extend', (options: any) => {
|
|
159
158
|
const userInclude = (nuxt.options.componentMeta && typeof nuxt.options.componentMeta === 'object')
|
|
@@ -165,14 +164,6 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
165
164
|
...createComponentMetaExcludeFilters(resolve, dir, layerPath, userInclude)
|
|
166
165
|
]
|
|
167
166
|
})
|
|
168
|
-
|
|
169
|
-
nuxt.hook('nitro:config', (nitroConfig) => {
|
|
170
|
-
nitroConfig.publicAssets ||= []
|
|
171
|
-
nitroConfig.publicAssets.push({
|
|
172
|
-
dir: resolve('./runtime/public'),
|
|
173
|
-
maxAge: 60 * 60 * 24 * 30
|
|
174
|
-
})
|
|
175
|
-
})
|
|
176
167
|
}
|
|
177
168
|
})
|
|
178
169
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { addServerHandler, createResolver, defineNuxtModule, logger } from '@nuxt/kit'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { readdir, readFile } from 'node:fs/promises'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { parse as parseYaml } from 'yaml'
|
|
6
|
+
|
|
7
|
+
interface SkillEntry {
|
|
8
|
+
name: string
|
|
9
|
+
description: string
|
|
10
|
+
files: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const SKILL_NAME_REGEX = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/
|
|
14
|
+
const MAX_NAME_LENGTH = 64
|
|
15
|
+
|
|
16
|
+
const log = logger.withTag('@movk/nuxt-docs')
|
|
17
|
+
|
|
18
|
+
export default defineNuxtModule({
|
|
19
|
+
meta: {
|
|
20
|
+
name: 'skills'
|
|
21
|
+
},
|
|
22
|
+
async setup(_options, nuxt) {
|
|
23
|
+
const skillsDir = join(nuxt.options.rootDir, 'skills')
|
|
24
|
+
if (!existsSync(skillsDir)) return
|
|
25
|
+
|
|
26
|
+
const catalog = await scanSkills(skillsDir)
|
|
27
|
+
if (!catalog.length) return
|
|
28
|
+
|
|
29
|
+
log.info(`Found ${catalog.length} agent skill${catalog.length > 1 ? 's' : ''}: ${catalog.map(s => s.name).join(', ')}`)
|
|
30
|
+
|
|
31
|
+
nuxt.options.runtimeConfig.skills = { catalog }
|
|
32
|
+
|
|
33
|
+
const { resolve } = createResolver(import.meta.url)
|
|
34
|
+
|
|
35
|
+
nuxt.hook('nitro:config', (nitroConfig) => {
|
|
36
|
+
nitroConfig.serverAssets ||= []
|
|
37
|
+
nitroConfig.serverAssets.push({ baseName: 'skills', dir: skillsDir })
|
|
38
|
+
|
|
39
|
+
nitroConfig.prerender ||= {}
|
|
40
|
+
nitroConfig.prerender.routes ||= []
|
|
41
|
+
nitroConfig.prerender.routes.push('/.well-known/skills/index.json')
|
|
42
|
+
for (const skill of catalog) {
|
|
43
|
+
for (const file of skill.files) {
|
|
44
|
+
nitroConfig.prerender.routes.push(`/.well-known/skills/${skill.name}/${file}`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
addServerHandler({
|
|
50
|
+
route: '/.well-known/skills/index.json',
|
|
51
|
+
handler: resolve('./runtime/server/routes/skills-index')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
addServerHandler({
|
|
55
|
+
route: '/.well-known/skills/**',
|
|
56
|
+
handler: resolve('./runtime/server/routes/skills-files')
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
function parseFrontmatter(content: string): { name?: string, description?: string } | null {
|
|
62
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
63
|
+
if (!match?.[1]) return null
|
|
64
|
+
try {
|
|
65
|
+
return parseYaml(match[1])
|
|
66
|
+
} catch {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function validateSkillName(name: string, dirName: string): boolean {
|
|
72
|
+
if (name.length > MAX_NAME_LENGTH) {
|
|
73
|
+
log.warn(`Skill "${name}" exceeds ${MAX_NAME_LENGTH} character limit`)
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
if (!SKILL_NAME_REGEX.test(name) || name.includes('--')) {
|
|
77
|
+
log.warn(`Skill name "${name}" does not match the Agent Skills naming spec`)
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
if (name !== dirName) {
|
|
81
|
+
log.warn(`Skill name "${name}" does not match directory name "${dirName}"`)
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function listFilesRecursively(dir: string, base: string = ''): Promise<string[]> {
|
|
88
|
+
const files: string[] = []
|
|
89
|
+
const entries = await readdir(dir, { withFileTypes: true })
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const relPath = base ? `${base}/${entry.name}` : entry.name
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
files.push(...await listFilesRecursively(join(dir, entry.name), relPath))
|
|
94
|
+
} else {
|
|
95
|
+
files.push(relPath)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return files
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function scanSkills(skillsDir: string): Promise<SkillEntry[]> {
|
|
102
|
+
const catalog: SkillEntry[] = []
|
|
103
|
+
const entries = await readdir(skillsDir, { withFileTypes: true })
|
|
104
|
+
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
if (!entry.isDirectory()) continue
|
|
107
|
+
|
|
108
|
+
const skillDir = join(skillsDir, entry.name)
|
|
109
|
+
const skillMdPath = join(skillDir, 'SKILL.md')
|
|
110
|
+
|
|
111
|
+
if (!existsSync(skillMdPath)) continue
|
|
112
|
+
|
|
113
|
+
const content = await readFile(skillMdPath, 'utf-8')
|
|
114
|
+
const frontmatter = parseFrontmatter(content)
|
|
115
|
+
|
|
116
|
+
if (!frontmatter?.description) {
|
|
117
|
+
log.warn(`Skipping skill "${entry.name}": missing description in SKILL.md frontmatter`)
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const name = frontmatter.name || entry.name
|
|
122
|
+
if (!validateSkillName(name, entry.name)) continue
|
|
123
|
+
|
|
124
|
+
const allFiles = await listFilesRecursively(skillDir)
|
|
125
|
+
const files = allFiles.filter(f => !f.split('/').some(s => s.startsWith('.')))
|
|
126
|
+
const sortedFiles = ['SKILL.md', ...files.filter(f => f !== 'SKILL.md')]
|
|
127
|
+
|
|
128
|
+
catalog.push({
|
|
129
|
+
name,
|
|
130
|
+
description: frontmatter.description,
|
|
131
|
+
files: sortedFiles
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return catalog
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
declare module 'nuxt/schema' {
|
|
139
|
+
interface RuntimeConfig {
|
|
140
|
+
skills: {
|
|
141
|
+
catalog: SkillEntry[]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const CONTENT_TYPES: Record<string, string> = {
|
|
2
|
+
'.md': 'text/markdown; charset=utf-8',
|
|
3
|
+
'.json': 'application/json; charset=utf-8',
|
|
4
|
+
'.yaml': 'text/yaml; charset=utf-8',
|
|
5
|
+
'.yml': 'text/yaml; charset=utf-8',
|
|
6
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
7
|
+
'.py': 'text/plain; charset=utf-8',
|
|
8
|
+
'.sh': 'text/plain; charset=utf-8',
|
|
9
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
10
|
+
'.ts': 'text/plain; charset=utf-8'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getContentType(path: string): string {
|
|
14
|
+
const ext = path.slice(path.lastIndexOf('.'))
|
|
15
|
+
return CONTENT_TYPES[ext] || 'application/octet-stream'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default defineEventHandler(async (event) => {
|
|
19
|
+
const url = getRequestURL(event)
|
|
20
|
+
const prefix = '/.well-known/skills/'
|
|
21
|
+
const idx = url.pathname.indexOf(prefix)
|
|
22
|
+
if (idx === -1) {
|
|
23
|
+
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const filePath = decodeURIComponent(url.pathname.slice(idx + prefix.length))
|
|
27
|
+
|
|
28
|
+
if (!filePath || filePath.includes('..')) {
|
|
29
|
+
throw createError({ statusCode: 400, statusMessage: 'Bad Request' })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { skills } = useRuntimeConfig(event)
|
|
33
|
+
const skillName = filePath.split('/')[0]
|
|
34
|
+
if (!skills.catalog.some((s: { name: string }) => s.name === skillName)) {
|
|
35
|
+
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const storage = useStorage('assets:skills')
|
|
39
|
+
const content = await storage.getItemRaw<string>(filePath)
|
|
40
|
+
|
|
41
|
+
if (!content) {
|
|
42
|
+
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setResponseHeader(event, 'content-type', getContentType(filePath))
|
|
46
|
+
setResponseHeader(event, 'cache-control', 'public, max-age=3600')
|
|
47
|
+
|
|
48
|
+
return content
|
|
49
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export default defineEventHandler((event) => {
|
|
2
|
+
const { skills } = useRuntimeConfig(event)
|
|
3
|
+
|
|
4
|
+
setResponseHeader(event, 'content-type', 'application/json')
|
|
5
|
+
setResponseHeader(event, 'cache-control', 'public, max-age=3600')
|
|
6
|
+
|
|
7
|
+
return { skills: skills.catalog }
|
|
8
|
+
})
|
package/nuxt.config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { defineNuxtConfig } from 'nuxt/config'
|
|
|
2
2
|
import pkg from './package.json'
|
|
3
3
|
import { createResolver, useNuxt } from '@nuxt/kit'
|
|
4
4
|
import { join } from 'pathe'
|
|
5
|
+
import { createAlibabaPuHuiTiProvider } from './providers/alibaba-puhuiti'
|
|
5
6
|
|
|
6
7
|
const { resolve } = createResolver(import.meta.url)
|
|
7
8
|
|
|
@@ -95,6 +96,7 @@ export default defineNuxtConfig({
|
|
|
95
96
|
|
|
96
97
|
nitro: {
|
|
97
98
|
prerender: {
|
|
99
|
+
routes: ['/', '/sitemap.xml'],
|
|
98
100
|
crawlLinks: true,
|
|
99
101
|
failOnError: false,
|
|
100
102
|
autoSubfolderIndex: false
|
|
@@ -119,8 +121,7 @@ export default defineNuxtConfig({
|
|
|
119
121
|
cfg.optimizeDeps.include.push(
|
|
120
122
|
'tailwindcss/colors',
|
|
121
123
|
'@movk/nuxt-docs > @movk/core',
|
|
122
|
-
'@movk/nuxt-docs > prettier'
|
|
123
|
-
'@movk/nuxt-docs > reka-ui'
|
|
124
|
+
'@movk/nuxt-docs > prettier'
|
|
124
125
|
)
|
|
125
126
|
|
|
126
127
|
// AI Chat static deps — only pre-bundle when the feature is actually enabled.
|
|
@@ -167,9 +168,11 @@ export default defineNuxtConfig({
|
|
|
167
168
|
},
|
|
168
169
|
|
|
169
170
|
fonts: {
|
|
171
|
+
providers: {
|
|
172
|
+
'alibaba-puhuiti': createAlibabaPuHuiTiProvider('https://cdn.mhaibaraai.cn/fonts')
|
|
173
|
+
},
|
|
170
174
|
families: [
|
|
171
|
-
{ name: '
|
|
172
|
-
{ name: 'Noto Sans SC', global: true, provider: 'local' }
|
|
175
|
+
{ name: 'Alibaba PuHuiTi', provider: 'alibaba-puhuiti', global: true }
|
|
173
176
|
]
|
|
174
177
|
},
|
|
175
178
|
|
|
@@ -183,7 +186,8 @@ export default defineNuxtConfig({
|
|
|
183
186
|
clientBundle: {
|
|
184
187
|
scan: true,
|
|
185
188
|
includeCustomCollections: true
|
|
186
|
-
}
|
|
189
|
+
},
|
|
190
|
+
provider: 'iconify'
|
|
187
191
|
},
|
|
188
192
|
|
|
189
193
|
llms: {
|
package/nuxt.schema.ts
CHANGED
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.17.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>",
|
|
@@ -39,20 +39,20 @@
|
|
|
39
39
|
"README.md"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@ai-sdk/gateway": "^3.0.
|
|
43
|
-
"@ai-sdk/mcp": "^1.0.
|
|
44
|
-
"@ai-sdk/vue": "^3.0.
|
|
45
|
-
"@iconify-json/lucide": "^1.2.
|
|
46
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
42
|
+
"@ai-sdk/gateway": "^3.0.88",
|
|
43
|
+
"@ai-sdk/mcp": "^1.0.32",
|
|
44
|
+
"@ai-sdk/vue": "^3.0.146",
|
|
45
|
+
"@iconify-json/lucide": "^1.2.101",
|
|
46
|
+
"@iconify-json/simple-icons": "^1.2.76",
|
|
47
47
|
"@iconify-json/vscode-icons": "^1.2.45",
|
|
48
48
|
"@movk/core": "^1.2.2",
|
|
49
49
|
"@nuxt/a11y": "^1.0.0-alpha.1",
|
|
50
50
|
"@nuxt/content": "^3.12.0",
|
|
51
51
|
"@nuxt/image": "^2.0.0",
|
|
52
52
|
"@nuxt/kit": "^4.4.2",
|
|
53
|
-
"@nuxt/ui": "^4.6.
|
|
54
|
-
"@nuxtjs/mcp-toolkit": "^0.
|
|
55
|
-
"@nuxtjs/mdc": "^0.21.
|
|
53
|
+
"@nuxt/ui": "^4.6.1",
|
|
54
|
+
"@nuxtjs/mcp-toolkit": "^0.13.3",
|
|
55
|
+
"@nuxtjs/mdc": "^0.21.1",
|
|
56
56
|
"@nuxtjs/robots": "^6.0.6",
|
|
57
57
|
"@octokit/rest": "^22.0.1",
|
|
58
58
|
"@shikijs/core": "^4.0.2",
|
|
@@ -63,26 +63,27 @@
|
|
|
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.
|
|
67
|
-
"defu": "^6.1.
|
|
66
|
+
"ai": "^6.0.146",
|
|
67
|
+
"defu": "^6.1.6",
|
|
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.2",
|
|
74
74
|
"nuxt-site-config": "^4.0.7",
|
|
75
75
|
"ohash": "^2.0.11",
|
|
76
76
|
"pathe": "^2.0.3",
|
|
77
77
|
"pkg-types": "^2.3.0",
|
|
78
78
|
"prettier": "^3.8.1",
|
|
79
|
-
"reka-ui": "^2.9.2",
|
|
80
79
|
"scule": "^1.3.0",
|
|
81
80
|
"shiki-stream": "^0.1.4",
|
|
82
81
|
"shiki-transformer-color-highlight": "^1.1.0",
|
|
83
82
|
"tailwindcss": "^4.2.2",
|
|
84
83
|
"ufo": "^1.6.3",
|
|
84
|
+
"unifont": "^0.7.4",
|
|
85
85
|
"vue-component-meta": "^3.2.6",
|
|
86
|
+
"yaml": "^2.8.3",
|
|
86
87
|
"zod": "^4.3.6"
|
|
87
88
|
}
|
|
88
89
|
}
|
|
@@ -32,9 +32,9 @@ export default defineCachedEventHandler(async (event) => {
|
|
|
32
32
|
const allCommits = await Promise.all(
|
|
33
33
|
paths.map(path =>
|
|
34
34
|
octokit.rest.repos.listCommits({
|
|
35
|
-
sha: github.branch
|
|
36
|
-
owner: github.owner
|
|
37
|
-
repo: github.name
|
|
35
|
+
sha: github.branch!,
|
|
36
|
+
owner: github.owner!,
|
|
37
|
+
repo: github.name!,
|
|
38
38
|
path,
|
|
39
39
|
since: github.since,
|
|
40
40
|
per_page: github.per_page || 100,
|
|
@@ -29,9 +29,9 @@ export default defineCachedEventHandler(async (event) => {
|
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
31
|
const commits = await octokit.rest.repos.listCommits({
|
|
32
|
-
sha: github.branch
|
|
33
|
-
owner: github.owner
|
|
34
|
-
repo: github.name
|
|
32
|
+
sha: github.branch!,
|
|
33
|
+
owner: github.owner!,
|
|
34
|
+
repo: github.name!,
|
|
35
35
|
path,
|
|
36
36
|
per_page: 1
|
|
37
37
|
}).then(res => res.data).catch(() => [])
|
|
@@ -58,8 +58,8 @@ export default defineCachedEventHandler(async (event) => {
|
|
|
58
58
|
const prMatch = commit.commit.message.match(/#(\d+)/)
|
|
59
59
|
if (prMatch?.[1]) {
|
|
60
60
|
const prData = await octokit.rest.pulls.get({
|
|
61
|
-
owner: github.owner
|
|
62
|
-
repo: github.name
|
|
61
|
+
owner: github.owner!,
|
|
62
|
+
repo: github.name!,
|
|
63
63
|
pull_number: Number.parseInt(prMatch[1])
|
|
64
64
|
}).then(res => res.data).catch(() => null)
|
|
65
65
|
|
|
@@ -17,8 +17,8 @@ export default defineCachedEventHandler(async () => {
|
|
|
17
17
|
const octokit = new Octokit({ auth: process.env.NUXT_GITHUB_TOKEN })
|
|
18
18
|
|
|
19
19
|
const releases = await octokit.rest.repos.listReleases({
|
|
20
|
-
owner: github.owner
|
|
21
|
-
repo: github.name
|
|
20
|
+
owner: github.owner!,
|
|
21
|
+
repo: github.name!
|
|
22
22
|
}).then(res => res.data).catch(() => [])
|
|
23
23
|
|
|
24
24
|
return releases
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|