├── .DS_Store
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github
└── FUNDING.yml
├── .gitignore
├── .gitmodules
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .vscode
└── extensions.json
├── LICENSE
├── README.md
├── compile_model.sh
├── components.json
├── docker-compose.yml
├── icon.png
├── index.html
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── shims.vue.d.ts
├── src
├── App.spec.ts
├── App.vue
├── assets
│ └── css
│ │ └── tailwind.css
├── components
│ ├── dark-toggle
│ │ └── DarkToggle.vue
│ ├── domain
│ │ └── bot
│ │ │ ├── CreateBotForm.vue
│ │ │ └── UpdateBotForm.vue
│ └── ui
│ │ ├── alert-dialog
│ │ ├── AlertDialog.vue
│ │ ├── AlertDialogAction.vue
│ │ ├── AlertDialogCancel.vue
│ │ ├── AlertDialogContent.vue
│ │ ├── AlertDialogDescription.vue
│ │ ├── AlertDialogFooter.vue
│ │ ├── AlertDialogHeader.vue
│ │ ├── AlertDialogTitle.vue
│ │ ├── AlertDialogTrigger.vue
│ │ └── index.ts
│ │ ├── badge
│ │ ├── Badge.vue
│ │ └── index.ts
│ │ ├── button
│ │ ├── Button.vue
│ │ └── index.ts
│ │ ├── card
│ │ ├── Card.vue
│ │ ├── CardContent.vue
│ │ ├── CardDescription.vue
│ │ ├── CardFooter.vue
│ │ ├── CardHeader.vue
│ │ ├── CardTitle.vue
│ │ └── index.ts
│ │ ├── chat
│ │ └── ChatMessage.vue
│ │ ├── command
│ │ ├── Command.vue
│ │ ├── CommandDialog.vue
│ │ ├── CommandEmpty.vue
│ │ ├── CommandGroup.vue
│ │ ├── CommandInput.vue
│ │ ├── CommandItem.vue
│ │ ├── CommandList.vue
│ │ ├── CommandSeparator.vue
│ │ ├── CommandShortcut.vue
│ │ └── index.ts
│ │ ├── dialog
│ │ ├── Dialog.vue
│ │ ├── DialogClose.vue
│ │ ├── DialogContent.vue
│ │ ├── DialogDescription.vue
│ │ ├── DialogFooter.vue
│ │ ├── DialogHeader.vue
│ │ ├── DialogTitle.vue
│ │ ├── DialogTrigger.vue
│ │ └── index.ts
│ │ ├── form
│ │ ├── FormControl.vue
│ │ ├── FormDescription.vue
│ │ ├── FormItem.vue
│ │ ├── FormLabel.vue
│ │ ├── FormMessage.vue
│ │ ├── index.ts
│ │ └── useFormField.ts
│ │ ├── input
│ │ ├── Input.vue
│ │ └── index.ts
│ │ ├── loading
│ │ └── Loading.vue
│ │ ├── menu-item
│ │ └── MenuItem.vue
│ │ ├── popover
│ │ ├── Popover.vue
│ │ ├── PopoverContent.vue
│ │ ├── PopoverTrigger.vue
│ │ └── index.ts
│ │ ├── skeleton
│ │ ├── Skeleton.vue
│ │ └── index.ts
│ │ ├── slider
│ │ ├── Slider.vue
│ │ └── index.ts
│ │ ├── switch
│ │ ├── Switch.vue
│ │ └── index.ts
│ │ ├── table
│ │ ├── Table.vue
│ │ ├── TableBody.vue
│ │ ├── TableCaption.vue
│ │ ├── TableCell.vue
│ │ ├── TableEmpty.vue
│ │ ├── TableFooter.vue
│ │ ├── TableHead.vue
│ │ ├── TableHeader.vue
│ │ ├── TableRow.vue
│ │ └── index.ts
│ │ ├── text
│ │ ├── Text.vue
│ │ └── index.ts
│ │ ├── textarea
│ │ ├── Textarea.vue
│ │ └── index.ts
│ │ ├── toast
│ │ ├── Toast.vue
│ │ ├── ToastAction.vue
│ │ ├── ToastClose.vue
│ │ ├── ToastDescription.vue
│ │ ├── ToastProvider.vue
│ │ ├── ToastTitle.vue
│ │ ├── ToastViewport.vue
│ │ ├── Toaster.vue
│ │ ├── index.ts
│ │ └── use-toast.ts
│ │ └── tooltip
│ │ ├── QuestionMark.vue
│ │ ├── Tooltip.vue
│ │ ├── TooltipContent.vue
│ │ ├── TooltipProvider.vue
│ │ ├── TooltipTrigger.vue
│ │ └── index.ts
├── composables
│ ├── useDb.ts
│ ├── useLLM.ts
│ ├── useModel.ts
│ └── useWebGPU.ts
├── lib
│ └── utils.ts
├── main.ts
├── router
│ └── index.ts
├── store
│ ├── chat.ts
│ └── main.ts
├── styles.css
├── types.ts
├── views
│ ├── Home.vue
│ ├── Models.vue
│ └── Prompts.vue
└── web-worker.ts
├── static
└── logo.png
├── tailwind.config.js
├── todo.md
├── tsconfig.app.json
├── tsconfig.base.json
├── tsconfig.json
├── tsconfig.spec.json
└── vite.config.ts
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zuramai/ChatLLM/ef347adf68d62abf7d138b5e07deb8f4bc7ab053/.DS_Store
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": ["**/*"],
4 | "plugins": ["@nx"],
5 | "overrides": [
6 | {
7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
8 | "rules": {
9 | "@nx/enforce-module-boundaries": [
10 | "error",
11 | {
12 | "enforceBuildableLibDependency": true,
13 | "allow": [],
14 | "depConstraints": [
15 | {
16 | "sourceTag": "*",
17 | "onlyDependOnLibsWithTags": ["*"]
18 | }
19 | ]
20 | }
21 | ]
22 | }
23 | },
24 | {
25 | "files": ["*.ts", "*.tsx"],
26 | "extends": ["plugin:@nx/typescript"],
27 | "rules": {}
28 | },
29 | {
30 | "files": ["*.js", "*.jsx"],
31 | "extends": ["plugin:@nx/javascript"],
32 | "rules": {}
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: zuramai # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: saugi # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | dist
5 | dev-dist
6 | tmp
7 | /out-tsc
8 |
9 | # dependencies
10 | node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | yarn-error.log
35 | testem.log
36 | /typings
37 |
38 | # System Files
39 | .DS_Store
40 | Thumbs.db
41 |
42 | .nx/cache
43 |
44 | .yarn/*
45 | !.yarn/releases
46 | !.yarn/plugins
47 | .pnp.*
48 | cache
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "frontend/libs/web-llm"]
2 | path = libs/web-llm
3 | url = https://github.com/zuramai/web-llm
4 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | strict-peer-dependencies=false
2 | auto-install-peers=true
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 | /dist
3 | /coverage
4 | /.nx/cache
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "nrwl.angular-console",
4 | "esbenp.prettier-vscode",
5 | "dbaeumer.vscode-eslint"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Saugi
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 | # ChatLLM
2 |
3 | Chat with LLM directly in your browser using your own GPU (powered by WebGPU)
4 |
5 | 
6 |
7 | 1. Clone the repository
8 | ```
9 | git clone https://github.com/zuramai/ChatLLM --recursive
10 | cd ChatLLM
11 | ```
12 |
13 | 2. Install
14 | ```
15 | pnpm install
16 | ```
17 |
18 | 3. Run
19 | ```
20 | pnpm run dev
21 | ```
22 |
23 | # License
24 | This project is under MIT License
25 |
--------------------------------------------------------------------------------
/compile_model.sh:
--------------------------------------------------------------------------------
1 | mlc_llm convert_weight ../models/orca_mini_3b --quantization q4f32_1 -o dist/orca_mini_3b-q4f32_1
2 | mlc_llm compile dist/orca_mini_3b-q4f32_1/mlc-chat-config.json --device webgpu -o dist/orca_mini_3b-q4f32_1/orca_mini_3b-q4f32_1.wasm
3 | mlc_llm gen_config ../models/orca_mini_3b --quantization q4f32_1 -o dist/orca_mini_3b-q4f32_1 --conv-template wizardlm_7b
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "style": "default",
3 | "typescript": true,
4 | "tailwind": {
5 | "config": "tailwind.config.js",
6 | "css": "src/assets/index.css",
7 | "baseColor": "slate",
8 | "cssVariables": true
9 | },
10 | "framework": "vite",
11 | "aliases": {
12 | "components": "src/components",
13 | "utils": "src/lib/utils"
14 | }
15 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 |
3 | services:
4 | pg:
5 | container_name: pg
6 | image: postgres
7 | restart: always
8 | volumes:
9 | - db-data:/var/lib/postgresql/data
10 | environment:
11 | POSTGRES_PASSWORD: postgres
12 | POSTGRES_USER: postgres
13 | ports:
14 | - 5432:5432
15 | app:
16 | build:
17 | context: ./frontend
18 | dockerfile: Dockerfile
19 | container_name: llm-frontend
20 | command: pnpm run dev & pnpm run llm:dev
21 | ports:
22 | - '3000:3000'
23 | volumes:
24 | - ./frontend:/app
25 | - /app/node_modules
26 |
27 | volumes:
28 | db-data:
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zuramai/ChatLLM/ef347adf68d62abf7d138b5e07deb8f4bc7ab053/icon.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ChatLLM
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "llmchat",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vue-tsc --noEmit && vite build",
8 | "preview": "vite preview"
9 | },
10 | "private": true,
11 | "dependencies": {
12 | "@mlc-ai/web-llm": "^0.2.50",
13 | "@swc/helpers": "~0.5.12",
14 | "@vee-validate/zod": "^4.13.2",
15 | "@vueuse/core": "^10.11.0",
16 | "class-variance-authority": "^0.7.0",
17 | "clsx": "^2.1.1",
18 | "esbuild": "^0.21.5",
19 | "localforage": "^1.10.0",
20 | "lucide-vue-next": "^0.292.0",
21 | "pdfjs-dist": "^4.4.168",
22 | "pinia": "^2.1.7",
23 | "radix-vue": "^1.9.1",
24 | "rollup": "^4.18.1",
25 | "tailwind-merge": "^2.4.0",
26 | "tailwindcss-animate": "^1.0.7",
27 | "vee-validate": "^4.13.2",
28 | "vue": "^3.4.31",
29 | "vue-router": "^4.4.0",
30 | "zod": "^3.23.8"
31 | },
32 | "devDependencies": {
33 | "@swc-node/register": "~1.6.8",
34 | "@swc/cli": "~0.1.65",
35 | "@swc/core": "~1.3.107",
36 | "@types/node": "18.7.1",
37 | "@typescript-eslint/eslint-plugin": "^6.21.0",
38 | "@typescript-eslint/parser": "^6.21.0",
39 | "@vitejs/plugin-vue": "^4.6.2",
40 | "@vitest/coverage-v8": "~0.32.4",
41 | "@vitest/ui": "~0.32.4",
42 | "@vue/eslint-config-prettier": "7.1.0",
43 | "@vue/eslint-config-typescript": "^11.0.3",
44 | "@vue/test-utils": "^2.4.6",
45 | "@vue/tsconfig": "^0.4.0",
46 | "autoprefixer": "^10.4.19",
47 | "eslint": "~8.46.0",
48 | "eslint-config-prettier": "^9.1.0",
49 | "eslint-plugin-vue": "^9.27.0",
50 | "jsdom": "~22.1.0",
51 | "loglevel": "^1.9.1",
52 | "postcss": "^8.4.39",
53 | "prettier": "^2.8.8",
54 | "tailwindcss": "^3.4.5",
55 | "tslib": "^2.6.3",
56 | "typescript": "~5.2.2",
57 | "vite": "~4.3.9",
58 | "vite-plugin-dts": "~2.3.0",
59 | "vite-plugin-pwa": "^0.17.5",
60 | "vite-plugin-top-level-await": "^1.4.1",
61 | "vitest": "~0.32.4",
62 | "vue-tsc": "^1.8.27"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/shims.vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue';
2 |
--------------------------------------------------------------------------------
/src/App.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 |
3 | import { mount } from '@vue/test-utils';
4 | import App from './App.vue';
5 |
6 | describe('App', () => {
7 | it('renders properly', () => {
8 | const wrapper = mount(App, {});
9 | expect(wrapper.text()).toContain('Welcome frontend 👋');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
68 |
69 |
70 |
71 |
85 |
86 |
138 |
139 |
140 |
143 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/src/assets/css/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: white;
8 | --foreground: black;
9 | --main-bg: rgba(244, 246, 251, 0.592);
10 |
11 | --scrollbarBG: #f7f7f7;
12 | --thumbBG: #90A4AE;
13 |
14 | --heading-color: hsl(0, 0%, 2%);
15 |
16 | --muted: hsl(210 40% 96.1%);
17 | --muted-foreground: hsl(215.4 16.3% 46.9%);
18 |
19 | --popover: hsl(0 0% 100%);
20 | --popover-foreground: hsl(222.2 84% 4.9%);
21 |
22 | --card: hsl(0 0% 100%);
23 | --card-foreground: hsl(222.2 84% 4.9%);
24 |
25 | --border: hsl(214.3 31.8% 91.4%);
26 | --input: hsl(213, 27%, 92%);
27 |
28 | --primary: rgb(70, 154, 196);
29 | --primary-foreground: hsl(210 40% 98%);
30 |
31 | --secondary: hsl(210, 27%, 94%);
32 | --secondary-foreground: hsl(222.2 47.4% 11.2%);
33 |
34 | --accent: hsl(210 40% 96.1%);
35 | --accent-foreground: hsl(222.2 47.4% 11.2%);
36 |
37 | --destructive: hsl(348 100% 57%);
38 | --destructive-foreground: hsl(210 40% 98%);
39 |
40 | --success: hsl(167.26deg 52.66% 48.79%);
41 | --success-foreground: hsl(136, 0%, 100%);
42 |
43 | --ring: hsl(222.2 84% 4.9%);
44 |
45 | --radius: 0.5rem;
46 | }
47 |
48 | }
49 |
50 | .dark {
51 | --background: hsl(180, 31%, 6%);
52 | --foreground: hsl(218 11% 65%);
53 | --main-bg: rgba(24, 28, 40, 0.481);;
54 |
55 | --scrollbarBG: #1b1e2f;
56 | --thumbBG: #90A4AE;
57 |
58 | --muted: hsl(217.2 32.6% 17.5%);
59 | --muted-foreground: hsl(215 20.2% 65.1%);
60 |
61 | --popover: hsl(222.2 84% 4.9%);
62 | --popover-foreground: hsl(210 40% 98%);
63 |
64 | --card: hsl(222.2 84% 4.9%);
65 | --card-foreground: hsl(218 11% 65%);
66 | --border: hsl(217.2 32.6% 17.5%);
67 | --input: hsl(217.2 32.6% 17.5%);
68 |
69 | --primary: rgb(70, 154, 196);
70 | --primary-foreground: hsl(210 40% 98%);
71 |
72 | --secondary: hsl(217.2 32.6% 17.5%);
73 | --secondary-foreground: hsl(210 40% 98%);
74 |
75 | --accent: hsl(217.2 32.6% 17.5%);
76 | --accent-foreground: hsl(210 40% 98%);
77 |
78 | --destructive: hsl(350 70% 56%);
79 | --destructive-foreground: hsl(210 40% 98%);
80 |
81 | --ring: hsl(212.7 26.8% 83.9%);
82 | --heading-color: hsl(210 40% 98%);
83 | }
84 | .chat-messages {
85 | scrollbar-width: thin;
86 | }
87 | html {
88 | --scrollbarBG: #1b1e2f;
89 | --thumbBG: #90A4AE;
90 | }
91 | .chat-messages::-webkit-scrollbar {
92 | width: 11px;
93 | }
94 | .chat-messages {
95 | scrollbar-width: thin;
96 | scrollbar-color: var(--thumbBG) var(--scrollbarBG);
97 | }
98 | .chat-messages::-webkit-scrollbar-track {
99 | background: var(--scrollbarBG);
100 | }
101 | .chat-messages::-webkit-scrollbar-thumb {
102 | background-color: var(--thumbBG) ;
103 | border-radius: 6px;
104 | border: 3px solid var(--scrollbarBG);
105 | }
106 | .body-content main {
107 | background: var(--main-bg);
108 | }
109 | @layer base {
110 | * {
111 | @apply border-border;
112 | }
113 | body {
114 | @apply bg-background text-foreground;
115 | }
116 | h1,h2,h3,h4,h5,h6 {
117 | color: var(--heading-color);
118 | }
119 | }
--------------------------------------------------------------------------------
/src/components/dark-toggle/DarkToggle.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/domain/bot/CreateBotForm.vue:
--------------------------------------------------------------------------------
1 |
101 |
102 |
249 |
--------------------------------------------------------------------------------
/src/components/domain/bot/UpdateBotForm.vue:
--------------------------------------------------------------------------------
1 |
106 |
107 |
251 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialog.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogAction.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogCancel.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogContent.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
23 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogDescription.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogFooter.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogHeader.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogTitle.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogTrigger.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AlertDialog } from './AlertDialog.vue'
2 | export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'
3 | export { default as AlertDialogContent } from './AlertDialogContent.vue'
4 | export { default as AlertDialogHeader } from './AlertDialogHeader.vue'
5 | export { default as AlertDialogTitle } from './AlertDialogTitle.vue'
6 | export { default as AlertDialogDescription } from './AlertDialogDescription.vue'
7 | export { default as AlertDialogFooter } from './AlertDialogFooter.vue'
8 | export { default as AlertDialogAction } from './AlertDialogAction.vue'
9 | export { default as AlertDialogCancel } from './AlertDialogCancel.vue'
10 |
--------------------------------------------------------------------------------
/src/components/ui/badge/Badge.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/badge/index.ts:
--------------------------------------------------------------------------------
1 | import { type VariantProps, cva } from 'class-variance-authority'
2 |
3 | export { default as Badge } from './Badge.vue'
4 |
5 | export const badgeVariants = cva(
6 | 'inline-flex items-center rounded-full 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',
7 | {
8 | variants: {
9 | variant: {
10 | default:
11 | 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
12 | secondary:
13 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
14 | destructive:
15 | 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
16 | outline: 'text-foreground',
17 | },
18 | },
19 | defaultVariants: {
20 | variant: 'default',
21 | },
22 | },
23 | )
24 |
25 | export type BadgeVariants = VariantProps
26 |
--------------------------------------------------------------------------------
/src/components/ui/button/Button.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/components/ui/button/index.ts:
--------------------------------------------------------------------------------
1 | import { cva } from 'class-variance-authority'
2 |
3 | export { default as Button } from './Button.vue'
4 |
5 | export const buttonVariants = cva(
6 | 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
7 | {
8 | variants: {
9 | variant: {
10 | default: 'bg-primary text-primary-foreground hover:bg-primary/90',
11 | destructive:
12 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
13 | danger:
14 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
15 | 'outline-danger':
16 | 'bg-transparent border-destructive border text-destructive hover:bg-destructive/90 hover:text-white',
17 | outline:
18 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
19 | secondary:
20 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
21 | ghost: 'hover:bg-accent hover:text-accent-foreground',
22 | link: 'text-primary underline-offset-4 hover:underline',
23 | },
24 | size: {
25 | default: 'h-10 px-4 py-2',
26 | sm: 'h-9 rounded-md px-3',
27 | lg: 'h-11 rounded-md px-8',
28 | icon: 'h-10 w-10',
29 | },
30 | },
31 | defaultVariants: {
32 | variant: 'default',
33 | size: 'default',
34 | },
35 | },
36 | )
37 |
--------------------------------------------------------------------------------
/src/components/ui/card/Card.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/components/ui/card/CardContent.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/card/CardDescription.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/card/CardFooter.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/card/CardHeader.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/card/CardTitle.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/ui/card/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Card } from './Card.vue'
2 | export { default as CardHeader } from './CardHeader.vue'
3 | export { default as CardTitle } from './CardTitle.vue'
4 | export { default as CardDescription } from './CardDescription.vue'
5 | export { default as CardContent } from './CardContent.vue'
6 | export { default as CardFooter } from './CardFooter.vue'
7 |
--------------------------------------------------------------------------------
/src/components/ui/chat/ChatMessage.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/ui/command/Command.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/ui/command/CommandDialog.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/ui/command/CommandEmpty.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/command/CommandGroup.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
16 |
17 | {{ heading }}
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/ui/command/CommandInput.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/ui/command/CommandItem.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/ui/command/CommandList.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/ui/command/CommandSeparator.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/command/CommandShortcut.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/components/ui/command/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Command } from './Command.vue'
2 | export { default as CommandDialog } from './CommandDialog.vue'
3 | export { default as CommandEmpty } from './CommandEmpty.vue'
4 | export { default as CommandGroup } from './CommandGroup.vue'
5 | export { default as CommandInput } from './CommandInput.vue'
6 | export { default as CommandItem } from './CommandItem.vue'
7 | export { default as CommandList } from './CommandList.vue'
8 | export { default as CommandSeparator } from './CommandSeparator.vue'
9 | export { default as CommandShortcut } from './CommandShortcut.vue'
10 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/Dialog.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogClose.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogContent.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
28 |
29 |
38 |
39 |
40 |
43 |
44 | Close
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogDescription.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogFooter.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogHeader.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogTitle.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogTrigger.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Dialog } from './Dialog.vue'
2 | export { default as DialogClose } from './DialogClose.vue'
3 | export { default as DialogTrigger } from './DialogTrigger.vue'
4 | export { default as DialogHeader } from './DialogHeader.vue'
5 | export { default as DialogTitle } from './DialogTitle.vue'
6 | export { default as DialogDescription } from './DialogDescription.vue'
7 | export { default as DialogContent } from './DialogContent.vue'
8 | export { default as DialogFooter } from './DialogFooter.vue'
9 |
--------------------------------------------------------------------------------
/src/components/ui/form/FormControl.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/form/FormDescription.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/ui/form/FormItem.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/components/ui/form/FormLabel.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/ui/form/FormMessage.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/form/index.ts:
--------------------------------------------------------------------------------
1 | export { Form, Field as FormField } from 'vee-validate'
2 | export { default as FormItem } from './FormItem.vue'
3 | export { default as FormLabel } from './FormLabel.vue'
4 | export { default as FormControl } from './FormControl.vue'
5 | export { default as FormMessage } from './FormMessage.vue'
6 | export { default as FormDescription } from './FormDescription.vue'
7 |
--------------------------------------------------------------------------------
/src/components/ui/form/useFormField.ts:
--------------------------------------------------------------------------------
1 | import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate'
2 | import { inject } from 'vue'
3 | import { FORM_ITEM_INJECTION_KEY } from './FormItem.vue'
4 |
5 | export function useFormField() {
6 | const fieldContext = inject(FieldContextKey)
7 | const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY)
8 |
9 | const fieldState = {
10 | valid: useIsFieldValid(),
11 | isDirty: useIsFieldDirty(),
12 | isTouched: useIsFieldTouched(),
13 | error: useFieldError(),
14 | }
15 |
16 | if (!fieldContext)
17 | throw new Error('useFormField should be used within ')
18 |
19 | const { name } = fieldContext
20 | const id = fieldItemContext
21 |
22 | return {
23 | id,
24 | name,
25 | formItemId: `${id}-form-item`,
26 | formDescriptionId: `${id}-form-item-description`,
27 | formMessageId: `${id}-form-item-message`,
28 | ...fieldState,
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/ui/input/Input.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/components/ui/input/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Input } from './Input.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/loading/Loading.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/ui/menu-item/MenuItem.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/ui/popover/Popover.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/ui/popover/PopoverContent.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/components/ui/popover/PopoverTrigger.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/popover/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Popover } from './Popover.vue'
2 | export { default as PopoverTrigger } from './PopoverTrigger.vue'
3 | export { default as PopoverContent } from './PopoverContent.vue'
4 |
--------------------------------------------------------------------------------
/src/components/ui/skeleton/Skeleton.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/skeleton/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Skeleton } from './Skeleton.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/slider/Slider.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/components/ui/slider/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Slider } from './Slider.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/switch/Switch.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
33 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/components/ui/switch/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Switch } from './Switch.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/table/Table.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/table/TableBody.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/table/TableCaption.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/table/TableCell.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
16 |
17 | |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/ui/table/TableEmpty.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/components/ui/table/TableFooter.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/table/TableHead.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/table/TableHeader.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/table/TableRow.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/table/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Table } from './Table.vue'
2 | export { default as TableBody } from './TableBody.vue'
3 | export { default as TableCell } from './TableCell.vue'
4 | export { default as TableHead } from './TableHead.vue'
5 | export { default as TableHeader } from './TableHeader.vue'
6 | export { default as TableRow } from './TableRow.vue'
7 | export { default as TableCaption } from './TableCaption.vue'
8 | export { default as TableEmpty } from './TableEmpty.vue'
9 |
--------------------------------------------------------------------------------
/src/components/ui/text/Text.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/ui/text/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Text } from './Text.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/textarea/Textarea.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/ui/textarea/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Textarea } from './Textarea.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/toast/Toast.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/components/ui/toast/ToastAction.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/ui/toast/ToastClose.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/ui/toast/ToastDescription.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/ui/toast/ToastProvider.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/toast/ToastTitle.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/ui/toast/ToastViewport.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/ui/toast/Toaster.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{ toast.title }}
15 |
16 |
17 |
18 |
19 |
20 |
21 | {{ toast.description }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/ui/toast/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Toaster } from './Toaster.vue'
2 | export { default as Toast } from './Toast.vue'
3 | export { default as ToastViewport } from './ToastViewport.vue'
4 | export { default as ToastAction } from './ToastAction.vue'
5 | export { default as ToastClose } from './ToastClose.vue'
6 | export { default as ToastTitle } from './ToastTitle.vue'
7 | export { default as ToastDescription } from './ToastDescription.vue'
8 | export { default as ToastProvider } from './ToastProvider.vue'
9 | export { toast, useToast } from './use-toast'
10 |
11 | import { cva } from 'class-variance-authority'
12 |
13 | export const toastVariants = cva(
14 | 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
15 | {
16 | variants: {
17 | variant: {
18 | default: 'border bg-background text-foreground',
19 | destructive: 'destructive group border-destructive bg-destructive text-destructive-foreground',
20 | success: 'success group border-success bg-success text-success-foreground',
21 | },
22 | },
23 | defaultVariants: {
24 | variant: 'default',
25 | },
26 | },
27 | )
28 |
--------------------------------------------------------------------------------
/src/components/ui/toast/use-toast.ts:
--------------------------------------------------------------------------------
1 | import { computed, ref } from 'vue'
2 | import type { Component, VNode } from 'vue'
3 | import type { ToastProps } from './Toast.vue'
4 |
5 | const TOAST_LIMIT = 1
6 | const TOAST_REMOVE_DELAY = 1000000
7 |
8 | export type StringOrVNode =
9 | | string
10 | | VNode
11 | | (() => VNode)
12 |
13 | type ToasterToast = ToastProps & {
14 | id: string
15 | title?: string
16 | description?: StringOrVNode
17 | action?: Component
18 | }
19 |
20 | const actionTypes = {
21 | ADD_TOAST: 'ADD_TOAST',
22 | UPDATE_TOAST: 'UPDATE_TOAST',
23 | DISMISS_TOAST: 'DISMISS_TOAST',
24 | REMOVE_TOAST: 'REMOVE_TOAST',
25 | } as const
26 |
27 | let count = 0
28 |
29 | function genId() {
30 | count = (count + 1) % Number.MAX_VALUE
31 | return count.toString()
32 | }
33 |
34 | type ActionType = typeof actionTypes
35 |
36 | type Action =
37 | | {
38 | type: ActionType['ADD_TOAST']
39 | toast: ToasterToast
40 | }
41 | | {
42 | type: ActionType['UPDATE_TOAST']
43 | toast: Partial
44 | }
45 | | {
46 | type: ActionType['DISMISS_TOAST']
47 | toastId?: ToasterToast['id']
48 | }
49 | | {
50 | type: ActionType['REMOVE_TOAST']
51 | toastId?: ToasterToast['id']
52 | }
53 |
54 | interface State {
55 | toasts: ToasterToast[]
56 | }
57 |
58 | const toastTimeouts = new Map>()
59 |
60 | function addToRemoveQueue(toastId: string) {
61 | if (toastTimeouts.has(toastId))
62 | return
63 |
64 | const timeout = setTimeout(() => {
65 | toastTimeouts.delete(toastId)
66 | dispatch({
67 | type: actionTypes.REMOVE_TOAST,
68 | toastId,
69 | })
70 | }, TOAST_REMOVE_DELAY)
71 |
72 | toastTimeouts.set(toastId, timeout)
73 | }
74 |
75 | const state = ref({
76 | toasts: [],
77 | })
78 |
79 | function dispatch(action: Action) {
80 | switch (action.type) {
81 | case actionTypes.ADD_TOAST:
82 | state.value.toasts = [action.toast, ...state.value.toasts].slice(0, TOAST_LIMIT)
83 | break
84 |
85 | case actionTypes.UPDATE_TOAST:
86 | state.value.toasts = state.value.toasts.map(t =>
87 | t.id === action.toast.id ? { ...t, ...action.toast } : t,
88 | )
89 | break
90 |
91 | case actionTypes.DISMISS_TOAST: {
92 | const { toastId } = action
93 |
94 | if (toastId) {
95 | addToRemoveQueue(toastId)
96 | }
97 | else {
98 | state.value.toasts.forEach((toast) => {
99 | addToRemoveQueue(toast.id)
100 | })
101 | }
102 |
103 | state.value.toasts = state.value.toasts.map(t =>
104 | t.id === toastId || toastId === undefined
105 | ? {
106 | ...t,
107 | open: false,
108 | }
109 | : t,
110 | )
111 | break
112 | }
113 |
114 | case actionTypes.REMOVE_TOAST:
115 | if (action.toastId === undefined)
116 | state.value.toasts = []
117 | else
118 | state.value.toasts = state.value.toasts.filter(t => t.id !== action.toastId)
119 |
120 | break
121 | }
122 | }
123 |
124 | function useToast() {
125 | return {
126 | toasts: computed(() => state.value.toasts),
127 | toast,
128 | dismiss: (toastId?: string) => dispatch({ type: actionTypes.DISMISS_TOAST, toastId }),
129 | }
130 | }
131 |
132 | type Toast = Omit
133 |
134 | function toast(props: Toast) {
135 | const id = genId()
136 |
137 | const update = (props: ToasterToast) =>
138 | dispatch({
139 | type: actionTypes.UPDATE_TOAST,
140 | toast: { ...props, id },
141 | })
142 |
143 | const dismiss = () => dispatch({ type: actionTypes.DISMISS_TOAST, toastId: id })
144 |
145 | dispatch({
146 | type: actionTypes.ADD_TOAST,
147 | toast: {
148 | ...props,
149 | id,
150 | open: true,
151 | onOpenChange: (open: boolean) => {
152 | if (!open)
153 | dismiss()
154 | },
155 | },
156 | })
157 |
158 | return {
159 | id,
160 | dismiss,
161 | update,
162 | }
163 | }
164 |
165 | export { toast, useToast }
166 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/QuestionMark.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
11 | ?
12 |
13 |
14 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/Tooltip.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/TooltipContent.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/TooltipProvider.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/TooltipTrigger.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Tooltip } from './Tooltip.vue'
2 | export { default as TooltipContent } from './TooltipContent.vue'
3 | export { default as TooltipTrigger } from './TooltipTrigger.vue'
4 | export { default as TooltipProvider } from './TooltipProvider.vue'
5 |
--------------------------------------------------------------------------------
/src/composables/useDb.ts:
--------------------------------------------------------------------------------
1 | import { useStorage } from "@vueuse/core";
2 | import type { Bot, IChatMessage, ChatRole } from "../types";
3 | import { watch, type Ref, type WatchCallback } from "vue";
4 |
5 | const DB_VERSION = 1
6 | const DB_NAME = "llm"
7 | let db: IDBDatabase;
8 |
9 | export const useDb = () => {
10 | const closeConnection = () => {
11 | if(!db) return
12 | db.close()
13 | }
14 | const drop = () => {
15 | indexedDB.deleteDatabase(DB_NAME)
16 | }
17 | const getConnection = (): Promise => {
18 | return new Promise((res, rej) => {
19 | if (db) return res(db)
20 | const request = window.indexedDB.open(DB_NAME, DB_VERSION)
21 | request.onerror = () => {
22 | console.log('Error connecting to database')
23 | return rej(false)
24 | }
25 | request.onsuccess = function (e) {
26 | console.log('Database connected!')
27 | db = this.result
28 | return res(this.result)
29 | }
30 | request.onupgradeneeded = function(e) {
31 | console.log('Upgrade needed')
32 | this.result.createObjectStore("bots", {autoIncrement: true, keyPath: 'id'});
33 | const messageStore = this.result.createObjectStore("messages", {autoIncrement: true, keyPath: 'id'});
34 | messageStore.createIndex('botIdIndex', "botId")
35 | messageStore.createIndex('dateIndex', "date")
36 | db = this.result
37 | res(this.result)
38 | }
39 | })
40 | }
41 |
42 | const getMessages = (botId: number): Promise => {
43 | return new Promise(async (resolve, reject) => {
44 | const conn = await getConnection()
45 | const read = conn.transaction('messages', 'readonly')
46 |
47 | const store = read.objectStore('messages')
48 | let result = []
49 | read.oncomplete = () => {
50 | resolve(result.reverse())
51 | }
52 | store.openCursor().onsuccess = function(e) {
53 | let cursor = this.result
54 | if(cursor) {
55 | if (cursor.value.botId == botId) {
56 | result.push(cursor.value)
57 | }
58 | cursor.continue()
59 | }
60 | }
61 |
62 | })
63 | }
64 |
65 | const insertMessage = (botId: number, role: ChatRole, message: string) => {
66 | console.trace('insert message db')
67 | return new Promise(async (resolve, reject) => {
68 | const conn = await getConnection()
69 | const read = conn.transaction('messages', 'readwrite')
70 | read.oncomplete = () => {
71 | resolve(true)
72 | }
73 | const chatMessage: IChatMessage = {
74 | botId: botId,
75 | date: Date.now(),
76 | message,
77 | role: role
78 | }
79 | const store = read.objectStore('messages')
80 | store.put(chatMessage)
81 | })
82 | }
83 |
84 | const getBots = (): Promise => {
85 | return new Promise(async (resolve, reject) => {
86 | const conn = await getConnection()
87 | const read = conn.transaction(['bots'], 'readonly')
88 |
89 | const store = read.objectStore('bots')
90 | let result = []
91 | read.oncomplete = () => {
92 | resolve(result)
93 | }
94 | store.openCursor().onsuccess = function(e) {
95 | let cursor = this.result
96 | if (cursor) {
97 | result.push(cursor.value)
98 | cursor.continue()
99 | }
100 | }
101 |
102 | })
103 | }
104 |
105 | const insertBot = (bot: Bot) => {
106 | return new Promise(async (resolve, reject) => {
107 | const conn = await getConnection()
108 | const read = conn.transaction('bots', 'readwrite')
109 | read.oncomplete = () => {
110 | console.log("Insert success")
111 | resolve(true)
112 | }
113 | const store = read.objectStore('bots')
114 | store.put(bot)
115 | })
116 | }
117 | const activeBotId = useStorage("activeBotId", 1);
118 |
119 | const setActiveBotId = (id: number) => {
120 | activeBotId.value = id
121 | }
122 |
123 | const getActiveBotId = (): Ref => {
124 | return activeBotId
125 | }
126 |
127 | const getActiveBot = async (): Promise => {
128 | const allBots = await getBots()
129 | const find = allBots.find(bot => bot.id == activeBotId.value)
130 |
131 | if (!find) throw new Error("Bot not found");
132 | return find
133 | }
134 |
135 | const updateBot = async (data: Bot) => {
136 | const conn = await getConnection()
137 | const tx = conn.transaction('bots', 'readwrite')
138 | const store = tx.objectStore("bots")
139 | store.put(data)
140 | }
141 |
142 | const removeBotDocument = async () => {
143 | const conn = await getConnection()
144 | const currentActiveBot = await getActiveBot()
145 | const tx = conn.transaction('bots', 'readwrite')
146 | const store = tx.objectStore("bots")
147 | currentActiveBot.document.filename = currentActiveBot.document.text = ""
148 | console.log('updated bot', currentActiveBot)
149 | store.put(currentActiveBot)
150 | }
151 |
152 | const clearCurrentBotChat = (): Promise => {
153 | return new Promise(async (resolve, reject) => {
154 | const conn = await getConnection()
155 | const read = conn.transaction('messages', 'readwrite')
156 |
157 | const store = read.objectStore('messages')
158 | let result = []
159 | read.oncomplete = () => {
160 | resolve()
161 | }
162 | store.openCursor().onsuccess = function(e) {
163 | let cursor = this.result
164 | if(cursor) {
165 | console.log(cursor.value)
166 | if (cursor.value.botId == activeBotId.value) {
167 | cursor.delete()
168 | }
169 | cursor.continue()
170 | }
171 | }
172 |
173 | })
174 | }
175 |
176 | return {
177 | activeBotId,
178 | drop,
179 | closeConnection,
180 | getConnection,
181 | getMessages,
182 | insertMessage,
183 | getBots,
184 | insertBot,
185 | getActiveBot,
186 | getActiveBotId,
187 | setActiveBotId,
188 | clearCurrentBotChat,
189 | updateBot,
190 | removeBotDocument,
191 | }
192 | }
--------------------------------------------------------------------------------
/src/composables/useLLM.ts:
--------------------------------------------------------------------------------
1 | import { ref } from "vue"
2 | import { useDb } from "./useDb"
3 | import { CreateMLCEngine, type ChatCompletionRequest, type ChatOptions, type ChatCompletionChunk, type ChatCompletionMessageParam, MLCEngine, prebuiltAppConfig, WebWorkerMLCEngine } from "@mlc-ai/web-llm"
4 | import type { Bot, ChatRole, IChatMessage } from "@/types"
5 | import { additionalModels, useModel } from "./useModel"
6 | import { useChatStore } from "@/store/chat"
7 |
8 |
9 | export const useLLM = () => {
10 | const db = useDb()
11 | const activeBot = ref()
12 | const chatStore = useChatStore()
13 |
14 | let chat = new WebWorkerMLCEngine(
15 | new Worker(new URL("../web-worker.ts", import.meta.url), {
16 | type: "module",
17 | }),
18 | {
19 | appConfig: {
20 | model_list: [...prebuiltAppConfig.model_list, ...additionalModels],
21 | useIndexedDBCache: false,
22 | },
23 | }
24 | )
25 | let isFirstLoad = true
26 |
27 | const unloadModel = async () => {
28 | console.log('unload model')
29 | await chat.unload()
30 | }
31 |
32 | const models = useModel()
33 |
34 | // Load a model
35 | const loadModel = async (model_id: string, onModelLoadingCb?: (report) => void) => {
36 | console.log({onModelLoadingCb})
37 | if(onModelLoadingCb)
38 | chat.setInitProgressCallback(onModelLoadingCb);
39 |
40 | const chatOptions: ChatOptions = {
41 | // conv_config: {},
42 | // repetition_penalty: activeBot.value.params.repetition_penalty[0],
43 | // temperature: activeBot.value.params.temperature[0],
44 | // top_p: activeBot.value.params.top_p[0],
45 | }
46 | try {
47 | console.log('model loading')
48 | await chat.reload(model_id)
49 | } catch (err) {
50 | throw err
51 | }
52 | }
53 |
54 | const infer = async (text: ChatCompletionMessageParam[], cb: (msg: ChatCompletionChunk, i: number) => void) => {
55 | console.log(activeBot.value.params)
56 | const request: ChatCompletionRequest = {
57 | stream: true,
58 | top_p: activeBot.value.params.top_p[0],
59 | max_tokens: activeBot.value.params.max_gen_len[0],
60 | frequency_penalty: activeBot.value.params.frequency_penalty[0],
61 | temperature: activeBot.value.params.temperature[0],
62 | messages: [
63 | {
64 | "role": "system",
65 | "content": activeBot.value.prompt
66 | },
67 | ...text
68 | ],
69 | };
70 | const chunks = chat.chat.completions.create(request)
71 | let i = 0
72 | for await (const chunk of await chunks) {
73 | if(i == 0 && chunk.choices[0].delta.content == '\n') continue
74 | cb(chunk, i)
75 | i++
76 | }
77 | console.log(await chat.getMessage()); // the final response
78 | console.log(await chat.runtimeStatsText());
79 |
80 | isFirstLoad = false
81 | }
82 |
83 | const messages = ref([])
84 |
85 | const insertBot = (bot: Bot) => {
86 | chatStore.bots.push(bot)
87 | db.insertBot(bot)
88 | }
89 |
90 |
91 |
92 | const insertMessage = (botId: number, role: ChatRole, message: string) => {
93 | // Insert the message to the very first index
94 | messages.value.unshift({
95 | botId: botId,
96 | date: Date.now(),
97 | message: message,
98 | role: role,
99 | })
100 | }
101 |
102 | return {
103 | insertBot,
104 | loadModel,
105 | unloadModel,
106 | messages,
107 | infer,
108 | activeBot,
109 | insertMessage
110 | }
111 | }
--------------------------------------------------------------------------------
/src/composables/useModel.ts:
--------------------------------------------------------------------------------
1 | import type { AppConfig, ModelRecord } from '@mlc-ai/web-llm';
2 |
3 | const baseUrl = 'http://localhost:5174';
4 |
5 | export const useModel = (): (ModelRecord & {
6 | name: string;
7 | size: string;
8 | conv_template: string;
9 | info: {
10 | author: string;
11 | link: string;
12 | releaseDate: string;
13 | usedFor: string[];
14 | languageSupport?: string[];
15 | notes?: string;
16 | };
17 | })[] => {
18 | return [
19 | {
20 | name: 'Llama 3 8B',
21 | model: 'https://huggingface.co/mlc-ai/Llama-3-8B-Instruct-q4f32_1-MLC',
22 | model_id: 'Llama-3-8B-Instruct-q4f32_1-MLC-1k',
23 | size: '7709.09',
24 | model_lib:
25 | 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/Llama-2-13b-chat-hf/Llama-2-13b-chat-hf-q4f16_1-ctx4k_cs1k-webgpu.wasm',
26 | vram_required_MB: 5295.7,
27 | conv_template: 'llama-2',
28 | low_resource_required: false,
29 | required_features: ['shader-f16'],
30 | info: {
31 | author: 'Meta',
32 | link: 'https://huggingface.co/meta-llama/Meta-Llama-3-8B',
33 | releaseDate: '2024',
34 | usedFor: ['General'],
35 | languageSupport: ['English', 'other'],
36 | notes: '',
37 | },
38 | },
39 | {
40 | name: 'SmolLM 1.7B',
41 | model:
42 | 'https://huggingface.co/mlc-ai/https://huggingface.co/mlc-ai/SmolLM-360M-Instruct-q4f32_1-MLC',
43 | model_id: 'SmolLM-360M-Instruct-q4f32_1-MLC',
44 | model_lib:
45 | 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/Llama-2-70b-chat-hf/Llama-2-70b-chat-hf-q4f16_1-ctx4k_cs1k-webgpu.wasm',
46 | size: '1924.38',
47 | vram_required_MB: 1924.38,
48 | low_resource_required: true,
49 | conv_template: 'llama-2',
50 | required_features: ['shader-f16'],
51 | info: {
52 | author: 'Huggingface',
53 | link: 'https://huggingface.co/HuggingFaceTB/SmolLM-1.7B-Instruct',
54 | releaseDate: 'Jul 2024',
55 | usedFor: ['General'],
56 | languageSupport: ['English'],
57 | notes: 'The models primarily understand and generate content in English. They can produce text on a variety of topics, but the generated content may not always be factually accurate, logically consistent, or free from biases present in the training data. These models should be used as assistive tools rather than definitive sources of information.',
58 | },
59 | },
60 | {
61 | name: 'RedPajama 3B',
62 | model:
63 | "https://huggingface.co/mlc-ai/RedPajama-INCITE-Chat-3B-v1-q4f32_1-MLC",
64 | model_id: "RedPajama-INCITE-Chat-3B-v1-q4f32_1-MLC-1k",
65 | model_lib:
66 | modelLibURLPrefix +
67 | modelVersion +
68 | "/RedPajama-INCITE-Chat-3B-v1-q4f32_1-ctx2k_cs1k-webgpu.wasm",
69 | vram_required_MB: 2558.09,
70 | size: "2558.09",
71 | low_resource_required: true,
72 | conv_template: 'redpajama_chat',
73 | info: {
74 | author: 'Together Computer',
75 | link: 'https://huggingface.co/togethercomputer/RedPajama-INCITE-Chat-3B-v1',
76 | releaseDate: 'May 2023',
77 | usedFor: ['General'],
78 | languageSupport: ['English'],
79 | notes: '',
80 | },
81 | },
82 | {
83 | name: 'WizardMath 7B',
84 | model: 'https://huggingface.co/mlc-ai/WizardMath-7B-V1.1-q4f16_1-MLC',
85 | model_id: 'WizardMath-7B-V1.1-q4f16_1-MLC',
86 | size: '6119.02',
87 | model_lib:
88 | 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/Mistral-7B-Instruct-v0.2/Mistral-7B-Instruct-v0.2-q4f16_1-sw4k_cs1k-webgpu.wasm',
89 | vram_required_MB: 6079.02,
90 | conv_template: 'wizard_coder_or_math',
91 | low_resource_required: false,
92 | required_features: ['shader-f16'],
93 | info: {
94 | author: 'WizardLM',
95 | link: 'https://huggingface.co/WizardLMTeam/WizardMath-7B-V1.1',
96 | releaseDate: 'December 2023',
97 | usedFor: ['General', 'Math'],
98 | languageSupport: ['English'],
99 | notes: 'a model that not only understands the complex language of mathematics but also resolves it with an almost artisanal finesse.',
100 | },
101 | },
102 | {
103 | name: 'Qwen2 1.5B',
104 | model:
105 | 'https://huggingface.co/mlc-ai/Qwen1.5-1.8B-Chat-q4f16_1-MLC/resolve/main/',
106 | model_id: 'Qwen2-1.5B-Instruct-q4f16_1-MLC',
107 | model_lib:
108 | 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/Qwen-1_8B-Chat/Qwen1.5-1.8B-Chat-q4f16_1-webgpu.wasm',
109 | vram_required_MB: 1629.75,
110 | size: "1629.75",
111 | conv_template: 'qwen',
112 | low_resource_required: false,
113 | required_features: ['shader-f16'],
114 | info: {
115 | author: 'Qwen Org',
116 | link: 'https://qwenlm.github.io/blog/qwen2/',
117 | releaseDate: 'June 2024',
118 | usedFor: ['General', "Math"],
119 | languageSupport: ['English', 'German', 'French', 'Spanish', 'Portuguese', 'Italian', 'Dutch', 'Vietnamese', 'Thai', 'Indonesian', 'Malay', 'Lao', 'Burmese', 'etc'],
120 | notes: 'Qwen 2 is an advanced AI model that surpasses its predecessor, Qwen 1.5, with five size options and training in 27 languages. It excels in natural language understanding, coding, and mathematics, rivalling top models like GPT-4 and LLaMA 32. Key capabilities include language versatility, code generation, and solving complex mathematical problems',
121 | },
122 | },
123 | {
124 | name: 'Phi1.5',
125 | model: 'https://huggingface.co/mlc-ai/phi-1_5-q4f32_1-MLC',
126 | model_id: 'phi-1_5-q4f32_1-MLC-1k',
127 | size: '1682.09',
128 | model_lib:
129 | 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-llm-models/v0_2_43/phi-1_5-q4f32_1-ctx2k_cs1k-webgpu.wasm',
130 | vram_required_MB: 1682.09,
131 | conv_template: 'llama-2',
132 | low_resource_required: true,
133 | info: {
134 | author: 'Microsoft',
135 | link: 'https://huggingface.co/microsoft/phi-1_5',
136 | releaseDate: '',
137 | usedFor: ['General'],
138 | languageSupport: ['English'],
139 | notes: "Given the nature of the training data, Phi-1.5 is best suited for prompts using the QA format, the chat format, and the code format. Note that Phi-1.5, being a base model, often produces irrelevant text following the main answer. In the following example, we've truncated the answer for illustrative purposes only.",
140 | },
141 | },
142 | {
143 | name: 'Phi 3',
144 | conv_template: '',
145 | size: '',
146 | model: "https://huggingface.co/mlc-ai/Phi-3-mini-4k-instruct-q4f32_1-MLC",
147 | model_id: "Phi-3-mini-4k-instruct-q4f32_1-MLC-1k",
148 | model_lib:
149 | modelLibURLPrefix +
150 | modelVersion +
151 | "/Phi-3-mini-4k-instruct-q4f32_1-ctx4k_cs1k-webgpu.wasm",
152 | vram_required_MB: 3179.12,
153 | low_resource_required: true,
154 | overrides: {
155 | context_window_size: 1024,
156 | },
157 | info: {
158 | author: 'Microsoft',
159 | link: 'https://huggingface.co/microsoft/Phi-3-mini-4k-instruct',
160 | releaseDate: 'June 2024',
161 | usedFor: ['General','Research'],
162 | languageSupport: ['English'],
163 | notes: `The model is intended for broad commercial and research use in English. The model provides uses for general purpose AI systems and applications which require
164 | memory/compute constrained environments;
165 | latency bound scenarios;
166 | strong reasoning (especially math and logic).
167 | Our model is designed to accelerate research on language and multimodal models, for use as a building block for generative AI powered features.`,
168 | },
169 | },
170 | // TinyLlama
171 | {
172 | name: 'TinyLlama 1.1B',
173 | model:
174 | 'https://huggingface.co/mlc-ai/TinyLlama-1.1B-Chat-v1.0-q4f32_1-MLC',
175 | model_id: 'TinyLlama-1.1B-Chat-v1.0-q4f32_1-MLC',
176 | model_lib:
177 | 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-llm-models/v0_2_43/TinyLlama-1.1B-Chat-v1.0-q4f32_1-ctx2k_cs1k-webgpu.wasm',
178 | vram_required_MB: 992.11,
179 | size: '992.11',
180 | conv_template: 'llama-2',
181 | low_resource_required: true,
182 | info: {
183 | author: 'Peiyuan Zhang',
184 | link: 'https://github.com/jzhang38/TinyLlama',
185 | releaseDate: 'October 2023',
186 | usedFor: ['General'],
187 | languageSupport: ['English'],
188 | notes: 'Small but strong model',
189 | },
190 | },
191 | ];
192 | };
193 | const modelLibURLPrefix =
194 | 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-llm-models/';
195 | const modelVersion = 'v0_2_48';
196 |
197 | export const additionalModels: ModelRecord[] = [
198 | {
199 | model: 'https://huggingface.co/mlc-ai/SmolLM-360M-Instruct-q4f32_1-MLC',
200 | model_id: 'SmolLM-360M-Instruct-q4f32_1-MLC',
201 | model_lib:
202 | modelLibURLPrefix +
203 | modelVersion +
204 | '/SmolLM-360M-Instruct-q4f32_1-ctx2k_cs1k-webgpu.wasm',
205 | vram_required_MB: 419.61,
206 | low_resource_required: true,
207 |
208 | overrides: {
209 | context_window_size: 2048,
210 | },
211 | },
212 | ];
213 |
214 | export const allModels = [...useModel(), ...additionalModels];
215 |
--------------------------------------------------------------------------------
/src/composables/useWebGPU.ts:
--------------------------------------------------------------------------------
1 | type GPUDeviceInfo = {
2 | adapter: GPUAdapter | null;
3 | device: GPUDevice | null;
4 | adapterInfo: GPUAdapterInfo | null;
5 | checked: boolean;
6 | unsupportedReason: string | null;
7 | };
8 |
9 | export const useWebGPU = () => {
10 | let adapter
11 | let device
12 | const isWebGPUSupported = async () => {
13 | try {
14 | if (!navigator.gpu) {
15 | return false
16 | }
17 | adapter = await navigator.gpu.requestAdapter()
18 | if(!adapter) {
19 | return false
20 | }
21 |
22 | device = await adapter.requestDevice()
23 | return true
24 | }catch(e){
25 | return false
26 | }
27 | }
28 |
29 | return {
30 | adapter,
31 | device,
32 | isWebGPUSupported
33 | }
34 | }
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 | import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
4 |
5 | export function cn(...inputs: ClassValue[]) {
6 | return twMerge(clsx(inputs))
7 | }
8 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import "./assets/css/tailwind.css"
2 | import './styles.css';
3 | import { createApp } from 'vue';
4 | import App from './App.vue';
5 | import router from './router'
6 | import { createPinia } from 'pinia'
7 | const pinia = createPinia()
8 |
9 | const app = createApp(App);
10 |
11 | app
12 | .use(router)
13 | .use(pinia)
14 |
15 | app.mount('#root');
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createWebHistory, createRouter } from "vue-router";
2 |
3 | export default createRouter({
4 | history: createWebHistory(),
5 | routes: [
6 | {
7 | path: '',
8 | name: 'home',
9 | component: () => import('../views/Home.vue')
10 | },
11 | {
12 | path: '/model-list',
13 | name: 'models',
14 | component: () => import('../views/Models.vue')
15 | },
16 | {
17 | path: '/prompts',
18 | name: 'prompts',
19 | component: () => import('../views/Prompts.vue')
20 | },
21 | ]
22 | })
--------------------------------------------------------------------------------
/src/store/chat.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from "pinia";
2 | import { onMounted, ref } from "vue";
3 | import type { Bot } from "../types";
4 | import { useDb } from "../composables/useDb";
5 |
6 | export const useChatStore = defineStore('chat', () => {
7 | const bots = ref([])
8 |
9 | const fetchBots = async () => {
10 | const db = useDb()
11 | const res = await db.getBots()
12 | bots.value = res
13 | console.log(res)
14 | }
15 |
16 | onMounted(() => {
17 | const db = useDb()
18 |
19 | // Insert experimental bot
20 | // bots.value.forEach(b => {
21 | // db.insertBot(JSON.parse(JSON.stringify(b)))
22 | // })
23 | // db.drop()
24 | })
25 |
26 | return {
27 | fetchBots,
28 | bots
29 | }
30 | })
--------------------------------------------------------------------------------
/src/store/main.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zuramai/ChatLLM/ef347adf68d62abf7d138b5e07deb8f4bc7ab053/src/store/main.ts
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | -webkit-text-size-adjust: 100%;
3 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
4 | 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
5 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
6 | line-height: 1.5;
7 | tab-size: 4;
8 | scroll-behavior: smooth;
9 | }
10 | body {
11 | font-family: inherit;
12 | line-height: inherit;
13 | margin: 0;
14 | }
15 | h1,
16 | h2,
17 | p,
18 | pre {
19 | margin: 0;
20 | }
21 | *,
22 | ::before,
23 | ::after {
24 | box-sizing: border-box;
25 | border-width: 0;
26 | border-style: solid;
27 | border-color: currentColor;
28 | }
29 | h1,
30 | h2 {
31 | font-size: inherit;
32 | font-weight: inherit;
33 | }
34 | a {
35 | color: inherit;
36 | text-decoration: inherit;
37 | }
38 | pre {
39 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
40 | 'Liberation Mono', 'Courier New', monospace;
41 | }
42 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export interface Bot {
2 | id: number
3 | name: string
4 | description: string
5 | prompt: string
6 | botId?: string
7 | params: {
8 | top_p: number[],
9 | temperature: number[],
10 | frequency_penalty: number[]
11 | max_gen_len: number[]
12 | }
13 | }
14 |
15 | export type ChatRole = "bot" | "user"
16 | export interface IChatMessage {
17 | botId: number
18 | role: ChatRole
19 | message: string
20 | date: number
21 | }
22 |
23 | export type IChat = IChatMessage[]
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
212 |
213 |
214 |
215 | Create a bot to start chatting
216 |
217 |
218 |
219 |
220 |
221 |
222 |
226 |
227 |
228 |
229 | Loading model:
230 | {{ loadingProgress }}%
231 |
232 |
233 |
237 |
242 |
247 |
248 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
264 |
265 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
415 |
416 |
417 |
--------------------------------------------------------------------------------
/src/views/Models.vue:
--------------------------------------------------------------------------------
1 |
68 |
69 |
70 |
71 |
75 |
76 |
77 |
159 |
160 |
161 |
162 |
163 |
164 |
170 |
--------------------------------------------------------------------------------
/src/views/Prompts.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
25 |
26 |
--------------------------------------------------------------------------------
/src/web-worker.ts:
--------------------------------------------------------------------------------
1 | import log from "loglevel";
2 | import { WebWorkerMLCEngineHandler } from "@mlc-ai/web-llm";
3 |
4 | let handler: WebWorkerMLCEngineHandler;
5 |
6 | self.addEventListener("message", (event) => {});
7 |
8 | self.onmessage = (msg: MessageEvent) => {
9 | if (!handler) {
10 | handler = new WebWorkerMLCEngineHandler();
11 | log.info("Web Worker: Web-LLM Engine Activated");
12 | }
13 | handler.onmessage(msg);
14 | };
--------------------------------------------------------------------------------
/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zuramai/ChatLLM/ef347adf68d62abf7d138b5e07deb8f4bc7ab053/static/logo.png
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const animate = require("tailwindcss-animate")
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | darkMode: ["class"],
6 |
7 | content: [
8 | './src/**/*.{ts,tsx,vue}',
9 | ],
10 |
11 | theme: {
12 | container: {
13 | center: true,
14 | padding: "2rem",
15 | screens: {
16 | "2xl": "1400px",
17 | },
18 | },
19 | extend: {
20 | colors: {
21 | border: "var(--border)",
22 | input: "var(--input)",
23 | ring: "var(--ring)",
24 | background: "var(--background)",
25 | foreground: "var(--foreground)",
26 | primary: {
27 | DEFAULT: "var(--primary)",
28 | foreground: "var(--primary-foreground)",
29 | },
30 | secondary: {
31 | DEFAULT: "var(--secondary)",
32 | foreground: "var(--secondary-foreground)",
33 | },
34 | destructive: {
35 | DEFAULT: "var(--destructive)",
36 | foreground: "var(--destructive-foreground)",
37 | },
38 | success: {
39 | DEFAULT: "var(--success)",
40 | foreground: "var(--success-foreground)",
41 | },
42 | muted: {
43 | DEFAULT: "var(--muted)",
44 | foreground: "var(--muted-foreground)",
45 | },
46 | accent: {
47 | DEFAULT: "var(--accent)",
48 | foreground: "var(--accent-foreground)",
49 | },
50 | popover: {
51 | DEFAULT: "var(--popover)",
52 | foreground: "var(--popover-foreground)",
53 | },
54 | card: {
55 | DEFAULT: "var(--card)",
56 | foreground: "var(--card-foreground)",
57 | },
58 | },
59 | borderRadius: {
60 | lg: "var(--radius)",
61 | md: "calc(var(--radius) - 2px)",
62 | sm: "calc(var(--radius) - 4px)",
63 | },
64 | keyframes: {
65 | "accordion-down": {
66 | from: { height: 0 },
67 | to: { height: "var(--radix-accordion-content-height)" },
68 | },
69 | "accordion-up": {
70 | from: { height: "var(--radix-accordion-content-height)" },
71 | to: { height: 0 },
72 | },
73 | },
74 | animation: {
75 | "accordion-down": "accordion-down 0.2s ease-out",
76 | "accordion-up": "accordion-up 0.2s ease-out",
77 | },
78 | },
79 | },
80 | plugins: [animate],
81 | }
--------------------------------------------------------------------------------
/todo.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | - [x] Read PDF
4 | - [ ] Feed with pdf
5 | - [ ] Save read result of pdf to IndexedDB
6 | - [ ] Choose model
7 | - [ ] List model
8 | - [ ] Check if model have been downloaded or not
9 | - [ ] PWA support
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "composite": true,
5 | "outDir": "dist",
6 | "types": ["vite/client", "vite-plugin-pwa/vue"],
7 | "baseUrl": ".",
8 | "paths": {
9 | "@acme/counter": ["libs/counter/src/index.ts"],
10 | "@/*": ["src/*"]
11 | },
12 | },
13 | "exclude": [
14 | "src/**/*.spec.ts",
15 | "src/**/*.test.ts",
16 | "src/**/*.spec.vue",
17 | "src/**/*.test.vue"
18 | ],
19 | "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.vue"]
20 | }
21 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "importHelpers": true,
11 | "target": "es2015",
12 | "module": "esnext",
13 | "lib": ["es2020", "dom"],
14 | "skipLibCheck": true,
15 | "skipDefaultLibCheck": true,
16 | "baseUrl": ".",
17 | "paths": {
18 | "tvmjs": ["./libs/tvmjs/src/index.ts"],
19 | }
20 | },
21 | "exclude": ["node_modules", "tmp"]
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "esModuleInterop": false,
5 | "allowSyntheticDefaultImports": true,
6 | "jsx": "preserve",
7 | "jsxImportSource": "vue",
8 | "moduleResolution": "node",
9 | "types": ["vite/client", "vite-plugin-pwa/vue"],
10 | "typeRoots": ["./node_modules/@types/", "./types", "./node_modules"],
11 | "resolveJsonModule": true,
12 | "paths": {
13 | "@/*": ["./src/*"]
14 | },
15 | "verbatimModuleSyntax": true
16 | },
17 | "files": [],
18 | "include": [],
19 | "references": [
20 | {
21 | "path": "./tsconfig.app.json"
22 | },
23 | {
24 | "path": "./tsconfig.spec.json"
25 | }
26 | ],
27 | "extends": "./tsconfig.base.json"
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "composite": true,
6 | "types": [
7 | "vitest/globals",
8 | "vitest/importMeta",
9 | "vite/client",
10 | "node",
11 | "vitest"
12 | ]
13 | },
14 | "include": [
15 | "vite.config.ts",
16 | "src/**/*.test.ts",
17 | "src/**/*.spec.ts",
18 | "src/**/*.test.tsx",
19 | "src/**/*.spec.tsx",
20 | "src/**/*.test.js",
21 | "src/**/*.spec.js",
22 | "src/**/*.test.jsx",
23 | "src/**/*.spec.jsx",
24 | "src/**/*.d.ts"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path';
3 | import { fileURLToPath } from 'url';
4 | import { defineConfig } from 'vite';
5 | import vue from '@vitejs/plugin-vue';
6 | import topLevelAwait from 'vite-plugin-top-level-await';
7 | import { VitePWA } from 'vite-plugin-pwa';
8 |
9 | export default defineConfig({
10 | publicDir: 'static',
11 | cacheDir: '../../node_modules/.vite/frontend',
12 | resolve: {
13 | alias: {
14 | '@': path.resolve(__dirname, './src'),
15 | },
16 | },
17 | build: {
18 | target: 'es2020',
19 | },
20 | server: {
21 | port: 5174,
22 | host: 'localhost',
23 | },
24 |
25 | preview: {
26 | port: 4300,
27 | host: 'localhost',
28 | },
29 |
30 | plugins: [
31 | vue(),
32 | VitePWA({
33 | workbox: {
34 | globPatterns: ['**/*'],
35 | maximumFileSizeToCacheInBytes: 6000000
36 | },
37 | registerType: 'autoUpdate',
38 | devOptions: {
39 | enabled: true,
40 | },
41 | manifest: {
42 | name: 'ChatLLM',
43 | description: 'LLM Chat Application',
44 | short_name: 'LLM',
45 | icons: [
46 | {
47 | src: 'logo.png',
48 | sizes: '512x512',
49 | type: 'image/png',
50 | },
51 | ],
52 | },
53 | }),
54 | topLevelAwait({
55 | // The export name of top-level await promise for each chunk module
56 | promiseExportName: '__tla',
57 | // The function to generate import names of top-level await promise in each chunk module
58 | promiseImportName: (i) => `__tla_${i}`,
59 | }),
60 | ],
61 |
62 | // Uncomment this if you are using workers.
63 | // worker: {
64 | // plugins: [ nxViteTsPaths() ],
65 | // },
66 |
67 | optimizeDeps: {
68 | // exclude: ['@tensorflow/tfjs-backend-wasm']
69 | },
70 | });
71 |
--------------------------------------------------------------------------------