├── .env.example
├── .github
└── workflows
│ └── quality.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── LICENSE.md
├── README.md
├── components.json
├── drizzle.config.ts
├── eslint.config.js
├── package.json
├── pnpm-lock.yaml
├── src
├── app.css
├── app.d.ts
├── app.html
├── hooks.server.ts
├── hooks.ts
├── lib
│ ├── ai
│ │ └── models.ts
│ ├── components
│ │ ├── app-sidebar.svelte
│ │ ├── artifact
│ │ │ └── index.ts
│ │ ├── auth-form.svelte
│ │ ├── chat-header.svelte
│ │ ├── chat.svelte
│ │ ├── code-block.svelte
│ │ ├── icons
│ │ │ ├── arrow-up.svelte
│ │ │ ├── check-circle-fill.svelte
│ │ │ ├── chevron-down.svelte
│ │ │ ├── chevron-up.svelte
│ │ │ ├── globe.svelte
│ │ │ ├── loader.svelte
│ │ │ ├── lock.svelte
│ │ │ ├── message.svelte
│ │ │ ├── more-horizontal.svelte
│ │ │ ├── panel-left.svelte
│ │ │ ├── paperclip.svelte
│ │ │ ├── pencil-edit.svelte
│ │ │ ├── plus.svelte
│ │ │ ├── share.svelte
│ │ │ ├── sidebar-left.svelte
│ │ │ ├── sparkles.svelte
│ │ │ ├── stop.svelte
│ │ │ ├── trash.svelte
│ │ │ └── vercel.svelte
│ │ ├── markdown
│ │ │ ├── index.ts
│ │ │ └── renderer.svelte
│ │ ├── message-reasoning.svelte
│ │ ├── messages.svelte
│ │ ├── messages
│ │ │ ├── overview.svelte
│ │ │ ├── preview-message.svelte
│ │ │ └── thinking-message.svelte
│ │ ├── model-selector.svelte
│ │ ├── multimodal-input.svelte
│ │ ├── preview-attachment.svelte
│ │ ├── sidebar-history
│ │ │ ├── history.svelte
│ │ │ ├── index.ts
│ │ │ └── item.svelte
│ │ ├── sidebar-toggle.svelte
│ │ ├── sidebar-user-nav.svelte
│ │ ├── submit-button.svelte
│ │ ├── suggested-actions.svelte
│ │ ├── ui
│ │ │ ├── alert-dialog
│ │ │ │ ├── alert-dialog-action.svelte
│ │ │ │ ├── alert-dialog-cancel.svelte
│ │ │ │ ├── alert-dialog-content.svelte
│ │ │ │ ├── alert-dialog-description.svelte
│ │ │ │ ├── alert-dialog-footer.svelte
│ │ │ │ ├── alert-dialog-header.svelte
│ │ │ │ ├── alert-dialog-overlay.svelte
│ │ │ │ ├── alert-dialog-title.svelte
│ │ │ │ ├── alert-dialog-trigger.svelte
│ │ │ │ └── index.ts
│ │ │ ├── button
│ │ │ │ ├── button.svelte
│ │ │ │ └── index.ts
│ │ │ ├── dropdown-menu
│ │ │ │ ├── dropdown-menu-checkbox-item.svelte
│ │ │ │ ├── dropdown-menu-content.svelte
│ │ │ │ ├── dropdown-menu-group-heading.svelte
│ │ │ │ ├── dropdown-menu-group.svelte
│ │ │ │ ├── dropdown-menu-item.svelte
│ │ │ │ ├── dropdown-menu-label.svelte
│ │ │ │ ├── dropdown-menu-radio-group.svelte
│ │ │ │ ├── dropdown-menu-radio-item.svelte
│ │ │ │ ├── dropdown-menu-separator.svelte
│ │ │ │ ├── dropdown-menu-shortcut.svelte
│ │ │ │ ├── dropdown-menu-sub-content.svelte
│ │ │ │ ├── dropdown-menu-sub-trigger.svelte
│ │ │ │ ├── dropdown-menu-trigger.svelte
│ │ │ │ └── index.ts
│ │ │ ├── input
│ │ │ │ ├── index.ts
│ │ │ │ └── input.svelte
│ │ │ ├── label
│ │ │ │ ├── index.ts
│ │ │ │ └── label.svelte
│ │ │ ├── separator
│ │ │ │ ├── index.ts
│ │ │ │ └── separator.svelte
│ │ │ ├── sheet
│ │ │ │ ├── index.ts
│ │ │ │ ├── sheet-close.svelte
│ │ │ │ ├── sheet-content.svelte
│ │ │ │ ├── sheet-description.svelte
│ │ │ │ ├── sheet-footer.svelte
│ │ │ │ ├── sheet-header.svelte
│ │ │ │ ├── sheet-overlay.svelte
│ │ │ │ ├── sheet-title.svelte
│ │ │ │ └── sheet-trigger.svelte
│ │ │ ├── sidebar
│ │ │ │ ├── constants.ts
│ │ │ │ ├── context.svelte.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── sidebar-content.svelte
│ │ │ │ ├── sidebar-footer.svelte
│ │ │ │ ├── sidebar-group-action.svelte
│ │ │ │ ├── sidebar-group-content.svelte
│ │ │ │ ├── sidebar-group-label.svelte
│ │ │ │ ├── sidebar-group.svelte
│ │ │ │ ├── sidebar-header.svelte
│ │ │ │ ├── sidebar-input.svelte
│ │ │ │ ├── sidebar-inset.svelte
│ │ │ │ ├── sidebar-menu-action.svelte
│ │ │ │ ├── sidebar-menu-badge.svelte
│ │ │ │ ├── sidebar-menu-button.svelte
│ │ │ │ ├── sidebar-menu-item.svelte
│ │ │ │ ├── sidebar-menu-skeleton.svelte
│ │ │ │ ├── sidebar-menu-sub-button.svelte
│ │ │ │ ├── sidebar-menu-sub-item.svelte
│ │ │ │ ├── sidebar-menu-sub.svelte
│ │ │ │ ├── sidebar-menu.svelte
│ │ │ │ ├── sidebar-provider.svelte
│ │ │ │ ├── sidebar-rail.svelte
│ │ │ │ ├── sidebar-separator.svelte
│ │ │ │ ├── sidebar-trigger.svelte
│ │ │ │ └── sidebar.svelte
│ │ │ ├── skeleton
│ │ │ │ ├── index.ts
│ │ │ │ └── skeleton.svelte
│ │ │ ├── sonner
│ │ │ │ ├── index.ts
│ │ │ │ └── sonner.svelte
│ │ │ ├── textarea
│ │ │ │ ├── index.ts
│ │ │ │ └── textarea.svelte
│ │ │ └── tooltip
│ │ │ │ ├── index.ts
│ │ │ │ ├── tooltip-content.svelte
│ │ │ │ └── tooltip-trigger.svelte
│ │ └── visibility-selector.svelte
│ ├── errors
│ │ ├── ai.ts
│ │ ├── db.ts
│ │ └── tagged-error.ts
│ ├── hooks
│ │ ├── chat-history.svelte.ts
│ │ ├── is-mobile.svelte.ts
│ │ ├── local-storage.svelte.ts
│ │ ├── lock.ts
│ │ └── selected-model.svelte.ts
│ ├── server
│ │ ├── ai
│ │ │ ├── models.ts
│ │ │ ├── prompts.ts
│ │ │ └── utils.ts
│ │ ├── auth
│ │ │ ├── handle.ts
│ │ │ └── index.ts
│ │ └── db
│ │ │ ├── migrate.ts
│ │ │ ├── migrations
│ │ │ ├── 0000_ambiguous_dragon_man.sql
│ │ │ └── meta
│ │ │ │ ├── 0000_snapshot.json
│ │ │ │ └── _journal.json
│ │ │ ├── queries.ts
│ │ │ ├── schema.ts
│ │ │ └── utils.ts
│ └── utils
│ │ ├── chat.ts
│ │ ├── constants.ts
│ │ ├── reactivity.svelte.ts
│ │ ├── shadcn.ts
│ │ └── types.ts
├── params
│ └── authType.ts
└── routes
│ ├── (auth)
│ ├── [authType=authType]
│ │ ├── +page.server.ts
│ │ └── +page.svelte
│ └── signout
│ │ └── +page.server.ts
│ ├── (chat)
│ ├── +layout.server.ts
│ ├── +layout.svelte
│ ├── +layout.ts
│ ├── +page.svelte
│ ├── api
│ │ ├── chat
│ │ │ ├── +server.ts
│ │ │ └── visibility
│ │ │ │ └── +server.ts
│ │ ├── files
│ │ │ └── upload
│ │ │ │ └── +server.ts
│ │ ├── history
│ │ │ └── +server.ts
│ │ ├── suggestions
│ │ │ └── [documentId]
│ │ │ │ └── +server.ts
│ │ ├── synchronized-cookie
│ │ │ └── [cookieName]
│ │ │ │ └── +server.ts
│ │ └── vote
│ │ │ └── [chatId]
│ │ │ ├── +server.ts
│ │ │ └── [messageId]
│ │ │ └── +server.ts
│ └── chat
│ │ └── [chatId]
│ │ ├── +page.server.ts
│ │ └── +page.svelte
│ └── +layout.svelte
├── static
├── favicon.png
├── fonts
│ ├── geist-mono.woff2
│ └── geist.woff2
└── opengraph-image.png
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts
/.env.example:
--------------------------------------------------------------------------------
1 | # The following keys are automatically created when you deploy with Vercel
2 | # and use the suggested integrations.
3 |
4 | # Get your xAI API Key here for chat models: https://console.x.ai/
5 | XAI_API_KEY=****
6 |
7 | # Get your Groq API Key here for reasoning models: https://console.groq.com/keys
8 | GROQ_API_KEY=****
9 |
10 | # Instructions to create a Vercel Blob Store here: https://vercel.com/docs/storage/vercel-blob
11 | BLOB_READ_WRITE_TOKEN=****
12 |
13 | # Instructions to create a database here: https://vercel.com/docs/storage/vercel-postgres/quickstart
14 | POSTGRES_URL=****
15 |
--------------------------------------------------------------------------------
/.github/workflows/quality.yml:
--------------------------------------------------------------------------------
1 | name: Quality
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | lint:
11 | name: Lint
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - name: Enable corepack
16 | run: npm i -g --force corepack && corepack enable
17 | - uses: actions/setup-node@v4
18 | with:
19 | node-version: 20.x
20 | cache: 'pnpm'
21 |
22 | - run: pnpm install
23 | env:
24 | CI: true
25 |
26 | - run: pnpm lint
27 |
28 | check:
29 | name: Type Check
30 | runs-on: ubuntu-latest
31 | steps:
32 | - uses: actions/checkout@v4
33 | - name: Enable corepack
34 | run: npm i -g --force corepack && corepack enable
35 | - uses: actions/setup-node@v4
36 | with:
37 | node-version: 20.x
38 | cache: 'pnpm'
39 |
40 | - run: pnpm install
41 | env:
42 | CI: true
43 |
44 | - run: pnpm check
45 | env:
46 | POSTGRES_URL: ''
47 | XAI_API_KEY: ''
48 | GROQ_API_KEY: ''
49 | BLOB_READ_WRITE_TOKEN: ''
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Output
4 | .output
5 | .vercel
6 | .netlify
7 | .wrangler
8 | /.svelte-kit
9 | /build
10 |
11 | # OS
12 | .DS_Store
13 | Thumbs.db
14 |
15 | # Env
16 | .env
17 | .env.*
18 | !.env.example
19 | !.env.test
20 |
21 | # Vite
22 | vite.config.js.timestamp-*
23 | vite.config.ts.timestamp-*
24 | .env*.local
25 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Package Managers
2 | package-lock.json
3 | pnpm-lock.yaml
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
7 | "overrides": [
8 | {
9 | "files": "*.svelte",
10 | "options": {
11 | "parser": "svelte"
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2025 Vercel, Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
SvelteKit AI Chatbot
4 |
5 |
6 |
7 | An Open-Source AI Chatbot Template Built With SvelteKit and the AI SDK by Vercel. 8 |
9 | 10 |11 | Features · 12 | Model Providers · 13 | Deploy Your Own · 14 | Running locally 15 |
16 |
20 | {@render children?.()}
21 |
22 | {:else}
23 |
27 | {@render children?.()}
28 |
29 |
10 |
15 | This is an
16 |
21 | open source
22 |
23 | chatbot template built with SvelteKit and the AI SDK by Vercel. It uses the
24 | streamText
25 | function in the server and the
26 | useChat
hook on the client to create a seamless
27 | chat experience.
28 |
30 | You can learn more about the AI SDK by visiting the 31 | 36 | docs 37 | 38 | . 39 |
40 |18 | Use your email and password to {signInSignUp.toLowerCase()} 19 |
20 |57 | {question} 58 | 59 | {cta} 60 | 61 | {postscript} 62 |
63 | {/snippet} 64 | -------------------------------------------------------------------------------- /src/routes/(auth)/signout/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { deleteSessionTokenCookie, invalidateSession } from '$lib/server/auth/index.js'; 2 | import { redirect } from '@sveltejs/kit'; 3 | 4 | export function load({ locals, cookies }) { 5 | if (locals.session) { 6 | invalidateSession(locals.session.id); 7 | deleteSessionTokenCookie(cookies); 8 | } 9 | 10 | redirect(307, '/signin'); 11 | } 12 | -------------------------------------------------------------------------------- /src/routes/(chat)/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import { chatModels, DEFAULT_CHAT_MODEL } from '$lib/ai/models'; 2 | import { SelectedModel } from '$lib/hooks/selected-model.svelte.js'; 3 | 4 | export async function load({ cookies, locals }) { 5 | const { user } = locals; 6 | const sidebarCollapsed = cookies.get('sidebar:state') !== 'true'; 7 | 8 | let modelId = cookies.get('selected-model'); 9 | if (!modelId || !chatModels.find((model) => model.id === modelId)) { 10 | modelId = DEFAULT_CHAT_MODEL; 11 | cookies.set('selected-model', modelId, { 12 | path: '/', 13 | expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30), 14 | httpOnly: true, 15 | sameSite: 'lax' 16 | }); 17 | } 18 | 19 | return { 20 | user, 21 | sidebarCollapsed, 22 | selectedChatModel: new SelectedModel(modelId) 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/routes/(chat)/+layout.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |