Skip to content

Commit d5c57df

Browse files
committed
feat(examples): add shadcn registry blocks
1 parent 6d8a926 commit d5c57df

File tree

12 files changed

+970
-36
lines changed

12 files changed

+970
-36
lines changed

apps/registry/server/routes/[component].json.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ interface RegistryItemSchema {
4040
dependencies?: string[]
4141
devDependencies?: string[]
4242
registryDependencies?: string[]
43-
files?: Array<Pick<RegistryFile, 'path' | 'type'>>
43+
files?: Array<Pick<RegistryFile, 'path' | 'type' | 'target'>>
4444
[k: string]: unknown
4545
}
4646

@@ -103,7 +103,7 @@ export default eventHandler(async (event) => {
103103

104104
const packageDir = join(process.cwd(), '..', '..', 'packages', 'elements')
105105
const srcDir = join(packageDir, 'src')
106-
const examplesDir = join(process.cwd(), '..', '..', 'apps', 'test', 'app', 'examples')
106+
const examplesDir = join(process.cwd(), '..', '..', 'packages', 'examples', 'src')
107107

108108
// Read package.json for dependencies
109109
const packageJsonPath = join(packageDir, 'package.json')
@@ -147,6 +147,7 @@ export default eventHandler(async (event) => {
147147
type: 'registry:component',
148148
path: `registry/default/ai-elements/${rel}`,
149149
content: parsedContent,
150+
target: `components/ai-elements/${rel}`,
150151
})
151152
}
152153

@@ -167,6 +168,7 @@ export default eventHandler(async (event) => {
167168
type: 'registry:block',
168169
path: `registry/default/examples/${name}`,
169170
content: parsedContent,
171+
target: `components/ai-elements/examples/${name}`,
170172
}
171173
}),
172174
)
@@ -195,7 +197,7 @@ export default eventHandler(async (event) => {
195197
type: 'registry:component',
196198
title: toTitle(group),
197199
description: `AI-powered ${group.replace('-', ' ')} components.`,
198-
files: groupToFiles.get(group)!.map(f => ({ path: f.path, type: f.type })),
200+
files: groupToFiles.get(group)!.map(f => ({ path: f.path, type: f.type, target: f.target })),
199201
}))
200202

