Compare commits

...

7 Commits

Author SHA1 Message Date
Benjamin Canac
16a4b0bbb1 up 2025-05-15 16:48:25 +02:00
Benjamin Canac
dd34d49cd9 Merge branch 'v3' into docs/ai-search 2025-05-15 16:47:37 +02:00
Benjamin Canac
d29e1481f2 chore(deps): update @nuxt/ui-pro 2025-05-15 15:57:54 +02:00
Benjamin Canac
79aa161c6d docs(showcase): remove submit button 2025-05-15 15:42:25 +02:00
Benjamin Canac
94ea75f441 docs(content): update badges 2025-05-15 15:30:02 +02:00
Benjamin Canac
1820e3cc6f up 2025-05-09 19:10:05 +02:00
Benjamin Canac
450948646a docs(app): implement AI search 2025-05-09 18:26:28 +02:00
12 changed files with 323 additions and 131 deletions

View File

@@ -12,7 +12,6 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
})
const links = useLinks()
const searchLinks = useSearchLinks()
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
@@ -42,7 +41,6 @@ useServerSeoMeta({
useFaviconFromTheme()
const { frameworks, modules } = useSharedData()
const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation)
provide('navigation', mappedNavigation)
@@ -65,23 +63,7 @@ provide('navigation', mappedNavigation)
<template v-if="!route.path.startsWith('/examples')">
<Footer />
<ClientOnly>
<LazyUContentSearch
:links="searchLinks"
:files="files"
:groups="[{
id: 'framework',
label: 'Framework',
items: frameworks
}, {
id: 'module',
label: 'Module',
items: modules
}]"
:navigation="filteredNavigation"
:fuse="{ resultLimit: 100 }"
/>
</ClientOnly>
<Search :files="files" :navigation="filteredNavigation" />
</template>
</UApp>
</template>

View File

