├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── biome.json ├── components.json ├── contentlayer.config.js ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── avatar-1.png ├── avatar-2.png ├── chain-workflow.svg ├── og.jpg ├── orchestrator-workflow.svg ├── parallelization-workflow.svg ├── r │ ├── app-01.json │ ├── app-02.json │ ├── app-03.json │ ├── base-handle.json │ ├── base-node.json │ ├── chat-01.json │ ├── chat-02.json │ ├── chat-03.json │ ├── chat-demo.json │ ├── chat-input-demo.json │ ├── chat-input-unstyled-initial-rows.json │ ├── chat-input-unstyled.json │ ├── chat-input.json │ ├── chat-message-area-demo-alignment.json │ ├── chat-message-area-demo-streaming.json │ ├── chat-message-area-demo.json │ ├── chat-message-area.json │ ├── chat-message-demo-avatar-image.json │ ├── chat-message-demo-bubble.json │ ├── chat-message-demo-default.json │ ├── chat-message-demo-full.json │ ├── chat-message-demo-markdown-content.json │ ├── chat-message-demo-without-avatar.json │ ├── chat-message-demo.json │ ├── chat-message.json │ ├── editable-handle-demo.json │ ├── editable-handle.json │ ├── flow-chain.json │ ├── flow-orchestrator.json │ ├── flow-parallelization.json │ ├── flow-routing.json │ ├── generate-text-node-demo.json │ ├── generate-text-node.json │ ├── index.json │ ├── jsx-renderer-demo.json │ ├── jsx-renderer.json │ ├── jsx-utils.json │ ├── labeled-handle.json │ ├── markdown-content-demo.json │ ├── markdown-content.json │ ├── markdown-streaming-demo.json │ ├── model-selector-demo.json │ ├── model-selector-disabled-demo.json │ ├── model-selector.json │ ├── node-header-status.json │ ├── node-header.json │ ├── prompt-crafter-node-demo.json │ ├── prompt-crafter-node.json │ ├── resizable-node-demo.json │ ├── resizable-node.json │ ├── status-edge-demo.json │ ├── status-edge.json │ ├── text-input-node-demo.json │ ├── text-input-node.json │ ├── use-scroll-to-bottom.json │ ├── use-textarea-resize.json │ ├── visualize-text-node-demo.json │ └── visualize-text-node.json └── routing-workflow.svg ├── src ├── __registry__ │ ├── blocks │ │ ├── app-01 │ │ │ └── page.tsx │ │ ├── app-02 │ │ │ └── page.tsx │ │ ├── app-03 │ │ │ └── page.tsx │ │ ├── chat-01 │ │ │ └── page.tsx │ │ ├── chat-02 │ │ │ └── page.tsx │ │ ├── chat-03 │ │ │ └── page.tsx │ │ ├── flow-01 │ │ │ └── page.tsx │ │ ├── flow-chain │ │ │ └── page.tsx │ │ ├── flow-orchestrator │ │ │ └── page.tsx │ │ ├── flow-parallelization │ │ │ └── page.tsx │ │ └── flow-routing │ │ │ └── page.tsx │ └── index.tsx ├── app │ ├── (app) │ │ ├── ai-agents │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── blocks │ │ │ ├── [...categories] │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── docs │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── examples │ │ │ ├── chat-01 │ │ │ │ └── page.tsx │ │ │ ├── chat-02 │ │ │ │ └── page.tsx │ │ │ ├── chat-03 │ │ │ │ └── page.tsx │ │ │ └── chat-04 │ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── playground │ │ │ └── page.tsx │ ├── (canvas) │ │ └── canvas │ │ │ └── page.tsx │ ├── (view) │ │ └── view │ │ │ └── [name] │ │ │ └── page.tsx │ ├── api │ │ ├── ai │ │ │ ├── chat │ │ │ │ └── route.ts │ │ │ ├── generate │ │ │ │ └── route.ts │ │ │ ├── persona │ │ │ │ └── route.ts │ │ │ └── x-profile │ │ │ │ └── route.ts │ │ └── workflow │ │ │ └── execute │ │ │ └── route.ts │ ├── favicon.ico │ ├── fonts │ │ ├── GeistMonoVF.woff │ │ └── GeistVF.woff │ ├── layout.tsx │ ├── not-found.tsx │ └── sitemap.ts ├── components │ ├── agents │ │ └── workflows │ │ │ ├── chain-workflow.tsx │ │ │ ├── hooks │ │ │ └── use-workflow-animation.ts │ │ │ ├── orchestrator-workflow.tsx │ │ │ ├── parallelization-workflow.tsx │ │ │ └── routing-workflow.tsx │ ├── announcement.tsx │ ├── blocks │ │ ├── block-display.tsx │ │ ├── block-viewer.tsx │ │ └── blocks-nav.tsx │ ├── callout.tsx │ ├── code-block-command.tsx │ ├── code-block-wrapper.tsx │ ├── command-menu.tsx │ ├── component-preview-simple.tsx │ ├── component-preview.tsx │ ├── component-source.tsx │ ├── copy-button.tsx │ ├── flow │ │ └── devtools.tsx │ ├── icons │ │ ├── github-icon.tsx │ │ ├── logo-icon.tsx │ │ └── x-icon.tsx │ ├── layout │ │ ├── docs-nav.tsx │ │ ├── header.tsx │ │ ├── main-nav.tsx │ │ ├── mobile-nav.tsx │ │ ├── page-header.tsx │ │ └── site-footer.tsx │ ├── mdx-components.tsx │ ├── pager.tsx │ ├── providers │ │ └── theme-provider.tsx │ ├── theme │ │ └── theme-toggle.tsx │ ├── toc.tsx │ ├── ui │ │ ├── accordion.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── popover.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ └── tooltip.tsx │ └── v0-button.tsx ├── config │ ├── docs.ts │ └── site.ts ├── content │ └── docs │ │ ├── blocks │ │ └── index.mdx │ │ ├── components │ │ ├── chat-input.mdx │ │ ├── chat-message-area.mdx │ │ ├── chat-message.mdx │ │ ├── jsx-renderer.mdx │ │ ├── markdown-content.mdx │ │ └── model-selector.mdx │ │ ├── index.mdx │ │ ├── installation │ │ └── index.mdx │ │ └── react-flow │ │ ├── components │ │ ├── editable-handle.mdx │ │ ├── generate-text-node.mdx │ │ ├── prompt-crafter-node.mdx │ │ ├── resizable-node.mdx │ │ ├── status-edge.mdx │ │ ├── text-input-node.mdx │ │ └── visualize-text-node.mdx │ │ └── index.mdx ├── hooks │ ├── use-config.ts │ ├── use-copy-to-clipboard.ts │ └── use-mobile.tsx ├── lib │ ├── blocks.ts │ ├── events.ts │ ├── highlight-code.ts │ ├── registry.ts │ ├── rehype-component.ts │ ├── rehype-npm-command.ts │ ├── toc.ts │ └── utils.ts ├── registry │ ├── blocks │ │ ├── app-01 │ │ │ ├── canvas │ │ │ │ └── page.tsx │ │ │ ├── components │ │ │ │ ├── chat-dialog.tsx │ │ │ │ ├── code-editor.tsx │ │ │ │ ├── copy-button.tsx │ │ │ │ ├── editor-layout.tsx │ │ │ │ ├── editor-toolbar.tsx │ │ │ │ ├── preview.tsx │ │ │ │ └── versions.tsx │ │ │ ├── hooks │ │ │ │ └── generation-store.ts │ │ │ ├── page.tsx │ │ │ └── route.ts │ │ ├── app-02 │ │ │ ├── components │ │ │ │ └── persona-display.tsx │ │ │ ├── lib │ │ │ │ ├── example-businesses.ts │ │ │ │ └── persona.ts │ │ │ ├── page.tsx │ │ │ └── route.ts │ │ ├── app-03 │ │ │ ├── components │ │ │ │ ├── profile-generate-dialog.tsx │ │ │ │ ├── theme-toggle.tsx │ │ │ │ ├── toolbar.tsx │ │ │ │ └── x-preview.tsx │ │ │ ├── layout.tsx │ │ │ ├── lib │ │ │ │ ├── profile-examples.ts │ │ │ │ └── x.ts │ │ │ ├── page.tsx │ │ │ └── route.ts │ │ ├── chat-01 │ │ │ ├── components │ │ │ │ ├── chat.tsx │ │ │ │ ├── nav-user.tsx │ │ │ │ └── sidebar-app.tsx │ │ │ ├── page.tsx │ │ │ └── route.ts │ │ ├── chat-02 │ │ │ ├── components │ │ │ │ └── app-sidebar.tsx │ │ │ ├── page.tsx │ │ │ └── route.ts │ │ ├── chat-03 │ │ │ ├── components │ │ │ │ └── chat.tsx │ │ │ ├── page.tsx │ │ │ └── route.ts │ │ ├── flow-chain │ │ │ ├── components │ │ │ │ ├── error-indicator.tsx │ │ │ │ └── nodes-panel.tsx │ │ │ ├── lib │ │ │ │ └── news-summarization-chain.ts │ │ │ ├── page.tsx │ │ │ └── route.ts │ │ ├── flow-orchestrator │ │ │ ├── components │ │ │ │ ├── error-indicator.tsx │ │ │ │ └── nodes-panel.tsx │ │ │ ├── lib │ │ │ │ └── developer-tasks-orchestrator.ts │ │ │ ├── page.tsx │ │ │ └── route.ts │ │ ├── flow-parallelization │ │ │ ├── components │ │ │ │ ├── error-indicator.tsx │ │ │ │ └── nodes-panel.tsx │ │ │ ├── lib │ │ │ │ └── exam-creator-parallelization.ts │ │ │ ├── page.tsx │ │ │ └── route.ts │ │ └── flow-routing │ │ │ ├── components │ │ │ ├── error-indicator.tsx │ │ │ └── nodes-panel.tsx │ │ │ ├── lib │ │ │ └── content-creator-routing.ts │ │ │ ├── page.tsx │ │ │ └── route.ts │ ├── examples │ │ ├── chat-demo.tsx │ │ ├── chat-input-demo.tsx │ │ ├── chat-input-unstyled-initial-rows.tsx │ │ ├── chat-input-unstyled.tsx │ │ ├── chat-message-area-demo-alignment.tsx │ │ ├── chat-message-area-demo-streaming.tsx │ │ ├── chat-message-area-demo.tsx │ │ ├── chat-message-demo-avatar-image.tsx │ │ ├── chat-message-demo-bubble.tsx │ │ ├── chat-message-demo-default.tsx │ │ ├── chat-message-demo-full.tsx │ │ ├── chat-message-demo-markdown-content.tsx │ │ ├── chat-message-demo-without-avatar.tsx │ │ ├── chat-message-demo.tsx │ │ ├── editable-handle-demo.tsx │ │ ├── generate-text-node-demo.tsx │ │ ├── jsx-renderer-demo.tsx │ │ ├── markdown-content-demo.tsx │ │ ├── markdown-streaming-demo.tsx │ │ ├── model-selector-demo.tsx │ │ ├── model-selector-disabled-demo.tsx │ │ ├── prompt-crafter-node-demo.tsx │ │ ├── resizable-node-demo.tsx │ │ ├── status-edge-demo.tsx │ │ ├── text-input-node-demo.tsx │ │ └── visualize-text-node-demo.tsx │ ├── hooks │ │ ├── flow │ │ │ └── use-workflow.ts │ │ ├── use-scroll-to-bottom.ts │ │ └── use-textarea-resize.ts │ ├── index.ts │ ├── lib │ │ ├── flow │ │ │ ├── generate-ai-text-internal.ts │ │ │ ├── generate-ai-text.ts │ │ │ ├── node-factory.ts │ │ │ ├── server-node-processors.ts │ │ │ ├── sse-workflow-execution-client.ts │ │ │ ├── sse-workflow-execution-engine.ts │ │ │ ├── workflow-execution-engine.ts │ │ │ └── workflow.ts │ │ └── jsx-utils.ts │ ├── registry-blocks.ts │ ├── registry-categories.ts │ ├── registry-examples.ts │ ├── registry-hooks.ts │ ├── registry-lib.ts │ ├── registry-ui.ts │ ├── schema.ts │ └── ui │ │ ├── chat-input.tsx │ │ ├── chat-message-area.tsx │ │ ├── chat-message.tsx │ │ ├── flow │ │ ├── base-handle.tsx │ │ ├── base-node.tsx │ │ ├── editable-handle.tsx │ │ ├── generate-text-node-controller.tsx │ │ ├── generate-text-node.tsx │ │ ├── labeled-handle.tsx │ │ ├── node-header-status.tsx │ │ ├── node-header.tsx │ │ ├── prompt-crafter-node-controller.tsx │ │ ├── prompt-crafter-node.tsx │ │ ├── resizable-node.tsx │ │ ├── status-edge-controller.tsx │ │ ├── status-edge.tsx │ │ ├── text-input-node-controller.tsx │ │ ├── text-input-node.tsx │ │ ├── visualize-text-node-controller.tsx │ │ └── visualize-text-node.tsx │ │ ├── jsx-renderer.tsx │ │ ├── markdown-content.tsx │ │ └── model-selector.tsx ├── scripts │ └── build-registry.ts ├── styles │ ├── globals.css │ └── mdx.css └── types │ ├── nav.ts │ └── unist.ts ├── tailwind.config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | # contentlayer 40 | .contentlayer 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Alwurts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-ai 2 | 3 | A collection of beautifully designed chat interface components that you can copy and paste into your apps. Accessible. Customizable. Open Source. 4 | 5 | ![hero](/public/og.jpg) 6 | 7 | [simple-ai.dev](https://simple-ai.dev) 8 | 9 | ## Documentation 10 | 11 | Visit [simple-ai.dev/docs](https://simple-ai.dev/docs) to view the documentation. 12 | 13 | ## Features 14 | 15 | - Chat-First Design - Components specifically designed for chat and conversational interfaces 16 | - Modern UX Patterns - Implements patterns seen in leading AI chat applications 17 | - Copy and Paste - Use components directly in your app and customize them to your needs 18 | - Dark Mode - Built-in dark mode support 19 | - TypeScript - Written in TypeScript for better developer experience 20 | - Vercel AI SDK Compatible - Works seamlessly with Vercel AI SDK 21 | 22 | ## Installation 23 | 24 | ```bash 25 | # Install shadcn/ui first 26 | npx shadcn-ui@latest init 27 | 28 | # Then you can add simple-ai components 29 | npx shadcn@latest add https://simple-ai.dev/r/chat-message.json 30 | ``` 31 | 32 | ## License 33 | 34 | Licensed under the MIT License. 35 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": true 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "tab", 15 | "indentWidth": 2, 16 | "lineWidth": 80 17 | }, 18 | "organizeImports": { 19 | "enabled": true 20 | }, 21 | "linter": { 22 | "enabled": true, 23 | "rules": { 24 | "recommended": true, 25 | "correctness": { 26 | "noUndeclaredDependencies": "error", 27 | "noUndeclaredVariables": "error", 28 | "noUnusedFunctionParameters": "error", 29 | "noUnusedImports": "error", 30 | "noUnusedVariables": "error", 31 | "useHookAtTopLevel": "error" 32 | }, 33 | "style": { 34 | "useBlockStatements": "error", 35 | "useFilenamingConvention": { 36 | "level": "error", 37 | "options": { 38 | "strictCase": true, 39 | "filenameCases": ["kebab-case"] 40 | } 41 | } 42 | } 43 | } 44 | }, 45 | "javascript": { 46 | "formatter": { 47 | "quoteStyle": "double", 48 | "trailingCommas": "all", 49 | "semicolons": "always", 50 | "arrowParentheses": "always" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | import { createContentlayerPlugin } from "next-contentlayer2"; 2 | import { withPlausibleProxy } from "next-plausible"; 3 | 4 | /** @type {import('next').NextConfig} */ 5 | const nextConfig = withPlausibleProxy({ 6 | customDomain: "https://plausible.alwurts.com" 7 | })({ 8 | experimental: { 9 | outputFileTracingIncludes: { 10 | "/blocks/*": ["./registry/**/*"], 11 | }, 12 | }, 13 | }); 14 | 15 | const withContentlayer = createContentlayerPlugin({ 16 | // Additional Contentlayer config options 17 | }); 18 | 19 | export default withContentlayer(nextConfig); 20 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/avatar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alwurts/simple-ai/c129c2455a9c0c842294b95a6275835697384bc3/public/avatar-1.png -------------------------------------------------------------------------------- /public/avatar-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alwurts/simple-ai/c129c2455a9c0c842294b95a6275835697384bc3/public/avatar-2.png -------------------------------------------------------------------------------- /public/og.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alwurts/simple-ai/c129c2455a9c0c842294b95a6275835697384bc3/public/og.jpg -------------------------------------------------------------------------------- /public/r/base-handle.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-handle", 3 | "type": "registry:ui", 4 | "dependencies": [ 5 | "@xyflow/react" 6 | ], 7 | "files": [ 8 | { 9 | "path": "ui/flow/base-handle.tsx", 10 | "content": "import { cn } from \"@/lib/utils\";\nimport { Handle, type HandleProps } from \"@xyflow/react\";\nimport React from \"react\";\n\nexport const BaseHandle = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes & HandleProps\n>(({ className, ...props }, ref) => (\n\t\n));\n\nBaseHandle.displayName = \"BaseHandle\";\n", 11 | "type": "registry:ui", 12 | "target": "components/flow/base-handle.tsx" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /public/r/base-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-node", 3 | "type": "registry:ui", 4 | "files": [ 5 | { 6 | "path": "ui/flow/base-node.tsx", 7 | "content": "import { cn } from \"@/lib/utils\";\nimport React from \"react\";\n\nexport const BaseNode = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes & { selected?: boolean }\n>(({ className, selected, ...props }, ref) => (\n\t\n\t\ttabIndex={0}\n\t\t{...props}\n\t/>\n));\nBaseNode.displayName = \"BaseNode\";\n", 8 | "type": "registry:ui", 9 | "target": "components/flow/base-node.tsx" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/chat-input-demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-input-demo", 3 | "type": "registry:example", 4 | "registryDependencies": [ 5 | "http://localhost:4567/r/chat-input.json" 6 | ], 7 | "files": [ 8 | { 9 | "path": "examples/chat-input-demo.tsx", 10 | "content": "\"use client\";\n\nimport {\n\tChatInput,\n\tChatInputSubmit,\n\tChatInputTextArea,\n} from \"@/components/ui/chat-input\";\nimport { useState } from \"react\";\nimport { toast } from \"sonner\";\n\nexport default function ChatInputDemo() {\n\tconst [value, setValue] = useState(\"\");\n\tconst [isLoading, setIsLoading] = useState(false);\n\n\tconst handleSubmit = () => {\n\t\tsetIsLoading(true);\n\t\tsetTimeout(() => {\n\t\t\ttoast(value);\n\t\t\tsetIsLoading(false);\n\t\t}, 1000);\n\t};\n\n\treturn (\n\t\t
\n\t\t\t setValue(e.target.value)}\n\t\t\t\tonSubmit={handleSubmit}\n\t\t\t\tloading={isLoading}\n\t\t\t\tonStop={() => setIsLoading(false)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t
\n\t);\n}\n", 11 | "type": "registry:example", 12 | "target": "" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /public/r/chat-input-unstyled-initial-rows.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-input-unstyled-initial-rows", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/chat-input-unstyled-initial-rows.tsx", 7 | "content": "\"use client\";\n\nimport {\n\tChatInput,\n\tChatInputSubmit,\n\tChatInputTextArea,\n} from \"@/components/ui/chat-input\";\nimport { useState } from \"react\";\nimport { toast } from \"sonner\";\n\nexport default function ChatInputDemo() {\n\tconst [value, setValue] = useState(\"\");\n\tconst [isLoading, setIsLoading] = useState(false);\n\n\tconst handleSubmit = () => {\n\t\tsetIsLoading(true);\n\t\tsetTimeout(() => {\n\t\t\ttoast(value);\n\t\t\tsetIsLoading(false);\n\t\t}, 1000);\n\t};\n\n\treturn (\n\t\t
\n\t\t\t setValue(e.target.value)}\n\t\t\t\tonSubmit={handleSubmit}\n\t\t\t\tloading={isLoading}\n\t\t\t\tonStop={() => setIsLoading(false)}\n\t\t\t\trows={4}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/chat-input-unstyled.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-input-unstyled", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/chat-input-unstyled.tsx", 7 | "content": "\"use client\";\n\nimport {\n\tChatInput,\n\tChatInputSubmit,\n\tChatInputTextArea,\n} from \"@/components/ui/chat-input\";\nimport { useState } from \"react\";\nimport { toast } from \"sonner\";\n\nexport default function ChatInputDemo() {\n\tconst [value, setValue] = useState(\"\");\n\tconst [isLoading, setIsLoading] = useState(false);\n\n\tconst handleSubmit = () => {\n\t\tsetIsLoading(true);\n\t\tsetTimeout(() => {\n\t\t\ttoast(value);\n\t\t\tsetIsLoading(false);\n\t\t}, 1000);\n\t};\n\n\treturn (\n\t\t
\n\t\t\t setValue(e.target.value)}\n\t\t\t\tonSubmit={handleSubmit}\n\t\t\t\tloading={isLoading}\n\t\t\t\tonStop={() => setIsLoading(false)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/chat-message-demo-avatar-image.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-message-demo-avatar-image", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/chat-message-demo-avatar-image.tsx", 7 | "content": "import {\n\tChatMessage,\n\tChatMessageAvatar,\n\tChatMessageContent,\n} from \"@/components/ui/chat-message\";\n\nconst messages = [\n\t{\n\t\tid: \"1\",\n\t\tcontent: \"Hey how are you?\",\n\t\ttype: \"user\",\n\t},\n\t{\n\t\tid: \"2\",\n\t\tcontent: \"I'm fine, thanks for asking!\",\n\t\ttype: \"assistant\",\n\t},\n\t{\n\t\tid: \"3\",\n\t\tcontent: \"Great!\",\n\t\ttype: \"user\",\n\t},\n];\n\nexport default function ChatMessageDemoAvatarImage() {\n\treturn (\n\t\t
\n\t\t\t{messages.map((message) => (\n\t\t\t\t\n\t\t\t\t\t{message.type === \"assistant\" && (\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\t\n\t\t\t\t\t{message.type === \"user\" && (\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t))}\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/chat-message-demo-bubble.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-message-demo-bubble", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/chat-message-demo-bubble.tsx", 7 | "content": "import {\n\tChatMessage,\n\tChatMessageAvatar,\n\tChatMessageContent,\n} from \"@/components/ui/chat-message\";\n\nconst messages = [\n\t{\n\t\tid: \"1\",\n\t\tcontent: \"Hey how are you?\",\n\t\ttype: \"user\",\n\t},\n\t{\n\t\tid: \"2\",\n\t\tcontent: \"I'm fine, thanks for asking!\",\n\t\ttype: \"assistant\",\n\t},\n\t{\n\t\tid: \"3\",\n\t\tcontent: \"Great!\",\n\t\ttype: \"user\",\n\t},\n];\n\nexport default function ChatMessageDemoBubble() {\n\treturn (\n\t\t
\n\t\t\t{messages.map((message) => (\n\t\t\t\t\n\t\t\t\t\t{message.type === \"assistant\" && }\n\t\t\t\t\t\n\t\t\t\t\t{message.type === \"user\" && }\n\t\t\t\t\n\t\t\t))}\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/chat-message-demo-default.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-message-demo-default", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/chat-message-demo-default.tsx", 7 | "content": "import {\n\tChatMessage,\n\tChatMessageAvatar,\n\tChatMessageContent,\n} from \"@/components/ui/chat-message\";\n\nconst messages = [\n\t{\n\t\tid: \"1\",\n\t\tcontent: \"Hey how are you?\",\n\t\ttype: \"user\",\n\t},\n\t{\n\t\tid: \"2\",\n\t\tcontent: \"I'm fine, thanks for asking!\",\n\t\ttype: \"assistant\",\n\t},\n\t{\n\t\tid: \"3\",\n\t\tcontent: \"Great!\",\n\t\ttype: \"user\",\n\t},\n];\n\nexport default function ChatMessageDemoDefault() {\n\treturn (\n\t\t
\n\t\t\t{messages.map((message) => (\n\t\t\t\t\n\t\t\t\t\t{message.type === \"assistant\" && }\n\t\t\t\t\t\n\t\t\t\t\t{message.type === \"user\" && }\n\t\t\t\t\n\t\t\t))}\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/chat-message-demo-full.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-message-demo-full", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/chat-message-demo-full.tsx", 7 | "content": "import {\n\tChatMessage,\n\tChatMessageAvatar,\n\tChatMessageContent,\n} from \"@/components/ui/chat-message\";\n\nconst messages = [\n\t{\n\t\tid: \"1\",\n\t\tcontent: \"Hey how are you?\",\n\t\ttype: \"user\",\n\t},\n\t{\n\t\tid: \"2\",\n\t\tcontent: \"I'm fine, thanks for asking!\",\n\t\ttype: \"assistant\",\n\t},\n\t{\n\t\tid: \"3\",\n\t\tcontent: \"Great!\",\n\t\ttype: \"user\",\n\t},\n];\n\nexport default function ChatMessageDemoFull() {\n\treturn (\n\t\t
\n\t\t\t{messages.map((message) => (\n\t\t\t\t\n\t\t\t\t\t{message.type === \"assistant\" && }\n\t\t\t\t\t\n\t\t\t\t\t{message.type === \"user\" && }\n\t\t\t\t\n\t\t\t))}\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/chat-message-demo-markdown-content.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-message-demo-markdown-content", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/chat-message-demo-markdown-content.tsx", 7 | "content": "import {\n\tChatMessage,\n\tChatMessageAvatar,\n\tChatMessageContent,\n} from \"@/components/ui/chat-message\";\n\nconst messages = [\n\t{\n\t\tid: \"1\",\n\t\tcontent: \"Can you give me a summary of today's tech news?\",\n\t\ttype: \"user\",\n\t},\n\t{\n\t\tid: \"2\",\n\t\tcontent: `Sure, here's a summary of today's tech news:\n### Technology\n\nLatest developments in Technology:\n* OpenAI announces new breakthrough in language models\n* Tech giants collaborate on AI safety standards\n* Quantum computing reaches new milestone\n`,\n\t\ttype: \"assistant\",\n\t},\n\t{\n\t\tid: \"3\",\n\t\tcontent: \"Thanks for the update!\",\n\t\ttype: \"user\",\n\t},\n];\n\nexport default function ChatMessageDemoMarkdownContent() {\n\treturn (\n\t\t
\n\t\t\t{messages.map((message) => (\n\t\t\t\t\n\t\t\t\t\t{message.type === \"assistant\" && }\n\t\t\t\t\t\n\t\t\t\t\t{message.type === \"user\" && }\n\t\t\t\t\n\t\t\t))}\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/chat-message-demo-without-avatar.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-message-demo-without-avatar", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/chat-message-demo-without-avatar.tsx", 7 | "content": "import { ChatMessage, ChatMessageContent } from \"@/components/ui/chat-message\";\n\nconst messages = [\n\t{\n\t\tid: \"1\",\n\t\tcontent: \"Hey how are you?\",\n\t\ttype: \"user\",\n\t},\n\t{\n\t\tid: \"2\",\n\t\tcontent: \"I'm fine, thanks for asking!\",\n\t\ttype: \"assistant\",\n\t},\n\t{\n\t\tid: \"3\",\n\t\tcontent: \"Great!\",\n\t\ttype: \"user\",\n\t},\n];\n\nexport default function ChatMessageDemoWithoutAvatar() {\n\treturn (\n\t\t
\n\t\t\t{messages.map((message) => (\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t))}\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/chat-message-demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-message-demo", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/chat-message-demo.tsx", 7 | "content": "import {\n\tChatMessage,\n\tChatMessageAvatar,\n\tChatMessageContent,\n} from \"@/components/ui/chat-message\";\n\nconst messages = [\n\t{\n\t\tid: \"1\",\n\t\tcontent: \"Hey how are you?\",\n\t\ttype: \"user\",\n\t},\n\t{\n\t\tid: \"2\",\n\t\tcontent: \"I'm fine, thanks for asking!\",\n\t\ttype: \"assistant\",\n\t},\n\t{\n\t\tid: \"3\",\n\t\tcontent: \"Great!\",\n\t\ttype: \"user\",\n\t},\n];\n\nexport default function ChatMessageDemo() {\n\treturn (\n\t\t
\n\t\t\t{messages.map((message) => (\n\t\t\t\t\n\t\t\t\t\t{message.type === \"assistant\" && }\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t))}\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/jsx-renderer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsx-renderer", 3 | "type": "registry:ui", 4 | "description": "A component that renders JSX strings with access to tailwind, shadcn components and lucide icons.", 5 | "dependencies": [ 6 | "react-jsx-parser" 7 | ], 8 | "registryDependencies": [ 9 | "http://localhost:4567/r/jsx-utils.json" 10 | ], 11 | "files": [ 12 | { 13 | "path": "ui/jsx-renderer.tsx", 14 | "content": "import { completeJsxTag } from \"@/lib/jsx-utils\";\nimport * as React from \"react\";\nimport JsxParser from \"react-jsx-parser\";\n\ninterface JsxRendererProps extends React.HTMLAttributes {\n\tjsx: string;\n\tfixIncompleteJsx?: boolean;\n\tcomponents: Record;\n}\n\nconst JsxRenderer = React.forwardRef(\n\t({ className, jsx, fixIncompleteJsx = true, components }, ref) => {\n\t\tconst processedJsx = React.useMemo(() => {\n\t\t\treturn fixIncompleteJsx ? completeJsxTag(jsx) : jsx;\n\t\t}, [jsx, fixIncompleteJsx]);\n\n\t\treturn (\n\t\t\t\n\t\t);\n\t},\n);\nJsxRenderer.displayName = \"JsxRenderer\";\n\nexport { JsxRenderer };\n", 15 | "type": "registry:ui", 16 | "target": "" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /public/r/labeled-handle.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "labeled-handle", 3 | "type": "registry:ui", 4 | "dependencies": [ 5 | "@xyflow/react" 6 | ], 7 | "files": [ 8 | { 9 | "path": "ui/flow/labeled-handle.tsx", 10 | "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { HandleProps } from \"@xyflow/react\";\nimport React from \"react\";\n\nimport { BaseHandle } from \"@/components/flow/base-handle\";\n\nconst flexDirections = {\n\ttop: \"flex-col\",\n\tright: \"flex-row-reverse justify-end\",\n\tbottom: \"flex-col-reverse justify-end\",\n\tleft: \"flex-row\",\n};\n\nconst LabeledHandle = React.forwardRef<\n\tHTMLDivElement,\n\tHandleProps &\n\t\tReact.HTMLAttributes & {\n\t\t\ttitle: string;\n\t\t\thandleClassName?: string;\n\t\t\tlabelClassName?: string;\n\t\t}\n>(\n\t(\n\t\t{ className, labelClassName, handleClassName, title, position, ...props },\n\t\tref,\n\t) => (\n\t\t\n\t\t\t\n\t\t\t{/* biome-ignore lint/a11y/noLabelWithoutControl: */}\n\t\t\t\n\t\t\n\t),\n);\n\nLabeledHandle.displayName = \"LabeledHandle\";\n\nexport { LabeledHandle };\n", 11 | "type": "registry:ui", 12 | "target": "components/flow/labeled-handle.tsx" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /public/r/markdown-content-demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-content-demo", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/markdown-content-demo.tsx", 7 | "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { MarkdownContent } from \"../ui/markdown-content\";\n\nexport default function MarkdownContentDemo() {\n\tconst [content] = useState(\n\t\t`# Markdown Example\n\nThis is a sample paragraph that demonstrates how markdown content can be rendered in your application. It shows various markdown elements working together.\n\n## Features List\n\n- Clean and simple syntax\n- Support for headings and paragraphs\n- Bullet point lists\n- Easy to read and write\n- Customizable styling\n\n## Text Formatting\n\nYou can make text **bold**, *italic*, or ***both***. You can also use ~~strikethrough~~ text.\n\n### Links and Images\n\nHere's a [link to GitHub](https://github.com) and below is a sample image:\n\n![Markdown Logo](https://markdown-here.com/img/icon256.png)\n\n### Code Examples\n\nInline code: \\`const greeting = \"Hello World!\"\\`\n\n#### Code Block\t\n\n\\`\\`\\`javascript\n// Code block\nfunction sayHello() {\n console.log(\"Hello!\");\n}\n\\`\\`\\`\n\n### Blockquotes\n\n> This is a blockquote\n> It can span multiple lines\n>> And can be nested\n\n### Tables\n\n| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Row 1 | Data | Data |\n| Row 2 | Data | Data |\n\n### Task Lists\n\n- [x] Completed task\n- [ ] Pending task\n- [ ] Another task\n\n### Horizontal Rule\n\n---\n\n### Ordered List\n\n1. First item\n2. Second item\n3. Third item\n 1. Sub-item 1\n 2. Sub-item 2\n`,\n\t);\n\treturn (\n\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/model-selector-demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "model-selector-demo", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/model-selector-demo.tsx", 7 | "content": "\"use client\";\n\nimport { type Model, ModelSelector } from \"@/components/ui/model-selector\";\nimport { useState } from \"react\";\n\nexport default function ModelSelectorDemo() {\n\tconst [model, setModel] = useState(\"deepseek-chat\");\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/model-selector-disabled-demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "model-selector-disabled-demo", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/model-selector-disabled-demo.tsx", 7 | "content": "\"use client\";\n\nimport { type Model, ModelSelector } from \"@/components/ui/model-selector\";\nimport { useState } from \"react\";\n\nexport default function ModelSelectorDisabledDemo() {\n\tconst [model, setModel] = useState(\"deepseek-chat\");\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/node-header-status.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-header-status", 3 | "type": "registry:ui", 4 | "dependencies": [ 5 | "@xyflow/react" 6 | ], 7 | "registryDependencies": [ 8 | "badge" 9 | ], 10 | "files": [ 11 | { 12 | "path": "ui/flow/node-header-status.tsx", 13 | "content": "import { cn } from \"@/lib/utils\";\n\nimport { Badge } from \"@/components/ui/badge\";\n\nexport const NodeHeaderStatus = ({\n\tstatus,\n}: {\n\tstatus?: \"idle\" | \"processing\" | \"success\" | \"error\";\n}) => {\n\tconst statusColors = {\n\t\tidle: \"bg-muted text-muted-foreground\",\n\t\tprocessing: \"bg-orange-500 text-white\",\n\t\tsuccess: \"bg-green-500 text-white\",\n\t\terror: \"bg-red-500 text-white\",\n\t};\n\treturn (\n\t\t\n\t\t\t{status ? status : \"idle\"}\n\t\t\n\t);\n};\n\nNodeHeaderStatus.displayName = \"NodeHeaderStatus\";\n", 14 | "type": "registry:ui", 15 | "target": "components/flow/node-header-status.tsx" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /public/r/resizable-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resizable-node", 3 | "type": "registry:ui", 4 | "dependencies": [ 5 | "@xyflow/react" 6 | ], 7 | "registryDependencies": [ 8 | "http://localhost:4567/r/base-node.json" 9 | ], 10 | "files": [ 11 | { 12 | "path": "ui/flow/resizable-node.tsx", 13 | "content": "import { cn } from \"@/lib/utils\";\nimport { BaseNode } from \"@/components/flow/base-node\";\nimport { NodeResizer } from \"@xyflow/react\";\nimport React from \"react\";\n\nexport const ResizableNode = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes & {\n\t\tselected: boolean;\n\t}\n>(({ className, selected, style, children, ...props }, ref) => (\n\t\n\t\t\n\t\t{children}\n\t\n));\nResizableNode.displayName = \"ResizableNode\";\n", 14 | "type": "registry:ui", 15 | "target": "components/flow/resizable-node.tsx" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /public/r/status-edge-demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "status-edge-demo", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/status-edge-demo.tsx", 7 | "content": "\"use client\";\n\nimport \"@xyflow/react/dist/style.css\";\n\nimport { Background, ReactFlow, ReactFlowProvider } from \"@xyflow/react\";\n\nimport { StatusEdge } from \"@/components/flow/status-edge\";\n\nconst defaultNodes = [\n\t{\n\t\tid: \"1\",\n\t\tposition: { x: 200, y: 200 },\n\t\tdata: { label: \"Node\" },\n\t},\n\t{\n\t\tid: \"2\",\n\t\tposition: { x: 400, y: 400 },\n\t\tdata: { label: \"Node\" },\n\t},\n];\n\nconst defaultEdges = [\n\t{\n\t\tid: \"e1-2\",\n\t\tsource: \"1\",\n\t\ttarget: \"2\",\n\t\ttype: \"status\",\n\t\tdata: {\n\t\t\terror: true,\n\t\t},\n\t},\n];\n\nconst edgeTypes = {\n\tstatus: StatusEdge,\n};\n\nexport default function StatusEdgeDemo() {\n\tconst defaultViewport = { x: -150, y: -50, zoom: 1.1 };\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/status-edge.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "status-edge", 3 | "type": "registry:ui", 4 | "dependencies": [ 5 | "@xyflow/react" 6 | ], 7 | "files": [ 8 | { 9 | "path": "ui/flow/status-edge.tsx", 10 | "content": "import {\n\ttype Edge,\n\ttype EdgeProps,\n\tBaseEdge as FlowBaseEdge,\n\tgetBezierPath,\n} from \"@xyflow/react\";\nimport type { CSSProperties } from \"react\";\n\nexport type StatusEdge = Edge<\n\t{\n\t\terror?: boolean;\n\t},\n\t\"status\"\n> & {\n\ttype: \"status\";\n\tsourceHandle: string;\n\ttargetHandle: string;\n};\n\nexport function StatusEdge({\n\tsourceX,\n\tsourceY,\n\ttargetX,\n\ttargetY,\n\tsourcePosition,\n\ttargetPosition,\n\tdata,\n\tselected,\n}: EdgeProps) {\n\tconst [edgePath] = getBezierPath({\n\t\tsourceX,\n\t\tsourceY,\n\t\tsourcePosition,\n\t\ttargetX,\n\t\ttargetY,\n\t\ttargetPosition,\n\t});\n\n\tconst edgeStyle: CSSProperties = {\n\t\tstroke: data?.error ? \"#ef4444\" : selected ? \"#3b82f6\" : \"#b1b1b7\",\n\t\tstrokeWidth: selected ? 3 : 2,\n\t\ttransition: \"stroke 0.2s, stroke-width 0.2s\",\n\t};\n\n\treturn ;\n}\n", 11 | "type": "registry:ui", 12 | "target": "components/flow/status-edge.tsx" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /public/r/use-textarea-resize.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-textarea-resize", 3 | "type": "registry:hook", 4 | "files": [ 5 | { 6 | "path": "hooks/use-textarea-resize.ts", 7 | "content": "\"use client\";\n\nimport { useLayoutEffect, useRef } from \"react\";\nimport type { ComponentProps } from \"react\";\n\nexport function useTextareaResize(\n\tvalue: ComponentProps<\"textarea\">[\"value\"],\n\trows = 1,\n) {\n\tconst textareaRef = useRef(null);\n\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: \n\tuseLayoutEffect(() => {\n\t\tconst textArea = textareaRef.current;\n\n\t\tif (textArea) {\n\t\t\t// Get the line height to calculate minimum height based on rows\n\t\t\tconst computedStyle = window.getComputedStyle(textArea);\n\t\t\tconst lineHeight = Number.parseInt(computedStyle.lineHeight, 10) || 20;\n\t\t\tconst padding =\n\t\t\t\tNumber.parseInt(computedStyle.paddingTop, 10) +\n\t\t\t\tNumber.parseInt(computedStyle.paddingBottom, 10);\n\n\t\t\t// Calculate minimum height based on rows\n\t\t\tconst minHeight = lineHeight * rows + padding;\n\n\t\t\t// Reset height to auto first to get the correct scrollHeight\n\t\t\ttextArea.style.height = \"0px\";\n\t\t\tconst scrollHeight = Math.max(textArea.scrollHeight, minHeight);\n\n\t\t\t// Set the final height\n\t\t\ttextArea.style.height = `${scrollHeight + 2}px`;\n\t\t}\n\t}, [textareaRef, value, rows]);\n\n\treturn textareaRef;\n}\n", 8 | "type": "registry:hook", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/r/visualize-text-node-demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "visualize-text-node-demo", 3 | "type": "registry:example", 4 | "files": [ 5 | { 6 | "path": "examples/visualize-text-node-demo.tsx", 7 | "content": "\"use client\";\n\nimport \"@xyflow/react/dist/style.css\";\n\nimport {\n\tBackground,\n\ttype Connection,\n\ttype EdgeChange,\n\ttype Node,\n\ttype NodeChange,\n\ttype NodeTypes,\n\tReactFlow,\n\tReactFlowProvider,\n\taddEdge,\n\tapplyEdgeChanges,\n\tapplyNodeChanges,\n} from \"@xyflow/react\";\n\nimport { VisualizeTextNode } from \"@/components/flow/visualize-text-node\";\nimport { useCallback, useState } from \"react\";\n\nconst nodeTypes: NodeTypes = {\n\t\"visualize-text\": VisualizeTextNode,\n};\n\nconst initialNodes = [\n\t{\n\t\tid: \"node-1\",\n\t\ttype: \"visualize-text\",\n\t\tposition: { x: 0, y: -50 },\n\t\tdata: {\n\t\t\tinput:\n\t\t\t\t\"### I support markdown\\n\\nVisualize text coming from other nodes\\n\\n- 1\\n- 2\\n- 3\",\n\t\t\tstatus: \"success\",\n\t\t},\n\t},\n];\n\nexport default function VisualizeTextNodeDemo() {\n\tconst [nodes, setNodes] = useState(initialNodes);\n\tconst [edges, setEdges] = useState([]);\n\n\t// Add default viewport configuration\n\tconst defaultViewport = { x: 100, y: 75, zoom: 1.3 };\n\n\tconst onNodesChange = useCallback(\n\t\t(changes: NodeChange[]) =>\n\t\t\tsetNodes((nds) => applyNodeChanges(changes, nds)),\n\t\t[],\n\t);\n\tconst onEdgesChange = useCallback(\n\t\t(changes: EdgeChange[]) =>\n\t\t\tsetEdges((eds) => applyEdgeChanges(changes, eds)),\n\t\t[],\n\t);\n\tconst onConnect = useCallback(\n\t\t(connection: Connection) => setEdges((eds) => addEdge(connection, eds)),\n\t\t[],\n\t);\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t
\n\t);\n}\n", 8 | "type": "registry:example", 9 | "target": "" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /src/__registry__/blocks/app-01/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { EditorLayout } from "@/registry/blocks/app-01/components/editor-layout"; 4 | import { Versions } from "@/registry/blocks/app-01/components/versions"; 5 | 6 | export default function Page() { 7 | return ( 8 |
9 | 10 | 11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/__registry__/blocks/app-03/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTrackEvent } from "@/lib/events"; 4 | import { ProfileGenerateDialog } from "@/registry/blocks/app-03/components/profile-generate-dialog"; 5 | import { Toolbar } from "@/registry/blocks/app-03/components/toolbar"; 6 | import { XPreview } from "@/registry/blocks/app-03/components/x-preview"; 7 | import type { 8 | XProfile, 9 | profileGenerationSchema, 10 | } from "@/registry/blocks/app-03/lib/x"; 11 | import type { DeepPartial } from "ai"; 12 | import { useCompletion } from "ai/react"; 13 | import { useEffect, useState } from "react"; 14 | import type { z } from "zod"; 15 | 16 | export default function XProfileGenerator() { 17 | const [profile, setProfile] = useState>({}); 18 | const track = useTrackEvent(); 19 | 20 | const { isLoading, complete, completion } = useCompletion({ 21 | api: "/api/ai/x-profile", 22 | onFinish: (prompt, completion) => { 23 | setProfile((profile) => ({ 24 | ...profile, 25 | bio: completion, 26 | })); 27 | track({ 28 | name: "block_used", 29 | properties: { 30 | used_block: "app-03", 31 | used_block_ai_prompt: prompt, 32 | used_block_ai_completion: completion, 33 | }, 34 | }); 35 | }, 36 | experimental_throttle: 90, 37 | }); 38 | 39 | useEffect(() => { 40 | if (completion) { 41 | setProfile((profile) => ({ 42 | ...profile, 43 | bio: completion, 44 | })); 45 | } 46 | }, [completion]); 47 | 48 | function onSubmit(values: z.infer) { 49 | setProfile({ 50 | displayName: values.displayName, 51 | username: values.username, 52 | }); 53 | complete(JSON.stringify(values)); 54 | } 55 | 56 | return ( 57 |
58 | 59 | } 63 | /> 64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/__registry__/blocks/chat-01/page.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Breadcrumb, 3 | BreadcrumbItem, 4 | BreadcrumbList, 5 | BreadcrumbPage, 6 | } from "@/components/ui/breadcrumb"; 7 | import { Separator } from "@/components/ui/separator"; 8 | import { 9 | SidebarInset, 10 | SidebarProvider, 11 | SidebarTrigger, 12 | } from "@/components/ui/sidebar"; 13 | import { Chat } from "@/registry/blocks/chat-01/components/chat"; 14 | import { SidebarApp } from "@/registry/blocks/chat-01/components/sidebar-app"; 15 | 16 | export default function Page() { 17 | return ( 18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | Project Management & Task Tracking 30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/__registry__/blocks/chat-02/page.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Breadcrumb, 3 | BreadcrumbItem, 4 | BreadcrumbLink, 5 | BreadcrumbList, 6 | BreadcrumbPage, 7 | BreadcrumbSeparator, 8 | } from "@/components/ui/breadcrumb"; 9 | import { 10 | SidebarInset, 11 | SidebarProvider, 12 | SidebarTrigger, 13 | } from "@/components/ui/sidebar"; 14 | import { AppSidebar } from "@/registry/blocks/chat-02/components/app-sidebar"; 15 | 16 | export default function Page() { 17 | return ( 18 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | Building Your Application 32 | 33 | 34 | 35 | 36 | Data Fetching 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/__registry__/blocks/chat-03/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { Chat } from "@/registry/blocks/chat-03/components/chat"; 5 | import { X } from "lucide-react"; 6 | import { useState } from "react"; 7 | 8 | export default function Page() { 9 | const [open, setOpen] = useState(true); 10 | 11 | return ( 12 |
13 | 16 |
17 | {open && ( 18 |
19 |
20 |

Chat

21 | 29 |
30 | 31 |
32 | )} 33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/app/(app)/blocks/[...categories]/page.tsx: -------------------------------------------------------------------------------- 1 | import { BlockDisplay } from "@/components/blocks/block-display"; 2 | import { getAllBlockIds } from "@/lib/blocks"; 3 | import { registryCategories } from "@/registry/registry-categories"; 4 | 5 | export const dynamicParams = false; 6 | 7 | export async function generateStaticParams() { 8 | return registryCategories.map((category) => ({ 9 | categories: [category.slug], 10 | })); 11 | } 12 | 13 | export default async function BlocksPage({ 14 | params, 15 | }: { 16 | params: { categories?: string[] }; 17 | }) { 18 | const blocks = await getAllBlockIds( 19 | ["registry:block"], 20 | params.categories ?? [], 21 | ); 22 | 23 | return blocks.map((name) => ( 24 |
28 | 29 |
30 | )); 31 | } 32 | -------------------------------------------------------------------------------- /src/app/(app)/blocks/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | 3 | import { 4 | PageActions, 5 | PageHeader, 6 | PageHeaderDescription, 7 | PageHeaderHeading, 8 | } from "@/components/layout/page-header"; 9 | import { Button } from "@/components/ui/button"; 10 | 11 | import "@/styles/mdx.css"; 12 | import { Announcement } from "@/components/announcement"; 13 | import { BlocksNav } from "@/components/blocks/blocks-nav"; 14 | import Link from "next/link"; 15 | import type { ReactNode } from "react"; 16 | 17 | export const metadata: Metadata = { 18 | title: "Building Blocks for AI", 19 | description: 20 | "Beautifully designed. Copy and paste into your apps. Open Source.", 21 | keywords: ["ai", "blocks", "open source", "ai blocks", "ai blocks for ai"], 22 | }; 23 | 24 | export default function BlocksLayout({ 25 | children, 26 | }: { 27 | children: ReactNode; 28 | }) { 29 | return ( 30 | <> 31 | 32 | 33 | Building Blocks for AI 34 | 35 | Building blocks for AI. Copy and paste into your apps. Works with all 36 | React frameworks. Open Source.. 37 | 38 | 39 | All of our blocks use the Vercel AI SDK, click{" "} 40 | 41 | here 42 | {" "} 43 | for more details. 44 | 45 | 46 | 49 | 50 | 51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 |
{children}
59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/app/(app)/blocks/page.tsx: -------------------------------------------------------------------------------- 1 | import { BlockDisplay } from "@/components/blocks/block-display"; 2 | 3 | const FEATURED_BLOCKS = ["chat-01", "app-01", "app-02"]; 4 | 5 | export default async function BlocksPage() { 6 | return ( 7 |
8 | {FEATURED_BLOCKS.map((block) => ( 9 |
13 | 14 |
15 | ))} 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/(app)/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { DocsNav } from "@/components/layout/docs-nav"; 2 | import { docsConfig } from "@/config/docs"; 3 | import type { ReactNode } from "react"; 4 | 5 | export default function DocsLayout({ 6 | children, 7 | }: { 8 | children: ReactNode; 9 | }) { 10 | return ( 11 |
12 |
13 | 18 | {children} 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/app/(app)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { SiteHeader } from "@/components/layout/header"; 2 | import { SiteFooter } from "@/components/layout/site-footer"; 3 | import type { ReactNode } from "react"; 4 | 5 | interface AppLayoutProps { 6 | children: ReactNode; 7 | } 8 | 9 | export default function AppLayout({ children }: AppLayoutProps) { 10 | return ( 11 |
12 |
13 | 14 |
{children}
15 | 16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(view)/view/[name]/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { notFound } from "next/navigation"; 3 | import * as React from "react"; 4 | 5 | import { siteConfig } from "@/config/site"; 6 | import { absoluteUrl, cn } from "@/lib/utils"; 7 | 8 | import "@/styles/mdx.css"; 9 | import { getAllBlockIds } from "@/lib/blocks"; 10 | import { getRegistryComponent, getRegistryItem } from "@/lib/registry"; 11 | 12 | const getCachedRegistryItem = React.cache(async (name: string) => { 13 | return await getRegistryItem(name); 14 | }); 15 | 16 | export const dynamicParams = false; 17 | 18 | export async function generateMetadata({ 19 | params, 20 | }: { 21 | params: { 22 | name: string; 23 | }; 24 | }): Promise { 25 | const { name } = params; 26 | const item = await getCachedRegistryItem(name); 27 | 28 | if (!item) { 29 | return {}; 30 | } 31 | 32 | const title = item.name; 33 | const description = item.description; 34 | 35 | return { 36 | title: `${item.name}${item.description ? ` - ${item.description}` : ""}`, 37 | description, 38 | openGraph: { 39 | title, 40 | description, 41 | type: "article", 42 | url: absoluteUrl(`/blocks/${item.name}`), 43 | images: [ 44 | { 45 | url: siteConfig.ogImage, 46 | width: 1200, 47 | height: 630, 48 | alt: siteConfig.name, 49 | }, 50 | ], 51 | }, 52 | twitter: { 53 | card: "summary_large_image", 54 | title, 55 | description, 56 | images: [siteConfig.ogImage], 57 | creator: "@alwurts", 58 | }, 59 | }; 60 | } 61 | 62 | export async function generateStaticParams() { 63 | const blockIds = await getAllBlockIds(); 64 | return blockIds.map((name) => ({ 65 | name, 66 | })); 67 | } 68 | 69 | export default async function BlockPage({ 70 | params, 71 | }: { 72 | params: { 73 | name: string; 74 | }; 75 | }) { 76 | const { name } = params; 77 | const item = await getCachedRegistryItem(name); 78 | const Component = getRegistryComponent(name); 79 | 80 | if (!item || !Component) { 81 | return notFound(); 82 | } 83 | 84 | return ( 85 | <> 86 |
87 | 88 |
89 | 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /src/app/api/ai/persona/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ProductPersonaSchema, 3 | UserPersonaSchema, 4 | } from "@/registry/blocks/app-02/lib/persona"; 5 | import { createDeepSeek } from "@ai-sdk/deepseek"; 6 | import { streamObject } from "ai"; 7 | import { z } from "zod"; 8 | 9 | // Allow streaming responses up to 30 seconds 10 | export const maxDuration = 30; 11 | 12 | export async function POST(req: Request) { 13 | const data = await req.json(); 14 | const { businessName, industry, targetAudience, productDescription } = data; 15 | 16 | const deepSeekClient = createDeepSeek({ 17 | baseURL: process.env.AI_GATEWAY_DEEPSEEK_URL, 18 | }); 19 | 20 | try { 21 | const result = streamObject({ 22 | model: deepSeekClient("deepseek-chat"), 23 | system: ` 24 | 25 | 26 | - Generate BOTH user and product personas based on the business details provided 27 | - Use realistic but fictional details 28 | - Include relevant emojis in appropriate fields 29 | - Ensure personas are aligned with the business context 30 | - Make the personas complementary - the user persona should be an ideal customer for the product persona 31 | 32 | 33 | `, 34 | prompt: `Business Name: ${businessName} 35 | Industry: ${industry} 36 | Target Audience: ${targetAudience} 37 | Product Description: ${productDescription}`, 38 | schema: z.object({ 39 | userPersona: UserPersonaSchema, 40 | productPersona: ProductPersonaSchema, 41 | }), 42 | }); 43 | 44 | return result.toTextStreamResponse(); 45 | } catch (error) { 46 | console.error(error); 47 | return new Response("Internal Server Error", { status: 500 }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/api/workflow/execute/route.ts: -------------------------------------------------------------------------------- 1 | import { generateAITextInternal } from "@/registry/lib/flow/generate-ai-text-internal"; 2 | import { serverNodeProcessors } from "@/registry/lib/flow/server-node-processors"; 3 | import { executeServerWorkflow } from "@/registry/lib/flow/sse-workflow-execution-engine"; 4 | import type { WorkflowDefinition } from "@/registry/lib/flow/workflow"; 5 | import type { GenerateTextNode } from "@/registry/ui/flow/generate-text-node"; 6 | import { NextResponse } from "next/server"; 7 | 8 | export const maxDuration = 60; 9 | 10 | export async function POST(req: Request) { 11 | try { 12 | const { workflow } = await req.json(); 13 | 14 | if (!workflow) { 15 | return NextResponse.json( 16 | { error: "No workflow data provided" }, 17 | { status: 400 }, 18 | ); 19 | } 20 | 21 | const workflowDefinition: WorkflowDefinition = workflow; 22 | 23 | // Create a stream for SSE 24 | const stream = new ReadableStream({ 25 | async start(controller) { 26 | await executeServerWorkflow( 27 | workflowDefinition, 28 | { 29 | ...serverNodeProcessors, 30 | "generate-text": async (node, targetsData) => { 31 | const generateNode = node as GenerateTextNode; 32 | const system = targetsData?.system; 33 | const prompt = targetsData?.prompt; 34 | if (!prompt) { 35 | throw new Error("Prompt not found"); 36 | } 37 | 38 | const result = await generateAITextInternal({ 39 | prompt, 40 | system, 41 | model: generateNode.data.config.model, 42 | tools: generateNode.data.dynamicHandles.tools, 43 | }); 44 | 45 | return result.parsedResult; 46 | }, 47 | }, 48 | controller, 49 | ); 50 | }, 51 | }); 52 | 53 | return new Response(stream, { 54 | headers: { 55 | "Content-Type": "text/event-stream", 56 | "Cache-Control": "no-cache", 57 | Connection: "keep-alive", 58 | }, 59 | }); 60 | } catch (error) { 61 | return NextResponse.json( 62 | { error: error instanceof Error ? error.message : "Unknown error" }, 63 | { status: 500 }, 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alwurts/simple-ai/c129c2455a9c0c842294b95a6275835697384bc3/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alwurts/simple-ai/c129c2455a9c0c842294b95a6275835697384bc3/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alwurts/simple-ai/c129c2455a9c0c842294b95a6275835697384bc3/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Separator } from "@/components/ui/separator"; 2 | 3 | export default function NotFound() { 4 | return ( 5 |
6 |
7 | Not Found 8 | 9 | 404 10 |
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/app/sitemap.ts: -------------------------------------------------------------------------------- 1 | import { docsConfig } from "@/config/docs"; 2 | import { siteConfig } from "@/config/site"; 3 | import { registryCategories } from "@/registry/registry-categories"; 4 | import type { MetadataRoute } from "next"; 5 | 6 | function getAllDocRoutes( 7 | items: (typeof docsConfig.sidebarNav)[number]["items"], 8 | ): string[] { 9 | return items.reduce((acc, item) => { 10 | if (item.href) { 11 | acc.push(item.href); 12 | } 13 | if (item.items?.length) { 14 | acc.push(...getAllDocRoutes(item.items)); 15 | } 16 | return acc; 17 | }, []); 18 | } 19 | 20 | export default function sitemap(): MetadataRoute.Sitemap { 21 | // Base routes 22 | const routes = ["", "/blocks", "/docs"]; 23 | 24 | // Add block category routes 25 | const blockCategoryRoutes = registryCategories.map( 26 | (category) => `/blocks/${category.slug}`, 27 | ); 28 | 29 | // Add documentation routes 30 | const docRoutes = getAllDocRoutes( 31 | docsConfig.sidebarNav.flatMap((section) => section.items), 32 | ); 33 | 34 | // Combine all routes 35 | const allRoutes = [...routes, ...blockCategoryRoutes, ...docRoutes]; 36 | 37 | return allRoutes.map((route) => ({ 38 | url: `${siteConfig.url}${route}`, 39 | lastModified: new Date().toISOString(), 40 | changeFrequency: "daily", 41 | priority: 42 | route === "" 43 | ? 1 44 | : route.startsWith("/blocks") 45 | ? 0.8 46 | : route.startsWith("/docs") 47 | ? 0.9 48 | : 0.5, 49 | })) as MetadataRoute.Sitemap; 50 | } 51 | -------------------------------------------------------------------------------- /src/components/announcement.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowRight } from "lucide-react"; 2 | import Link from "next/link"; 3 | 4 | export function Announcement() { 5 | return ( 6 | 10 | 11 | Newly Released AI Agents Workflows 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/blocks/block-display.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { z } from "zod"; 3 | 4 | import { BlockViewer } from "@/components/blocks/block-viewer"; 5 | import { highlightCode } from "@/lib/highlight-code"; 6 | import { 7 | createFileTreeForRegistryItemFiles, 8 | getRegistryItem, 9 | } from "@/lib/registry"; 10 | import type { registryItemFileSchema } from "@/registry/schema"; 11 | 12 | export async function BlockDisplay({ name }: { name: string }) { 13 | const item = await getCachedRegistryItem(name); 14 | 15 | if (!item?.files) { 16 | return null; 17 | } 18 | 19 | const [tree, highlightedFiles] = await Promise.all([ 20 | getCachedFileTree(item.files), 21 | getCachedHighlightedFiles(item.files), 22 | ]); 23 | 24 | return ( 25 | 26 | ); 27 | } 28 | 29 | const getCachedRegistryItem = React.cache(async (name: string) => { 30 | return await getRegistryItem(name); 31 | }); 32 | 33 | const getCachedFileTree = React.cache( 34 | async (files: Array<{ path: string; target?: string }>) => { 35 | if (!files) { 36 | return null; 37 | } 38 | 39 | return await createFileTreeForRegistryItemFiles(files); 40 | }, 41 | ); 42 | 43 | const getCachedHighlightedFiles = React.cache( 44 | async (files: z.infer[]) => { 45 | return await Promise.all( 46 | files.map(async (file) => ({ 47 | ...file, 48 | highlightedContent: await highlightCode(file.content ?? ""), 49 | })), 50 | ); 51 | }, 52 | ); 53 | -------------------------------------------------------------------------------- /src/components/blocks/blocks-nav.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | import { usePathname } from "next/navigation"; 5 | 6 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 7 | import { registryCategories } from "@/registry/registry-categories"; 8 | 9 | export function BlocksNav() { 10 | const pathname = usePathname(); 11 | 12 | return ( 13 |
14 | 15 |
16 | 20 | {registryCategories.map((category) => ( 21 | 26 | ))} 27 |
28 | 29 |
30 |
31 | ); 32 | } 33 | 34 | function BlocksNavLink({ 35 | category, 36 | isActive, 37 | }: { 38 | category: (typeof registryCategories)[number]; 39 | isActive: boolean; 40 | }) { 41 | if (category.hidden) { 42 | return null; 43 | } 44 | 45 | return ( 46 | 52 | {category.name} 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/components/callout.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; 2 | import { cn } from "@/lib/utils"; 3 | import type { ComponentProps } from "react"; 4 | 5 | export function Callout({ 6 | title, 7 | children, 8 | icon, 9 | className, 10 | ...props 11 | }: ComponentProps & { icon?: string }) { 12 | return ( 13 | 14 | {icon && {icon}} 15 | {title && {title}} 16 | {children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/code-block-wrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { Button } from "@/components/ui/button"; 6 | import { 7 | Collapsible, 8 | CollapsibleContent, 9 | CollapsibleTrigger, 10 | } from "@/components/ui/collapsible"; 11 | import { cn } from "@/lib/utils"; 12 | 13 | interface CodeBlockProps extends React.HTMLAttributes { 14 | expandButtonTitle?: string; 15 | } 16 | 17 | export function CodeBlockWrapper({ 18 | expandButtonTitle = "View Code", 19 | className, 20 | children, 21 | ...props 22 | }: CodeBlockProps) { 23 | const [isOpened, setIsOpened] = React.useState(false); 24 | 25 | return ( 26 | 27 |
28 | 32 |
38 | {children} 39 |
40 |
41 |
47 | 48 | 51 | 52 |
53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/components/component-preview-simple.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Index } from "@/__registry__"; 4 | import * as React from "react"; 5 | 6 | import { Loader2 } from "lucide-react"; 7 | import { useMemo } from "react"; 8 | 9 | interface ComponentPreviewSimpleProps 10 | extends React.HTMLAttributes { 11 | name: string; 12 | } 13 | 14 | export function ComponentPreviewSimple({ name }: ComponentPreviewSimpleProps) { 15 | const Preview = useMemo(() => { 16 | const Component = Index[name]?.component; 17 | 18 | if (!Component) { 19 | return ( 20 |

21 | Component{" "} 22 | 23 | {name} 24 | {" "} 25 | not found in registry. 26 |

27 | ); 28 | } 29 | 30 | return ; 31 | }, [name]); 32 | 33 | return ( 34 | 37 | 38 | Loading... 39 |
40 | } 41 | > 42 | {Preview} 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/components/component-source.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type * as React from "react"; 4 | 5 | import { CodeBlockWrapper } from "@/components/code-block-wrapper"; 6 | import { cn } from "@/lib/utils"; 7 | 8 | interface ComponentSourceProps extends React.HTMLAttributes { 9 | src: string; 10 | } 11 | 12 | export function ComponentSource({ children, className }: ComponentSourceProps) { 13 | return ( 14 | 18 | {children} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/copy-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { CheckIcon, ClipboardIcon } from "lucide-react"; 4 | import * as React from "react"; 5 | 6 | import { Button, type ButtonProps } from "@/components/ui/button"; 7 | import { type Event, useTrackEvent } from "@/lib/events"; 8 | import { cn } from "@/lib/utils"; 9 | import { useEffect } from "react"; 10 | 11 | interface CopyButtonProps extends ButtonProps { 12 | value: string; 13 | src?: string; 14 | event?: Event["name"]; 15 | } 16 | 17 | export async function copyToClipboardWithMeta( 18 | value: string, 19 | event?: Event, 20 | track?: (event: Event) => void, 21 | ) { 22 | navigator.clipboard.writeText(value); 23 | if (event && track) { 24 | track(event); 25 | } 26 | } 27 | 28 | export function CopyButton({ 29 | value, 30 | className, 31 | src, 32 | event, 33 | variant = "ghost", 34 | ...props 35 | }: CopyButtonProps) { 36 | const [hasCopied, setHasCopied] = React.useState(false); 37 | const track = useTrackEvent(); 38 | 39 | // biome-ignore lint/correctness/useExhaustiveDependencies: 40 | useEffect(() => { 41 | setTimeout(() => { 42 | setHasCopied(false); 43 | }, 2000); 44 | }, [hasCopied]); 45 | 46 | return ( 47 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/components/icons/logo-icon.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from "react"; 2 | 3 | export function LogoIcon(props: SVGProps) { 4 | return ( 5 | 13 | Logo 14 | 23 | 32 | 41 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/components/icons/x-icon.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from "react"; 2 | 3 | export function XIcon(props: SVGProps) { 4 | return ( 5 | 12 | X 13 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/layout/header.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { siteConfig } from "@/config/site"; 4 | 5 | import { CommandMenu } from "../command-menu"; 6 | import { GithubIcon } from "../icons/github-icon"; 7 | import { XIcon } from "../icons/x-icon"; 8 | import { ThemeToggle } from "../theme/theme-toggle"; 9 | import { Button } from "../ui/button"; 10 | import { MainNav } from "./main-nav"; 11 | import { MobileNav } from "./mobile-nav"; 12 | 13 | export function SiteHeader() { 14 | return ( 15 |
16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 |
24 | 48 |
49 |
50 |
51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /src/components/layout/main-nav.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | import { usePathname } from "next/navigation"; 5 | 6 | import { docsConfig } from "@/config/docs"; 7 | import { siteConfig } from "@/config/site"; 8 | import { cn } from "@/lib/utils"; 9 | import { LogoIcon } from "../icons/logo-icon"; 10 | 11 | export function MainNav() { 12 | const pathname = usePathname(); 13 | 14 | return ( 15 |
16 | 17 | 18 | 19 | {siteConfig.name} 20 | 21 | 22 | 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/layout/page-header.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import type { HTMLAttributes } from "react"; 3 | 4 | function PageHeader({ 5 | className, 6 | children, 7 | ...props 8 | }: HTMLAttributes) { 9 | return ( 10 |
11 |
12 |
13 | {children} 14 |
15 |
16 |
17 | ); 18 | } 19 | 20 | function PageHeaderHeading({ 21 | className, 22 | ...props 23 | }: HTMLAttributes) { 24 | return ( 25 |

32 | ); 33 | } 34 | 35 | function PageHeaderDescription({ 36 | className, 37 | ...props 38 | }: HTMLAttributes) { 39 | return ( 40 |

47 | ); 48 | } 49 | 50 | function PageActions({ className, ...props }: HTMLAttributes) { 51 | return ( 52 |

59 | ); 60 | } 61 | 62 | export { PageActions, PageHeader, PageHeaderDescription, PageHeaderHeading }; 63 | -------------------------------------------------------------------------------- /src/components/layout/site-footer.tsx: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "@/config/site"; 2 | 3 | export function SiteFooter() { 4 | return ( 5 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/components/pager.tsx: -------------------------------------------------------------------------------- 1 | import type { NavItem, NavItemWithChildren } from "@/types/nav"; 2 | // biome-ignore lint/correctness/noUndeclaredDependencies: 3 | import type { Doc } from "contentlayer/generated"; 4 | import { ChevronLeft, ChevronRight } from "lucide-react"; 5 | import Link from "next/link"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | import { docsConfig } from "@/config/docs"; 9 | 10 | interface DocsPagerProps { 11 | doc: Doc; 12 | } 13 | 14 | export function DocsPager({ doc }: DocsPagerProps) { 15 | const pager = getPagerForDoc(doc); 16 | 17 | if (!pager) { 18 | return null; 19 | } 20 | 21 | return ( 22 |
23 | {pager?.prev?.href && ( 24 | 30 | )} 31 | {pager?.next?.href && ( 32 | 38 | )} 39 |
40 | ); 41 | } 42 | 43 | export function getPagerForDoc(doc: Doc) { 44 | const nav = docsConfig.sidebarNav; 45 | const flattenedLinks = [null, ...flatten(nav), null]; 46 | const activeIndex = flattenedLinks.findIndex( 47 | (link) => doc.slug === link?.href, 48 | ); 49 | const prev = activeIndex !== 0 ? flattenedLinks[activeIndex - 1] : null; 50 | const next = 51 | activeIndex !== flattenedLinks.length - 1 52 | ? flattenedLinks[activeIndex + 1] 53 | : null; 54 | return { 55 | prev, 56 | next, 57 | }; 58 | } 59 | 60 | export function flatten(links: NavItemWithChildren[]): NavItem[] { 61 | return links 62 | .reduce((flat, link) => { 63 | return flat.concat(link.items?.length ? flatten(link.items) : link); 64 | }, []) 65 | .filter((link) => !link?.disabled); 66 | } 67 | -------------------------------------------------------------------------------- /src/components/providers/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | ThemeProvider as NextThemesProvider, 5 | type ThemeProviderProps, 6 | } from "next-themes"; 7 | 8 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 9 | return {children}; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/theme/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button, type ButtonProps } from "@/components/ui/button"; 4 | import { cn } from "@/lib/utils"; 5 | import { MoonIcon, SunIcon } from "lucide-react"; 6 | import { useTheme } from "next-themes"; 7 | import { useCallback } from "react"; 8 | 9 | export const ThemeToggle = ({ className, ...props }: ButtonProps) => { 10 | const { theme, setTheme } = useTheme(); 11 | 12 | const toggleTheme = useCallback(() => { 13 | setTheme(theme === "light" ? "dark" : "light"); 14 | }, [theme, setTheme]); 15 | 16 | return ( 17 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as AccordionPrimitive from "@radix-ui/react-accordion"; 4 | import { ChevronDown } from "lucide-react"; 5 | import * as React from "react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Accordion = AccordionPrimitive.Root; 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )); 21 | AccordionItem.displayName = "AccordionItem"; 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className, 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )); 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )); 55 | AccordionContent.displayName = AccordionPrimitive.Content.displayName; 56 | 57 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; 58 | -------------------------------------------------------------------------------- /src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import { type VariantProps, cva } from "class-variance-authority"; 2 | import * as React from "react"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | }, 20 | ); 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )); 33 | Alert.displayName = "Alert"; 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )); 45 | AlertTitle.displayName = "AlertTitle"; 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )); 57 | AlertDescription.displayName = "AlertDescription"; 58 | 59 | export { Alert, AlertTitle, AlertDescription }; 60 | -------------------------------------------------------------------------------- /src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root; 6 | 7 | export { AspectRatio }; 8 | -------------------------------------------------------------------------------- /src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as AvatarPrimitive from "@radix-ui/react-avatar"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )); 21 | Avatar.displayName = AvatarPrimitive.Root.displayName; 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )); 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName; 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )); 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; 49 | 50 | export { Avatar, AvatarImage, AvatarFallback }; 51 | -------------------------------------------------------------------------------- /src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import { type VariantProps, cva } from "class-variance-authority"; 2 | import type * as React from "react"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | }, 24 | ); 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ); 34 | } 35 | 36 | export { Badge, badgeVariants }; 37 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "@radix-ui/react-slot"; 2 | import { type VariantProps, cva } from "class-variance-authority"; 3 | import * as React from "react"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | }, 35 | ); 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean; 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button"; 46 | return ( 47 | 52 | ); 53 | }, 54 | ); 55 | Button.displayName = "Button"; 56 | 57 | export { Button, buttonVariants }; 58 | -------------------------------------------------------------------------------- /src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )); 18 | Card.displayName = "Card"; 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )); 30 | CardHeader.displayName = "CardHeader"; 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
41 | )); 42 | CardTitle.displayName = "CardTitle"; 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLDivElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |
53 | )); 54 | CardDescription.displayName = "CardDescription"; 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |
61 | )); 62 | CardContent.displayName = "CardContent"; 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )); 74 | CardFooter.displayName = "CardFooter"; 75 | 76 | export { 77 | Card, 78 | CardHeader, 79 | CardFooter, 80 | CardTitle, 81 | CardDescription, 82 | CardContent, 83 | }; 84 | -------------------------------------------------------------------------------- /src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; 4 | 5 | const Collapsible = CollapsiblePrimitive.Root; 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }; 12 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ); 18 | }, 19 | ); 20 | Input.displayName = "Input"; 21 | 22 | export { Input }; 23 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as LabelPrimitive from "@radix-ui/react-label"; 4 | import { type VariantProps, cva } from "class-variance-authority"; 5 | import * as React from "react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 11 | ); 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )); 24 | Label.displayName = LabelPrimitive.Root.displayName; 25 | 26 | export { Label }; 27 | -------------------------------------------------------------------------------- /src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as PopoverPrimitive from "@radix-ui/react-popover"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Popover = PopoverPrimitive.Root; 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger; 11 | 12 | const PopoverAnchor = PopoverPrimitive.Anchor; 13 | 14 | const PopoverContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 18 | 19 | 29 | 30 | )); 31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName; 32 | 33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; 34 | -------------------------------------------------------------------------------- /src/components/ui/resizable.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GripVertical } from "lucide-react"; 4 | import * as ResizablePrimitive from "react-resizable-panels"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | import type { ComponentProps } from "react"; 8 | 9 | const ResizablePanelGroup = ({ 10 | className, 11 | ...props 12 | }: ComponentProps) => ( 13 | 20 | ); 21 | 22 | const ResizablePanel = ResizablePrimitive.Panel; 23 | 24 | const ResizableHandle = ({ 25 | withHandle, 26 | className, 27 | ...props 28 | }: ComponentProps & { 29 | withHandle?: boolean; 30 | }) => ( 31 | div]:rotate-90", 34 | className, 35 | )} 36 | {...props} 37 | > 38 | {withHandle && ( 39 |
40 | 41 |
42 | )} 43 |
44 | ); 45 | 46 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; 47 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )); 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )); 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; 47 | 48 | export { ScrollArea, ScrollBar }; 49 | -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref, 15 | ) => ( 16 | 27 | ), 28 | ); 29 | Separator.displayName = SeparatorPrimitive.Root.displayName; 30 | 31 | export { Separator }; 32 | -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import type { HTMLAttributes } from "react"; 3 | 4 | function Skeleton({ className, ...props }: HTMLAttributes) { 5 | return ( 6 |
10 | ); 11 | } 12 | 13 | export { Skeleton }; 14 | -------------------------------------------------------------------------------- /src/components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as SliderPrimitive from "@radix-ui/react-slider"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Slider = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 21 | 22 | 23 | 24 | 25 | )); 26 | Slider.displayName = SliderPrimitive.Root.displayName; 27 | 28 | export { Slider }; 29 | -------------------------------------------------------------------------------- /src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | import type { ComponentProps } from "react"; 5 | import { Toaster as Sonner } from "sonner"; 6 | 7 | type ToasterProps = ComponentProps; 8 | 9 | const Toaster = ({ ...props }: ToasterProps) => { 10 | const { theme = "system" } = useTheme(); 11 | 12 | return ( 13 | 29 | ); 30 | }; 31 | 32 | export { Toaster }; 33 | -------------------------------------------------------------------------------- /src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as SwitchPrimitives from "@radix-ui/react-switch"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Switch = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 25 | 26 | )); 27 | Switch.displayName = SwitchPrimitives.Root.displayName; 28 | 29 | export { Switch }; 30 | -------------------------------------------------------------------------------- /src/components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as TabsPrimitive from "@radix-ui/react-tabs"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Tabs = TabsPrimitive.Root; 9 | 10 | const TabsList = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )); 23 | TabsList.displayName = TabsPrimitive.List.displayName; 24 | 25 | const TabsTrigger = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 37 | )); 38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; 39 | 40 | const TabsContent = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 | 52 | )); 53 | TabsContent.displayName = TabsPrimitive.Content.displayName; 54 | 55 | export { Tabs, TabsList, TabsTrigger, TabsContent }; 56 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |