├── .gitignore ├── README.md ├── app ├── components │ ├── chat.tsx │ ├── layouts │ │ └── dashboard.tsx │ └── ui │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── dropdown-menu.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── pagination.tsx │ │ ├── progress.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── slider.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ └── tooltip.tsx ├── entry.server.tsx ├── globals.css ├── lib │ ├── chat.server.ts │ ├── chat.ts │ ├── db.server.ts │ ├── settings.server.ts │ ├── utils.server.ts │ └── utils.ts ├── root.tsx └── routes │ ├── _index.tsx │ ├── chat.($chatId)._index.tsx │ ├── settings._index.tsx │ ├── settings.ollama.tsx │ └── settings.tsx ├── components.json ├── drizzle.config.ts ├── migrations ├── 0000_brainy_dorian_gray.sql └── meta │ ├── 0000_snapshot.json │ └── _journal.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.cjs ├── public └── favicon.ico ├── startup.cjs ├── tailwind.config.cjs ├── tsconfig.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | node_modules 4 | *.sqlite 5 | vite.config.ts.timestamp* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Local Llama 2 | 3 | A local frontend for [Ollama](https://ollama.com/) build on [Remix](https://remix.run/). Use it as is or as a starting point for your own project. 4 | 5 | ## Getting Started 6 | 7 | **Install the application globally** 8 | 9 | ```bash 10 | npm i -g localllama 11 | ``` 12 | 13 | **Run the application** 14 | 15 | ```bash 16 | npx localllama 17 | ``` 18 | 19 | ## Development 20 | 21 | **Clone the repository** 22 | 23 | ```bash 24 | git clone https://github.com/jacob-ebey/localllama.git 25 | ``` 26 | 27 | **Install dependencies** 28 | 29 | ```bash 30 | pnpm i 31 | ``` 32 | 33 | **Run in development mode** 34 | 35 | ```bash 36 | pnpm dev 37 | ``` 38 | -------------------------------------------------------------------------------- /app/components/chat.tsx: -------------------------------------------------------------------------------- 1 | import Markdown from "react-markdown"; 2 | 3 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 4 | import { cn } from "@/lib/utils"; 5 | 6 | export function ChatMessage({ 7 | children, 8 | from, 9 | pending, 10 | }: { 11 | children: string; 12 | from: "assistant" | "user"; 13 | pending?: boolean; 14 | }) { 15 | return ( 16 | 17 | 18 | {from} 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /app/components/layouts/dashboard.tsx: -------------------------------------------------------------------------------- 1 | import { Link, NavLink, useLocation, useMatches } from "@remix-run/react"; 2 | import { 3 | Bot, 4 | Ellipsis, 5 | Home, 6 | Menu, 7 | MessageSquare, 8 | Settings, 9 | } from "lucide-react"; 10 | 11 | import { 12 | Breadcrumb, 13 | BreadcrumbItem, 14 | BreadcrumbLink, 15 | BreadcrumbList, 16 | BreadcrumbPage, 17 | BreadcrumbSeparator, 18 | } from "@/components/ui/breadcrumb"; 19 | import { Button } from "@/components/ui/button"; 20 | import { 21 | DropdownMenu, 22 | DropdownMenuContent, 23 | DropdownMenuItem, 24 | DropdownMenuTrigger, 25 | } from "@/components/ui/dropdown-menu"; 26 | import { 27 | Sheet, 28 | SheetContent, 29 | SheetDescription, 30 | SheetTitle, 31 | SheetTrigger, 32 | } from "@/components/ui/sheet"; 33 | import { 34 | Tooltip, 35 | TooltipContent, 36 | TooltipProvider, 37 | TooltipTrigger, 38 | } from "@/components/ui/tooltip"; 39 | import { Fragment } from "react/jsx-runtime"; 40 | 41 | export type BreadCrumb = 42 | | string 43 | | { name: string; to: string } 44 | | ((data: unknown) => string | { name: string; to: string }); 45 | 46 | export function DashboardLayout({ children }: { children: React.ReactNode }) { 47 | const location = useLocation(); 48 | const matches = useMatches(); 49 | 50 | const leafMatch = matches.slice(-1)[0]; 51 | const breadcrumbs = 52 | (leafMatch?.handle as { breadcrumbs?: BreadCrumb[] })?.breadcrumbs ?? []; 53 | const breadcrumbsToRender = breadcrumbs.map((breadcrumb, i) => { 54 | if (typeof breadcrumb === "function") { 55 | breadcrumb = breadcrumb(leafMatch.data); 56 | } 57 | const separator = i === 0 ? null : ; 58 | if (typeof breadcrumb === "string") { 59 | return ( 60 | 61 | {separator} 62 | 63 | {breadcrumb} 64 | 65 | 66 | ); 67 | } 68 | return ( 69 | 70 | {separator} 71 | 72 | 73 | {breadcrumb.name} 74 | 75 | 76 | 77 | ); 78 | }); 79 | 80 | return ( 81 |
82 | 83 | 133 | 134 |
135 |
136 | 137 | 138 | 142 | 143 | 144 | Main navigation 145 | 146 | Access all the main features. 147 | 148 | 172 | 173 | 174 | 175 | {breadcrumbsToRender} 176 | 177 |
178 | {/* 179 | */} 184 |
185 | 186 | 187 | 190 | 191 | 192 | 193 | Global Settings 194 | 195 | 196 | 197 |
198 | {children} 199 |
200 |
201 | ); 202 | } 203 | -------------------------------------------------------------------------------- /app/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-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /app/components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons" 3 | import { Slot } from "@radix-ui/react-slot" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Breadcrumb = React.forwardRef< 8 | HTMLElement, 9 | React.ComponentPropsWithoutRef<"nav"> & { 10 | separator?: React.ReactNode 11 | } 12 | >(({ ...props }, ref) =>