@movk/nuxt-docs 1.14.1 → 1.15.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.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/app/app.config.ts +5 -32
  3. package/app/app.vue +54 -32
  4. package/app/components/DocsAsideRightBottom.vue +1 -1
  5. package/app/components/OgImage/OgImageDocs.takumi.vue +90 -0
  6. package/app/components/PageHeaderLinks.vue +6 -16
  7. package/app/components/content/CommitChangelog.vue +1 -0
  8. package/app/components/content/Mermaid.vue +3 -1
  9. package/app/components/header/HeaderCTA.vue +1 -10
  10. package/app/components/theme-picker/ThemePicker.vue +22 -33
  11. package/app/composables/useTheme.ts +64 -84
  12. package/app/composables/useToolCall.ts +5 -8
  13. package/app/error.vue +1 -1
  14. package/app/pages/docs/[...slug].vue +6 -6
  15. package/app/plugins/theme.ts +39 -68
  16. package/app/templates/landing.vue +5 -2
  17. package/app/templates/releases.vue +5 -2
  18. package/app/types/index.d.ts +8 -57
  19. package/content.config.ts +1 -0
  20. package/modules/ai-chat/index.ts +16 -26
  21. package/modules/ai-chat/runtime/components/AiChat.vue +3 -3
  22. package/modules/ai-chat/runtime/components/AiChatFloatingInput.vue +8 -8
  23. package/modules/ai-chat/runtime/components/AiChatPanel.vue +216 -231
  24. package/modules/ai-chat/runtime/components/AiChatPreStream.vue +0 -14
  25. package/modules/ai-chat/runtime/composables/useAIChat.ts +25 -73
  26. package/modules/ai-chat/runtime/composables/useModels.ts +0 -19
  27. package/modules/ai-chat/runtime/server/api/ai-chat.ts +74 -48
  28. package/modules/ai-chat/runtime/server/utils/getModel.ts +1 -9
  29. package/modules/ai-chat/runtime/types.ts +5 -0
  30. package/nuxt.config.ts +42 -36
  31. package/nuxt.schema.ts +14 -99
  32. package/package.json +25 -29
  33. package/server/mcp/tools/get-page.ts +5 -47
  34. package/server/mcp/tools/list-getting-started-guides.ts +1 -3
  35. package/server/mcp/tools/list-pages.ts +9 -44
  36. package/utils/git.ts +26 -79
  37. package/app/components/OgImage/Nuxt.vue +0 -247
  38. package/app/composables/useAnalytics.ts +0 -7
  39. package/modules/ai-chat/runtime/components/AiChatReasoning.vue +0 -49
  40. package/modules/ai-chat/runtime/components/AiChatSlideoverFaq.vue +0 -38
  41. package/modules/ai-chat/runtime/components/AiChatToolCall.vue +0 -31
  42. package/modules/ai-chat/runtime/server/utils/docs_agent.ts +0 -54
@@ -1,14 +1,19 @@
1
1
  import { themeIcons } from '../utils/theme'
2
- import { omit } from '@movk/core'
2
+ import { omit, kebabCase } from '@movk/core'
3
+ import { useLocalStorage } from '@vueuse/core'
3
4
  import colors from 'tailwindcss/colors'
4
5
 
5
6
  export function useTheme() {
6
7
  const appConfig = useAppConfig()
7
8
  const colorMode = useColorMode()
8
9
  const site = useSiteConfig()
9
- const { track } = useAnalytics()
10
10
 
11
- const neutralColors = ['slate', 'gray', 'zinc', 'neutral', 'stone']
11
+ const radius = useLocalStorage(`${site.name}-ui-radius`, 0.25)
12
+ const font = useLocalStorage(`${site.name}-ui-font`, 'Public Sans')
13
+ const _iconSet = useLocalStorage(`${site.name}-ui-icons`, 'lucide')
14
+ const blackAsPrimary = useLocalStorage(`${site.name}-ui-black-as-primary`, false)
15
+
16
+ const neutralColors = ['slate', 'gray', 'zinc', 'neutral', 'stone', 'taupe', 'mauve', 'mist', 'olive']
12
17
  const neutral = computed({
13
18
  get() {
14
19
  return appConfig.ui.colors.neutral
@@ -16,7 +21,6 @@ export function useTheme() {
16
21
  set(option) {
17
22
  appConfig.ui.colors.neutral = option
18
23
  window.localStorage.setItem(`${site.name}-ui-neutral`, appConfig.ui.colors.neutral)
19
- if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'neutral', value: option })
20
24
  }
21
25
  })
22
26
 
@@ -29,36 +33,12 @@ export function useTheme() {
29
33
  set(option) {
30
34
  appConfig.ui.colors.primary = option
31
35
  window.localStorage.setItem(`${site.name}-ui-primary`, appConfig.ui.colors.primary)
32
- setBlackAsPrimary(false)
33
- if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'primary', value: option })
36
+ blackAsPrimary.value = false
34
37
  }