201203
const exampleItems: RegistryItemSchema[] = exampleFiles.map((ef) => {
@@ -205,7 +207,7 @@ export default eventHandler(async (event) => {
205207
type: 'registry:block',
206208
title: `${toTitle(name)} Example`,
207209
description: `Example implementation of ${name.replace('-', ' ')}.`,
208-
files: [{ path: ef.path, type: ef.type }],
210+
files: [{ path: ef.path, type: ef.type, target: ef.target }],
209211
}
210212
})
211213

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<script setup lang="ts">
2+
import { Message, MessageAvatar, MessageContent } from '@repo/elements/message'
3+
import { Response } from '@repo/elements/response'
4+
import { onBeforeUnmount, onMounted, ref } from 'vue'
5+
6+
const assistantMessageTokens = [
7+
'To',
8+
' get',
9+
' the',
10+
' **',
11+
'weather',
12+
' in',
13+
' Tokyo',
14+
'**',
15+
' using',
16+
' an',
17+
' API',
18+
' call',
19+
',',
20+
' you',
21+
' can',
22+
' use',
23+
' the',
24+
' [',
25+
'OpenWeatherMap',
26+
'](',
27+
'https://openweathermap.org/api',
28+
')',
29+
' API',
30+
'.',
31+
' After',
32+
' signing',
33+
' up',
34+
',',
35+
' you',
36+
' can',
37+
' make',
38+
' a',
39+
' request',
40+
' to',
41+
' their',
42+
' API',
43+
':',
44+
'\n\n',
45+
'```',
46+
'bash',
47+
'\n',
48+
'curl',
49+
' -X',
50+
' GET',
51+
' "https://api.openweathermap.org/data/2.5/weather?q=Tokyo&appid=YOUR_API_KEY"',
52+
' \\',
53+
'\n',
54+
' --header',
55+
' "Content-Type:',
56+
' application/json"',
57+
' \\',
58+
'\n',
59+
' --header',
60+
' "Accept:',
61+
' application/json"',
62+
'\n',
63+
'```',
64+
'\n\n',
65+
'This',
66+
' will',
67+
' return',
68+
' a',
69+
' JSON',
70+
' object',
71+
' with',
72+
' the',
73+
' weather',
74+
' data',
75+
' for',
76+
' Tokyo',
77+
'.',
78+
]
79+
80+
const content = ref('')
81+
82+
let interval: number | undefined
83+
onMounted(() => {
84+
let currentContent = ''
85+
let index = 0
86+
interval = window.setInterval(() => {
87+
if (index < assistantMessageTokens.length) {
88+
currentContent += assistantMessageTokens[index]
89+
content.value = currentContent
90+
index++
91+
}
92+
else if (interval) {
93+
clearInterval(interval)
94+
interval = undefined
95+
}
96+
}, 100)
97+
})
98+
99+
onBeforeUnmount(() => {
100+
if (interval)
101+
clearInterval(interval)
102+
})
103+
</script>
104+
105+
<template>
106+
<div class="space-y-2">
107+
<Message from="user">
108+
<MessageContent>
109+
<Response>What is the weather in Tokyo?</Response>
110+
</MessageContent>
111+
<MessageAvatar
112+
name="Hayden Bleasel"
113+
src="https://github.com/haydenbleasel.png"
114+
/>
115+
</Message>
116+
117+
<Message from="assistant">
118+
<MessageContent>
119+
<Response>{{ content }}</Response>
120+
</MessageContent>
121+
<MessageAvatar name="OpenAI" src="https://github.com/openai.png" />
122+
</Message>
123+
</div>
124+
</template>

apps/test/app/pages/index.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import { Card, CardContent, CardHeader, CardTitle } from '@repo/shadcn-vue/components/ui/card'
33
import Conversation from '~/examples/conversation.vue'
4+
import MessageMarkdown from '~/examples/message-markdown.vue'
45
import Message from '~/examples/message.vue'
56
import PromptInput from '~/examples/prompt-input.vue'
67
import Response from '~/examples/response.vue'
@@ -10,6 +11,7 @@ const components = [
1011
{ name: 'PromptInput', Component: PromptInput },
1112
{ name: 'Conversation', Component: Conversation },
1213
{ name: 'Response', Component: Response },
14+
{ name: 'MessageMarkdown', Component: MessageMarkdown },
1315
]
1416
</script>
1517

packages/examples/package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
"private": true,
55
"description": "Examples for the repo.",
66
"exports": {
7-
"./*": "./src/*"
7+
"./*": "./src/*.ts"
8+
},
9+
"dependencies": {
10+
"@repo/elements": "workspace:*",
11+
"ai": "^5.0.29",
12+
"lucide-vue-next": "^0.542.0",
13+
"nanoid": "^5.1.5",
14+
"vue": "^3.5.21"
15+
},
16+
"devDependencies": {
17+
"typescript": "^5.9.2"
818
}
919
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
<script setup lang="ts">
2+
import { Conversation, ConversationContent, ConversationScrollButton } from '@repo/elements/conversation'
3+
import { Message, MessageAvatar, MessageContent } from '@repo/elements/message'
4+
import { nanoid } from 'nanoid'
5+
import { onMounted, onUnmounted, ref } from 'vue'
6+
7+
interface MessageItem {
8+
key: string
9+
value: string
10+
name: string
11+
avatar: string
12+
}
13+
14+
const messages: MessageItem[] = [
15+
{
16+
key: nanoid(),
17+
value: 'Hello, how are you?',
18+
name: 'Alex Johnson',
19+
avatar: 'https://github.com/haydenbleasel.png',
20+
},
21+
{
22+
key: nanoid(),
23+
value: 'I\'m good, thank you! How can I assist you today?',
24+
name: 'AI Assistant',
25+
avatar: 'https://github.com/openai.png',
26+
},
27+
{
28+
key: nanoid(),
29+
value: 'I\'m looking for information about your services.',
30+
name: 'Alex Johnson',
31+
avatar: 'https://github.com/haydenbleasel.png',
32+
},
33+
{
34+
key: nanoid(),
35+
value: 'Sure! We offer a variety of AI solutions. What are you interested in?',
36+
name: 'AI Assistant',
37+
avatar: 'https://github.com/openai.png',
38+
},
39+
{
40+
key: nanoid(),
41+
value: 'I\'m interested in natural language processing tools.',
42+
name: 'Alex Johnson',
43+
avatar: 'https://github.com/haydenbleasel.png',
44+
},
45+
{
46+
key: nanoid(),
47+
value: 'Great choice! We have several NLP APIs. Would you like a demo?',
48+
name: 'AI Assistant',
49+
avatar: 'https://github.com/openai.png',
50+
},
51+
{
52+
key: nanoid(),
53+
value: 'Yes, a demo would be helpful.',
54+
name: 'Alex Johnson',
55+
avatar: 'https://github.com/haydenbleasel.png',
56+
},
57+
{
58+
key: nanoid(),
59+
value: 'Alright, I can show you a sentiment analysis example. Ready?',
60+
name: 'AI Assistant',
61+
avatar: 'https://github.com/openai.png',
62+
},
63+
{
64+
key: nanoid(),
65+
value: 'Yes, please proceed.',
66+
name: 'Alex Johnson',
67+
avatar: 'https://github.com/haydenbleasel.png',
68+
},
69+
{
70+
key: nanoid(),
71+
value: 'Here is a sample: \'I love this product!\' → Positive sentiment.',
72+
name: 'AI Assistant',
73+
avatar: 'https://github.com/openai.png',
74+
},
75+
{
76+
key: nanoid(),
77+
value: 'Impressive! Can it handle multiple languages?',
78+
name: 'Alex Johnson',
79+
avatar: 'https://github.com/haydenbleasel.png',
80+
},
81+
{
82+
key: nanoid(),
83+
value: 'Absolutely, our models support over 20 languages.',
84+
name: 'AI Assistant',
85+
avatar: 'https://github.com/openai.png',
86+
},
87+
{
88+
key: nanoid(),
89+
value: 'How do I get started with the API?',
90+
name: 'Alex Johnson',
91+
avatar: 'https://github.com/haydenbleasel.png',
92+
},
93+
{
94+
key: nanoid(),
95+
value: 'You can sign up on our website and get an API key instantly.',
96+
name: 'AI Assistant',
97+
avatar: 'https://github.com/openai.png',
98+
},
99+
{
100+
key: nanoid(),
101+
value: 'Is there a free trial available?',
102+
name: 'Alex Johnson',
103+
avatar: 'https://github.com/haydenbleasel.png',
104+
},
105+
{
106+
key: nanoid(),
107+
value: 'Yes, we offer a 14-day free trial with full access.',
108+
name: 'AI Assistant',
109+
avatar: 'https://github.com/openai.png',
110+
},
111+
{
112+
key: nanoid(),
113+
value: 'What kind of support do you provide?',
114+
name: 'Alex Johnson',
115+
avatar: 'https://github.com/haydenbleasel.png',
116+
},
117+
{
118+
key: nanoid(),
119+
value: 'We provide 24/7 chat and email support for all users.',
120+
name: 'AI Assistant',
121+
avatar: 'https://github.com/openai.png',
122+
},
123+
{
124+
key: nanoid(),
125+
value: 'Thank you for the information!',
126+
name: 'Alex Johnson',
127+
avatar: 'https://github.com/haydenbleasel.png',
128+
},
129+
{
130+
key: nanoid(),
131+
value: 'You\'re welcome! Let me know if you have any more questions.',
132+
name: 'AI Assistant',
133+
avatar: 'https://github.com/openai.png',
134+
},
135+
]
136+
137+
const visibleMessages = ref<MessageItem[]>([])
138+
139+
let timer: number | null = null
140+
141+
onMounted(() => {
142+
let index = 0
143+
timer = window.setInterval(() => {
144+
const next = messages[index]
145+
if (!next) {
146+
if (timer !== null) {
147+
clearInterval(timer)
148+
timer = null
149+
}
150+
return
151+
}
152+
visibleMessages.value = [...visibleMessages.value, next]
153+
index += 1
154+
}, 500)
155+
})
156+
157+
onUnmounted(() => {
158+
if (timer !== null) {
159+
clearInterval(timer)
160+
timer = null
161+
}
162+
})
163+
</script>
164+
165+
<template>
166+
<div class="h-[498px] rounded-xl border">
167+
<Conversation class="relative h-full">
168+
<ConversationContent class="space-y-2">
169+
<Message v-for="(m, idx) in visibleMessages" :key="m.key" :from="idx % 2 === 0 ? 'user' : 'assistant'">
170+
<MessageContent>{{ m.value }}</MessageContent>
171+
<MessageAvatar :src="m.avatar" :name="m.name" />
172+
</Message>
173+
</ConversationContent>
174+
<ConversationScrollButton />
175+
</Conversation>
176+
</div>
177+
</template>

packages/examples/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export { default as Conversation } from './conversation.vue'
2+
export { default as MessageMarkdown } from './message-markdown.vue'
3+
export { default as Message } from './message.vue'
4+
export { default as PromptInput } from './prompt-input.vue'
5+
export { default as Response } from './response.vue'

0 commit comments

Comments
 (0)