@@ -0,0 +1,199 @@
<script setup lang="ts">
import type { DefineComponent } from 'vue'
import type { ContentNavigationItem } from '@nuxt/content'
import { useChat } from '@ai-sdk/vue'
import ProseStreamPre from './prose/PreStream.vue'
const components = {
pre: ProseStreamPre as unknown as DefineComponent
}
interface ContentSearchFile {
id: string
title: string
titles: string[]
level: number
content: string
}
defineProps<{
files?: ContentSearchFile[]
navigation?: ContentNavigationItem[]
}>()
const { frameworks, modules } = useSharedData()
const { messages, input, handleSubmit, status, error, reload, setMessages } = useChat({
maxSteps: 2
})
const ai = ref(false)
const searchTerm = ref('')
const links = computed(() => [{
label: 'Ask AI',
icon: 'i-lucide-bot',
onSelect: (e: any) => {
e.preventDefault()
ai.value = true
}
}, {
label: 'Docs',
icon: 'i-lucide-square-play',
to: '/getting-started'
}, {
label: 'Components',
icon: 'i-lucide-square-code',
to: '/components'
}, {
icon: 'i-lucide-sparkles',
label: 'Pro > Features',
description: 'A collection of premium Vue components.',
to: '/pro'
}, {
icon: 'i-lucide-credit-card',
label: 'Pro > Pricing',
description: 'Free in development, buy when ready to launch.',
to: '/pro/pricing'
}, {
icon: 'i-lucide-panels-top-left',
label: 'Pro > Templates',
description: 'Official templates made with Nuxt UI Pro.',
to: '/pro/templates'
}, {
icon: 'i-lucide-circle-check',
label: 'Pro > Activate',
description: 'Enable Nuxt UI Pro in your production projects.',
to: '/pro/activate'
}, {
label: 'Figma',
icon: 'i-simple-icons-figma',
to: '/figma'
}, {
icon: 'i-lucide-presentation',
label: 'Community > Showcase',
description: 'Check out some of the amazing projects built with Nuxt UI.',
to: '/showcase'
}, {
label: 'Community > Contribution',
description: 'A comprehensive guide on contributing to Nuxt UI, including project structure, development workflow, and best practices.',
icon: 'i-lucide-git-pull-request-arrow',
to: '/getting-started/contribution'
}, {
label: 'Community > Roadmap',
description: 'Track our development progress in real-time.',
icon: 'i-lucide-map',
to: '/roadmap'
}, {
label: 'Community > Devtools',
description: 'Integrate Nuxt UI with Nuxt Devtools with Compodium.',
icon: 'i-lucide-code',
to: 'https://github.com/romhml/compodium',
target: '_blank'
}, {
label: 'Community > Team',
description: 'Meet the team behind Nuxt UI.',
icon: 'i-lucide-users',
to: '/team'
}, {
label: 'Releases',
icon: 'i-lucide-rocket',
to: 'https://github.com/nuxt/ui/releases',
target: '_blank'
}])
const groups = computed(() => [{
id: 'ai',
label: 'AI',
ignoreFilter: true,
items: [{
label: searchTerm.value ? `Ask Nuxt AI for “${searchTerm.value}` : 'Ask Nuxt AI',
icon: 'i-lucide-bot',
onSelect: (e: any) => {
e.preventDefault()
ai.value = true
if (searchTerm.value) {
setMessages([{
id: '1',
role: 'user',
content: searchTerm.value
}])
reload()
}
}
}]
}, {
id: 'framework',
label: 'Framework',
items: frameworks.value
}, {
id: 'module',
label: 'Module',
items: modules.value
}])
function onClose(e: Event) {
console.log('onClose')
e.preventDefault()
ai.value = false
}
</script>
<template>
<LazyUContentSearch
v-model:search-term="searchTerm"
:links="links"
:files="files"
:groups="groups"
:navigation="navigation"
:fuse="{ resultLimit: 100 }"
>
<template v-if="ai" #content>
<UChatPalette>
<UChatMessages
:messages="messages"
:status="status"
:user="{ side: 'left', variant: 'naked', icon: 'i-lucide-user' }"
:assistant="{ icon: 'i-lucide-bot' }"
>
<template #content="{ message }">
<MDCCached
v-if="message.toolInvocations?.[0]?.state === 'result'"
:value="message.toolInvocations?.[0]?.result"
:cache-key="message.id"
unwrap="p"
:components="components"
:parser-options="{ highlight: false }"
/>
<MDCCached
v-else-if="message.content.length > 0"
:value="message.content"
:cache-key="message.id"
unwrap="p"
:components="components"
:parser-options="{ highlight: false }"
/>
<span v-else class="italic font-light">
Searching documentation...
</span>
</template>
</UChatMessages>
<template #prompt>
<UChatPrompt
v-model="input"
icon="i-lucide-search"
variant="naked"
:error="error"
@submit="handleSubmit"
@close="onClose"
/>
</template>
</UChatPalette>
</template>
</LazyUContentSearch>
</template>

