2023-06-23 18:04:39 +00:00
|
|
|
import { useState } from 'react'
|
2023-06-27 16:35:51 +00:00
|
|
|
import path from 'path'
|
|
|
|
import os from 'os'
|
|
|
|
import { dialog, getCurrentWindow } from '@electron/remote'
|
2023-06-23 18:04:39 +00:00
|
|
|
|
2023-06-27 19:32:10 +00:00
|
|
|
const API_URL = 'http://127.0.0.1:7734'
|
2023-06-23 18:04:39 +00:00
|
|
|
|
|
|
|
type Message = {
|
2023-06-27 16:35:51 +00:00
|
|
|
sender: 'bot' | 'human'
|
2023-06-23 18:04:39 +00:00
|
|
|
content: string
|
|
|
|
}
|
|
|
|
|
2023-06-27 16:35:51 +00:00
|
|
|
const userInfo = os.userInfo()
|
|
|
|
|
|
|
|
async function generate(prompt: string, model: string, callback: (res: string) => void) {
|
2023-06-25 00:58:33 +00:00
|
|
|
const result = await fetch(`${API_URL}/generate`, {
|
2023-06-23 18:04:39 +00:00
|
|
|
method: 'POST',
|
2023-06-25 00:58:33 +00:00
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
},
|
2023-06-23 18:04:39 +00:00
|
|
|
body: JSON.stringify({
|
2023-06-27 16:35:51 +00:00
|
|
|
prompt,
|
|
|
|
model,
|
2023-06-23 18:04:39 +00:00
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
2023-06-27 16:35:51 +00:00
|
|
|
if (!result.ok) {
|
2023-06-23 18:04:39 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let reader = result.body.getReader()
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const { done, value } = await reader.read()
|
|
|
|
|
|
|
|
if (done) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2023-06-23 19:04:38 +00:00
|
|
|
let decoder = new TextDecoder()
|
|
|
|
let str = decoder.decode(value)
|
2023-06-27 21:39:26 +00:00
|
|
|
|
|
|
|
let re = /}\s*{/g
|
2023-06-25 00:58:33 +00:00
|
|
|
str = '[' + str.replace(re, '},{') + ']'
|
|
|
|
let messages = JSON.parse(str)
|
|
|
|
|
|
|
|
for (const message of messages) {
|
|
|
|
const choice = message.choices[0]
|
2023-06-28 02:08:53 +00:00
|
|
|
|
|
|
|
callback(choice.text)
|
|
|
|
|
2023-06-25 00:58:33 +00:00
|
|
|
if (choice.finish_reason === 'stop') {
|
2023-06-23 18:04:39 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2023-06-22 16:45:31 +00:00
|
|
|
|
2023-06-23 19:04:38 +00:00
|
|
|
export default function () {
|
2023-06-23 18:04:39 +00:00
|
|
|
const [prompt, setPrompt] = useState('')
|
|
|
|
const [messages, setMessages] = useState<Message[]>([])
|
2023-06-27 16:35:51 +00:00
|
|
|
const [model, setModel] = useState('')
|
|
|
|
const [generating, setGenerating] = useState(false)
|
2023-06-23 18:04:39 +00:00
|
|
|
|
2023-06-22 16:45:31 +00:00
|
|
|
return (
|
2023-06-23 19:04:38 +00:00
|
|
|
<div className='flex min-h-screen flex-1 flex-col justify-between bg-white'>
|
2023-06-27 16:35:51 +00:00
|
|
|
<header className='drag sticky top-0 z-50 flex h-14 w-full flex-row items-center border-b border-black/10 bg-white/75 backdrop-blur-md'>
|
2023-06-23 18:04:39 +00:00
|
|
|
<div className='mx-auto w-full max-w-xl leading-none'>
|
2023-06-27 16:35:51 +00:00
|
|
|
<h1 className='text-sm font-medium'>{path.basename(model).replace('.bin', '')}</h1>
|
2023-06-23 18:04:39 +00:00
|
|
|
</div>
|
|
|
|
</header>
|
2023-06-27 16:35:51 +00:00
|
|
|
{model ? (
|
|
|
|
<section className='mx-auto mb-10 w-full max-w-xl flex-1 break-words'>
|
|
|
|
{messages.map((m, i) => (
|
|
|
|
<div className='my-4 flex gap-4' key={i}>
|
|
|
|
<div className='flex-none pr-1 text-lg'>
|
|
|
|
{m.sender === 'human' ? (
|
|
|
|
<div className='mt-px flex h-6 w-6 items-center justify-center rounded-md bg-neutral-200 text-sm text-neutral-700'>
|
|
|
|
{userInfo.username[0].toUpperCase()}
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<div className='mt-0.5 flex h-6 w-6 items-center justify-center rounded-md bg-blue-600 text-sm text-white'>
|
|
|
|
{path.basename(model)[0].toUpperCase()}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
<div className='flex-1 text-gray-800'>
|
|
|
|
{m.content}
|
2023-06-27 20:16:14 +00:00
|
|
|
{m.sender === 'bot' && generating && i === messages.length - 1 && (
|
2023-06-27 16:35:51 +00:00
|
|
|
<span className='blink relative -top-[3px] left-1 text-[10px]'>█</span>
|
|
|
|
)}
|
|
|
|
</div>
|
2023-06-23 18:04:39 +00:00
|
|
|
</div>
|
2023-06-27 16:35:51 +00:00
|
|
|
))}
|
|
|
|
</section>
|
|
|
|
) : (
|
|
|
|
<section className='flex flex-1 select-none flex-col items-center justify-center pb-20'>
|
|
|
|
<h2 className='text-3xl font-light text-neutral-400'>No model selected</h2>
|
|
|
|
<button
|
|
|
|
onClick={async () => {
|
|
|
|
const res = await dialog.showOpenDialog(getCurrentWindow(), {
|
|
|
|
properties: ['openFile', 'multiSelections'],
|
2023-06-23 18:04:39 +00:00
|
|
|
})
|
2023-06-27 16:35:51 +00:00
|
|
|
if (res.canceled) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
setModel(res.filePaths[0])
|
|
|
|
}}
|
|
|
|
className='rounded-dm my-8 rounded-md bg-blue-600 px-4 py-2 text-sm text-white hover:brightness-110'
|
|
|
|
>
|
|
|
|
Open file...
|
|
|
|
</button>
|
|
|
|
</section>
|
|
|
|
)}
|
|
|
|
<div className='sticky bottom-0 bg-gradient-to-b from-transparent to-white'>
|
|
|
|
{model && (
|
|
|
|
<textarea
|
|
|
|
autoFocus
|
|
|
|
rows={1}
|
|
|
|
value={prompt}
|
|
|
|
placeholder='Send a message...'
|
|
|
|
onChange={e => setPrompt(e.target.value)}
|
|
|
|
className='mx-auto my-4 block w-full max-w-xl resize-none rounded-xl border border-gray-200 px-5 py-3.5 text-[15px] shadow-lg shadow-black/5 focus:outline-none'
|
|
|
|
onKeyDownCapture={async e => {
|
|
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
|
|
if (generating) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!prompt) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
await setMessages(messages => {
|
|
|
|
return [...messages, { sender: 'human', content: prompt }, { sender: 'bot', content: '' }]
|
|
|
|
})
|
2023-06-23 18:04:39 +00:00
|
|
|
|
2023-06-27 16:35:51 +00:00
|
|
|
setPrompt('')
|
2023-06-23 18:04:39 +00:00
|
|
|
|
2023-06-27 16:35:51 +00:00
|
|
|
setGenerating(true)
|
|
|
|
await generate(prompt, model, res => {
|
|
|
|
setMessages(messages => {
|
|
|
|
let last = messages[messages.length - 1]
|
|
|
|
return [...messages.slice(0, messages.length - 1), { ...last, content: last.content + res }]
|
|
|
|
})
|
2023-06-23 18:04:39 +00:00
|
|
|
})
|
2023-06-27 16:35:51 +00:00
|
|
|
setGenerating(false)
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
></textarea>
|
|
|
|
)}
|
2023-06-23 18:04:39 +00:00
|
|
|
</div>
|
2023-06-22 16:45:31 +00:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|