Skip to content

Commit

Permalink
fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
knoopx committed Oct 30, 2023
1 parent 0ac27f7 commit 4b3ad07
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 59 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

A one-stop-shop for all your LLM needs. Unleash the power of FOSS language models on your local machine.

# Usage

```bash
OLLAMA_ORIGINS="https://knoopx.github.io" ollama serve
```

or:

``````
Environment="OLLAMA_ORIGINS="https://knoopx.github.io"
echo "Environment=OLLAMA_ORIGINS=http://127.0.0.1:*,http://localhost:*,https://knoopx.github.io" >>/etc/systemd/system/ollama.service.d/environment.conf
``````

# Features

- UI
Expand Down
40 changes: 40 additions & 0 deletions src/app/AutoTextarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { cn } from "@/lib/utils"
import { Textarea } from "@/components/ui/textarea"
import { useRef, useState, useEffect } from "react"

export const AutoTextarea = ({
className,
onChange,
maxRows = 8,
...rest
}: {
className?: string
maxRows?: number
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
}) => {
const ref = useRef(null)
const [currentValue, setCurrentValue] = useState("") // you can manage data with it

useEffect(() => {
ref.current.style.height = "0px"
const scrollHeight = ref.current.scrollHeight
ref.current.style.height = `min(${
getComputedStyle(ref.current).lineHeight
} * ${maxRows + 1}, ${scrollHeight}px)`
}, [currentValue])

return (
<Textarea
ref={ref}
className={cn("resize-none", className)}
value={currentValue}
onChange={(e) => {
setCurrentValue(e.target.value)
onChange(e)
}}
{...rest}
/>
)
}

export default AutoTextarea
46 changes: 31 additions & 15 deletions src/app/ChatConversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@ import { useStore } from "@/store"
import { VscDebugContinue, VscDebugStart } from "react-icons/vsc"
import { BiArrowToTop } from "react-icons/bi"
import { Textarea } from "@/components/ui/textarea"
import { cn } from "@/lib/utils"
import { AutoTextarea } from "./AutoTextarea"

const PromptInput = observer(() => {
const { activeChat: chat } = useStore()
return (
<AutoTextarea
className="w-full"
value={chat.prompt}
onChange={(e) => chat.setPrompt(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
chat.userMessage(e.target.value)
chat.setPrompt("")
}
}}
autoFocus
placeholder="Enter text..."
/>
)
})

export const ChatConversation = observer(() => {
const { activeChat: chat } = useStore()
Expand All @@ -20,7 +42,14 @@ export const ChatConversation = observer(() => {
))}
</div>
</ScrollArea>
<div className="flex-none flex-col flex items-center space-x-4 space-y-2 p-8 min-h-0 mx-auto w-3/5">
<div
className={cn(
"flex-none flex-col flex items-center space-x-4 space-y-2 p-8 min-h-0 mx-auto w-3/5",
{
hidden: !chat.model,
},
)}
>
<div className="space-x-1">
<ToggleDarkButton />

Expand All @@ -40,20 +69,7 @@ export const ChatConversation = observer(() => {
<IoMdTrash size="1.5em" />
</Button>
</div>
<Textarea
className="w-full !min-h-[3em]"
value={chat.prompt}
onChange={(e) => chat.setPrompt(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
chat.userMessage(e.target.value)
chat.setPrompt("")
}
}}
autoFocus
placeholder="Enter text..."
/>
<PromptInput />
</div>
</div>
)
Expand Down
7 changes: 4 additions & 3 deletions src/app/ChatSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
AccordionItem,
AccordionTrigger,
} from "@radix-ui/react-accordion"
import AutoTextarea from "./AutoTextarea"

