| 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 |
|
|