├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── README.md
├── bun.lockb
├── components.json
├── index.html
├── package.json
├── postcss.config.js
├── scripts
└── pull.py
├── src
├── app
│ ├── ActiveRoute.tsx
│ ├── AgentView.tsx
│ ├── AppShell.tsx
│ ├── ChatView.tsx
│ ├── PlaygroundView.tsx
│ ├── Sidebar.tsx
│ ├── SidebarAgentList.tsx
│ └── SidebarChatList.tsx
├── components
│ ├── ActionOverlay.tsx
│ ├── AdapterPicker.tsx
│ ├── AgentAdapterPicker.tsx
│ ├── AgentConversation.tsx
│ ├── AgentGenerationParams.tsx
│ ├── AgentGenerationParamsAccordionItem.tsx
│ ├── AgentHistory.tsx
│ ├── AgentMessage.tsx
│ ├── AgentPicker.tsx
│ ├── AgentPromptTemplate.tsx
│ ├── AppAccordionItem.tsx
│ ├── AttachmentsAccordionItem.tsx
│ ├── AutoTextarea.tsx
│ ├── ChatConversation.tsx
│ ├── ChatConversationMessage.tsx
│ ├── ChatSettings.tsx
│ ├── Editor.tsx
│ ├── ModelPicker.tsx
│ ├── ModelSelect.tsx
│ ├── Picker.tsx
│ ├── PresetPicker.tsx
│ ├── PromptInput.tsx
│ ├── Select.tsx
│ ├── Sheet.tsx
│ ├── SidebarItem.tsx
│ ├── SidebarSection.tsx
│ ├── SliderCheckbox.tsx
│ ├── ThemeProvider.tsx
│ ├── ToggleDarkButton.tsx
│ ├── VariablesAccordionItem.tsx
│ └── ui
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── aspect-ratio.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── calendar.tsx
│ │ ├── card.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── command.tsx
│ │ ├── context-menu.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── form.tsx
│ │ ├── hover-card.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── menubar.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── radio-group.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── skeleton.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── toggle.tsx
│ │ ├── tooltip.tsx
│ │ └── use-toast.ts
├── index.css
├── index.tsx
├── lib
│ ├── adapters
│ │ ├── HuggingFace.ts
│ │ ├── Ollama.ts
│ │ └── index.ts
│ ├── extraction.ts
│ └── utils.tsx
├── store
│ ├── AdapterFactory.ts
│ ├── Agent.ts
│ ├── Attachment.ts
│ ├── Chat.ts
│ ├── Message.ts
│ ├── Model.ts
│ ├── Playground.ts
│ ├── State.ts
│ ├── Store.ts
│ ├── Template.ts
│ ├── defaults.ts
│ └── index.ts
└── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.js
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - master
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: oven-sh/setup-bun@v1
12 | - run: bun install
13 | - run: bun vite build --base="/llm-workbench/"
14 | - uses: actions/upload-pages-artifact@v2
15 | with:
16 | path: "dist"
17 |
18 | deploy:
19 | runs-on: ubuntu-latest
20 | needs: build
21 |
22 | permissions:
23 | pages: write
24 | id-token: write
25 |
26 | environment:
27 | name: github-pages
28 | url: ${{ steps.deployment.outputs.page_url }}
29 |
30 | steps:
31 | - uses: actions/configure-pages@v3
32 | - id: deployment
33 | uses: actions/deploy-pages@v2
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | # Logs
4 |
5 | logs
6 | _.log
7 | npm-debug.log_
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 | .pnpm-debug.log*
12 |
13 | # Diagnostic reports (https://nodejs.org/api/report.html)
14 |
15 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
16 |
17 | # Runtime data
18 |
19 | pids
20 | _.pid
21 | _.seed
22 | \*.pid.lock
23 |
24 | # Directory for instrumented libs generated by jscoverage/JSCover
25 |
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 |
30 | coverage
31 | \*.lcov
32 |
33 | # nyc test coverage
34 |
35 | .nyc_output
36 |
37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
38 |
39 | .grunt
40 |
41 | # Bower dependency directory (https://bower.io/)
42 |
43 | bower_components
44 |
45 | # node-waf configuration
46 |
47 | .lock-wscript
48 |
49 | # Compiled binary addons (https://nodejs.org/api/addons.html)
50 |
51 | build/Release
52 |
53 | # Dependency directories
54 |
55 | node_modules/
56 | jspm_packages/
57 |
58 | # Snowpack dependency directory (https://snowpack.dev/)
59 |
60 | web_modules/
61 |
62 | # TypeScript cache
63 |
64 | \*.tsbuildinfo
65 |
66 | # Optional npm cache directory
67 |
68 | .npm
69 |
70 | # Optional eslint cache
71 |
72 | .eslintcache
73 |
74 | # Optional stylelint cache
75 |
76 | .stylelintcache
77 |
78 | # Microbundle cache
79 |
80 | .rpt2_cache/
81 | .rts2_cache_cjs/
82 | .rts2_cache_es/
83 | .rts2_cache_umd/
84 |
85 | # Optional REPL history
86 |
87 | .node_repl_history
88 |
89 | # Output of 'npm pack'
90 |
91 | \*.tgz
92 |
93 | # Yarn Integrity file
94 |
95 | .yarn-integrity
96 |
97 | # dotenv environment variable files
98 |
99 | .env
100 | .env.development.local
101 | .env.test.local
102 | .env.production.local
103 | .env.local
104 |
105 | # parcel-bundler cache (https://parceljs.org/)
106 |
107 | .cache
108 | .parcel-cache
109 |
110 | # Next.js build output
111 |
112 | .next
113 | out
114 |
115 | # Nuxt.js build / generate output
116 |
117 | .nuxt
118 | dist
119 |
120 | # Gatsby files
121 |
122 | .cache/
123 |
124 | # Comment in the public line in if your project uses Gatsby and not Next.js
125 |
126 | # https://nextjs.org/blog/next-9-1#public-directory-support
127 |
128 | # public
129 |
130 | # vuepress build output
131 |
132 | .vuepress/dist
133 |
134 | # vuepress v2.x temp and cache directory
135 |
136 | .temp
137 | .cache
138 |
139 | # Docusaurus cache and generated files
140 |
141 | .docusaurus
142 |
143 | # Serverless directories
144 |
145 | .serverless/
146 |
147 | # FuseBox cache
148 |
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 |
153 | .dynamodb/
154 |
155 | # TernJS port file
156 |
157 | .tern-port
158 |
159 | # Stores VSCode versions used for testing VSCode extensions
160 |
161 | .vscode-test
162 |
163 | # yarn v2
164 |
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.\*
170 |
171 | # IntelliJ based IDEs
172 | .idea
173 |
174 | # Finder (MacOS) folder config
175 | .DS_Store
176 |
177 | models/
178 | ```
179 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LLM Workbench
2 |
3 | LLM Workbench is a user-friendly web interface designed for large language models, built with React and MobX, styled using Shadcn UI. It serves as a one-stop solution for all your large language model needs, enabling you to harness the power of free, open-source language models on your local machine.
4 |
5 | 
6 | 
7 | 
8 |
9 |
10 |
11 | ### Getting Started
12 |
13 | To start your journey, choose between a HuggingFace Text Inference Generation Endpoint or Ollama.
14 |
15 | #### HuggingFace Text Inference Generation Endpoint
16 |
17 | ```bash
18 | docker run --gpus all --shm-size 1g -p 8080:80 -v (pwd)/models:/data ghcr.io/huggingface/text-generation-inference:1.1.0 --trust-remote-code --model-id TheBloke/deepseek-coder-33B-instruct-AWQ --quantize awq
19 | ```
20 |
21 | #### Ollama
22 |
23 | ```bash
24 | OLLAMA_ORIGINS="https://knoopx.github.io" ollama serve
25 | ```
26 |
27 | or add this line to `/etc/systemd/system/ollama.service`:
28 |
29 | ```bash
30 | Environment=OLLAMA_ORIGINS="https://knoopx.github.io"
31 | ```
32 |
33 | Restart Ollama using these commands:
34 |
35 | ```bash
36 | systemctl daemon-reload
37 | systemctl restart ollama
38 | ```
39 |
40 | ## 🎭 Features
41 |
42 | ### 💬 Chat Interface
43 |
44 | - **Simple, clean interface**: We've designed a user-friendly interface that makes it easy for you to interact with the AI model.
45 | - **Output streaming**: See the generated text in real-time as you type your prompt.
46 | - **Regenerate/Continue/Undo/Clear**: Use these buttons to control the generation process.
47 | - **Markdown Rendering**: The AI will generate text that supports Markdown formatting, making it easy for you to create styled content.
48 | - **Generation canceling**: Stop the generation process at any time by clicking the "Cancel" button.
49 | - **Dark mode**: Prefer working in the dark? Toggle on Dark mode for a more comfortable experience.
50 | - **Attachments**: Attach files to your chat messages (pdf, docx, and plain-text supported only).
51 |
52 | ### 🛹 Playground
53 |
54 | - **Copilot-alike inline completion**: Type your prompt and let the AI suggest completions as you type.
55 | - **Tab to accept**: Press the Tab key to accept the suggested completion.
56 | - **Cltr+Enter to re-generate**: Hit Ctrl+Enter to re-generate the response with the same prompt.
57 |
58 | ### 🤖 Agents
59 |
60 | - **Connection Adapters**: We support various connection adapters, including Ollama and HuggingFace TGI (local or remote).
61 | - **Complete generation control**: Customize the agent behavior with system prompts, conversation history, and chat prompt templates using [liquidjs](https://liquidjs.com/).
62 |
63 | # Future Ideas
64 |
65 | - Import/Export chats - Importing and exporting chat data for convenience.
66 | - Token Counter - A feature to count tokens in text.
67 | - Copy fenced block to clipboard - The ability to copy a code block and paste it into the clipboard.
68 | - Collapsible side panels - Side panels that can be expanded or collapsed for better organization.
69 | - [window.ai](https://windowai.io/) integration
70 |
71 | Code Interpreters:
72 |
73 | - Hugging Face agents ([@huggingface/agents](https://github.com/huggingface/agents))
74 | - Aider ([paul-gauthier/aider](https://github.com/paul-gauthier/aider))
75 | - Functionary ([MeetKai/functionary](https://github.com/MeetKai/functionary))
76 | - NexusRaven model ([Nexusflow/NexusRaven-13B](https://huggingface.co/Nexusflow/NexusRaven-13B))
77 | - Open procedures database ([KillianLucas/open-procedures](https://raw.githubusercontent.com/KillianLucas/open-procedures/main/procedures_db.json))
78 | - ReACT ([www.promptingguide.ai/techniques/react](https://www.promptingguide.ai/techniques/react))
79 |
80 | Model management features:
81 |
82 | - Hugging Face Hub ([@huggingface/hub](https://huggingface.co/docs/huggingface.js/hub/modules))
83 | - GPT4All catalog ([nomic-ai/gpt4all](https://raw.githubusercontent.com/nomic-ai/gpt4all/main/gpt4all-chat/metadata/models2.json))
84 | - LM Studio catalog [lmstudio-ai/model-catalog](https://raw.githubusercontent.com/lmstudio-ai/model-catalog/main/catalog.json)
85 |
86 | RAG, embeddings and vector search:
87 |
88 | - Client vector search ([yusufhilmi/client-vector-search](https://github.com/yusufhilmi/client-vector-search)) - in-browser vector database.
89 | - Fully local PDF chatbot ([jacoblee93/fully-local-pdf-chatbot](https://github.com/jacoblee93/fully-local-pdf-chatbot)) - related.
90 | - SemanticFinder ([do-me.github.io/SemanticFinder](https://do-me.github.io/SemanticFinder/)) - related.
91 |
92 | Other potential pipelines to consider:
93 |
94 | - TTS (Text-to-Speech) - convert text into speech.
95 | - Reformatting - such as punctuation and re-punctuation models ([ldenoue/distilbert-base-re-punctuate](https://huggingface.co/ldenoue/distilbert-base-re-punctuate)).
96 | - Summarization - summarize long text into shorter versions ([ldenoue/distilbart-cnn-6-6](https://huggingface.co/ldenoue/distilbart-cnn-6-6)).
97 | - Translation - convert text between languages.
98 | - Automatic speech recognition pipeline ([transformers.js](https://huggingface.co/docs/transformers.js/api/pipelines#pipelinesautomaticspeechrecognitionpipeline)) - convert spoken words into written text.
99 | - Named Entity Recognition (NER) - identify and classify entities in text ([Xenova/bert-base-NER](https://huggingface.co/Xenova/bert-base-NER), [wink-nlp](https://winkjs.org/wink-nlp/wink-nlp-in-browsers.html)).
100 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/knoopx/llm-workbench/cdc6d06b92b5684268c27650b870c0736fd02598/bun.lockb
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "index.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LLM Workbench
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "llm-workbench",
3 | "type": "module",
4 | "homepage": "https://knoopx.github.io/llm-workbench/",
5 | "browserslist": {
6 | "production": [
7 | ">0.2%",
8 | "not dead",
9 | "not op_mini all"
10 | ],
11 | "development": [
12 | "last 1 chrome version",
13 | "last 1 firefox version",
14 | "last 1 safari version"
15 | ]
16 | },
17 | "devDependencies": {
18 | "@types/node": "^20.8.9",
19 | "@types/react": "^18.2.33",
20 | "@types/react-dom": "^18.2.14",
21 | "autoprefixer": "^10.4.16",
22 | "bun-types": "latest",
23 | "postcss": "^8.4.31",
24 | "tailwindcss": "^3.3.5"
25 | },
26 | "peerDependencies": {
27 | "typescript": "^5.0.0"
28 | },
29 | "dependencies": {
30 | "@hookform/resolvers": "^3.3.2",
31 | "@huggingface/inference": "^2.6.4",
32 | "@microflash/rehype-starry-night": "^3.0.0",
33 | "@radix-ui/react-accordion": "^1.1.2",
34 | "@radix-ui/react-alert-dialog": "^1.0.5",
35 | "@radix-ui/react-aspect-ratio": "^1.0.3",
36 | "@radix-ui/react-avatar": "^1.0.4",
37 | "@radix-ui/react-checkbox": "^1.0.4",
38 | "@radix-ui/react-collapsible": "^1.0.3",
39 | "@radix-ui/react-context-menu": "^2.1.5",
40 | "@radix-ui/react-dialog": "^1.0.5",
41 | "@radix-ui/react-dropdown-menu": "^2.0.6",
42 | "@radix-ui/react-hover-card": "^1.0.7",
43 | "@radix-ui/react-icons": "^1.3.0",
44 | "@radix-ui/react-label": "^2.0.2",
45 | "@radix-ui/react-menubar": "^1.0.4",
46 | "@radix-ui/react-navigation-menu": "^1.1.4",
47 | "@radix-ui/react-popover": "^1.0.7",
48 | "@radix-ui/react-progress": "^1.0.3",
49 | "@radix-ui/react-radio-group": "^1.1.3",
50 | "@radix-ui/react-scroll-area": "^1.0.5",
51 | "@radix-ui/react-select": "^2.0.0",
52 | "@radix-ui/react-separator": "^1.0.3",
53 | "@radix-ui/react-slider": "^1.1.2",
54 | "@radix-ui/react-slot": "^1.0.2",
55 | "@radix-ui/react-switch": "^1.0.3",
56 | "@radix-ui/react-tabs": "^1.0.4",
57 | "@radix-ui/react-toast": "^1.1.5",
58 | "@radix-ui/react-toggle": "^1.0.3",
59 | "@radix-ui/react-tooltip": "^1.0.7",
60 | "@tailwindcss/typography": "^0.5.10",
61 | "@vitejs/plugin-react": "^4.1.0",
62 | "@xenova/transformers": "^2.7.0",
63 | "class-variance-authority": "^0.7.0",
64 | "clsx": "^2.0.0",
65 | "cmdk": "^0.2.0",
66 | "date-fns": "^2.30.0",
67 | "dedent": "^1.5.1",
68 | "he": "^1.2.0",
69 | "liquidjs": "^10.9.3",
70 | "lucide-react": "^0.290.0",
71 | "mammoth": "^1.6.0",
72 | "minisearch": "^6.2.0",
73 | "mobx": "^6.10.2",
74 | "mobx-react": "^9.0.1",
75 | "mobx-state-tree": "^5.3.0",
76 | "pdfjs-dist": "^3.11.174",
77 | "react": "^18.2.0",
78 | "react-day-picker": "^8.9.1",
79 | "react-dom": "^18.2.0",
80 | "react-hook-form": "^7.47.0",
81 | "react-icons": "^4.11.0",
82 | "react-markdown": "^9.0.0",
83 | "rehype-format": "^5.0.0",
84 | "rehype-highlight": "^7.0.0",
85 | "rehype-katex": "^7.0.0",
86 | "rehype-parse": "^9.0.0",
87 | "rehype-remark": "^10.0.0",
88 | "rehype-stringify": "^10.0.0",
89 | "remark-emoji": "^4.0.1",
90 | "remark-gfm": "^4.0.0",
91 | "remark-math": "^6.0.0",
92 | "remark-retext": "^6.0.0",
93 | "retext-keywords": "^8.0.1",
94 | "tailwind-merge": "^1.14.0",
95 | "tailwindcss-animate": "^1.0.7",
96 | "unified-stream": "^3.0.0",
97 | "use-debounce": "^9.0.4",
98 | "vite": "^4.5.0",
99 | "zod": "^3.22.4"
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/pull.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | from argparse import ArgumentParser
3 | from pathlib import Path
4 |
5 | from huggingface_hub import HfApi, hf_hub_download
6 |
7 | parser = ArgumentParser()
8 | parser.add_argument("repo_id", type=str)
9 | parser.add_argument("--quant", type=str, default="Q4_K_M")
10 |
11 | args = parser.parse_args()
12 | api = HfApi()
13 |
14 | files = api.list_repo_files(args.repo_id)
15 |
16 | for file in files:
17 | if args.quant in file and ".gguf" in file:
18 | target_path = Path("models") / args.repo_id
19 | target_path.mkdir(parents=True, exist_ok=True)
20 |
21 | hf_hub_download(args.repo_id, file, local_dir=target_path)
22 | model_file = target_path / "Modelfile"
23 | model_file.write_text(f"FROM {file}")
24 |
25 | model_name = Path(file).stem.replace(f".{args.quant}", "")
26 | subprocess.run(["ollama", "create", model_name, "-f", str(model_file)])
27 |
--------------------------------------------------------------------------------
/src/app/ActiveRoute.tsx:
--------------------------------------------------------------------------------
1 | import { useStore } from "@/store"
2 | import { AgentView } from "./AgentView"
3 | import { ChatView } from "./ChatView"
4 | import { observer } from "mobx-react"
5 | import { PlaygroundView } from "./PlaygroundView"
6 |
7 | export const ActiveRoute = observer(() => {
8 | const {
9 | state: { route, resource },
10 | } = useStore()
11 |
12 | if (resource) {
13 | if (route === "chat") {
14 | return
15 | } else if (route === "agent") {
16 | return (
17 |
20 | )
21 | }
22 | }
23 |
24 | if (route === "playground") {
25 | return
26 | }
27 |
28 | return null
29 | })
30 |
--------------------------------------------------------------------------------
/src/app/AgentView.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { useStore } from "@/store"
3 | import { Input } from "@/components/ui/input"
4 | import { AgentGenerationParams } from "../components/AgentGenerationParams"
5 | import { Button } from "@/components/ui/button"
6 | import { IoMdCopy } from "react-icons/io"
7 | import { Instance, getSnapshot } from "mobx-state-tree"
8 | import { Agent } from "@/store/Agent"
9 | import { AgentConversation } from "../components/AgentConversation"
10 | import { AgentAdapterPicker } from "../components/AgentAdapterPicker"
11 |
12 | export const AgentView = observer(() => {
13 | const {
14 | addAgent,
15 | state: { resource: agent },
16 | } = useStore()
17 |
18 | return (
19 |
20 |
21 |
22 |
Agent Name
23 |
24 | agent.update({ name: e.target.value })}
29 | />
30 |
40 |
41 |
42 |
46 |
47 |
48 |
49 |
50 |
51 |
Default Parameters
52 |
53 |
54 |
55 |
56 | )
57 | })
58 |
--------------------------------------------------------------------------------
/src/app/AppShell.tsx:
--------------------------------------------------------------------------------
1 | import { Sidebar } from "./Sidebar"
2 | import { observer } from "mobx-react"
3 | import { ActiveRoute } from "./ActiveRoute"
4 |
5 | const AppShell = observer(() => {
6 | return (
7 |
8 |
9 |
10 |
11 | )
12 | })
13 |
14 | export default AppShell
15 |
--------------------------------------------------------------------------------
/src/app/ChatView.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { ChatSettings } from "../components/ChatSettings"
3 | import { ChatConversation } from "../components/ChatConversation"
4 | import { useStore } from "@/store"
5 |
6 | export const ChatView = observer(() => {
7 | const {
8 | state: { resource: chat },
9 | } = useStore()
10 |
11 | return (
12 |
13 |
14 |
15 |
16 | )
17 | })
18 |
--------------------------------------------------------------------------------
/src/app/PlaygroundView.tsx:
--------------------------------------------------------------------------------
1 | import { AgentGenerationParams } from "@/components/AgentGenerationParams"
2 | import { useStore } from "@/store"
3 | import { observer } from "mobx-react"
4 | import { AgentAdapterPicker } from "../components/AgentAdapterPicker"
5 | import { useDebouncedCallback } from "use-debounce"
6 | export const PlaygroundView = observer(() => {
7 | const {
8 | playground: { agent, generate, abortController, output, update },
9 | } = useStore()
10 |
11 | const debounced = useDebouncedCallback(() => {
12 | generate()
13 | }, 200)
14 |
15 | return (
16 |
57 | )
58 | })
59 |
--------------------------------------------------------------------------------
/src/app/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { useStore } from "@/store"
2 | import { observer } from "mobx-react"
3 | import { ChatList } from "./SidebarChatList"
4 | import { AgentList } from "./SidebarAgentList"
5 | import { SidebarItem } from "../components/SidebarItem"
6 | import { SidebarSection } from "../components/SidebarSection"
7 | import { ToggleDarkButton } from "@/components/ToggleDarkButton"
8 | import { VscTools } from "react-icons/vsc"
9 | export const Sidebar = observer(() => {
10 | const { agents, chats } = useStore()
11 |
12 | return (
13 |
14 |
15 |
16 |
}>
17 |
18 | Playground
19 |
20 |
21 |
22 |
23 | )
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/SidebarAgentList.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button"
2 | import { useStore } from "@/store"
3 | import { IoMdAdd } from "react-icons/io"
4 | import { observer } from "mobx-react"
5 | import { Instance } from "mobx-state-tree"
6 | import { Agent } from "@/store/chat"
7 | import { IoTrashOutline } from "react-icons/io5"
8 | import { PiRobot } from "react-icons/pi"
9 | import { SidebarItem } from "@/components/SidebarItem"
10 |
11 | export const AgentList = observer(
12 | ({ agents }: { agents: Instance[] }) => {
13 | const store = useStore()
14 |
15 | const sorted = agents.slice().sort((a, b) => a.name.localeCompare(b.name))
16 |
17 | return (
18 |
19 |
20 |
21 | Agents
22 |
25 |
26 |
27 | {sorted.map((agent) => (
28 | {
37 | e.stopPropagation()
38 | store.removeAgent(agent)
39 | }}
40 | >
41 |
42 |
43 | }
44 | >
45 | {agent.name ? (
46 | agent.name
47 | ) : (
48 | (No Name)
49 | )}
50 |
51 | ))}
52 |
53 |
54 | )
55 | },
56 | )
57 |
--------------------------------------------------------------------------------
/src/app/SidebarChatList.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 | import { Button } from "@/components/ui/button"
3 | import { useStore } from "@/store"
4 | import { BsChatRightText } from "react-icons/bs"
5 | import { IoMdAdd } from "react-icons/io"
6 | import { observer } from "mobx-react"
7 | import { IoTrashOutline } from "react-icons/io5"
8 | import { SidebarItem } from "@/components/SidebarItem"
9 |
10 | export const ChatList = observer(() => {
11 | const { newChat, removeChat, chats } = useStore()
12 |
13 | // sort chats by recent date
14 | const sorted = chats
15 | .slice()
16 | .sort((a, b) => b.date?.getTime() - a.date?.getTime())
17 |
18 | return (
19 |
20 |
21 |
22 |
Chats
23 |
26 |
27 |
28 | {sorted.map((chat) => (
29 |
{
38 | e.stopPropagation()
39 | removeChat(chat)
40 | }}
41 | >
42 |
43 |
44 | }
45 | >
46 |
47 | {/* {chat.agent && (
48 |
{chat.agent?.name}
49 | )} */}
50 |
51 | {chat.title}
52 |
53 |
54 |
55 | ))}
56 |
57 |
58 | )
59 | })
60 |
--------------------------------------------------------------------------------
/src/components/ActionOverlay.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 | import { observer } from "mobx-react"
3 |
4 | export const ActionOverlay = observer(
5 | ({
6 | children,
7 | actions,
8 | className,
9 | }: {
10 | children: React.ReactNode
11 | actions: React.ReactNode
12 | className?: string
13 | }) => (
14 |
15 | {children}
16 |
22 | {actions}
23 |
24 |
25 | ),
26 | )
27 |
--------------------------------------------------------------------------------
/src/components/AdapterPicker.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react";
2 | import { Picker } from "@/components/Picker";
3 | import { Instance } from "mobx-state-tree";
4 | import { Agent } from "@/store/Agent";
5 | import { AdapterList } from "@/lib/adapters";
6 |
7 | export const AdapterPicker: React.FC<{
8 | agent: Instance;
9 | }> = observer(({ agent }) => {
10 | const options = AdapterList.map((adapter) => ({
11 | key: adapter,
12 | value: adapter,
13 | }));
14 |
15 | return (
16 |
17 |
Adapter
18 |
{
22 | agent.adapter.update({ type });
23 | if (agent.adapter.baseUrl == "") {
24 | if (agent.adapter.type == "Ollama") {
25 | agent.adapter.update({ baseUrl: "http://localhost:11434" });
26 | } else if (agent.adapter.type == "HuggingFace") {
27 | agent.adapter.update({ baseUrl: "http://localhost:8080" });
28 | }
29 | }
30 | }} />
31 |
32 | );
33 | });
34 |
--------------------------------------------------------------------------------
/src/components/AgentAdapterPicker.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { ModelPicker } from "./ModelPicker"
3 | import { Input } from "@/components/ui/input"
4 | import { Instance } from "mobx-state-tree"
5 | import { Agent } from "@/store/Agent"
6 | import { cn } from "@/lib/utils"
7 | import { AdapterPicker } from "./AdapterPicker"
8 |
9 | export const AgentAdapterPicker: React.FC<{
10 | className?: string
11 | agent: Instance
12 | }> = observer(({ className, agent }) => (
13 |
14 |
15 |
16 |
17 | Endpoint
18 | agent.adapter.update({ baseUrl: e.target.value })}
22 | />
23 |
24 | {agent.adapter.isMultiModal && (
25 |
26 | Model
27 |
28 |
29 | )}
30 |
31 | ))
32 |
--------------------------------------------------------------------------------
/src/components/AgentConversation.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react";
2 | import { Input } from "@/components/ui/input";
3 | import { AgentHistory } from "./AgentHistory";
4 | import { AgentPromptTemplate } from "./AgentPromptTemplate";
5 | import { Button } from "@/components/ui/button";
6 | import { IoMdAdd, IoMdTrash } from "react-icons/io";
7 | import { AutoTextarea } from "@/components/AutoTextarea";
8 | import {
9 | DropdownMenu,
10 | DropdownMenuContent,
11 | DropdownMenuTrigger
12 | } from "@/components/ui/dropdown-menu";
13 |
14 | export const AgentConversation = observer(({ agent }) => (
15 |
16 |
17 |
18 | Conversation History
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Templates
30 |
31 |
32 |
33 |
36 |
37 |
42 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {agent.templates.map((template) => (
60 |
61 |
62 |
{template.id}
63 |
70 |
71 |
72 |
{
75 | template.update({ content: e.target.value });
76 | }} />
77 |
78 | ))}
79 |
80 |
81 |
82 |
83 |
84 |
Prompt Template
85 |
86 |
87 | Preview
88 |
89 | {agent.promptPreview}
90 |
91 |
92 |
93 |
94 | ));
95 |
--------------------------------------------------------------------------------
/src/components/AgentGenerationParams.tsx:
--------------------------------------------------------------------------------
1 | import { SliderCheckbox } from "./SliderCheckbox"
2 | import { Instance } from "mobx-state-tree"
3 | import { Agent } from "@/store/Agent"
4 | import { observer, useLocalStore } from "mobx-react"
5 | import { cn } from "@/lib/utils"
6 | import { Input } from "./ui/input"
7 | import { Label } from "./ui/label"
8 | import { Checkbox } from "./ui/checkbox"
9 | import { IoMdTrash } from "react-icons/io"
10 | import { Button } from "./ui/button"
11 |
12 | const GENERATION_PARAMS = [
13 | {
14 | id: "num_predict",
15 | label: "Max. Tokens",
16 | max: 1024 * 8,
17 | step: 64,
18 | },
19 | {
20 | id: "temperature",
21 | label: "Temperature",
22 | max: 1,
23 | step: 0.1,
24 | },
25 | {
26 | id: "top_k",
27 | label: "Top K",
28 | max: 1,
29 | step: 0.1,
30 | },
31 | {
32 | id: "top_p",
33 | label: "Top P",
34 | max: 1,
35 | step: 0.1,
36 | },
37 | {
38 | id: "num_ctx",
39 | label: "Context Window",
40 | max: 1024 * 8,
41 | step: 64,
42 | },
43 | {
44 | id: "repeat_last_n",
45 | label: "Repeat Window",
46 | max: 1024 * 8,
47 | step: 64,
48 | },
49 | {
50 | id: "repeat_penalty",
51 | label: "Reptition Penalty",
52 | max: 2,
53 | step: 0.1,
54 | },
55 | ]
56 |
57 | const AgentStopPatternEditor: React.FC<{
58 | agent: Instance
59 | }> = observer(({ agent }) => {
60 | const state = useLocalStore(() => ({
61 | draft: "",
62 | update(value: string) {
63 | this.draft = value
64 | },
65 | }))
66 |
67 | return (
68 |
69 |
90 |
91 |
111 | {agent.parameters?.stop.map((pattern) => (
112 |
113 |
114 |
127 |
128 | ))}
129 |
130 |
131 | )
132 | })
133 |
134 | export const AgentGenerationParams: React.FC<{
135 | agent: Instance
136 | }> = observer(({ className, agent }) => {
137 | const changeProps = (key: string) => ({
138 | value: agent.parameters[key],
139 | onChange: (value: string) => {
140 | agent.update({ parameters: { ...agent.parameters, [key]: value } })
141 | },
142 | checked: agent.checkedOptions.includes(key),
143 | onCheckedChange: (value: boolean) => {
144 | if (value) {
145 | agent.update({
146 | checkedOptions: Array.from(new Set([...agent.checkedOptions, key])),
147 | })
148 | } else {
149 | agent.update({
150 | checkedOptions: agent.checkedOptions.filter((x) => x !== key),
151 | })
152 | }
153 | },
154 | })
155 |
156 | const parameters = GENERATION_PARAMS.filter((param) =>
157 | agent.adapter.parameters.includes(param.id),
158 | )
159 |
160 | return (
161 |
162 | {parameters.map((param) => (
163 |
164 | ))}
165 |
166 |
167 |
168 | )
169 | })
170 |
--------------------------------------------------------------------------------
/src/components/AgentGenerationParamsAccordionItem.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { MdTune } from "react-icons/md"
3 | import { Instance } from "mobx-state-tree"
4 | import { AgentGenerationParams } from "./AgentGenerationParams"
5 | import { AppAccordionItem } from "./AppAccordionItem"
6 | import { Agent } from "@/store/Agent"
7 |
8 | export const AgentInferenceParamsAccordionItem = observer(
9 | ({ agent }: { agent: Instance }) => {
10 | return (
11 |
12 |
13 |
14 | )
15 | },
16 | )
17 |
--------------------------------------------------------------------------------
/src/components/AgentHistory.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { Agent } from "@/store/Agent"
3 | import { Instance } from "mobx-state-tree"
4 | import { AgentMessage } from "./AgentMessage"
5 | import { Message } from "@/store/Message"
6 |
7 | export const AgentHistory: React.FC<{
8 | agent: Instance
9 | }> = observer(({ agent }) => (
10 |
11 |
12 | {agent.messages.map((message: Instance
, i: number) => (
13 |
14 | ))}
15 |
16 |
17 | ))
18 |
--------------------------------------------------------------------------------
/src/components/AgentMessage.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { Button } from "@/components/ui/button"
3 | import { IoMdTrash } from "react-icons/io"
4 | import { Select } from "./Select"
5 | import { AutoTextarea } from "./AutoTextarea"
6 |
7 | export const AgentMessage = observer(({ message, ...rest }) => (
8 |
9 |
10 |
32 |
40 |
41 | ))
42 |
--------------------------------------------------------------------------------
/src/components/AgentPicker.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { Instance } from "mobx-state-tree"
3 | import { useStore } from "@/store"
4 | import { Chat } from "@/store/Chat"
5 |
6 | import { Picker } from "./Picker"
7 |
8 | export const AgentPicker = observer(
9 | ({ chat, ...props }: { chat: Instance }) => {
10 | const { agents } = useStore()
11 |
12 | const options = agents.map((agent) => ({
13 | key: agent.name,
14 | value: agent.name,
15 | }))
16 |
17 | return (
18 | {
23 | chat.update({ agent: agents.find((a) => a.name === agent) })
24 | }}
25 | />
26 | )
27 | },
28 | )
29 |
--------------------------------------------------------------------------------
/src/components/AgentPromptTemplate.tsx:
--------------------------------------------------------------------------------
1 | import { Agent } from "@/store/Agent"
2 | import { Instance } from "mobx-state-tree"
3 | import { observer } from "mobx-react"
4 | import { AutoTextarea } from "@/components/AutoTextarea"
5 |
6 | export const AgentPromptTemplate: React.FC<{
7 | agent: Instance
8 | }> = observer(({ agent }) => {
9 | return (
10 |
14 | agent.update({ promptTemplate: e.target.value })
15 | }
16 | />
17 | )
18 | })
19 |
--------------------------------------------------------------------------------
/src/components/AppAccordionItem.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import {
3 | AccordionContent,
4 | AccordionItem,
5 | AccordionTrigger,
6 | } from "@radix-ui/react-accordion"
7 |
8 | export const AppAccordionItem = observer(
9 | ({ id, icon: Icon, title, children }) => (
10 |
11 |
12 |
13 | {title}
14 |
15 |
16 | {children}
17 |
18 |
19 | ),
20 | )
21 |
--------------------------------------------------------------------------------
/src/components/AttachmentsAccordionItem.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { ImAttachment } from "react-icons/im"
3 | import { Input } from "@/components/ui/input"
4 | import { processFile } from "@/lib/extraction"
5 | import { AppAccordionItem } from "./AppAccordionItem"
6 | import { Button } from "@/components/ui/button"
7 |
8 | export const AttachmentsAccordionItem = observer(({ chat }) => {
9 | async function processFiles(files: FileList) {
10 | const results = []
11 | for (const file of files) {
12 | console.log(`Processing ${file.name}...`)
13 | try {
14 | const content = await processFile(file)
15 | if (content) {
16 | results.push({
17 | name: file.name,
18 | content,
19 | path: file.webkitRelativePath ? file.webkitRelativePath : file.name,
20 | size: file.size,
21 | type: file.type,
22 | })
23 | console.log(file.webkitRelativePath)
24 | } else {
25 | console.warn(`File ${file.name} is empty`)
26 | }
27 | } catch (e) {
28 | console.error(e)
29 | }
30 | }
31 | return results
32 | }
33 |
34 | const handleAttachment = async (e) => {
35 | const attachments = await processFiles(e.target.files)
36 | chat.update({ attachments })
37 | e.target.value = null
38 | }
39 |
40 | return (
41 |
42 |
50 |
53 |
54 |
55 | {chat.attachments.map((attachment) => (
56 |
57 | {attachment.path}
58 |
59 | ))}
60 |
61 |
62 | )
63 | })
64 |
--------------------------------------------------------------------------------
/src/components/AutoTextarea.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 | import { Textarea } from "@/components/ui/textarea"
3 | import { useRef, useEffect, PropsWithoutRef } from "react"
4 | import { observer } from "mobx-react"
5 |
6 | export const AutoTextarea = observer(
7 | ({
8 | className,
9 | value,
10 | onChange,
11 | maxRows = 8,
12 | ...rest
13 | }: PropsWithoutRef & {
14 | maxRows?: number
15 | onChange: (e: React.ChangeEvent) => void
16 | }) => {
17 | const ref = useRef(null)
18 |
19 | useEffect(() => {
20 | if (!ref.current) return
21 | ref.current.style.height = "0px"
22 | const { scrollHeight } = ref.current
23 | ref.current.style.height = `min(${
24 | getComputedStyle(ref.current).lineHeight
25 | } * ${maxRows + 1}, ${scrollHeight}px)`
26 | }, [value])
27 |
28 | return (
29 |
39 | )
40 | },
41 | )
42 |
--------------------------------------------------------------------------------
/src/components/ChatConversation.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { Button } from "./ui/button"
3 | import { IoMdTrash, IoMdRefresh } from "react-icons/io"
4 | import { ScrollArea } from "./ui/scroll-area"
5 | import { ChatConversationMessage } from "./ChatConversationMessage"
6 | import { VscDebugContinue, VscDebugStart, VscDebugStop } from "react-icons/vsc"
7 | import { BiArrowToTop } from "react-icons/bi"
8 | import { cn } from "@/lib/utils"
9 | import { useEffect, useRef } from "react"
10 | import { Instance } from "mobx-state-tree"
11 | import { Chat } from "@/store/Chat"
12 | import { reaction, toJS } from "mobx"
13 | import { ChatUserInput } from "./PromptInput"
14 | import { animate } from "@/lib/animation"
15 |
16 | const useReaction = (observe: () => any, fn: () => void, deps: any[] = []) => {
17 | useEffect(() => {
18 | return reaction(observe, fn)
19 | }, deps)
20 | }
21 |
22 | export const ChatConversation = observer(
23 | ({ chat }: { chat: Instance }) => {
24 | const ref = useRef(null)
25 |
26 | const scrollToBottom = () => {
27 | const scrollContainer = ref.current?.querySelector(
28 | "[data-radix-scroll-area-viewport]",
29 | )
30 | if (!scrollContainer) return
31 |
32 | if (scrollContainer.scrollHeight > scrollContainer.scrollTop) {
33 | scrollContainer.scrollTop = scrollContainer.scrollHeight
34 | }
35 | }
36 |
37 | useEffect(() => scrollToBottom(), [])
38 | useReaction(() => chat.lastAssistantMessage?.content, scrollToBottom)
39 |
40 | return (
41 |
42 |
43 |
44 | {chat.messages.map((message, i) => (
45 |
46 | ))}
47 |
48 |
49 |
57 |
58 | {chat.isRunning ? (
59 |
66 | ) : (
67 | <>
68 |
71 |
74 |
77 |
80 |
83 | >
84 | )}
85 |
86 |
87 |
88 |
89 | )
90 | },
91 | )
92 |
--------------------------------------------------------------------------------
/src/components/ChatConversationMessage.tsx:
--------------------------------------------------------------------------------
1 | import Markdown from "react-markdown"
2 | import { cn } from "@/lib/utils"
3 | import { TbPrompt } from "react-icons/tb"
4 | import { cloneElement } from "react"
5 | import { observer, useLocalStore } from "mobx-react"
6 |
7 | import remarkMath from "remark-math"
8 | import remarkGfm from "remark-gfm"
9 | import remarkEmoji from "remark-emoji"
10 | import rehypeHighlight from "rehype-highlight"
11 | import rehypeKatex from "rehype-katex"
12 |
13 | import "katex/dist/katex.min.css"
14 | import "highlight.js/styles/github-dark.css"
15 | import { ActionOverlay } from "@/components/ActionOverlay"
16 | import { IoMdCopy, IoMdMore, IoMdTrash } from "react-icons/io"
17 | import { AiOutlineEdit } from "react-icons/ai"
18 | import { Button } from "@/components/ui/button"
19 | import {
20 | DropdownMenu,
21 | DropdownMenuContent,
22 | DropdownMenuItem,
23 | DropdownMenuTrigger,
24 | } from "@/components/ui/dropdown-menu"
25 | import { AutoTextarea } from "@/components/AutoTextarea"
26 | import { Instance } from "mobx-state-tree"
27 | import { Chat } from "@/store/Chat"
28 |
29 | const MessagePreview: React.FC<{
30 | content: string
31 | role: string
32 | }> = observer(({ content, role }) => (
33 |
44 | {cloneElement(children, {
45 | className: cn(
46 | "rounded-md p-4 !bg-gray-900",
47 | children.props.className,
48 | ),
49 | })}
50 |
51 | )
52 | },
53 | }}
54 | >
55 | {content}
56 |
57 | ))
58 |
59 | const EmptyMessage: React.FC<{
60 | message: Instance
61 | }> = observer(({ message }) => (
62 |
67 | ...
68 |
69 | ))
70 |
71 | const MessageDropdownMenu: React.FC<{
72 | children: React.ReactNode
73 | }> = observer(({ children }) => (
74 |
75 |
76 |
77 |
80 |
81 | {children}
82 |
83 |
84 | ))
85 |
86 | const MessageAvatar: React.FC<{
87 | chat: Instance
88 | role: string
89 | }> = observer(({ chat, role }) => (
90 |
91 |
102 | {role !== "system" &&
103 | (role == "user" ? : role[0].toUpperCase())}
104 |
105 |
106 | ))
107 |
108 | export const ChatConversationMessage = observer(({ message }) => {
109 | const { role, content, isEmpty, chat } = message
110 |
111 | const state = useLocalStore(() => ({
112 | isEditing: false,
113 | setEditing(value: boolean) {
114 | state.isEditing = value
115 | },
116 | }))
117 |
118 | return (
119 |
122 | {
124 | navigator.clipboard.writeText(content)
125 | }}
126 | >
127 |
128 | Copy to clipboard
129 |
130 | {
132 | state.setEditing(true)
133 | }}
134 | >
135 |
136 | Edit
137 |
138 | {
140 | message.chat.remove(message)
141 | }}
142 | >
143 |
144 | Remove
145 |
146 |
147 | }
148 | >
149 |
150 |
151 |
152 | {state.isEditing ? (
153 |
message.update({ content: e.target.value })}
157 | onKeyDown={(e) => {
158 | if (e.key === "Enter" && !e.shiftKey) {
159 | e.preventDefault()
160 | state.setEditing(false)
161 | message.update({ content: e.target.value })
162 | }
163 | }}
164 | />
165 | ) : isEmpty ? (
166 |
167 | ) : (
168 |
169 | )}
170 |
171 |
172 |
173 | )
174 | })
175 |
--------------------------------------------------------------------------------
/src/components/ChatSettings.tsx:
--------------------------------------------------------------------------------
1 | import { SliderCheckbox } from "./SliderCheckbox"
2 | import { observer } from "mobx-react"
3 | import { Select } from "./Select"
4 | import { ModelPicker } from "./ModelPicker"
5 | import { useStore } from "@/store"
6 | import { ScrollArea } from "@/components/ui/scroll-area"
7 | import { TbPrompt } from "react-icons/tb"
8 | import { MdOutlineHistory, MdTune } from "react-icons/md"
9 | import { ImAttachment } from "react-icons/im"
10 | import { Input } from "@/components/ui/input"
11 | import { IoMdAdd, IoMdPerson, IoMdSettings, IoMdTrash } from "react-icons/io"
12 | import { PiTextbox } from "react-icons/pi"
13 | import { Button } from "@/components/ui/button"
14 | import { AutoTextarea } from "./AutoTextarea"
15 | import { AgentPicker } from "./AgentPicker"
16 | import { AttachmentsAccordionItem } from "@/components/AttachmentsAccordionItem"
17 | import {
18 | Accordion,
19 | AccordionContent,
20 | AccordionItem,
21 | AccordionTrigger,
22 | } from "@radix-ui/react-accordion"
23 |
24 | const ChatSettingsAccordionItem = observer(
25 | ({ id, icon: Icon, title, children }) => (
26 |
27 |
28 |
29 | {title}
30 |
31 |
32 | {children}
33 |
34 |
35 | ),
36 | )
37 |
38 | const SystemMessageAccordionItem = observer(() => {
39 | const store = useStore()
40 | const { activeChat: chat } = store
41 |
42 | return (
43 |
48 |
49 | {/*
*/}
50 |
chat.setSystemMessage(e.target.value)}
55 | />
56 |
57 |
58 | )
59 | })
60 |
61 | const UserMessageAccordionItem = observer(() => {
62 | const store = useStore()
63 | const { activeChat: chat } = store
64 | return (
65 |
70 |
71 | {/*
*/}
72 |
73 |
chat.setUserMessage(e.target.value)}
78 | />
79 |
80 |
81 | )
82 | })
83 |
84 | const PromptTemplateAccordion = observer(() => {
85 | const store = useStore()
86 | const { activeChat: chat } = store
87 |
88 | return (
89 |
94 |
95 |
96 |
chat.setChatTemplate(e.target.value)}
100 | />
101 |
102 |
103 |
104 | {chat.agent.template}
105 |
106 |
107 | )
108 | })
109 |
110 | const HistoryMessage = observer(({ message, ...rest }) => (
111 |
112 | {
116 | message.update({ role: e.target.value })
117 | }}
118 | {...rest}
119 | />
120 | {
124 | message.update({ content: e.target.value })
125 | }}
126 | {...rest}
127 | />
128 |
131 |
132 | ))
133 |
134 | const ChatHistoryAccordionItem = observer(() => {
135 | const store = useStore()
136 | const {
137 | state: { resource: chat },
138 | } = store
139 |
140 | return (
141 |
146 |
147 |
156 |
163 |
172 |
179 |
180 |
181 | {chat.history.map((message, i) => (
182 |
183 | ))}
184 |
185 |
186 | )
187 | })
188 |
189 | export const ChatSettings = observer(({ chat }) => (
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | ))
199 |
--------------------------------------------------------------------------------
/src/components/Editor.tsx:
--------------------------------------------------------------------------------
1 | import MonacoEditor from "react-monaco-editor"
2 | import * as monacoEditor from "monaco-editor"
3 |
4 | export const Editor = ({ agent }) => {
5 | return (
6 | {
21 | agent.update({ promptTemplate: value })
22 | debounced()
23 | }}
24 | editorDidMount={(
25 | editor: monacoEditor.editor.IStandaloneCodeEditor,
26 | monaco: typeof monacoEditor,
27 | ) => {
28 | monaco.languages.registerCompletionItemProvider("plaintext", {
29 | async provideInlineCompletionItems() {},
30 | })
31 | }}
32 | />
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/ModelPicker.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { IoMdRefresh } from "react-icons/io"
3 | import { Button } from "@/components/ui/button"
4 | import { ModelSelect } from "./ModelSelect"
5 | import { Instance } from "mobx-state-tree"
6 | import { Agent } from "@/store/Agent"
7 | import { Input } from "./ui/input"
8 |
9 | export const ModelPicker = observer(
10 | ({ agent }: { agent: Instance }) => {
11 | return (
12 |
13 | {agent.adapter.isMultiModal ? (
14 | <>
15 |
16 |
19 | >
20 | ) : (
21 |
25 | )}
26 |
27 | )
28 | },
29 | )
30 |
--------------------------------------------------------------------------------
/src/components/ModelSelect.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import {
3 | Select,
4 | SelectContent,
5 | SelectItem,
6 | SelectTrigger,
7 | SelectValue,
8 | } from "./ui/select"
9 | import { humanFileSize } from "@/lib/utils"
10 |
11 | export const ModelSelect = observer(({ agent, ...props }) => {
12 | const { models } = agent.adapter
13 |
14 | return (
15 |
37 | )
38 | })
39 |
--------------------------------------------------------------------------------
/src/components/Picker.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import {
3 | Select,
4 | SelectContent,
5 | SelectItem,
6 | SelectTrigger,
7 | SelectValue,
8 | } from "./ui/select"
9 |
10 | export const Picker = observer(
11 | ({
12 | options,
13 | value,
14 | onChange,
15 | ...props
16 | }: {
17 | onChange: (option: string) => void
18 | options: { key: string; value: string }[]
19 | }) => {
20 | options = options.sort((a, b) => a.value.localeCompare(b.value))
21 |
22 | return (
23 |
42 | )
43 | },
44 | )
45 |
--------------------------------------------------------------------------------
/src/components/PresetPicker.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button"
2 | import {
3 | DropdownMenu,
4 | DropdownMenuContent,
5 | DropdownMenuItem,
6 | DropdownMenuTrigger,
7 | } from "@/components/ui/dropdown-menu"
8 | import { observer } from "mobx-react"
9 | import { TbSelector } from "react-icons/tb"
10 | import { HiOutlineDocumentDownload } from "react-icons/hi"
11 |
12 | export const NewComponent: React.FC<{
13 | onSelect: (preset: any) => void
14 | presets: any[]
15 | }> = ({ onSelect, presets }) => {
16 | if (presets.length === 0) {
17 | return (
18 |
19 | No presets
20 |
21 | )
22 | }
23 | return (
24 | <>
25 | {presets.map((preset) => (
26 | onSelect(preset.value)}>
27 | {preset.key}
28 |
29 | ))}
30 | >
31 | )
32 | }
33 |
34 | export const PresetPicker = observer(
35 | ({
36 | presets = [],
37 | onSelect,
38 | }: {
39 | presets: any[]
40 | onSelect: (preset: any) => void
41 | }) => {
42 | return (
43 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
53 | )
54 | },
55 | )
56 |
--------------------------------------------------------------------------------
/src/components/PromptInput.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { AutoTextarea } from "./AutoTextarea"
3 | import { html2md } from "@/lib/utils"
4 | import { extractHTML } from "@/lib/utils"
5 |
6 | export const ChatUserInput = observer(({ chat }) => {
7 | const handleInput = async (input: string) => {
8 | if (input.startsWith("http")) {
9 | const body = await extractHTML(input)
10 | const md = html2md(body?.outerHTML)
11 | chat.addMessage({
12 | content: md,
13 | role: "user",
14 | })
15 | } else {
16 | chat.send(input)
17 | }
18 | }
19 |
20 | return (
21 | chat.update({ prompt: e.target.value })}
25 | onKeyDown={(e) => {
26 | if (e.key === "Enter" && !e.shiftKey) {
27 | e.preventDefault()
28 | chat.update({ prompt: "" })
29 | handleInput(e.target.value)
30 | }
31 | }}
32 | autoFocus
33 | placeholder="Enter text..."
34 | />
35 | )
36 | })
37 |
--------------------------------------------------------------------------------
/src/components/Select.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Select as Base,
3 | SelectContent,
4 | SelectItem,
5 | SelectTrigger,
6 | SelectValue,
7 | } from "@/components/ui/select"
8 | import { observer } from "mobx-react"
9 |
10 | export type Option = {
11 | key: string
12 | value: string
13 | }
14 |
15 | export const Select = observer(
16 | ({
17 | className,
18 | options = [],
19 | onChange,
20 | placeholder,
21 | ...props
22 | }: {
23 | className?: string
24 | value: string
25 | options: Option[]
26 | onChange: (value: string) => void
27 | placeholder?: string
28 | }) => {
29 | const sorted = options
30 | ?.slice()
31 | .filter((x) => x.value)
32 | .sort((a, b) => a.key.localeCompare(b.key))
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 | {sorted.map(({ key, value }) => (
42 |
43 | {key}
44 |
45 | ))}
46 |
47 |
48 | )
49 | },
50 | )
51 |
--------------------------------------------------------------------------------
/src/components/Sheet.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 |
3 | export const Sheet = observer(() => {
4 | return Sheet
5 | })
6 |
--------------------------------------------------------------------------------
/src/components/SidebarItem.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 | import { useStore } from "@/store"
3 | import { observer } from "mobx-react"
4 |
5 | export const SidebarItem = observer(
6 | ({
7 | children,
8 | icon: Icon,
9 | route,
10 | resource,
11 | actions,
12 | ...props
13 | }: {
14 | children: React.ReactNode
15 | }) => {
16 | const { state } = useStore()
17 |
18 | return (
19 |
36 | )
37 | },
38 | )
39 |
--------------------------------------------------------------------------------
/src/components/SidebarSection.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 |
3 | export const SidebarSection = observer(
4 | ({ title, actions, children }: { children: React.ReactNode }) => (
5 |
6 |
7 |
8 | {title}
9 | {actions}
10 |
11 |
12 | {children}
13 |
14 |
15 | ),
16 | )
17 |
--------------------------------------------------------------------------------
/src/components/SliderCheckbox.tsx:
--------------------------------------------------------------------------------
1 | import { Slider as Base } from "./ui/slider"
2 | import { Label } from "./ui/label"
3 | import { observer } from "mobx-react"
4 | import { Checkbox } from "./ui/checkbox"
5 | import { cn } from "@/lib/utils"
6 | import { Input } from "./ui/input"
7 |
8 | export const SliderCheckbox = observer(
9 | ({ label, id, value, onChange, checked, onCheckedChange, ...props }) => (
10 |
15 |
16 |
20 | {
24 | onCheckedChange(true)
25 | onChange(Number(e.target.value))
26 | }}
27 | />
28 |
29 |
{
33 | onCheckedChange(true)
34 | onChange(v[0])
35 | }}
36 | className="ml-6 [&_[role=slider]]:h-4 [&_[role=slider]]:w-4"
37 | aria-label={label}
38 | {...props}
39 | />
40 |
41 | ),
42 | )
43 |
--------------------------------------------------------------------------------
/src/components/ThemeProvider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from "react"
2 |
3 | type Theme = "dark" | "light" | "system"
4 |
5 | type ThemeProviderProps = {
6 | children: React.ReactNode
7 | defaultTheme?: Theme
8 | storageKey?: string
9 | }
10 |
11 | type ThemeProviderState = {
12 | theme: Theme
13 | setTheme: (theme: Theme) => void
14 | }
15 |
16 | const initialState: ThemeProviderState = {
17 | theme: "system",
18 | setTheme: () => null,
19 | }
20 |
21 | const ThemeProviderContext = createContext(initialState)
22 |
23 | export const ThemeProvider = ({
24 | children,
25 | defaultTheme = "system",
26 | storageKey = "ui-theme",
27 | ...props
28 | }: ThemeProviderProps) => {
29 | const [theme, setTheme] = useState(
30 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
31 | )
32 |
33 | useEffect(() => {
34 | const root = window.document.documentElement
35 |
36 | root.classList.remove("light", "dark")
37 |
38 | if (theme === "system") {
39 | const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
40 | .matches
41 | ? "dark"
42 | : "light"
43 |
44 | root.classList.add(systemTheme)
45 | return
46 | }
47 |
48 | root.classList.add(theme)
49 | }, [theme])
50 |
51 | const value = {
52 | theme,
53 | setTheme: (theme: Theme) => {
54 | localStorage.setItem(storageKey, theme)
55 | setTheme(theme)
56 | },
57 | }
58 |
59 | return (
60 |
61 | {children}
62 |
63 | )
64 | }
65 |
66 | export const useTheme = () => {
67 | const context = useContext(ThemeProviderContext)
68 |
69 | if (context === undefined)
70 | throw new Error("useTheme must be used within a ThemeProvider")
71 |
72 | return context
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/ToggleDarkButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button"
2 | import { BsFillLightbulbFill, BsLightbulb } from "react-icons/bs"
3 | import { useTheme } from "@/components/ThemeProvider"
4 |
5 | export const ToggleDarkButton = ({ size = "1em" }) => {
6 | const theme = useTheme()
7 |
8 | const toggleTheme = () => {
9 | theme.setTheme(theme.theme == "dark" ? "light" : "dark")
10 | }
11 |
12 | return (
13 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/VariablesAccordionItem.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react"
2 | import { useStore } from "@/store"
3 | import { Input } from "@/components/ui/input"
4 | import { IoMdAdd, IoMdTrash } from "react-icons/io"
5 | import { Button } from "@/components/ui/button"
6 | import { HiMiniVariable } from "react-icons/hi2"
7 | import { AppAccordionItem } from "./AppAccordionItem"
8 |
9 | export const VariablesAccordionItem = observer(() => {
10 | const store = useStore()
11 | const {
12 | state: { resource: agent },
13 | } = store
14 |
15 | return (
16 |
17 |
20 |
21 |
40 |
41 | )
42 | })
43 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
3 | import { ChevronDown } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Accordion = AccordionPrimitive.Root
8 |
9 | const AccordionItem = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
18 | ))
19 | AccordionItem.displayName = "AccordionItem"
20 |
21 | const AccordionTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, children, ...props }, ref) => (
25 |
26 | svg]:rotate-180",
30 | className
31 | )}
32 | {...props}
33 | >
34 | {children}
35 |
36 |
37 |
38 | ))
39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40 |
41 | const AccordionContent = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, children, ...props }, ref) => (
45 |
53 | {children}
54 |
55 | ))
56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
57 |
58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
59 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3 |
4 | import { cn } from "@/lib/utils"
5 | import { buttonVariants } from "@/components/ui/button"
6 |
7 | const AlertDialog = AlertDialogPrimitive.Root
8 |
9 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
10 |
11 | const AlertDialogPortal = AlertDialogPrimitive.Portal
12 |
13 | const AlertDialogOverlay = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef
16 | >(({ className, children, ...props }, ref) => (
17 |
25 | ))
26 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
27 |
28 | const AlertDialogContent = React.forwardRef<
29 | React.ElementRef,
30 | React.ComponentPropsWithoutRef
31 | >(({ className, ...props }, ref) => (
32 |
33 |
34 |
42 |
43 | ))
44 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
45 |
46 | const AlertDialogHeader = ({
47 | className,
48 | ...props
49 | }: React.HTMLAttributes) => (
50 |
57 | )
58 | AlertDialogHeader.displayName = "AlertDialogHeader"
59 |
60 | const AlertDialogFooter = ({
61 | className,
62 | ...props
63 | }: React.HTMLAttributes) => (
64 |
71 | )
72 | AlertDialogFooter.displayName = "AlertDialogFooter"
73 |
74 | const AlertDialogTitle = React.forwardRef<
75 | React.ElementRef,
76 | React.ComponentPropsWithoutRef
77 | >(({ className, ...props }, ref) => (
78 |
83 | ))
84 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
85 |
86 | const AlertDialogDescription = React.forwardRef<
87 | React.ElementRef,
88 | React.ComponentPropsWithoutRef
89 | >(({ className, ...props }, ref) => (
90 |
95 | ))
96 | AlertDialogDescription.displayName =
97 | AlertDialogPrimitive.Description.displayName
98 |
99 | const AlertDialogAction = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
110 |
111 | const AlertDialogCancel = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
124 | ))
125 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
126 |
127 | export {
128 | AlertDialog,
129 | AlertDialogPortal,
130 | AlertDialogOverlay,
131 | AlertDialogTrigger,
132 | AlertDialogContent,
133 | AlertDialogHeader,
134 | AlertDialogFooter,
135 | AlertDialogTitle,
136 | AlertDialogDescription,
137 | AlertDialogAction,
138 | AlertDialogCancel,
139 | }
140 |
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2 |
3 | const AspectRatio = AspectRatioPrimitive.Root
4 |
5 | export { AspectRatio }
6 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Avatar = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 | ))
19 | Avatar.displayName = AvatarPrimitive.Root.displayName
20 |
21 | const AvatarImage = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
30 | ))
31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
32 |
33 | const AvatarFallback = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 | ))
46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47 |
48 | export { Avatar, AvatarImage, AvatarFallback }
49 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "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",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap 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",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | xs: "h-8 rounded-md px-2",
25 | sm: "h-9 rounded-md px-3",
26 | lg: "h-11 rounded-md px-8",
27 | icon: "h-10 w-10",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | },
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | },
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
58 |
--------------------------------------------------------------------------------
/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { ChevronLeft, ChevronRight } from "lucide-react"
3 | import { DayPicker } from "react-day-picker"
4 |
5 | import { cn } from "@/lib/utils"
6 | import { buttonVariants } from "@/components/ui/button"
7 |
8 | export type CalendarProps = React.ComponentProps
9 |
10 | function Calendar({
11 | className,
12 | classNames,
13 | showOutsideDays = true,
14 | ...props
15 | }: CalendarProps) {
16 | return (
17 | ,
54 | IconRight: ({ ...props }) => ,
55 | }}
56 | {...props}
57 | />
58 | )
59 | }
60 | Calendar.displayName = "Calendar"
61 |
62 | export { Calendar }
63 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3 | import { Check } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Checkbox = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
19 |
22 |
23 |
24 |
25 | ))
26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
27 |
28 | export { Checkbox }
29 |
--------------------------------------------------------------------------------
/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
2 |
3 | const Collapsible = CollapsiblePrimitive.Root
4 |
5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
6 |
7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
8 |
9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
10 |
--------------------------------------------------------------------------------
/src/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { DialogProps } from "@radix-ui/react-dialog"
3 | import { Command as CommandPrimitive } from "cmdk"
4 | import { Search } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 | import { Dialog, DialogContent } from "@/components/ui/dialog"
8 |
9 | const Command = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 | ))
22 | Command.displayName = CommandPrimitive.displayName
23 |
24 | interface CommandDialogProps extends DialogProps {}
25 |
26 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
27 | return (
28 |
35 | )
36 | }
37 |
38 | const CommandInput = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
43 |
44 |
52 |
53 | ))
54 |
55 | CommandInput.displayName = CommandPrimitive.Input.displayName
56 |
57 | const CommandList = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
66 | ))
67 |
68 | CommandList.displayName = CommandPrimitive.List.displayName
69 |
70 | const CommandEmpty = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >((props, ref) => (
74 |
79 | ))
80 |
81 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
82 |
83 | const CommandGroup = React.forwardRef<
84 | React.ElementRef,
85 | React.ComponentPropsWithoutRef
86 | >(({ className, ...props }, ref) => (
87 |
95 | ))
96 |
97 | CommandGroup.displayName = CommandPrimitive.Group.displayName
98 |
99 | const CommandSeparator = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
110 |
111 | const CommandItem = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
123 | ))
124 |
125 | CommandItem.displayName = CommandPrimitive.Item.displayName
126 |
127 | const CommandShortcut = ({
128 | className,
129 | ...props
130 | }: React.HTMLAttributes) => {
131 | return (
132 |
139 | )
140 | }
141 | CommandShortcut.displayName = "CommandShortcut"
142 |
143 | export {
144 | Command,
145 | CommandDialog,
146 | CommandInput,
147 | CommandList,
148 | CommandEmpty,
149 | CommandGroup,
150 | CommandItem,
151 | CommandShortcut,
152 | CommandSeparator,
153 | }
154 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
3 | import { Check, ChevronRight, Circle } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const ContextMenu = ContextMenuPrimitive.Root
8 |
9 | const ContextMenuTrigger = ContextMenuPrimitive.Trigger
10 |
11 | const ContextMenuGroup = ContextMenuPrimitive.Group
12 |
13 | const ContextMenuPortal = ContextMenuPrimitive.Portal
14 |
15 | const ContextMenuSub = ContextMenuPrimitive.Sub
16 |
17 | const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
18 |
19 | const ContextMenuSubTrigger = React.forwardRef<
20 | React.ElementRef,
21 | React.ComponentPropsWithoutRef & {
22 | inset?: boolean
23 | }
24 | >(({ className, inset, children, ...props }, ref) => (
25 |
34 | {children}
35 |
36 |
37 | ))
38 | ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
39 |
40 | const ContextMenuSubContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
54 |
55 | const ContextMenuContent = React.forwardRef<
56 | React.ElementRef,
57 | React.ComponentPropsWithoutRef
58 | >(({ className, ...props }, ref) => (
59 |
60 |
68 |
69 | ))
70 | ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
71 |
72 | const ContextMenuItem = React.forwardRef<
73 | React.ElementRef,
74 | React.ComponentPropsWithoutRef & {
75 | inset?: boolean
76 | }
77 | >(({ className, inset, ...props }, ref) => (
78 |
87 | ))
88 | ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
89 |
90 | const ContextMenuCheckboxItem = React.forwardRef<
91 | React.ElementRef,
92 | React.ComponentPropsWithoutRef
93 | >(({ className, children, checked, ...props }, ref) => (
94 |
103 |
104 |
105 |
106 |
107 |
108 | {children}
109 |
110 | ))
111 | ContextMenuCheckboxItem.displayName =
112 | ContextMenuPrimitive.CheckboxItem.displayName
113 |
114 | const ContextMenuRadioItem = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, children, ...props }, ref) => (
118 |
126 |
127 |
128 |
129 |
130 |
131 | {children}
132 |
133 | ))
134 | ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
135 |
136 | const ContextMenuLabel = React.forwardRef<
137 | React.ElementRef,
138 | React.ComponentPropsWithoutRef & {
139 | inset?: boolean
140 | }
141 | >(({ className, inset, ...props }, ref) => (
142 |
151 | ))
152 | ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
153 |
154 | const ContextMenuSeparator = React.forwardRef<
155 | React.ElementRef,
156 | React.ComponentPropsWithoutRef
157 | >(({ className, ...props }, ref) => (
158 |
163 | ))
164 | ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
165 |
166 | const ContextMenuShortcut = ({
167 | className,
168 | ...props
169 | }: React.HTMLAttributes) => {
170 | return (
171 |
178 | )
179 | }
180 | ContextMenuShortcut.displayName = "ContextMenuShortcut"
181 |
182 | export {
183 | ContextMenu,
184 | ContextMenuTrigger,
185 | ContextMenuContent,
186 | ContextMenuItem,
187 | ContextMenuCheckboxItem,
188 | ContextMenuRadioItem,
189 | ContextMenuLabel,
190 | ContextMenuSeparator,
191 | ContextMenuShortcut,
192 | ContextMenuGroup,
193 | ContextMenuPortal,
194 | ContextMenuSub,
195 | ContextMenuSubContent,
196 | ContextMenuSubTrigger,
197 | ContextMenuRadioGroup,
198 | }
199 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as DialogPrimitive from "@radix-ui/react-dialog"
3 | import { X } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Dialog = DialogPrimitive.Root
8 |
9 | const DialogTrigger = DialogPrimitive.Trigger
10 |
11 | const DialogPortal = DialogPrimitive.Portal
12 |
13 | const DialogClose = DialogPrimitive.Close
14 |
15 | const DialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
29 |
30 | const DialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, children, ...props }, ref) => (
34 |
35 |
36 |
44 | {children}
45 |
46 |
47 | Close
48 |
49 |
50 |
51 | ))
52 | DialogContent.displayName = DialogPrimitive.Content.displayName
53 |
54 | const DialogHeader = ({
55 | className,
56 | ...props
57 | }: React.HTMLAttributes) => (
58 |
65 | )
66 | DialogHeader.displayName = "DialogHeader"
67 |
68 | const DialogFooter = ({
69 | className,
70 | ...props
71 | }: React.HTMLAttributes) => (
72 |
79 | )
80 | DialogFooter.displayName = "DialogFooter"
81 |
82 | const DialogTitle = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
94 | ))
95 | DialogTitle.displayName = DialogPrimitive.Title.displayName
96 |
97 | const DialogDescription = React.forwardRef<
98 | React.ElementRef,
99 | React.ComponentPropsWithoutRef
100 | >(({ className, ...props }, ref) => (
101 |
106 | ))
107 | DialogDescription.displayName = DialogPrimitive.Description.displayName
108 |
109 | export {
110 | Dialog,
111 | DialogPortal,
112 | DialogOverlay,
113 | DialogClose,
114 | DialogTrigger,
115 | DialogContent,
116 | DialogHeader,
117 | DialogFooter,
118 | DialogTitle,
119 | DialogDescription,
120 | }
121 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3 | import { Check, ChevronRight, Circle } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const DropdownMenu = DropdownMenuPrimitive.Root
8 |
9 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
10 |
11 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
12 |
13 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
14 |
15 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
16 |
17 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
18 |
19 | const DropdownMenuSubTrigger = React.forwardRef<
20 | React.ElementRef,
21 | React.ComponentPropsWithoutRef & {
22 | inset?: boolean
23 | }
24 | >(({ className, inset, children, ...props }, ref) => (
25 |
34 | {children}
35 |
36 |
37 | ))
38 | DropdownMenuSubTrigger.displayName =
39 | DropdownMenuPrimitive.SubTrigger.displayName
40 |
41 | const DropdownMenuSubContent = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, ...props }, ref) => (
45 |
53 | ))
54 | DropdownMenuSubContent.displayName =
55 | DropdownMenuPrimitive.SubContent.displayName
56 |
57 | const DropdownMenuContent = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, sideOffset = 4, ...props }, ref) => (
61 |
62 |
71 |
72 | ))
73 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
74 |
75 | const DropdownMenuItem = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef & {
78 | inset?: boolean
79 | }
80 | >(({ className, inset, ...props }, ref) => (
81 |
90 | ))
91 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
92 |
93 | const DropdownMenuCheckboxItem = React.forwardRef<
94 | React.ElementRef,
95 | React.ComponentPropsWithoutRef
96 | >(({ className, children, checked, ...props }, ref) => (
97 |
106 |
107 |
108 |
109 |
110 |
111 | {children}
112 |
113 | ))
114 | DropdownMenuCheckboxItem.displayName =
115 | DropdownMenuPrimitive.CheckboxItem.displayName
116 |
117 | const DropdownMenuRadioItem = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, children, ...props }, ref) => (
121 |
129 |
130 |
131 |
132 |
133 |
134 | {children}
135 |
136 | ))
137 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
138 |
139 | const DropdownMenuLabel = React.forwardRef<
140 | React.ElementRef,
141 | React.ComponentPropsWithoutRef & {
142 | inset?: boolean
143 | }
144 | >(({ className, inset, ...props }, ref) => (
145 |
154 | ))
155 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
156 |
157 | const DropdownMenuSeparator = React.forwardRef<
158 | React.ElementRef,
159 | React.ComponentPropsWithoutRef
160 | >(({ className, ...props }, ref) => (
161 |
166 | ))
167 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
168 |
169 | const DropdownMenuShortcut = ({
170 | className,
171 | ...props
172 | }: React.HTMLAttributes) => {
173 | return (
174 |
178 | )
179 | }
180 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
181 |
182 | export {
183 | DropdownMenu,
184 | DropdownMenuTrigger,
185 | DropdownMenuContent,
186 | DropdownMenuItem,
187 | DropdownMenuCheckboxItem,
188 | DropdownMenuRadioItem,
189 | DropdownMenuLabel,
190 | DropdownMenuSeparator,
191 | DropdownMenuShortcut,
192 | DropdownMenuGroup,
193 | DropdownMenuPortal,
194 | DropdownMenuSub,
195 | DropdownMenuSubContent,
196 | DropdownMenuSubTrigger,
197 | DropdownMenuRadioGroup,
198 | }
199 |
--------------------------------------------------------------------------------
/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const HoverCard = HoverCardPrimitive.Root
7 |
8 | const HoverCardTrigger = HoverCardPrimitive.Trigger
9 |
10 | const HoverCardContent = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14 |
24 | ))
25 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
26 |
27 | export { HoverCard, HoverCardTrigger, HoverCardContent }
28 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | export const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | },
22 | )
23 |
24 | Input.displayName = "Input"
25 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const labelVariants = cva(
8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9 | )
10 |
11 | const Label = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef &
14 | VariantProps
15 | >(({ className, ...props }, ref) => (
16 |
21 | ))
22 | Label.displayName = LabelPrimitive.Root.displayName
23 |
24 | export { Label }
25 |
--------------------------------------------------------------------------------
/src/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
3 | import { cva } from "class-variance-authority"
4 | import { ChevronDown } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const NavigationMenu = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
20 | {children}
21 |
22 |
23 | ))
24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
25 |
26 | const NavigationMenuList = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, ...props }, ref) => (
30 |
38 | ))
39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
40 |
41 | const NavigationMenuItem = NavigationMenuPrimitive.Item
42 |
43 | const navigationMenuTriggerStyle = cva(
44 | "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
45 | )
46 |
47 | const NavigationMenuTrigger = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, children, ...props }, ref) => (
51 |
56 | {children}{" "}
57 |
61 |
62 | ))
63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
64 |
65 | const NavigationMenuContent = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
77 | ))
78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
79 |
80 | const NavigationMenuLink = NavigationMenuPrimitive.Link
81 |
82 | const NavigationMenuViewport = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
87 |
95 |
96 | ))
97 | NavigationMenuViewport.displayName =
98 | NavigationMenuPrimitive.Viewport.displayName
99 |
100 | const NavigationMenuIndicator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
112 |
113 |
114 | ))
115 | NavigationMenuIndicator.displayName =
116 | NavigationMenuPrimitive.Indicator.displayName
117 |
118 | export {
119 | navigationMenuTriggerStyle,
120 | NavigationMenu,
121 | NavigationMenuList,
122 | NavigationMenuItem,
123 | NavigationMenuContent,
124 | NavigationMenuTrigger,
125 | NavigationMenuLink,
126 | NavigationMenuIndicator,
127 | NavigationMenuViewport,
128 | }
129 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as PopoverPrimitive from "@radix-ui/react-popover"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Popover = PopoverPrimitive.Root
7 |
8 | const PopoverTrigger = PopoverPrimitive.Trigger
9 |
10 | const PopoverContent = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14 |
15 |
25 |
26 | ))
27 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
28 |
29 | export { Popover, PopoverTrigger, PopoverContent }
30 |
--------------------------------------------------------------------------------
/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ProgressPrimitive from "@radix-ui/react-progress"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Progress = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, value, ...props }, ref) => (
10 |
18 |
22 |
23 | ))
24 | Progress.displayName = ProgressPrimitive.Root.displayName
25 |
26 | export { Progress }
27 |
--------------------------------------------------------------------------------
/src/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
3 | import { Circle } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const RadioGroup = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => {
11 | return (
12 |
17 | )
18 | })
19 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
20 |
21 | const RadioGroupItem = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, children, ...props }, ref) => {
25 | return (
26 |
34 |
35 |
36 |
37 |
38 | )
39 | })
40 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
41 |
42 | export { RadioGroup, RadioGroupItem }
43 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const ScrollArea = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, children, ...props }, ref) => (
10 |
15 |
16 | {children}
17 |
18 |
19 |
20 |
21 | ))
22 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
23 |
24 | const ScrollBar = React.forwardRef<
25 | React.ElementRef,
26 | React.ComponentPropsWithoutRef
27 | >(({ className, orientation = "vertical", ...props }, ref) => (
28 |
41 |
47 |
48 | ))
49 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
50 |
51 | export { ScrollArea, ScrollBar }
52 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SelectPrimitive from "@radix-ui/react-select"
3 | import { Check, ChevronDown } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Select = SelectPrimitive.Root
8 |
9 | const SelectGroup = SelectPrimitive.Group
10 |
11 | const SelectValue = SelectPrimitive.Value
12 |
13 | const SelectTrigger = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef
16 | >(({ className, children, ...props }, ref) => (
17 |
25 | {children}
26 |
27 |
28 |
29 |
30 | ))
31 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
32 |
33 | const SelectContent = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, children, position = "popper", ...props }, ref) => (
37 |
38 |
49 |
56 | {children}
57 |
58 |
59 |
60 | ))
61 | SelectContent.displayName = SelectPrimitive.Content.displayName
62 |
63 | const SelectLabel = React.forwardRef<
64 | React.ElementRef,
65 | React.ComponentPropsWithoutRef
66 | >(({ className, ...props }, ref) => (
67 |
72 | ))
73 | SelectLabel.displayName = SelectPrimitive.Label.displayName
74 |
75 | const SelectItem = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef
78 | >(({ className, children, ...props }, ref) => (
79 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | {children}
94 |
95 | ))
96 | SelectItem.displayName = SelectPrimitive.Item.displayName
97 |
98 | const SelectSeparator = React.forwardRef<
99 | React.ElementRef,
100 | React.ComponentPropsWithoutRef
101 | >(({ className, ...props }, ref) => (
102 |
107 | ))
108 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
109 |
110 | export {
111 | Select,
112 | SelectGroup,
113 | SelectValue,
114 | SelectTrigger,
115 | SelectContent,
116 | SelectLabel,
117 | SelectItem,
118 | SelectSeparator,
119 | }
120 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Separator = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(
10 | (
11 | { className, orientation = "horizontal", decorative = true, ...props },
12 | ref
13 | ) => (
14 |
25 | )
26 | )
27 | Separator.displayName = SeparatorPrimitive.Root.displayName
28 |
29 | export { Separator }
30 |
--------------------------------------------------------------------------------
/src/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SheetPrimitive from "@radix-ui/react-dialog"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 | import { X } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Sheet = SheetPrimitive.Root
9 |
10 | const SheetTrigger = SheetPrimitive.Trigger
11 |
12 | const SheetClose = SheetPrimitive.Close
13 |
14 | const SheetPortal = SheetPrimitive.Portal
15 |
16 | const SheetOverlay = React.forwardRef<
17 | React.ElementRef,
18 | React.ComponentPropsWithoutRef
19 | >(({ className, ...props }, ref) => (
20 |
28 | ))
29 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
30 |
31 | const sheetVariants = cva(
32 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
33 | {
34 | variants: {
35 | side: {
36 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
37 | bottom:
38 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
39 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
40 | right:
41 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
42 | },
43 | },
44 | defaultVariants: {
45 | side: "right",
46 | },
47 | }
48 | )
49 |
50 | interface SheetContentProps
51 | extends React.ComponentPropsWithoutRef,
52 | VariantProps {}
53 |
54 | const SheetContent = React.forwardRef<
55 | React.ElementRef,
56 | SheetContentProps
57 | >(({ side = "right", className, children, ...props }, ref) => (
58 |
59 |
60 |
65 | {children}
66 |
67 |
68 | Close
69 |
70 |
71 |
72 | ))
73 | SheetContent.displayName = SheetPrimitive.Content.displayName
74 |
75 | const SheetHeader = ({
76 | className,
77 | ...props
78 | }: React.HTMLAttributes) => (
79 |
86 | )
87 | SheetHeader.displayName = "SheetHeader"
88 |
89 | const SheetFooter = ({
90 | className,
91 | ...props
92 | }: React.HTMLAttributes) => (
93 |
100 | )
101 | SheetFooter.displayName = "SheetFooter"
102 |
103 | const SheetTitle = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ))
113 | SheetTitle.displayName = SheetPrimitive.Title.displayName
114 |
115 | const SheetDescription = React.forwardRef<
116 | React.ElementRef,
117 | React.ComponentPropsWithoutRef
118 | >(({ className, ...props }, ref) => (
119 |
124 | ))
125 | SheetDescription.displayName = SheetPrimitive.Description.displayName
126 |
127 | export {
128 | Sheet,
129 | SheetPortal,
130 | SheetOverlay,
131 | SheetTrigger,
132 | SheetClose,
133 | SheetContent,
134 | SheetHeader,
135 | SheetFooter,
136 | SheetTitle,
137 | SheetDescription,
138 | }
139 |
--------------------------------------------------------------------------------
/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SliderPrimitive from "@radix-ui/react-slider"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Slider = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 |
19 |
20 |
21 |
22 |
23 | ))
24 | Slider.displayName = SliderPrimitive.Root.displayName
25 |
26 | export { Slider }
27 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SwitchPrimitives from "@radix-ui/react-switch"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Switch = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 |
23 |
24 | ))
25 | Switch.displayName = SwitchPrimitives.Root.displayName
26 |
27 | export { Switch }
28 |
--------------------------------------------------------------------------------
/src/components/ui/table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Table = React.forwardRef<
6 | HTMLTableElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
16 | ))
17 | Table.displayName = "Table"
18 |
19 | const TableHeader = React.forwardRef<
20 | HTMLTableSectionElement,
21 | React.HTMLAttributes
22 | >(({ className, ...props }, ref) => (
23 |
24 | ))
25 | TableHeader.displayName = "TableHeader"
26 |
27 | const TableBody = React.forwardRef<
28 | HTMLTableSectionElement,
29 | React.HTMLAttributes
30 | >(({ className, ...props }, ref) => (
31 |
36 | ))
37 | TableBody.displayName = "TableBody"
38 |
39 | const TableFooter = React.forwardRef<
40 | HTMLTableSectionElement,
41 | React.HTMLAttributes
42 | >(({ className, ...props }, ref) => (
43 |
48 | ))
49 | TableFooter.displayName = "TableFooter"
50 |
51 | const TableRow = React.forwardRef<
52 | HTMLTableRowElement,
53 | React.HTMLAttributes
54 | >(({ className, ...props }, ref) => (
55 |
63 | ))
64 | TableRow.displayName = "TableRow"
65 |
66 | const TableHead = React.forwardRef<
67 | HTMLTableCellElement,
68 | React.ThHTMLAttributes
69 | >(({ className, ...props }, ref) => (
70 | |
78 | ))
79 | TableHead.displayName = "TableHead"
80 |
81 | const TableCell = React.forwardRef<
82 | HTMLTableCellElement,
83 | React.TdHTMLAttributes
84 | >(({ className, ...props }, ref) => (
85 | |
90 | ))
91 | TableCell.displayName = "TableCell"
92 |
93 | const TableCaption = React.forwardRef<
94 | HTMLTableCaptionElement,
95 | React.HTMLAttributes
96 | >(({ className, ...props }, ref) => (
97 |
102 | ))
103 | TableCaption.displayName = "TableCaption"
104 |
105 | export {
106 | Table,
107 | TableHeader,
108 | TableBody,
109 | TableFooter,
110 | TableHead,
111 | TableRow,
112 | TableCell,
113 | TableCaption,
114 | }
115 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TabsPrimitive from "@radix-ui/react-tabs"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Tabs = TabsPrimitive.Root
7 |
8 | const TabsList = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | TabsList.displayName = TabsPrimitive.List.displayName
22 |
23 | const TabsTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
35 | ))
36 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
37 |
38 | const TabsContent = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
50 | ))
51 | TabsContent.displayName = TabsPrimitive.Content.displayName
52 |
53 | export { Tabs, TabsList, TabsTrigger, TabsContent }
54 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | },
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ToastPrimitives from "@radix-ui/react-toast"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 | import { X } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ToastProvider = ToastPrimitives.Provider
9 |
10 | const ToastViewport = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
24 |
25 | const toastVariants = cva(
26 | "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",
27 | {
28 | variants: {
29 | variant: {
30 | default: "border bg-background text-foreground",
31 | destructive:
32 | "destructive group border-destructive bg-destructive text-destructive-foreground",
33 | },
34 | },
35 | defaultVariants: {
36 | variant: "default",
37 | },
38 | }
39 | )
40 |
41 | const Toast = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef &
44 | VariantProps
45 | >(({ className, variant, ...props }, ref) => {
46 | return (
47 |
52 | )
53 | })
54 | Toast.displayName = ToastPrimitives.Root.displayName
55 |
56 | const ToastAction = React.forwardRef<
57 | React.ElementRef,
58 | React.ComponentPropsWithoutRef
59 | >(({ className, ...props }, ref) => (
60 |
68 | ))
69 | ToastAction.displayName = ToastPrimitives.Action.displayName
70 |
71 | const ToastClose = React.forwardRef<
72 | React.ElementRef,
73 | React.ComponentPropsWithoutRef
74 | >(({ className, ...props }, ref) => (
75 |
84 |
85 |
86 | ))
87 | ToastClose.displayName = ToastPrimitives.Close.displayName
88 |
89 | const ToastTitle = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => (
93 |
98 | ))
99 | ToastTitle.displayName = ToastPrimitives.Title.displayName
100 |
101 | const ToastDescription = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | ToastDescription.displayName = ToastPrimitives.Description.displayName
112 |
113 | type ToastProps = React.ComponentPropsWithoutRef
114 |
115 | type ToastActionElement = React.ReactElement
116 |
117 | export {
118 | type ToastProps,
119 | type ToastActionElement,
120 | ToastProvider,
121 | ToastViewport,
122 | Toast,
123 | ToastTitle,
124 | ToastDescription,
125 | ToastClose,
126 | ToastAction,
127 | }
128 |
--------------------------------------------------------------------------------
/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Toast,
3 | ToastClose,
4 | ToastDescription,
5 | ToastProvider,
6 | ToastTitle,
7 | ToastViewport,
8 | } from "@/components/ui/toast"
9 | import { useToast } from "@/components/ui/use-toast"
10 |
11 | export function Toaster() {
12 | const { toasts } = useToast()
13 |
14 | return (
15 |
16 | {toasts.map(function ({ id, title, description, action, ...props }) {
17 | return (
18 |
19 |
20 | {title && {title}}
21 | {description && (
22 | {description}
23 | )}
24 |
25 | {action}
26 |
27 |
28 | )
29 | })}
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TogglePrimitive from "@radix-ui/react-toggle"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const toggleVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-transparent",
13 | outline:
14 | "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
15 | },
16 | size: {
17 | default: "h-10 px-3",
18 | sm: "h-9 px-2.5",
19 | lg: "h-11 px-5",
20 | },
21 | },
22 | defaultVariants: {
23 | variant: "default",
24 | size: "default",
25 | },
26 | }
27 | )
28 |
29 | const Toggle = React.forwardRef<
30 | React.ElementRef,
31 | React.ComponentPropsWithoutRef &
32 | VariantProps
33 | >(({ className, variant, size, ...props }, ref) => (
34 |
39 | ))
40 |
41 | Toggle.displayName = TogglePrimitive.Root.displayName
42 |
43 | export { Toggle, toggleVariants }
44 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider
7 |
8 | const Tooltip = TooltipPrimitive.Root
9 |
10 | const TooltipTrigger = TooltipPrimitive.Trigger
11 |
12 | const TooltipContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, sideOffset = 4, ...props }, ref) => (
16 |
25 | ))
26 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
27 |
28 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
29 |
--------------------------------------------------------------------------------
/src/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | // Inspired by react-hot-toast library
2 | import * as React from "react"
3 |
4 | import type {
5 | ToastActionElement,
6 | ToastProps,
7 | } from "@/components/ui/toast"
8 |
9 | const TOAST_LIMIT = 1
10 | const TOAST_REMOVE_DELAY = 1000000
11 |
12 | type ToasterToast = ToastProps & {
13 | id: string
14 | title?: React.ReactNode
15 | description?: React.ReactNode
16 | action?: ToastActionElement
17 | }
18 |
19 | const actionTypes = {
20 | ADD_TOAST: "ADD_TOAST",
21 | UPDATE_TOAST: "UPDATE_TOAST",
22 | DISMISS_TOAST: "DISMISS_TOAST",
23 | REMOVE_TOAST: "REMOVE_TOAST",
24 | } as const
25 |
26 | let count = 0
27 |
28 | function genId() {
29 | count = (count + 1) % Number.MAX_VALUE
30 | return count.toString()
31 | }
32 |
33 | type ActionType = typeof actionTypes
34 |
35 | type Action =
36 | | {
37 | type: ActionType["ADD_TOAST"]
38 | toast: ToasterToast
39 | }
40 | | {
41 | type: ActionType["UPDATE_TOAST"]
42 | toast: Partial
43 | }
44 | | {
45 | type: ActionType["DISMISS_TOAST"]
46 | toastId?: ToasterToast["id"]
47 | }
48 | | {
49 | type: ActionType["REMOVE_TOAST"]
50 | toastId?: ToasterToast["id"]
51 | }
52 |
53 | interface State {
54 | toasts: ToasterToast[]
55 | }
56 |
57 | const toastTimeouts = new Map>()
58 |
59 | const addToRemoveQueue = (toastId: string) => {
60 | if (toastTimeouts.has(toastId)) {
61 | return
62 | }
63 |
64 | const timeout = setTimeout(() => {
65 | toastTimeouts.delete(toastId)
66 | dispatch({
67 | type: "REMOVE_TOAST",
68 | toastId: toastId,
69 | })
70 | }, TOAST_REMOVE_DELAY)
71 |
72 | toastTimeouts.set(toastId, timeout)
73 | }
74 |
75 | export const reducer = (state: State, action: Action): State => {
76 | switch (action.type) {
77 | case "ADD_TOAST":
78 | return {
79 | ...state,
80 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
81 | }
82 |
83 | case "UPDATE_TOAST":
84 | return {
85 | ...state,
86 | toasts: state.toasts.map((t) =>
87 | t.id === action.toast.id ? { ...t, ...action.toast } : t
88 | ),
89 | }
90 |
91 | case "DISMISS_TOAST": {
92 | const { toastId } = action
93 |
94 | // ! Side effects ! - This could be extracted into a dismissToast() action,
95 | // but I'll keep it here for simplicity
96 | if (toastId) {
97 | addToRemoveQueue(toastId)
98 | } else {
99 | state.toasts.forEach((toast) => {
100 | addToRemoveQueue(toast.id)
101 | })
102 | }
103 |
104 | return {
105 | ...state,
106 | toasts: state.toasts.map((t) =>
107 | t.id === toastId || toastId === undefined
108 | ? {
109 | ...t,
110 | open: false,
111 | }
112 | : t
113 | ),
114 | }
115 | }
116 | case "REMOVE_TOAST":
117 | if (action.toastId === undefined) {
118 | return {
119 | ...state,
120 | toasts: [],
121 | }
122 | }
123 | return {
124 | ...state,
125 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
126 | }
127 | }
128 | }
129 |
130 | const listeners: Array<(state: State) => void> = []
131 |
132 | let memoryState: State = { toasts: [] }
133 |
134 | function dispatch(action: Action) {
135 | memoryState = reducer(memoryState, action)
136 | listeners.forEach((listener) => {
137 | listener(memoryState)
138 | })
139 | }
140 |
141 | type Toast = Omit
142 |
143 | function toast({ ...props }: Toast) {
144 | const id = genId()
145 |
146 | const update = (props: ToasterToast) =>
147 | dispatch({
148 | type: "UPDATE_TOAST",
149 | toast: { ...props, id },
150 | })
151 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
152 |
153 | dispatch({
154 | type: "ADD_TOAST",
155 | toast: {
156 | ...props,
157 | id,
158 | open: true,
159 | onOpenChange: (open) => {
160 | if (!open) dismiss()
161 | },
162 | },
163 | })
164 |
165 | return {
166 | id: id,
167 | dismiss,
168 | update,
169 | }
170 | }
171 |
172 | function useToast() {
173 | const [state, setState] = React.useState(memoryState)
174 |
175 | React.useEffect(() => {
176 | listeners.push(setState)
177 | return () => {
178 | const index = listeners.indexOf(setState)
179 | if (index > -1) {
180 | listeners.splice(index, 1)
181 | }
182 | }
183 | }, [state])
184 |
185 | return {
186 | ...state,
187 | toast,
188 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
189 | }
190 | }
191 |
192 | export { useToast, toast }
193 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 222.2 84% 4.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 222.2 84% 4.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 222.2 84% 4.9%;
15 |
16 | --primary: 222.2 47.4% 11.2%;
17 | --primary-foreground: 210 40% 98%;
18 |
19 | --secondary: 210 40% 96.1%;
20 | --secondary-foreground: 222.2 47.4% 11.2%;
21 |
22 | --muted: 210 40% 96.1%;
23 | --muted-foreground: 215.4 16.3% 46.9%;
24 |
25 | --accent: 210 40% 96.1%;
26 | --accent-foreground: 222.2 47.4% 11.2%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 210 40% 98%;
30 |
31 | --border: 214.3 31.8% 91.4%;
32 | --input: 214.3 31.8% 91.4%;
33 | --ring: 222.2 84% 4.9%;
34 |
35 | --radius: 0.5rem;
36 | }
37 |
38 | .dark {
39 | --background: 222.2 84% 4.9%;
40 | --foreground: 210 40% 98%;
41 |
42 | --card: 222.2 84% 4.9%;
43 | --card-foreground: 210 40% 98%;
44 |
45 | --popover: 222.2 84% 4.9%;
46 | --popover-foreground: 210 40% 98%;
47 |
48 | --primary: 210 40% 98%;
49 | --primary-foreground: 222.2 47.4% 11.2%;
50 |
51 | --secondary: 217.2 32.6% 17.5%;
52 | --secondary-foreground: 210 40% 98%;
53 |
54 | --muted: 217.2 32.6% 17.5%;
55 | --muted-foreground: 215 20.2% 65.1%;
56 |
57 | --accent: 217.2 32.6% 17.5%;
58 | --accent-foreground: 210 40% 98%;
59 |
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 210 40% 98%;
62 |
63 | --border: 217.2 32.6% 17.5%;
64 | --input: 217.2 32.6% 17.5%;
65 | --ring: 212.7 26.8% 83.9%;
66 | }
67 | }
68 |
69 | @layer base {
70 | * {
71 | @apply border-border;
72 | }
73 |
74 | body {
75 | @apply bg-background text-foreground;
76 | }
77 |
78 | ::-webkit-scrollbar {
79 | display: none;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import AppShell from "./app/AppShell"
4 | import { Store, StoreContext } from "./store"
5 | import { ThemeProvider } from "./components/ThemeProvider"
6 | import { onSnapshot } from "mobx-state-tree"
7 | import "./index.css"
8 |
9 | function loadStore() {
10 | let data = {}
11 | const raw = window.localStorage.getItem("store")
12 | if (raw) {
13 | data = JSON.parse(window.localStorage.getItem("store")!)
14 | }
15 | try {
16 | return Store.create(data)
17 | } catch (e) {
18 | console.error(e)
19 | if (
20 | window.confirm(
21 | "Could not to load application data from local storage, clear it?",
22 | )
23 | ) {
24 | window.localStorage.removeItem("store")
25 | return Store.create({})
26 | }
27 |
28 | throw e
29 | }
30 | }
31 | const store = loadStore()
32 |
33 | ReactDOM.createRoot(document.getElementById("root")!).render(
34 |
35 |
36 |
37 |
38 |
39 |
40 | ,
41 | )
42 |
43 | onSnapshot(store, (snapshot) => {
44 | window.localStorage.setItem("store", JSON.stringify(snapshot))
45 | })
46 |
--------------------------------------------------------------------------------
/src/lib/adapters/HuggingFace.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/huggingface/text-generation-inference/blob/main/docs/openapi.json
2 |
3 | import { HfInferenceEndpoint, TextGenerationArgs } from "@huggingface/inference"
4 | import { listModels } from "@huggingface/hub"
5 | import { GenerationParams } from "@/store/Agent"
6 | import { Instance } from "mobx-state-tree"
7 |
8 | function transformParams(
9 | params: Instance,
10 | ): TextGenerationArgs["parameters"] {
11 | return {
12 | max_new_tokens: params.num_predict,
13 | repetition_penalty: params.repeat_penalty,
14 | return_full_text: true,
15 | temperature: params.temperature,
16 | top_k: params.top_k,
17 | top_p: params.top_p,
18 | stop_sequences: params.stop,
19 | do_sample: true,
20 | // TODO: add support for these params
21 | // max_time
22 | // num_return_sequences
23 | // truncate
24 | }
25 | }
26 | export class HuggingFaceAdapter {
27 | endpoint: HfInferenceEndpoint
28 | parameters = [
29 | "num_predict",
30 | "repeat_last_n",
31 | "repeat_penalty",
32 | "stop",
33 | "temperature",
34 | "top_p",
35 | "top_k",
36 | ] as (keyof Instance)[]
37 |
38 | constructor(public baseUrl: string) {
39 | this.endpoint = new HfInferenceEndpoint(baseUrl)
40 | }
41 |
42 | async *completion(
43 | prompt: string,
44 | model: string, // hf endpoints are not multi-model
45 | options?: Instance,
46 | signal?: AbortSignal,
47 | ) {
48 | for await (const output of this.endpoint.textGenerationStream({
49 | inputs: prompt,
50 | parameters: transformParams(options ?? {}),
51 | })) {
52 | if (signal?.aborted) break
53 | yield output.token.text
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/lib/adapters/Ollama.ts:
--------------------------------------------------------------------------------
1 | import { GenerationParams } from "@/store/Agent"
2 | import { Instance } from "mobx-state-tree"
3 |
4 | type OllamaCompletionRequestParams = {
5 | num_ctx?: number
6 | num_predict?: number
7 | repeat_last_n?: number
8 | repeat_penalty?: number
9 | seed?: number
10 | stop?: string[]
11 | temperature?: number
12 | top_k?: number
13 | top_p?: number
14 | }
15 |
16 | export class OllamaAdapter {
17 | baseUrl: string
18 | parameters = [
19 | "num_ctx",
20 | "num_predict",
21 | "repeat_last_n",
22 | "repeat_penalty",
23 | "seed",
24 | "stop",
25 | "temperature",
26 | "top_p",
27 | "top_k",
28 | ] as (keyof Instance)[]
29 |
30 | constructor(baseUrl: string) {
31 | this.baseUrl = baseUrl
32 | }
33 |
34 | async models() {
35 | const models = await fetch(`${this.baseUrl}/api/tags`)
36 | const result = await models.json()
37 | return result.models
38 | }
39 |
40 | async meta(modelName: string) {
41 | const options = await fetch(`${this.baseUrl}/api/show`, {
42 | method: "POST",
43 | body: JSON.stringify({
44 | name: modelName,
45 | }),
46 | })
47 | return await options.json()
48 | }
49 |
50 | async embed(prompt: string, model: string) {
51 | const embeddings = await fetch(`${this.baseUrl}/api/embeddings`, {
52 | method: "POST",
53 | headers: {
54 | "Content-Type": "application/json",
55 | },
56 | body: JSON.stringify({ model, prompt }),
57 | })
58 | const res = await embeddings.json()
59 | return res.embedding
60 | }
61 |
62 | completionRequest(
63 | prompt: string,
64 | model: string,
65 | options?: OllamaCompletionRequestParams,
66 | signal?: AbortSignal,
67 | ) {
68 | return fetch(`${this.baseUrl}/api/generate`, {
69 | signal,
70 | method: "POST",
71 | headers: {
72 | "Content-Type": "application/json",
73 | },
74 | body: JSON.stringify({
75 | prompt,
76 | model,
77 | stream: true,
78 | template: "{{ .Prompt }}",
79 | options,
80 | }),
81 | })
82 | }
83 |
84 | async *completion(
85 | prompt: string,
86 | model: string,
87 | options?: Instance,
88 | signal?: AbortSignal,
89 | ) {
90 | const response = await this.completionRequest(
91 | prompt,
92 | model,
93 | options,
94 | signal,
95 | )
96 |
97 | if (!response.ok || !response.body) {
98 | throw new Error(
99 | `Failed to generate: ${response.status} ${await response.text()}`,
100 | )
101 | }
102 |
103 | const reader = response.body.getReader()
104 | const decoder = new TextDecoder()
105 |
106 | let remainder = ""
107 | while (true) {
108 | const { done, value } = await reader.read()
109 | if (done) {
110 | break
111 | }
112 | const chunks = (remainder + decoder.decode(value)).split("\n")
113 | for (const chunk of chunks) {
114 | if (!chunk) {
115 | continue
116 | }
117 | try {
118 | const json = JSON.parse(chunk)
119 | if (json.done) {
120 | break
121 | }
122 | yield json.response
123 | } catch (e) {
124 | if (e instanceof SyntaxError) {
125 | remainder += chunk
126 | continue
127 | }
128 | console.error(chunk)
129 | throw e
130 | }
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/lib/adapters/index.ts:
--------------------------------------------------------------------------------
1 | import { OllamaAdapter } from "."
2 | import { HuggingFaceAdapter } from "."
3 |
4 | export { HuggingFaceAdapter } from "./HuggingFace"
5 | export { OllamaAdapter } from "./Ollama"
6 |
7 | export const AdapterMap = {
8 | HuggingFace: HuggingFaceAdapter,
9 | Ollama: OllamaAdapter,
10 | }
11 |
12 | export const AdapterList = Object.keys(AdapterMap)
13 |
--------------------------------------------------------------------------------
/src/lib/extraction.ts:
--------------------------------------------------------------------------------
1 | import mammoth from "mammoth/mammoth.browser"
2 |
3 | function readFileInputEventAsArrayBuffer(file: File) {
4 | return new Promise((resolve, reject) => {
5 | const reader = new FileReader()
6 | reader.onerror = reject
7 | reader.onload = (e) => {
8 | const arrayBuffer = e.target.result
9 | resolve(arrayBuffer)
10 | }
11 | reader.readAsArrayBuffer(file)
12 | })
13 | }
14 |
15 | async function extractWithMammoth(file: File) {
16 | const arrayBuffer = await readFileInputEventAsArrayBuffer(file)
17 | const result = await mammoth.extractRawText({ arrayBuffer })
18 | return result.value
19 | }
20 |
21 | async function extractWithPDFParse(file: File) {
22 | const pdfjs = await import("pdfjs-dist/build/pdf")
23 | const pdfjsWorker = await import("pdfjs-dist/build/pdf.worker.entry")
24 | pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker
25 |
26 | async function getDoc(file) {
27 | const arrayBuffer = await readFileInputEventAsArrayBuffer(file)
28 | const result = await pdfjs.getDocument({
29 | data: arrayBuffer,
30 | useWorkerFetch: false,
31 | isEvalSupported: false,
32 | useSystemFonts: true,
33 | }).promise
34 | return result
35 | }
36 |
37 | const doc = await getDoc(file)
38 | const meta = await doc.getMetadata().catch(() => null)
39 |
40 | let text = ""
41 | for (let i = 1; i <= doc.numPages; i += 1) {
42 | const page = await doc.getPage(i)
43 | const content = await page.getTextContent()
44 |
45 | if (content.items.length === 0) {
46 | continue
47 | }
48 |
49 | text += content.items.map((item) => item.str).join("")
50 | }
51 |
52 | return text
53 | }
54 |
55 | export async function processFile(file: File) {
56 | if (file.name.endsWith(".docx")) {
57 | return extractWithMammoth(file)
58 | } else if (file.name.endsWith(".pdf")) {
59 | return extractWithPDFParse(file)
60 | } else if (file.type.startsWith("text/")) {
61 | return file.text()
62 | }
63 | }
64 |
65 | // function stripNonPrintableAndNormalize(
66 | // text: string,
67 | // stripSurrogatesAndFormats = true,
68 | // ) {
69 | // // strip control chars. optionally, keep surrogates and formats
70 | // if (stripSurrogatesAndFormats) {
71 | // // text = text.replace(/\p{C}/gu, "")
72 | // } else {
73 | // // text = text.replace(/\p{Cc}/gu, "")
74 | // text = text.replace(/\p{Co}/gu, "")
75 | // text = text.replace(/\p{Cn}/gu, "")
76 | // }
77 |
78 | // // other common tasks are to normalize newlines and other whitespace
79 |
80 | // // normalize newline
81 | // text = text.replace(/\n\r/g, "\n")
82 | // text = text.replace(/\p{Zl}/gu, "\n")
83 | // text = text.replace(/\p{Zp}/gu, "\n")
84 |
85 | // // normalize space
86 | // return text.replace(/\p{Zs}/gu, " ")
87 | // }
88 |
89 | // export async function processFile(file: File) {
90 | // const text = await _processFile(file)
91 | // return stripNonPrintableAndNormalize(text)
92 | // }
93 |
--------------------------------------------------------------------------------
/src/lib/utils.tsx:
--------------------------------------------------------------------------------
1 | import rehypeParse from "rehype-parse"
2 | import rehypeRemark from "rehype-remark"
3 | import remarkStringify from "remark-stringify"
4 | import remarkMath from "remark-math"
5 | import remarkGfm from "remark-gfm"
6 | import remarkEmoji from "remark-emoji"
7 | import { twMerge } from "tailwind-merge"
8 | import { unified } from "unified"
9 | import { type ClassValue, clsx } from "clsx"
10 |
11 | export function cn(...inputs: ClassValue[]) {
12 | return twMerge(clsx(inputs))
13 | }
14 |
15 | export const randomId = () => Math.random().toString(36).substr(2, 9)
16 |
17 | export function humanFileSize(size: number) {
18 | if (size > 0) {
19 | const i = Math.floor(Math.log(size) / Math.log(1024))
20 | return (
21 | (size / Math.pow(1024, i)).toFixed(2) * 1 +
22 | ["B", "kB", "MB", "GB", "TB"][i]
23 | )
24 | }
25 | return "0.00MB"
26 | }
27 |
28 | export function cosineSimilarity(v1: number[], v2: number[]) {
29 | if (v1.length !== v2.length) {
30 | return -1
31 | }
32 | let dotProduct = 0
33 | let v1_mag = 0
34 | let v2_mag = 0
35 | for (let i = 0; i < v1.length; i++) {
36 | dotProduct += v1[i] * v2[i]
37 | v1_mag += v1[i] ** 2
38 | v2_mag += v2[i] ** 2
39 | }
40 | return dotProduct / (Math.sqrt(v1_mag) * Math.sqrt(v2_mag))
41 | }
42 |
43 | export function zip(...arrays: any[]) {
44 | return arrays[0].map((_: any, i: string | number) =>
45 | arrays.map((array: { [x: string]: any }) => array[i]),
46 | )
47 | }
48 | export function wrapArray(value: any) {
49 | return Array.isArray(value) ? value : [value]
50 | }
51 |
52 | export function registerTag(
53 | engine,
54 | key: string,
55 | fn: (value: any, emitter: Emitter) => void,
56 | ) {
57 | engine.registerTag(
58 | key,
59 | class extends Tag {
60 | private value: Value
61 | constructor(
62 | token: TagToken,
63 | remainTokens: TopLevelToken[],
64 | liquid: Liquid,
65 | ) {
66 | super(token, remainTokens, liquid)
67 | this.value = new Value(token.args, liquid)
68 | }
69 | async render(ctx: Context, emitter: Emitter) {
70 | const value = await toPromise(this.value.value(ctx))
71 | fn(value, emitter)
72 | }
73 | },
74 | )
75 | }
76 | export function html2md(data: any) {
77 | return unified()
78 | .use(rehypeParse)
79 | .use(rehypeRemark)
80 | .use(remarkGfm)
81 | .use(remarkEmoji)
82 | .use(remarkMath)
83 | .use(remarkStringify)
84 | .processSync(data).value
85 | }
86 |
87 | export async function extractHTML(input: string) {
88 | const url = new URL(input)
89 | const response = await fetch(input)
90 | const html = await response.text()
91 | const doc = new DOMParser().parseFromString(html, "text/html")
92 | const body = doc.querySelector("body")
93 |
94 | function removeComments(node: Node) {
95 | if (node.nodeType === Node.COMMENT_NODE) {
96 | node.parentNode?.removeChild(node)
97 | } else {
98 | for (let i = 0; i < node.childNodes.length; i++) {
99 | removeComments(node.childNodes[i])
100 | }
101 | }
102 | }
103 |
104 | removeComments(body)
105 |
106 | body?.querySelectorAll("img").forEach((img) => {
107 | const src = img.getAttribute("src")
108 | if (!src?.startsWith("http")) {
109 | img.setAttribute("src", url.origin + src)
110 | }
111 | })
112 |
113 | body?.querySelectorAll("a").forEach((a) => {
114 | const href = a.getAttribute("href")
115 | if (!href?.startsWith("http")) {
116 | a.setAttribute("href", url.origin + href)
117 | }
118 | })
119 |
120 | return body
121 | }
122 |
--------------------------------------------------------------------------------
/src/store/AdapterFactory.ts:
--------------------------------------------------------------------------------
1 | import { types as t } from "mobx-state-tree"
2 | import { Instance } from "mobx-state-tree"
3 | import { AdapterList, AdapterMap } from "@/lib/adapters"
4 | import { Model } from "./Model"
5 | import { autorun } from "mobx"
6 |
7 | export const AdapterFactory = t
8 | .model("AdapterFactory", {
9 | baseUrl: t.optional(t.string, ""),
10 | models: t.optional(t.array(Model), []),
11 | type: t.optional(t.enumeration(AdapterList), AdapterList[0]),
12 | })
13 | .views((self) => ({
14 | get isMultiModal() {
15 | // todo: make an abstract adapter class for multi-model endpoints
16 | return self.type === "Ollama"
17 | },
18 | get instance() {
19 | return new AdapterMap[self.type as keyof typeof AdapterMap](self.baseUrl)
20 | },
21 |
22 | get parameters() {
23 | return this.instance.parameters
24 | },
25 | }))
26 | .actions((self) => ({
27 | afterCreate() {
28 | this.refreshModels()
29 | },
30 | update(props: Partial>) {
31 | Object.assign(self, props)
32 | },
33 | async refreshModels() {
34 | if (self.isMultiModal) {
35 | try {
36 | const models = await self.instance.models()
37 | this.update({ models })
38 | } catch (e) {
39 | // console.error(e)
40 | }
41 | }
42 | },
43 | }))
44 |
--------------------------------------------------------------------------------
/src/store/Agent.ts:
--------------------------------------------------------------------------------
1 | import { Liquid } from "liquidjs"
2 | import { destroy, types as t } from "mobx-state-tree"
3 | import { randomId } from "@/lib/utils"
4 | import { Message } from "./Message"
5 | import { Instance } from "mobx-state-tree"
6 | import { reaction, toJS } from "mobx"
7 | import { AdapterFactory } from "./AdapterFactory"
8 | import { Template } from "./Template"
9 |
10 | export const GenerationParams = t.model("Options", {
11 | temperature: t.optional(t.number, 0.7),
12 | top_k: t.optional(t.number, 40),
13 | top_p: t.optional(t.number, 0.9),
14 | repeat_last_n: t.optional(t.number, -1),
15 | repeat_penalty: t.optional(t.number, 1.18),
16 | num_predict: t.optional(t.number, -2),
17 | num_ctx: t.optional(t.number, 2048),
18 | stop: t.optional(t.array(t.string), []),
19 | })
20 |
21 | const Variable = t.model("Variable", {
22 | key: t.string,
23 | value: t.string,
24 | })
25 |
26 | type Turn = {
27 | user: string
28 | assistant: string
29 | }
30 |
31 | export const Agent = t
32 | .model("Agent", {
33 | id: t.optional(t.identifier, randomId),
34 | name: t.optional(t.string, ""),
35 | model: t.optional(t.string, ""),
36 | adapter: t.optional(AdapterFactory, {}),
37 | parameters: t.optional(GenerationParams, {}),
38 | checkedOptions: t.optional(t.array(t.string), []),
39 | promptTemplate: t.optional(t.string, ""),
40 | promptPreview: t.optional(t.string, ""),
41 | messages: t.array(Message),
42 | variables: t.array(Variable),
43 | templates: t.array(Template),
44 | })
45 |
46 | .views((self) => ({
47 | get overridedOptions() {
48 | return Object.fromEntries(
49 | self.checkedOptions.map((key) => [
50 | key,
51 | self.parameters[key as keyof typeof self.parameters],
52 | ]),
53 | )
54 | },
55 | get templateMap() {
56 | return new Map(self.templates.map((t) => [t.id, t.content]))
57 | },
58 | get engine() {
59 | const self = this
60 | return new Liquid({
61 | relativeReference: false,
62 | fs: {
63 | readFileSync(file: string) {
64 | return self.templateMap.get(file) ?? ""
65 | },
66 | async readFile(file: string) {
67 | return self.templateMap.get(file) ?? ""
68 | },
69 | existsSync(file) {
70 | return self.templateMap.has(file)
71 | },
72 | async exists(file) {
73 | return self.templateMap.has(file)
74 | },
75 | contains() {
76 | return true
77 | },
78 | resolve(_root, file, _ext) {
79 | return file
80 | },
81 | },
82 | })
83 | },
84 | async renderTemplate(template: string, extra = {}) {
85 | const locals = {
86 | time: new Date().toLocaleString(),
87 | ...extra,
88 | ...Object.fromEntries(self.variables.map((v) => [v.key, v.value])),
89 | }
90 | const templates = this.engine.parse(template)
91 |
92 | return await this.engine.render(templates, locals)
93 | },
94 | turns(messages: Instance[] = []): Turn[] {
95 | messages = messages.filter((message) => message.role !== "system")
96 | let prev: any
97 | return messages.reduce((acc, message) => {
98 | if (prev && message.role === "assistant") {
99 | acc.push({
100 | user: prev.content,
101 | assistant: message.content,
102 | })
103 | }
104 | prev = message
105 |
106 | return acc
107 | }, [])
108 | },
109 | async buildTemplate(messages: Instance[] = [], extra = {}) {
110 | messages = [...self.messages, ...messages]
111 |
112 | const turns = this.turns(messages)
113 |
114 | const locals = {
115 | ...extra,
116 | system: messages.filter((message) => message.role === "system"),
117 | turns,
118 | messages: messages
119 | .filter((message) => message.open || !message.isEmpty)
120 | .map((message) => ({
121 | closed: !message.open,
122 | ...message,
123 | })),
124 | }
125 |
126 | let result = await this.renderTemplate(self.promptTemplate, locals)
127 |
128 | let last = null
129 | while (last !== result) {
130 | last = result
131 | result = await this.renderTemplate(result, extra)
132 | }
133 | return result
134 | },
135 |
136 | completion(prompt: string, signal?: AbortSignal) {
137 | console.log(prompt)
138 | return self.adapter.instance.completion(
139 | prompt,
140 | self.model,
141 | this.overridedOptions as typeof self.parameters,
142 | signal,
143 | )
144 | },
145 | }))
146 | .actions((self) => ({
147 | afterCreate() {
148 | reaction(
149 | () =>
150 | [self.promptTemplate, self.templates, self.turns, self.messages].map(
151 | (x) => toJS(x),
152 | ),
153 | this.handlePromptTemplateChange,
154 | )
155 | this.handlePromptTemplateChange()
156 | },
157 | update(props: Partial>) {
158 | Object.assign(self, props)
159 | },
160 | remove(thing: any) {
161 | destroy(thing)
162 | },
163 | addVariable(variable: Partial>) {
164 | self.variables.push(
165 | Variable.create(variable as Instance),
166 | )
167 | },
168 | addMessage(message: Partial