35
38
  })
36
39
 
37
40
  const radiuses = [0, 0.125, 0.25, 0.375, 0.5]
38
- const radius = computed({
39
- get() {
40
- return appConfig.theme.radius
41
- },
42
- set(option) {
43
- appConfig.theme.radius = option
44
- window.localStorage.setItem(`${site.name}-ui-radius`, String(appConfig.theme.radius))
45
- if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'radius', value: option })
46
- }
47
- })
48
-
49
41
  const fonts = ['Public Sans', 'DM Sans', 'Geist', 'Inter', 'Poppins', 'Outfit', 'Raleway']
50
- const font = computed({
51
- get() {
52
- return appConfig.theme.font
53
- },
54
- set(option) {
55
- appConfig.theme.font = option
56
- if (appConfig.theme.font) {
57
- window.localStorage.setItem(`${site.name}-ui-font`, appConfig.theme.font)
58
- }
59
- if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'font', value: option })
60
- }
61
- })
62
42
 
63
43
  const icons = [{
64
44
  label: 'Lucide',
@@ -75,68 +55,75 @@ export function useTheme() {
75
55
  }]
76
56
  const icon = computed({
77
57
  get() {
78
- return appConfig.theme.icons
58
+ return _iconSet.value
79
59
  },
80
60
  set(option) {
81
- appConfig.theme.icons = option
61
+ _iconSet.value = option
82
62
  appConfig.ui.icons = themeIcons[option as keyof typeof themeIcons] as any
83
- if (appConfig.theme.icons) {
84
- window.localStorage.setItem(`${site.name}-ui-icons`, appConfig.theme.icons)
85
- }
86
- if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'icons', value: option })
87
63
  }
88
64
  })
89
65
 
90
- const modes = [
66
+ const modes = computed(() => [
91
67
  { label: 'light', icon: appConfig.ui.icons.light },
92
68
  { label: 'dark', icon: appConfig.ui.icons.dark },
93
69
  { label: 'system', icon: appConfig.ui.icons.system }
94
- ]
70
+ ])
95
71
  const mode = computed({
96
72
  get() {
97
73
  return colorMode.value
98
74
  },
99
75
  set(option) {
100
76
  colorMode.preference = option
101
- if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'color mode', value: option })
102
77
  }
103
78
  })
104
79
 