View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
import { ShikiCachedRenderer } from 'shiki-stream/vue'
const colorMode = useColorMode()
const highlighter = await useHighlighter()
const props = defineProps<{
code: string
language: string
class?: string
meta?: string
}>()
const trimmedCode = computed(() => {
return props.code.trim().replace(/`+$/, '')
})
const lang = computed(() => {
switch (props.language) {
case 'vue':
return 'vue'
case 'javascript':
return 'js'
case 'typescript':
return 'ts'
case 'css':
return 'css'
default:
return props.language
}
})
const key = computed(() => {
return `${lang.value}-${colorMode.value}`
})
</script>
<template>
<ProsePre v-bind="props">
<ShikiCachedRenderer
:key="key"
:highlighter="highlighter"
:code="trimmedCode"
:lang="lang"
:theme="colorMode.value === 'dark' ? 'material-theme-palenight' : 'material-theme-lighter'"
/>
</ProsePre>
</template>

View File

@@ -0,0 +1,21 @@
import { createHighlighter, type HighlighterGeneric } from 'shiki'
import { createJavaScriptRegexEngine } from 'shiki/engine-javascript.mjs'
let highlighter: HighlighterGeneric<any, any> | null = null
let promise: Promise<HighlighterGeneric<any, any>> | null = null
export const useHighlighter = async () => {
if (!promise) {
promise = createHighlighter({
langs: ['vue', 'js', 'ts', 'css', 'html', 'json', 'yaml', 'markdown', 'bash'],
themes: ['material-theme-palenight', 'material-theme-lighter'],
engine: createJavaScriptRegexEngine()
})
}
if (!highlighter) {
highlighter = await promise
}
return highlighter
}

View File

@@ -1,66 +0,0 @@
export function useSearchLinks() {
return [{
label: 'Docs',
icon: 'i-lucide-square-play',
to: '/getting-started'
}, {
label: 'Components',
icon: 'i-lucide-square-code',
to: '/components'
}, {
icon: 'i-lucide-sparkles',
label: 'Pro > Features',
description: 'A collection of premium Vue components.',
to: '/pro'
}, {
icon: 'i-lucide-credit-card',
label: 'Pro > Pricing',
description: 'Free in development, buy when ready to launch.',
to: '/pro/pricing'
}, {
icon: 'i-lucide-panels-top-left',
label: 'Pro > Templates',
description: 'Official templates made with Nuxt UI Pro.',
to: '/pro/templates'
}, {
icon: 'i-lucide-circle-check',
label: 'Pro > Activate',
description: 'Enable Nuxt UI Pro in your production projects.',
to: '/pro/activate'
}, {
label: 'Figma',
icon: 'i-simple-icons-figma',
to: '/figma'
}, {
icon: 'i-lucide-presentation',
label: 'Community > Showcase',
description: 'Check out some of the amazing projects built with Nuxt UI.',
to: '/showcase'
}, {
label: 'Community > Contribution',
description: 'A comprehensive guide on contributing to Nuxt UI, including project structure, development workflow, and best practices.',
icon: 'i-lucide-git-pull-request-arrow',
to: '/getting-started/contribution'
}, {
label: 'Community > Roadmap',
description: 'Track our development progress in real-time.',
icon: 'i-lucide-map',
to: '/roadmap'
}, {
label: 'Community > Devtools',
description: 'Integrate Nuxt UI with Nuxt Devtools with Compodium.',
icon: 'i-lucide-code',
to: 'https://github.com/romhml/compodium',
target: '_blank'
}, {
label: 'Community > Team',
description: 'Meet the team behind Nuxt UI.',
icon: 'i-lucide-users',
to: '/team'
}, {
label: 'Releases',
icon: 'i-lucide-rocket',
to: 'https://github.com/nuxt/ui/releases',
target: '_blank'
}]
}

View File

@@ -15,7 +15,6 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
})
const links = useLinks()
const searchLinks = useSearchLinks()
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
@@ -49,7 +48,6 @@ useServerSeoMeta({
useFaviconFromTheme()
const { frameworks, modules } = useSharedData()
const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation)
provide('navigation', mappedNavigation)
@@ -67,22 +65,6 @@ provide('navigation', mappedNavigation)
<Footer />
<ClientOnly>
<LazyUContentSearch
:links="searchLinks"
:files="files"
:groups="[{
id: 'framework',
label: 'Framework',
items: frameworks
}, {
id: 'module',
label: 'Module',
items: modules
}]"
:navigation="filteredNavigation"
:fuse="{ resultLimit: 100 }"
/>
</ClientOnly>
<Search :files="files" :navigation="filteredNavigation" />
</UApp>
</template>

View File

@@ -67,17 +67,6 @@ defineOgImageComponent('Docs', {
</li>
</ul>
</div>
<div class="flex justify-center -mb-[36px]">
<UButton
label="Submit your project"
trailing-icon="i-lucide-plus"
color="neutral"
size="lg"
to="https://github.com/nuxt/ui/edit/v3/docs/content/showcase.yml"
target="_blank"
/>
</div>
</UPageHero>
</UMain>
</template>

View File

@@ -21,10 +21,10 @@ Use the `items` prop as an array of objects with the following properties:
- `icon?: string`{lang="ts-type"}
- `avatar?: AvatarProps`{lang="ts-type"}
- `badge?: string | number | BadgeProps`{lang="ts-type"}
- `tooltip?: TooltipProps`{lang="ts-type"} :badge{label="Soon"}
- `tooltip?: TooltipProps`{lang="ts-type"}
- `trailingIcon?: string`{lang="ts-type"}
- `type?: 'label' | 'link'`{lang="ts-type"}
- `collapsible?: boolean`{lang="ts-type"} :badge{label="Soon"}
- `collapsible?: boolean`{lang="ts-type"}
- `defaultOpen?: boolean`{lang="ts-type"}
- `open?: boolean`{lang="ts-type"}
- `value?: string`{lang="ts-type"}
@@ -885,7 +885,7 @@ You can inspect the DOM to see each item's content being rendered.
## Examples
### With tooltips in items :badge{label="Soon" class="align-text-top"}
### With tooltips in items :badge{label="New" class="align-text-top"}
You can use the `tooltip` property to display a [Tooltip](/components/tooltip) around an item. This can be useful when the menu is collapsed.

View File

@@ -136,7 +136,7 @@ props:
---
::
### Tooltip :badge{label="Soon" class="align-text-top"}
### Tooltip :badge{label="New" class="align-text-top"}
Use the `tooltip` prop to display a [Tooltip](/components/tooltip) around the Slider thumbs with the current value. You can set it to `true` for default behavior or pass an object to customize it with any property from the [Tooltip](/components/tooltip#props) component.

View File

@@ -11,7 +11,7 @@
"@nuxt/content": "^3.5.1",
"@nuxt/image": "^1.10.0",
"@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@a30de4d",
"@nuxt/ui-pro": "^3.1.2",
"@nuxthub/core": "^0.8.27",
"@nuxtjs/plausible": "^1.2.0",
"@octokit/rest": "^21.1.1",
@@ -27,6 +27,7 @@
"nuxt-llms": "^0.1.2",
"nuxt-og-image": "^5.1.3",
"prettier": "^3.5.3",
"shiki-stream": "^0.1.2",
"shiki-transformer-color-highlight": "^1.0.0",
"sortablejs": "^1.15.6",
"superstruct": "^2.0.2",

View File

@@ -1,5 +1,6 @@
import { streamText } from 'ai'
import { streamText, tool } from 'ai'
import { createWorkersAI } from 'workers-ai-provider'
import { z } from 'zod'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
@@ -12,11 +13,30 @@ export default defineEventHandler(async (event) => {
}
: undefined
const workersAI = createWorkersAI({ binding: hubAI(), gateway })
const autorag = hubAutoRAG('ui3')
return streamText({
model: workersAI('@cf/meta/llama-3.2-3b-instruct'),
maxTokens: 10000,
system: 'You are a helpful assistant that can answer questions and help.',
messages
model: workersAI('@cf/meta/llama-3.3-70b-instruct-fp8-fast'),
messages,
system: `You are a helpful assistant for Nuxt UI. Check your knowledge base before answering any questions.
Only respond to questions using information from tool calls.
if no relevant information is found in the tool calls, respond, "Sorry, I don't know."
Format your markdown response using the following rules:
- Use the vue lang for code blocks syntax highlighting.
- Don't use markdown headings.
`,
tools: {
searchDocumentation: tool({
description: `search the documentation for information to answer questions.`,
parameters: z.object({
question: z.string().describe('the users question')
}),
execute: async ({ question }) => {
return (await autorag.aiSearch({
query: question
})).response
}
})
}
}).toDataStreamResponse()
})

32
pnpm-lock.yaml generated
View File

@@ -249,8 +249,8 @@ importers:
specifier: workspace:*
version: link:..
'@nuxt/ui-pro':
specifier: https://pkg.pr.new/@nuxt/ui-pro@a30de4d
version: https://pkg.pr.new/@nuxt/ui-pro@a30de4d(@babel/parser@7.27.2)(joi@17.13.3)(magicast@0.3.5)(superstruct@2.0.2)(typescript@5.8.3)(valibot@1.1.0(typescript@5.8.3))(vue@3.5.14(typescript@5.8.3))(yup@1.6.1)(zod@3.24.4)
specifier: ^3.1.2
version: 3.1.2(@babel/parser@7.27.2)(joi@17.13.3)(magicast@0.3.5)(superstruct@2.0.2)(typescript@5.8.3)(valibot@1.1.0(typescript@5.8.3))(vue@3.5.14(typescript@5.8.3))(yup@1.6.1)(zod@3.24.4)
'@nuxthub/core':
specifier: ^0.8.27
version: 0.8.27(db0@0.3.2(better-sqlite3@11.10.0))(ioredis@5.6.1)(magicast@0.3.5)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.1))
@@ -296,6 +296,9 @@ importers:
prettier:
specifier: ^3.5.3
version: 3.5.3
shiki-stream:
specifier: ^0.1.2
version: 0.1.2(react@19.1.0)(vue@3.5.14(typescript@5.8.3))
shiki-transformer-color-highlight:
specifier: ^1.0.0
version: 1.0.0
@@ -1398,9 +1401,8 @@ packages:
vitest:
optional: true
'@nuxt/ui-pro@https://pkg.pr.new/@nuxt/ui-pro@a30de4d':
resolution: {tarball: https://pkg.pr.new/@nuxt/ui-pro@a30de4d}
version: 3.1.1
'@nuxt/ui-pro@3.1.2':
resolution: {integrity: sha512-oGpmtCcAX0pEPbkWmX5bvvIZ8t3PWKqmxIYbuGJ9aCEMtM6+zS5g1LNBTl7RygyrWPV+sG+lF3r6OMytSnZPZQ==}
peerDependencies:
joi: ^17.13.0
superstruct: ^2.0.0
@@ -6465,6 +6467,17 @@ packages:
resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==}
engines: {node: '>= 0.4'}
shiki-stream@0.1.2:
resolution: {integrity: sha512-VtzJT2Sn2vwFoJEhKv71/M6Cl7e/m6p0vIVDgJsYKUpV7E+0zayJsuVuU2ltiSEeWUrqncHSPx8i/xKrRqK6Mw==}
peerDependencies:
react: ^19.0.0
vue: ^3.2.0
peerDependenciesMeta:
react:
optional: true
vue:
optional: true
shiki-transformer-color-highlight@1.0.0:
resolution: {integrity: sha512-WwoXcbSQF4Hcfu/F4V7jvZxqmix4f8KNYNlYiNwz0w9RcABqhdNQOLeRRw3VNV2LBYdNcNR0qd9HVnlV+D+uzg==}
@@ -9068,7 +9081,7 @@ snapshots:
- typescript
- yaml
'@nuxt/ui-pro@https://pkg.pr.new/@nuxt/ui-pro@a30de4d(@babel/parser@7.27.2)(joi@17.13.3)(magicast@0.3.5)(superstruct@2.0.2)(typescript@5.8.3)(valibot@1.1.0(typescript@5.8.3))(vue@3.5.14(typescript@5.8.3))(yup@1.6.1)(zod@3.24.4)':
'@nuxt/ui-pro@3.1.2(@babel/parser@7.27.2)(joi@17.13.3)(magicast@0.3.5)(superstruct@2.0.2)(typescript@5.8.3)(valibot@1.1.0(typescript@5.8.3))(vue@3.5.14(typescript@5.8.3))(yup@1.6.1)(zod@3.24.4)':
dependencies:
'@ai-sdk/vue': 1.2.12(vue@3.5.14(typescript@5.8.3))(zod@3.24.4)
'@nuxt/kit': 3.17.3(magicast@0.3.5)
@@ -15119,6 +15132,13 @@ snapshots:
shell-quote@1.8.2: {}
shiki-stream@0.1.2(react@19.1.0)(vue@3.5.14(typescript@5.8.3)):
dependencies:
'@shikijs/core': 3.4.0
optionalDependencies:
react: 19.1.0
vue: 3.5.14(typescript@5.8.3)
shiki-transformer-color-highlight@1.0.0:
dependencies:
'@shikijs/core': 3.4.0