|
import { useState } from 'react' |
|
import './App.css' |
|
import Dropdown from 'react-dropdown'; |
|
import 'react-dropdown/style.css'; |
|
|
|
function App() { |
|
const [prompt, setPrompt] = useState('') |
|
const [isLoading, setIsLoading] = useState(false) |
|
const [modelType,setModelType] = useState("pre") |
|
const [chat, setChat] = useState([]) |
|
|
|
|
|
const parseTextWithCodeBlocks = (text) => { |
|
const codeBlockRegex = /```(\w+)?\s*([\s\S]+?)\s*```/g; |
|
const parts = []; |
|
let lastIndex = 0; |
|
let match; |
|
|
|
while ((match = codeBlockRegex.exec(text)) !== null) { |
|
|
|
if (match.index > lastIndex) { |
|
parts.push({ |
|
type: 'text', |
|
content: text.slice(lastIndex, match.index) |
|
}); |
|
} |
|
|
|
|
|
parts.push({ |
|
type: 'code', |
|
language: match[1] || '', |
|
content: match[2] |
|
}); |
|
|
|
lastIndex = match.index + match[0].length; |
|
} |
|
|
|
|
|
if (lastIndex < text.length) { |
|
parts.push({ |
|
type: 'text', |
|
content: text.slice(lastIndex) |
|
}); |
|
} |
|
|
|
return parts; |
|
} |
|
|
|
const handleSubmit = async () => { |
|
chat.push({ src: "USER", "text": prompt }) |
|
setIsLoading(true) |
|
|
|
let system_prompt = " ### " |
|
|
|
const res = await fetch('http://127.0.0.1:8000/chat', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ prompt:modelType === "fine" ? prompt + system_prompt : prompt }), |
|
}) |
|
|
|
const reader = res.body.getReader() |
|
const decoder = new TextDecoder('utf-8') |
|
|
|
while (true) { |
|
const { value, done } = await reader.read() |
|
if (done) break |
|
const chunk = decoder.decode(value) |
|
if (chat[chat.length - 1].src === "AI") { |
|
chat[chat.length - 1].text += chunk |
|
} else { |
|
chat.push({ src: "AI", text: chunk }) |
|
chat[chat.length - 1].text = chunk |
|
} |
|
setChat([...chat]) |
|
} |
|
setIsLoading(false) |
|
setPrompt("") |
|
} |
|
|
|
return ( |
|
<div className='w-full flex-col gap-6 h-screen bg-white flex justify-center items-center p-4'> |
|
<header className='fixed top-0 left-0 p-4 flex justify-between'> |
|
<div className='flex gap-4'> |
|
<svg onClick={() => setChat([])} xmlns="http://www.w3.org/2000/svg" width="26" height="26" fill="currentColor" class="bi text-gray-700 bi-pencil-square" viewBox="0 0 16 16"> |
|
<path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z" /> |
|
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5z" /> |
|
</svg> |
|
<select value={modelType} onChange={(e)=>setModelType(e.target.value)} className='font-semibold focus:outline-none focus:border-0 text-lg text-gray-700'> |
|
<option value={"pre"} selected>pre trained</option> |
|
<option value={"fine"} selected>fine tuned</option> |
|
</select> |
|
</div> |
|
</header> |
|
{chat.length === 0 && <h1 className='text-3xl font-bold'>What can I Help With?</h1>} |
|
{chat.length !== 0 && |
|
<div |
|
className='w-screen h-[calc(100vh-6rem)] items-center justify-start flex flex-col pt-4 overflow-y-scroll' |
|
> |
|
{ |
|
<div className='h-full w-[44rem] flex flex-col gap-3'> |
|
{ |
|
chat.map((msg, index) => ( |
|
<div key={index} className={`${msg.src === "USER" ? 'justify-end' : 'justify-start'} flex`}> |
|
<div className={`${msg.src === "USER" ? 'bg-gray-200 font-mono rounded-[2rem] w-fit px-4 py-2' : ''} p-4`}> |
|
{msg.src === "AI" ? ( |
|
<div className="whitespace-pre-wrap"> |
|
{parseTextWithCodeBlocks(msg.text).map((part, i) => ( |
|
part.type === 'code' ? ( |
|
<div key={i} className="my-2"> |
|
<div className="bg-gray-800 text-gray-100 px-2 py-1 text-sm rounded-t-md"> |
|
{part.language || 'code'} |
|
</div> |
|
<pre className="bg-gray-100 p-3 rounded-b-md overflow-x-auto text-sm"> |
|
<code>{part.content}</code> |
|
</pre> |
|
</div> |
|
) : ( |
|
<span key={i}>{part.content}</span> |
|
) |
|
))} |
|
</div> |
|
) : ( |
|
<pre>{msg.text}</pre> |
|
)} |
|
</div> |
|
</div> |
|
)) |
|
} |
|
</div> |
|
} |
|
</div> |
|
} |
|
<div className='w-[50rem] p-4 h-fit shadow-[-2px_4px_6px_0px_rgba(0,_0,_0,_0.1)] rounded-[2rem] border border-gray-300'> |
|
<textarea onKeyDown={(e)=>e.key === "Enter" && handleSubmit()} |
|
placeholder='Ask anything...' |
|
className='max-h-[12rem] min-h-[2rem] w-full focus:outline-none' |
|
value={prompt} |
|
onChange={e => setPrompt(e.target.value)} |
|
></textarea> |
|
|
|
<div className='w-full flex justify-end mt-2'> |
|
{isLoading |
|
? |
|
<div className='cursor-pointer rounded-full w-[34px] h-[34px] flex justify-center items-center p-2 bg-black'> |
|
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="bi bi-square-fill text-white" viewBox="0 0 16 16"> |
|
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2z" /> |
|
</svg> |
|
</div> |
|
: |
|
<svg onClick={handleSubmit} xmlns="http://www.w3.org/2000/svg" width="34" height="34" fill="currentColor" className="cursor-pointer bi bi-arrow-up-circle-fill" viewBox="0 0 16 16"> |
|
<path d="M16 8A8 8 0 1 0 0 8a8 8 0 0 0 16 0m-7.5 3.5a.5.5 0 0 1-1 0V5.707L5.354 7.854a.5.5 0 1 1-.708-.708l3-3a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 5.707z" /> |
|
</svg>} |
|
</div> |
|
</div> |
|
</div> |
|
) |
|
} |
|
|
|
export default App |
|
|