├── src ├── vite-env.d.ts ├── components │ ├── Sheet.tsx │ ├── ui │ │ ├── aspect-ratio.tsx │ │ ├── skeleton.tsx │ │ ├── collapsible.tsx │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── separator.tsx │ │ ├── progress.tsx │ │ ├── input.tsx │ │ ├── toaster.tsx │ │ ├── checkbox.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── tooltip.tsx │ │ ├── badge.tsx │ │ ├── hover-card.tsx │ │ ├── popover.tsx │ │ ├── avatar.tsx │ │ ├── toggle.tsx │ │ ├── radio-group.tsx │ │ ├── alert.tsx │ │ ├── scroll-area.tsx │ │ ├── tabs.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── accordion.tsx │ │ ├── calendar.tsx │ │ ├── table.tsx │ │ ├── dialog.tsx │ │ ├── select.tsx │ │ ├── use-toast.ts │ │ ├── sheet.tsx │ │ ├── form.tsx │ │ ├── alert-dialog.tsx │ │ ├── toast.tsx │ │ ├── command.tsx │ │ ├── navigation-menu.tsx │ │ ├── context-menu.tsx │ │ └── dropdown-menu.tsx │ ├── SidebarSection.tsx │ ├── AgentPromptTemplate.tsx │ ├── AgentHistory.tsx │ ├── ActionOverlay.tsx │ ├── AgentGenerationParamsAccordionItem.tsx │ ├── ToggleDarkButton.tsx │ ├── AppAccordionItem.tsx │ ├── AgentPicker.tsx │ ├── ModelPicker.tsx │ ├── Editor.tsx │ ├── PromptInput.tsx │ ├── AgentAdapterPicker.tsx │ ├── SidebarItem.tsx │ ├── AdapterPicker.tsx │ ├── Picker.tsx │ ├── ModelSelect.tsx │ ├── AutoTextarea.tsx │ ├── Select.tsx │ ├── AgentMessage.tsx │ ├── SliderCheckbox.tsx │ ├── VariablesAccordionItem.tsx │ ├── PresetPicker.tsx │ ├── ThemeProvider.tsx │ ├── AttachmentsAccordionItem.tsx │ ├── ChatConversation.tsx │ ├── AgentConversation.tsx │ ├── AgentGenerationParams.tsx │ ├── ChatConversationMessage.tsx │ └── ChatSettings.tsx ├── store │ ├── Model.ts │ ├── index.ts │ ├── Template.ts │ ├── Message.ts │ ├── State.ts │ ├── Attachment.ts │ ├── Playground.ts │ ├── AdapterFactory.ts │ ├── defaults.ts │ ├── Store.ts │ ├── Agent.ts │ └── Chat.ts ├── lib │ ├── adapters │ │ ├── index.ts │ │ ├── HuggingFace.ts │ │ └── Ollama.ts │ ├── extraction.ts │ └── utils.tsx ├── app │ ├── AppShell.tsx │ ├── ChatView.tsx │ ├── ActiveRoute.tsx │ ├── Sidebar.tsx │ ├── SidebarAgentList.tsx │ ├── AgentView.tsx │ ├── SidebarChatList.tsx │ └── PlaygroundView.tsx ├── index.tsx └── index.css ├── bun.lockb ├── postcss.config.js ├── tsconfig.node.json ├── vite.config.js ├── index.html ├── components.json ├── .github └── workflows │ └── main.yml ├── scripts └── pull.py ├── tsconfig.json ├── tailwind.config.js ├── .gitignore ├── package.json └── README.md /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knoopx/llm-workbench/HEAD/bun.lockb -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Sheet.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react" 2 | 3 | export const Sheet = observer(() => { 4 | return
Sheet
5 | }) 6 | -------------------------------------------------------------------------------- /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/store/Model.ts: -------------------------------------------------------------------------------- 1 | import { types as t } from "mobx-state-tree" 2 | export const Model = t.model("Model", { 3 | name: t.string, 4 | digest: t.string, 5 | size: t.number, 6 | modified_at: t.string, 7 | }) 8 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import react from "@vitejs/plugin-react" 3 | import { defineConfig } from "vite" 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | "@": path.resolve(__dirname, "./src"), 10 | }, 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LLM Workbench 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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/store/index.ts: -------------------------------------------------------------------------------- 1 | import { Instance } from "mobx-state-tree" 2 | import { createContext, useContext } from "react" 3 | import { Store } from "./Store" 4 | 5 | export { Store } 6 | 7 | export const StoreContext = createContext | null>(null) 8 | 9 | export const useStore = () => { 10 | return useContext(StoreContext) 11 | } 12 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/store/Template.ts: -------------------------------------------------------------------------------- 1 | import { types as t } from "mobx-state-tree"; 2 | import { Instance } from "mobx-state-tree"; 3 | 4 | export const Template = t 5 | .model("Template", { 6 | id: t.identifier, 7 | content: t.optional(t.string, ""), 8 | }) 9 | .actions((self) => ({ 10 | update(props: Partial>) { 11 | Object.assign(self, props); 12 | }, 13 | })); 14 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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 |
18 | 19 |
20 | ) 21 | } 22 | } 23 | 24 | if (route === "playground") { 25 | return 26 | } 27 | 28 | return null 29 | }) 30 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/store/Message.ts: -------------------------------------------------------------------------------- 1 | import { getParent, getParentOfType, types as t } from "mobx-state-tree" 2 | import { Chat } from "./Chat" 3 | 4 | export const Message = t 5 | .model("Message", { 6 | role: t.optional(t.string, "user"), 7 | content: t.optional(t.string, ""), 8 | date: t.optional(t.Date, () => new Date()), 9 | open: t.optional(t.boolean, false), 10 | }) 11 | .views((self) => ({ 12 | get isEmpty() { 13 | return !self.content 14 | }, 15 | get chat() { 16 | return getParentOfType(self, Chat) 17 | }, 18 | get agent() { 19 | return getParent(self, 2) 20 | }, 21 | })) 22 | .actions((self) => ({ 23 | update(props: Partial) { 24 | Object.assign(self, props) 25 | }, 26 | remove() { 27 | self.agent.remove(self) 28 | }, 29 | })) 30 | -------------------------------------------------------------------------------- /src/store/State.ts: -------------------------------------------------------------------------------- 1 | import { types as t, Instance, getType } from "mobx-state-tree" 2 | import { Chat } from "./Chat" 3 | import { Agent } from "./Agent" 4 | 5 | export const State = t 6 | .model("State", { 7 | route: t.maybeNull(t.string), 8 | resource: t.maybeNull(t.reference(t.union(Chat, Agent))), 9 | }) 10 | .actions((self) => ({ 11 | update(state: Partial>) { 12 | Object.assign(self, state) 13 | }, 14 | navigate(target: any) { 15 | if (typeof target === "string") { 16 | return this.update({ route: target, resource: null }) 17 | } 18 | 19 | switch (getType(target)) { 20 | case Chat: 21 | return this.update({ route: "chat", resource: target }) 22 | case Agent: 23 | return this.update({ route: "agent", resource: target }) 24 | } 25 | }, 26 | })) 27 | -------------------------------------------------------------------------------- /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 |