const ChatSettingsAccordionItem = observer(
({ id, icon: Icon, title, children }) => (
Expand Down Expand Up @@ -46,7 +47,7 @@ const SystemMessageAccordionItem = observer(() => {
>
<div className="flex flex-col space-y-2">
{/* <PresetSelector presets={} onSelect={chat.setSystemMessage} /> */}
<Textarea
<AutoTextarea
className="flex-auto h-36"
placeholder="System Message"
value={chat.system_message}
Expand All @@ -69,7 +70,7 @@ const UserMessageAccordionItem = observer(() => {
<div className="flex flex-col space-y-2">
{/* <PresetSelector onSelect={chat.setSystemMessage} /> */}

<Textarea
<AutoTextarea
className="flex-auto h-36"
placeholder="User Message"
value={chat.user_message}
Expand Down Expand Up @@ -119,7 +120,7 @@ const PromptTemplateAccordion = observer(() => {
presets={store.presets.chat}
onSelect={chat.setChatTemplate}
/>
<Textarea
<AutoTextarea
className="flex-auto font-mono text-xs whitespace-pre h-36"
placeholder="Chat Template"
value={chat.chat_template}
Expand Down
12 changes: 12 additions & 0 deletions src/app/CopyOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
showCopy?: boolean
<div className="relative">

{showCopy && (
<a
className="block absolute right-0 top-0 m-2 p-2 bg-neutral-600 rounded cursor-pointer"
onClick={() => navigator.clipboard.writeText(value)}
>
<IoMdCopy />
</a>
)}
</div>
57 changes: 50 additions & 7 deletions src/app/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,73 @@ import { useStore } from "@/store"
import { observer } from "mobx-react"
import { ChatList } from "./SidebarChatList"
import { Input } from "@/components/ui/input"
import { IoIosFlash, IoIosFlashOff } from "react-icons/io"
import { IoIosFlash } from "react-icons/io"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { MdEdit } from "react-icons/md"

const ConnectButton = observer(() => {
const store = useStore()

return (
<Button
size="sm"
onClick={() => {
store?.refreshModels()
}}
>
<IoIosFlash size="1.2em" />
</Button>
)
})

const DisconnectButton = observer(() => {
const store = useStore()

return (
<Button
size="sm"
variant="ghost"
onClick={() => {
store?.setConnected(false)
}}
>
<MdEdit size="1.2em" />
</Button>
)
})

export const Sidebar = observer(
({ className, chats }: { className?: string; chats: any[] }) => {
const store = useStore()

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
store?.refreshModels()
}

return (
<div className={cn("flex flex-col divide-y-1 border-r-1", className)}>
<form
onSubmit={handleSubmit}
className={cn("flex flex-col divide-y-1 border-r-1", className)}
>
<ChatList chats={chats} />
<div className="flex justify-between items-center space-x-2 px-3 py-2">
{store?.connected ? (
<Label>
<span className="text-muted-foreground">{store.endpoint}</span>
</Label>
) : (
<Input value={store.endpoint} onChange={store.setEndpoint} />
<Input
className="h-auto"
value={store.endpoint}
onChange={store.setEndpoint}
/>
)}
<Button size="sm">
{store?.connected ? <IoIosFlashOff /> : <IoIosFlash />}
</Button>

{store?.connected ? <DisconnectButton /> : <ConnectButton />}
</div>
</div>
</form>
)
},
)
4 changes: 2 additions & 2 deletions src/app/SidebarChatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const ChatList = observer(
<div className="px-3 py-2">
<h2 className="flex mb-2 text-lg font-semibold tracking-tight justify-between items-center">
<span>Chats</span>
<Button size="icon" variant="ghost" onClick={store.newChat}>
<Button size="icon" variant="ghost" onClick={() => store.addChat()}>
<IoMdAdd />
</Button>
</h2>
Expand All @@ -38,7 +38,7 @@ export const ChatList = observer(

<span className="whitespace-nowrap text-ellipsis truncate italic text-muted-foreground">
{chat.title}
s</span>
</span>
</span>
<a
className="ml-auto"
Expand Down
6 changes: 3 additions & 3 deletions src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
"flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
/>
)
}
},
)
Textarea.displayName = "Textarea"

Expand Down
2 changes: 0 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,3 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
onSnapshot(store, (snapshot) => {
window.localStorage.setItem("store", JSON.stringify(snapshot))
})

console.log("Initialized")
13 changes: 8 additions & 5 deletions src/store/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const Chat = t
.model("Chat", {
id: t.optional(t.identifier, randomId),
prompt: t.optional(t.string, ""),
model: t.string,
model: t.maybeNull(t.string),
chat_template: t.optional(t.string, Object.values(CHAT_TEMPLATES)[0]),
attachments: t.array(t.string),
system_message: t.optional(t.string, DEFAULT_SYSTEM_MESSAGE),
Expand Down Expand Up @@ -55,7 +55,12 @@ export const Chat = t
return this.assistantMessages[this.assistantMessages.length - 1]
},
get title() {
if (this.userMessages.length === 0) return `New Chat with ${self.model}`
if (this.userMessages.length === 0) {
if (self.model) {
return `New Chat with ${self.model}`
}
return `New Chat`
}
return this.lastMessage?.content.slice(0, 20)
},
renderTemplate(template: string, extra = {}) {
Expand Down Expand Up @@ -134,7 +139,7 @@ export const Chat = t
}
self.messages.pop()
},
userMessage(content) {
userMessage(content: string) {
if (!content) return

const message = {
Expand All @@ -148,8 +153,6 @@ export const Chat = t
this.respond()
},
async respond() {
console.log(self.template)

const { client } = getParent(self, 2)
const stream = client.completion(self.template, self.model, self.options)

Expand Down
21 changes: 7 additions & 14 deletions src/store/Store.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { types as t, Instance, destroy } from "mobx-state-tree"
import { Chat } from "./Chat"
import { OllamaClient } from "@/lib/OllamaClient"
import { reaction } from "mobx"
import { Model } from "./Model"
import { Presets } from "./Presets"

Expand All @@ -27,34 +26,28 @@ export const Store = t
setConnected(connected: boolean) {
self.connected = connected
},
addChat(chat: Instance<typeof Chat>) {
addChat(chat: Instance<typeof Chat> = {} as Instance<typeof Chat>) {
self.chats.push(chat)
self.activeChat = self.chats[self.chats.length - 1]
},
newChat() {
if (self.models.length === 0) return
this.addChat({
model: self.models[0].name,
})
},
afterCreate() {
reaction(() => self.endpoint, this.refreshModels)
this.refreshModels()
},
refreshModels() {
self.client
.models()
.catch((err) => {
console.error(err)
this.setConnected(false)
})

.then((models) => {
this.setModels(models)
if (self.chats.length === 0) {
this.newChat()
this.addChat()
}
this.setConnected(true)
})
.catch((err) => {
console.error(err)
this.setConnected(false)
})
},
setModels(values: typeof self.models) {
self.models = values
Expand Down

0 comments on commit 4b3ad07

Please sign in to comment.