105
- function setBlackAsPrimary(value: boolean) {
106
- appConfig.theme.blackAsPrimary = value
107
- window.localStorage.setItem(`${site.name}-ui-black-as-primary`, String(value))
108
- if (appConfig.vercelAnalytics?.debug && value) track('Theme Changed', { setting: 'black as primary', value })
109
- }
80
+ const radiusStyle = computed(() => `:root { --ui-radius: ${radius.value}rem; }`)
81
+ const blackAsPrimaryStyle = computed(() => blackAsPrimary.value ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
82
+ const fontStyle = computed(() => `:root { --font-sans: '${font.value}', sans-serif; }`)
83
+
84
+ const link = computed(() => {
85
+ const name = font.value
86
+ if (name === 'Public Sans') return []
87
+ return [{
88
+ rel: 'stylesheet' as const,
89
+ href: `https://fonts.googleapis.com/css2?family=${encodeURIComponent(name)}:wght@400;500;600;700&display=swap`,
90
+ id: `font-${kebabCase(name)}`
91
+ }]
92
+ })
93
+
94
+ const style = [
95
+ { innerHTML: radiusStyle, id: `${site.name}-ui-radius`, tagPriority: -2 },
96
+ { innerHTML: blackAsPrimaryStyle, id: `${site.name}-ui-black-as-primary`, tagPriority: -2 },
97
+ { innerHTML: fontStyle, id: `${site.name}-ui-font`, tagPriority: -2 }
98
+ ]
110
99
 
111
100
  const hasCSSChanges = computed(() => {
112
- return appConfig.theme.radius !== 0.25
113
- || appConfig.theme.blackAsPrimary
114
- || appConfig.theme.font !== 'Public Sans'
101
+ return radius.value !== 0.25
102
+ || blackAsPrimary.value
103
+ || font.value !== 'Public Sans'
115
104
  })
116
105
 
117
106
  const hasAppConfigChanges = computed(() => {
118
107
  return appConfig.ui.colors.primary !== 'green'
119
108
  || appConfig.ui.colors.neutral !== 'slate'
120
- || appConfig.theme.icons !== 'lucide'
109
+ || _iconSet.value !== 'lucide'
121
110
  })
122
111
 
123
112
  function exportCSS(): string {
124
- if (appConfig.vercelAnalytics?.debug) track('Theme Exported', { type: 'css' })
125
-
126
113
  const lines = [
127
114
  '@import "tailwindcss";',
128
115
  '@import "@nuxt/ui";'
129
116
  ]
130
117
 
131
- if (appConfig.theme.font !== 'Public Sans') {
132
- lines.push('', '@theme {', ` --font-sans: '${appConfig.theme.font}', sans-serif;`, '}')
118
+ if (font.value !== 'Public Sans') {
119
+ lines.push('', '@theme {', ` --font-sans: '${font.value}', sans-serif;`, '}')
133
120
  }
134
121
 
135
122
  const rootLines: string[] = []
136
- if (appConfig.theme.radius !== 0.25) {
137
- rootLines.push(` --ui-radius: ${appConfig.theme.radius}rem;`)
123
+ if (radius.value !== 0.25) {
124
+ rootLines.push(` --ui-radius: ${radius.value}rem;`)
138
125
  }
139
- if (appConfig.theme.blackAsPrimary) {
126
+ if (blackAsPrimary.value) {
140
127
  rootLines.push(' --ui-primary: black;')
141
128
  }
142
129
 
@@ -144,33 +131,30 @@ export function useTheme() {
144
131
  lines.push('', ':root {', ...rootLines, '}')
145
132
  }
146
133
 
147
- if (appConfig.theme.blackAsPrimary) {
148
- lines.push('', '.dark {', ' --ui-primary: white;', '}')
134
+ const darkLines: string[] = []
135
+ if (blackAsPrimary.value) {
136
+ darkLines.push(' --ui-primary: white;')
149
137
  }
150
138
 
139
+ if (darkLines.length) {
140
+ lines.push('', '.dark {', ...darkLines, '}')
141
+ }
151
142
  return lines.join('\n')
152
143
  }
153
144
 
154
145
  function exportAppConfig(): string {
155
- if (appConfig.vercelAnalytics?.debug) track('Theme Exported', { type: 'appConfig' })
156
-
157
146
  const config: Record<string, any> = {}
158
147
 
159
- if (appConfig.ui.colors.primary !== 'green' || appConfig.ui.colors.neutral !== 'slate') {
160
- config.ui = { colors: {} }
161
- if (appConfig.ui.colors.primary !== 'green') {
162
- config.ui.colors.primary = appConfig.ui.colors.primary
163
- }
164
- if (appConfig.ui.colors.neutral !== 'slate') {
165
- config.ui.colors.neutral = appConfig.ui.colors.neutral
166
- }
148
+ const defaultColors: Record<string, string> = { primary: 'green', neutral: 'slate', secondary: 'blue', success: 'green', info: 'blue', warning: 'yellow', error: 'red' }
149
+ const colorEntries = Object.entries(defaultColors).filter(([key, def]) => (appConfig.ui.colors as any)[key] !== def)
150
+ if (colorEntries.length) {
151
+ config.ui = { colors: Object.fromEntries(colorEntries.map(([key]) => [key, (appConfig.ui.colors as any)[key]])) }
167
152
  }
168
153
 
169
- if (appConfig.theme.icons !== 'lucide') {
170
- const iconSet = appConfig.theme.icons
171
- const icons = themeIcons[iconSet as keyof typeof themeIcons]
154
+ if (_iconSet.value !== 'lucide') {
155
+ const iconMapping = themeIcons[_iconSet.value as keyof typeof themeIcons]
172
156
  config.ui = config.ui || {}
173
- config.ui.icons = icons
157
+ config.ui.icons = iconMapping
174
158
  }
175
159
 
176
160
  const configString = JSON.stringify(config, null, 2)
@@ -181,35 +165,31 @@ export function useTheme() {
181
165
  }
182
166
 
183
167
  function resetTheme() {
184
- if (appConfig.vercelAnalytics?.debug) track('Theme Reset')
185
-
186
- // Reset without triggering individual tracking events
187
168
  appConfig.ui.colors.primary = 'green'
188
169
  window.localStorage.removeItem(`${site.name}-ui-primary`)
189
170
 
190
171
  appConfig.ui.colors.neutral = 'slate'
191
172
  window.localStorage.removeItem(`${site.name}-ui-neutral`)
192
173
 
193
- appConfig.theme.radius = 0.25
194
- window.localStorage.removeItem(`${site.name}-ui-radius`)
195
-
196
- appConfig.theme.font = 'Public Sans'
197
- window.localStorage.removeItem(`${site.name}-ui-font`)
198
-
199
- appConfig.theme.icons = 'lucide'
174
+ radius.value = 0.25
175
+ font.value = 'Public Sans'
176
+ _iconSet.value = 'lucide'
200
177
  appConfig.ui.icons = themeIcons.lucide as any
201
- window.localStorage.removeItem(`${site.name}-ui-icons`)
178
+ blackAsPrimary.value = false
202
179
 
203
- appConfig.theme.blackAsPrimary = false
204
- window.localStorage.removeItem(`${site.name}-ui-black-as-primary`)
180
+ window.localStorage.removeItem(`${site.name}-ui-ai-theme`)
181
+ window.localStorage.removeItem(`${site.name}-ui-custom-colors`)
182
+ window.localStorage.removeItem(`${site.name}-ui-css-variables`)
205
183
  }
206
184
 
207
185
  return {
186
+ style,
187
+ link,
208
188
  neutralColors,
209
189
  neutral,
210
190
  primaryColors,
211
191
  primary,
212
- setBlackAsPrimary,
192
+ blackAsPrimary,
213
193
  radiuses,
214
194
  radius,
215
195
  fonts,
@@ -1,11 +1,8 @@
1
- export function useToolCall() {
2
- const tools: Record<string, string | ((args: any) => string)> = {
3
- 'list-pages': '列出所有文档页面',
4
- 'get-page': (args: any) => `检索 ${args?.path || '页面'}`,
5
- 'list-examples': '列出所有示例',
6
- 'get-example': (args: any) => `获取示例:${args?.exampleName || '示例'}`
7
- }
1
+ import type { ToolState } from '#ai-chat/types'
2
+
3
+ export function useToolCall(_state: ToolState, _toolName: string, _input: Record<string, string | undefined>) {
8
4
  return {
9
- tools
5
+ toolMessage: {} as Record<string, string>,
6
+ toolIcon: {} as Record<string, string>
10
7
  }
11
8
  }
package/app/error.vue CHANGED
@@ -54,7 +54,7 @@ provide('navigation', rootNavigation)
54
54
  <Footer v-if="$route.meta.footer !== false" />
55
55
 
56
56
  <ClientOnly>
57
- <LazyUContentSearch :files="files" :navigation="navigation" />
57
+ <UContentSearch :files="files" :navigation="navigation" />
58
58
  </ClientOnly>
59
59
  </div>
60
60
  </UApp>
@@ -9,7 +9,8 @@ definePageMeta({
9
9
  })
10
10
 
11
11
  const route = useRoute()
12
- const { toc, github } = useAppConfig()
12
+ const appConfig = useAppConfig()
13
+ const { toc, github } = appConfig
13
14
 
14
15
  const { data: page } = await useAsyncData(`docs-${kebabCase(route.path)}`, () => queryCollection('docs').path(route.path).first())
15
16
 
@@ -18,7 +19,6 @@ if (!page.value) {
18
19
  }
19
20
 
20
21
  const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
21
- const { shouldPushContent: shouldHideToc } = useAIChat()
22
22
 
23
23
  const { data: surround } = await useAsyncData(`surround-${(kebabCase(route.path))}`, () => {
24
24
  return queryCollectionItemSurroundings('docs', route.path, {
@@ -102,15 +102,15 @@ useSeoMeta({
102
102
  ogDescription: description
103
103
  })
104
104
 
105
- defineOgImageComponent('Nuxt', {
105
+ defineOgImage('Docs', {
106
106
  title,
107
107
  description,
108
- headline: breadcrumb.value?.[breadcrumb.value.length - 1]?.label || 'Movk Nuxt Docs'
108
+ siteName: site.name
109
109
  })
110
110
  </script>
111
111
 
112
112
  <template>
113
- <UPage v-if="page" :key="`page-${shouldHideToc}`">
113
+ <UPage v-if="page">
114
114
  <UPageHeader :title="title">
115
115
  <template #headline>
116
116
  <UBreadcrumb :items="breadcrumb" />
@@ -158,7 +158,7 @@ defineOgImageComponent('Nuxt', {
158
158
  <UContentSurround :surround="surround" />
159
159
  </UPageBody>
160
160
 
161
- <template v-if="page?.body?.toc?.links?.length && !shouldHideToc" #right>
161
+ <template v-if="page?.body?.toc?.links?.length" #right>
162
162
  <UContentToc
163
163
  :title="toc?.title"
164
164
  :links="page.body?.toc?.links"
@@ -7,84 +7,49 @@ export default defineNuxtPlugin({
7
7
  const site = useSiteConfig()
8
8
 
9
9
  if (import.meta.client) {
10
- function updateColor(type: 'primary' | 'neutral') {
11
- const color = localStorage.getItem(`${site.name}-ui-${type}`)
12
- if (color) {
13
- appConfig.ui.colors[type] = color
14
- }
15
- }
10
+ const primary = localStorage.getItem(`${site.name}-ui-primary`)
11
+ if (primary) appConfig.ui.colors.primary = primary
16
12
 
17
- function updateRadius() {
18
- const radius = localStorage.getItem(`${site.name}-ui-radius`)
19
- if (radius) {
20
- appConfig.theme.radius = Number.parseFloat(radius)
21
- }
22
- }
13
+ const neutral = localStorage.getItem(`${site.name}-ui-neutral`)
14
+ if (neutral) appConfig.ui.colors.neutral = neutral
23
15
 
24
- function updateBlackAsPrimary() {
25
- const blackAsPrimary = localStorage.getItem(`${site.name}-ui-black-as-primary`)
26
- if (blackAsPrimary) {
27
- appConfig.theme.blackAsPrimary = blackAsPrimary === 'true'
28
- }
29
- }
30
-
31
- function updateFont() {
32
- const font = localStorage.getItem(`${site.name}-ui-font`)
33
- if (font) {
34
- appConfig.theme.font = font
35
- }
36
- }
37
-
38
- updateColor('primary')
39
- updateColor('neutral')
40
- updateRadius()
41
- updateBlackAsPrimary()
42
- updateFont()
16
+ const icons = localStorage.getItem(`${site.name}-ui-icons`)
17
+ if (icons) appConfig.ui.icons = themeIcons[icons as keyof typeof themeIcons] as any
43
18
  }
44
19
 
45
- onNuxtReady(() => {
46
- function updateIcons() {
47
- const icons = localStorage.getItem(`${site.name}-ui-icons`)
48
- if (icons) {
49
- appConfig.theme.icons = icons
50
- appConfig.ui.icons = themeIcons[icons as keyof typeof themeIcons] as any
51
- }
52
- }
53
-
54
- updateIcons()
55
- })
56
-
57
20
  if (import.meta.server) {
58
21
  useHead({
59
22
  script: [{
60
23
  innerHTML: `
61
- let html = document.querySelector('style#nuxt-ui-colors').innerHTML;
62
-
63
- if (localStorage.getItem('${site.name}-ui-primary')) {
64
- const primaryColor = localStorage.getItem('${site.name}-ui-primary');
65
- if (primaryColor !== 'black') {
24
+ var colorsEl = document.querySelector('style#nuxt-ui-colors');
25
+ if (colorsEl) {
26
+ let html = colorsEl.innerHTML;
27
+ if (localStorage.getItem('${site.name}-ui-primary')) {
28
+ const primaryColor = localStorage.getItem('${site.name}-ui-primary');
29
+ if (primaryColor !== 'black') {
30
+ html = html.replace(
31
+ /(--ui-color-primary-\\d{2,3}:\\s*var\\(--color-)${appConfig.ui.colors.primary}(-\\d{2,3}.*?\\))/g,
32
+ \`$1\${primaryColor}$2\`
33
+ );
34
+ }
35
+ }
36
+ if (localStorage.getItem('${site.name}-ui-neutral')) {
37
+ let neutralColor = localStorage.getItem('${site.name}-ui-neutral');
66
38
  html = html.replace(
67
- /(--ui-color-primary-\\d{2,3}:\\s*var\\(--color-)${appConfig.ui.colors.primary}(-\\d{2,3}.*?\\))/g,
68
- \`$1\${primaryColor}$2\`
39
+ /(--ui-color-neutral-\\d{2,3}:\\s*var\\(--color-)${appConfig.ui.colors.neutral}(-\\d{2,3}.*?\\))/g,
40
+ \`$1\${neutralColor === 'neutral' ? 'old-neutral' : neutralColor}$2\`
69
41
  );
70
42
  }
71
- }
72
- if (localStorage.getItem('${site.name}-ui-neutral')) {
73
- let neutralColor = localStorage.getItem('${site.name}-ui-neutral');
74
- html = html.replace(
75
- /(--ui-color-neutral-\\d{2,3}:\\s*var\\(--color-)${appConfig.ui.colors.neutral}(-\\d{2,3}.*?\\))/g,
76
- \`$1\${neutralColor === 'neutral' ? 'old-neutral' : neutralColor}$2\`
77
- );
78
- }
79
43
 
80
- document.querySelector('style#nuxt-ui-colors').innerHTML = html;
44
+ colorsEl.innerHTML = html;
45
+ }
81
46
  `.replace(/\s+/g, ' '),
82
47
  type: 'text/javascript',
83
48
  tagPriority: -1
84
49
  }, {
85
50
  innerHTML: `
86
51
  if (localStorage.getItem('${site.name}-ui-radius')) {
87
- document.querySelector('style#nuxt-ui-radius').innerHTML = ':root { --ui-radius: ' + localStorage.getItem('${site.name}-ui-radius') + 'rem; }';
52
+ document.getElementById('${site.name}-ui-radius').innerHTML = ':root { --ui-radius: ' + localStorage.getItem('${site.name}-ui-radius') + 'rem; }';
88
53
  }
89
54
  `.replace(/\s+/g, ' '),
90
55
  type: 'text/javascript',
@@ -92,18 +57,24 @@ export default defineNuxtPlugin({
92
57
  }, {
93
58
  innerHTML: `
94
59
  if (localStorage.getItem('${site.name}-ui-black-as-primary') === 'true') {
95
- document.querySelector('style#nuxt-ui-black-as-primary').innerHTML = ':root { --ui-primary: black; } .dark { --ui-primary: white; }';
60
+ document.getElementById('${site.name}-ui-black-as-primary').innerHTML = ':root { --ui-primary: black; } .dark { --ui-primary: white; }';
96
61
  } else {
97
- document.querySelector('style#nuxt-ui-black-as-primary').innerHTML = '';
62
+ document.getElementById('${site.name}-ui-black-as-primary').innerHTML = '';
98
63
  }
99
64
  `.replace(/\s+/g, ' ')
100
65
  }, {
101
- innerHTML: `
102
- if (localStorage.getItem('${site.name}-ui-font')) {
103
- const font = localStorage.getItem('${site.name}-ui-font');
104
- document.querySelector('style#nuxt-ui-font').innerHTML = ':root { --font-sans: \\'' + font + '\\', sans-serif; }';
105
- }
106
- `.replace(/\s+/g, ' ')
66
+ innerHTML: [
67
+ `if (localStorage.getItem('${site.name}-ui-font')) {`,
68
+ `var font = localStorage.getItem('${site.name}-ui-font');`,
69
+ `document.getElementById('${site.name}-ui-font').innerHTML = ':root { --font-sans: \\'' + font + '\\', sans-serif; }';`,
70
+ `if (font !== 'Public Sans') {`,
71
+ `var lnk = document.createElement('link');`,
72
+ `lnk.rel = 'stylesheet';`,
73
+ `lnk.href = 'https://fonts.googleapis.com/css2?family=' + encodeURIComponent(font) + ':wght@400;500;600;700&display=swap';`,
74
+ `lnk.id = 'font-' + font.toLowerCase().replace(/\\s+/g, '-');`,
75
+ `document.head.appendChild(lnk);`,
76
+ `}}`
77
+ ].join(' ')
107
78
  }]
108
79
  })
109
80
  }
@@ -4,6 +4,8 @@ if (!page.value) {
4
4
  throw createError({ status: 404, statusText: 'Page not found', fatal: true })
5
5
  }
6
6
 
7
+ const site = useSiteConfig()
8
+
7
9
  const title = page.value.seo?.title || page.value.title
8
10
  const description = page.value.seo?.description || page.value.description
9
11
 
@@ -15,9 +17,10 @@ useSeoMeta({
15
17
  ogDescription: description
16
18
  })
17
19
 
18
- defineOgImageComponent('Nuxt', {
20
+ defineOgImage('Docs', {
19
21
  title,
20
- description
22
+ description,
23
+ siteName: site.name
21
24
  })
22
25
  </script>
23
26
 
@@ -6,6 +6,8 @@ if (!page.value) {
6
6
  throw createError({ status: 404, statusText: 'Page not found', fatal: true })
7
7
  }
8
8
 
9
+ const site = useSiteConfig()
10
+
9
11
  const title = page.value.seo?.title || page.value.title
10
12
  const description = page.value.seo?.description || page.value.description
11
13
 
@@ -16,9 +18,10 @@ useSeoMeta({
16
18
  ogDescription: description
17
19
  })
18
20
 
19
- defineOgImageComponent('Nuxt', {
21
+ defineOgImage('Docs', {
20
22
  title,
21
- description
23
+ description,
24
+ siteName: site.name
22
25
  })
23
26
 
24
27
  const { data: versions } = page.value.releases
@@ -6,10 +6,6 @@ export interface ExtendedButtonProps extends ButtonProps {
6
6
 
7
7
  declare module 'nuxt/schema' {
8
8
  interface AppConfig {
9
- vercelAnalytics: {
10
- enable: boolean
11
- debug: boolean
12
- }
13
9
  seo: {
14
10
  titleTemplate: string
15
11
  title: string
@@ -71,7 +67,7 @@ declare module 'nuxt/schema' {
71
67
  * 在文档侧边栏中显示“使用 AI 进行解释”按钮。
72
68
  * @default true
73
69
  */
74
- explainWithAi?: boolean
70
+ explainWithAi: boolean
75
71
  /**
76
72
  * 显示的常见问题解答问题。
77
73
  * @example 简单格式: ['如何安装?', '如何配置?']
@@ -97,16 +93,6 @@ declare module 'nuxt/schema' {
97
93
  * @default 'AI 助手'
98
94
  */
99
95
  title: string
100
- /**
101
- * 折叠按钮的文本。
102
- * @default '折叠‘
103
- */
104
- collapse: string
105
- /**
106
- * 展开按钮的文本。
107
- * @default '展开'
108
- */
109
- expand: string
110
96
  /**
111
97
  * 清除聊天记录按钮的文本。
112
98
  * @default '清除聊天记录'
@@ -117,26 +103,6 @@ declare module 'nuxt/schema' {
117
103
  * @default '关闭'
118
104
  */
119
105
  close: string
120
- /**
121
- * 加载时的提示文本。
122
- * @default 'Loading...'
123
- */
124
- loading: string
125
- /**
126
- * 询问任何事情文本
127
- * @default '问我任何事情...'
128
- */
129
- askAnything: string
130
- /**
131
- * 询问任何事情描述文本
132
- * @default '我可以帮助您浏览文档、解释概念并回答您的问题。'
133
- */
134
- askMeAnythingDescription: string
135
- /**
136
- * FAQ 建议标题文本。
137
- * @default 'FAQ 建议'
138
- */
139
- faq: string
140
106
  /**
141
107
  * 浮动输入框的占位符文本。
142
108
  * @default '输入你的问题...'
@@ -152,19 +118,9 @@ declare module 'nuxt/schema' {
152
118
  * @default '与 AI 聊天'
153
119
  */
154
120
  trigger: string
155
- /**
156
- * 思考时的提示文本。
157
- * @default '思考中...'
158
- */
159
- streaming: string
160
- /**
161
- * 思考后的提示文本。
162
- * @default '思考过程'
163
- */
164
- streamed: string
165
121
  /**
166
122
  * 使用 AI 进行解释按钮的文本。
167
- * @default '用 AI 解释此页面
123
+ * @default '用 AI 解释此页面'
168
124
  */
169
125
  explainWithAi: string
170
126
  }
@@ -172,11 +128,6 @@ declare module 'nuxt/schema' {
172
128
  * 图标配置。
173
129
  */
174
130
  icons: {
175
- /**
176
- * 加载时的图标。
177
- * @default i-lucide-loader
178
- */
179
- loading: string
180
131
  /**
181
132
  * AI 聊天触发按钮和滑出层头部的图标。
182
133
  * @default 'i-custom-ai'
@@ -184,28 +135,28 @@ declare module 'nuxt/schema' {
184
135
  trigger: string
185
136
  /**
186
137
  * "使用 AI 进行解释" 按钮的图标。
187
- * @default 'i-lucide-brain'
138
+ * @default 'i-lucide-bot-message-square'
188
139
  */
189
140
  explain: string
190
141
  /**
191
142
  * 思考时的图标。
192
- * @default ui.icons.chevronDown
143
+ * @default 'i-lucide-brain'
193
144
  */
194
- streaming: string
145
+ reasoning: string
195
146
  /**
196
147
  * 清除聊天记录按钮的图标。
197
- * @default 'i-lucide-trash-2'
148
+ * @default 'i-lucide-list-x'
198
149
  */
199
150
  clearChat: string
200
151
  /**
201
152
  * 关闭按钮的图标。
202
- * @default 'i-lucide-x'
153
+ * @default 'i-lucide-panel-right-close'
203
154
  */
204
155
  close: string
205
156
  /**
206
157
  * 用于映射不同 AI 提供商的图标。
207
158
  * @example { mistral: 'i-simple-icons-mistralai' }
208
- * @default { xxx: 'i-simple-xxx', mistral: 'i-simple-icons-mistralai', kwaipilot: 'i-lucide-wand', zai: 'i-lucide-wand' }
159
+ * @default { deepseek: 'i-hugeicons:deepseek', alibaba: 'i-hugeicons:qwen', zai: 'i-simple-icons:zig', moonshotai: 'i-hugeicons:kimi-ai', xai: 'i-hugeicons:grok-02' }
209
160
  */
210
161
  providers: Record<string, string>
211
162
  }
package/content.config.ts CHANGED
@@ -47,6 +47,7 @@ const collections: Record<string, DefinedCollection> = {
47
47
  exclude: ['index.md']
48
48
  },
49
49
  schema: z.object({
50
+ index: z.boolean().optional(),
50
51
  links: z.array(Button),
51
52
  category: z.string().optional(),
52
53
  navigation: z.object({