Spaces:
Runtime error
Runtime error
| import { useEffect, useRef, useState } from "react"; | |
| import { FileUpload } from "./FileUpload"; | |
| function Message({ message, role }: Message) { | |
| return ( | |
| <pre | |
| className={`p-4 mb-4 rounded-3xl shadow-md whitespace-pre-wrap ${ | |
| role === "user" | |
| ? "bg-blue-200 text-blue-800 ml-20 self-end" | |
| : "bg-green-200 text-green-800 mr-20 self-start" | |
| }`} | |
| > | |
| {message} | |
| </pre> | |
| ); | |
| } | |
| interface Message { | |
| message: string; | |
| role: "user" | "assistant"; | |
| } | |
| function App() { | |
| const scrollUpRef = useRef(false); | |
| const [result, setResult] = useState(""); | |
| const [error, setError] = useState(""); | |
| const [isStreaming, setIsStreaming] = useState(false); | |
| const [messages, setMessages] = useState<Message[]>([]); | |
| const [isFileUploaded, setIsFileUploaded] = useState(false); | |
| useEffect(() => { | |
| function handleWheel(event: WheelEvent) { | |
| if (isStreaming && event.deltaY < 0) { | |
| scrollUpRef.current = true; | |
| } | |
| } | |
| document.body.addEventListener("wheel", handleWheel); | |
| return () => { | |
| document.body.removeEventListener("wheel", handleWheel); | |
| }; | |
| }, [isStreaming]); | |
| const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { | |
| if (e.key === "Enter" && !e.shiftKey) { | |
| e.preventDefault(); | |
| e.currentTarget.form?.requestSubmit(); | |
| } | |
| }; | |
| async function onSubmit(e: React.FormEvent<HTMLFormElement>) { | |
| e.preventDefault(); | |
| const form = e.currentTarget; | |
| const formData = new FormData(form); | |
| const msg = formData.get("msg") as string; | |
| const scrollingElement = document.scrollingElement; | |
| if (msg.trim() === "") return; | |
| scrollUpRef.current = false; | |
| scrollingElement?.scrollTo({ | |
| behavior: "smooth", | |
| top: scrollingElement?.scrollHeight, | |
| }); | |
| setIsStreaming(true); | |
| setMessages((prev) => [ | |
| ...prev, | |
| { | |
| message: msg, | |
| role: "user", | |
| }, | |
| ]); | |
| try { | |
| const session_id = sessionStorage.getItem("session_id"); | |
| const response = await fetch(form.action, { | |
| method: "POST", | |
| headers: session_id | |
| ? { | |
| "X-Session-ID": session_id, | |
| } | |
| : undefined, | |
| body: msg, | |
| }); | |
| let result = ""; | |
| const reader = response.body?.getReader(); | |
| const decoder = new TextDecoder(); | |
| form.msg.value = ""; | |
| await reader?.read().then(function processText(msg): Promise<void> { | |
| const { value, done } = msg; | |
| const chunk = decoder.decode(value, { stream: true }); | |
| const scrollingElement = document.scrollingElement; | |
| if (done) { | |
| setResult(""); | |
| setMessages((prev) => [ | |
| ...prev, | |
| { | |
| message: result, | |
| role: "assistant", | |
| }, | |
| ]); | |
| if (scrollingElement) { | |
| scrollingElement.scrollTop = scrollingElement?.scrollHeight; | |
| } | |
| return Promise.resolve(); | |
| } | |
| result += chunk; | |
| setResult(result); | |
| if (!scrollUpRef.current && scrollingElement) { | |
| scrollingElement.scrollTop = scrollingElement.scrollHeight; | |
| } | |
| // Read some more, and call this function again | |
| return reader.read().then(processText); | |
| }); | |
| } catch (error) { | |
| setError("Error submitting message"); | |
| console.error("Error submitting message", error); | |
| } finally { | |
| setIsStreaming(false); | |
| } | |
| } | |
| return ( | |
| <div className="max-w-prose flex flex-col items-center justify-end min-h-screen pb-4 mx-auto"> | |
| {!isFileUploaded && ( | |
| <div className="w-full min-h-screen flex flex-col items-center justify-center"> | |
| <h1 className="text-5xl font-bold mb-2">DocTalk</h1> | |
| <p className="text-xl text-gray-500 mb-10 text-center"> | |
| Talk to your documents. | |
| </p> | |
| <FileUpload | |
| isFileUploaded={isFileUploaded} | |
| setIsFileUploaded={setIsFileUploaded} | |
| /> | |
| <div className="text-xs text-center text-gray-500 mt-4"> | |
| Built with{" "} | |
| <a | |
| target="_blank" | |
| href="https://openai.com/" | |
| rel="noopener noreferrer" | |
| className="underline text-blue-500" | |
| > | |
| OpenAI | |
| </a> | |
| ,{" "} | |
| <a | |
| target="_blank" | |
| href="https://fastapi.tiangolo.com/" | |
| rel="noopener noreferrer" | |
| className="underline text-blue-500" | |
| > | |
| FastAPI | |
| </a> | |
| ,{" "} | |
| <a | |
| target="_blank" | |
| href="https://qdrant.tech/" | |
| rel="noopener noreferrer" | |
| className="underline text-blue-500" | |
| > | |
| Qdrant | |
| </a> | |
| ,{" "} | |
| <a | |
| target="_blank" | |
| href="https://react.dev/" | |
| rel="noopener noreferrer" | |
| className="underline text-blue-500" | |
| > | |
| React | |
| </a> | |
| ,{" "} | |
| <a | |
| target="_blank" | |
| href="https://vitejs.dev/" | |
| rel="noopener noreferrer" | |
| className="underline text-blue-500" | |
| > | |
| Vite | |
| </a> | |
| , and{" "} | |
| <a | |
| target="_blank" | |
| href="https://tailwindcss.com/" | |
| rel="noopener noreferrer" | |
| className="underline text-blue-500" | |
| > | |
| TailwindCSS | |
| </a> | |
| </div> | |
| </div> | |
| )} | |
| <div className="w-full flex flex-col justify-end"> | |
| {messages.map(({ message, role }) => ( | |
| <Message message={message} role={role} key={`${message}-${role}`} /> | |
| ))} | |
| {result && <Message message={result} role="assistant" />} | |
| {error && <p className="text-red-500 text-center mb-4">{error}</p>} | |
| </div> | |
| {isFileUploaded && ( | |
| <form | |
| method="post" | |
| action="/api/chat" | |
| onSubmit={onSubmit} | |
| className="relative w-full" | |
| > | |
| <textarea | |
| id="msg" | |
| name="msg" | |
| rows={3} | |
| onKeyDown={onKeyDown} | |
| disabled={isStreaming} | |
| placeholder="Ask me anything about the document's contents..." | |
| className="block bg-white w-full p-4 pr-20 m-0 rounded-3xl shadow-md text-xl disabled:opacity-50 disabled:cursor-not-allowed resize-none" | |
| ></textarea> | |
| <button | |
| type="submit" | |
| disabled={isStreaming} | |
| aria-label="Send message" | |
| className="absolute right-0 bottom-0 bg-blue-500 text-white p-2 m-2 rounded-full shadow-md hover:bg-blue-600 focus:bg-blue-600 cursor-pointer transition-colors duration-300 disabled:opacity-50 disabled:cursor-not-allowed" | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| viewBox="0 0 24 24" | |
| fill="currentColor" | |
| className="w-6 h-6" | |
| > | |
| <path d="M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z" /> | |
| </svg> | |
| </button> | |
| </form> | |
| )} | |
| </div> | |
| ); | |
| } | |
| export default App; | |