diff --git a/src/app/(journal)/entry/page.tsx b/src/app/(journal)/entry/page.tsx index 1cfbdb2..cdaaf3a 100644 --- a/src/app/(journal)/entry/page.tsx +++ b/src/app/(journal)/entry/page.tsx @@ -1,25 +1,64 @@ "use client"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; -import { Sender } from "@/types"; +import { Sender, GeminiMessage } from "@/types"; import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { useState, useEffect, useRef } from "react"; const JournalPage = () => { const router = useRouter(); - const [text, setText] = useState(""); const [loading, setLoading] = useState(false); const [streaming, setStreaming] = useState(false); - const [responseText, setResponseText] = useState(""); + const [messages, setMessages] = useState([]); + const [responseText, setResponseText] = useState(""); // State for the response text + const textareaRef = useRef(null); + const debounceTimeout = useRef(null); + + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.focus(); // Set focus to the textarea when the component mounts + } + }, []); const handleTextChange = (e: React.ChangeEvent) => { setText(e.target.value); + setResponseText(""); // Clear response text when user starts typing + + // Clear previous debounce timeout + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } + + // Set new debounce timeout + debounceTimeout.current = setTimeout(() => { + handleGenerate(); // Call the function to generate AI response after 3 seconds of inactivity + }, 3000); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Tab" && !loading && responseText) { + e.preventDefault(); + setText((prev) => prev + responseText); // Append the response text to the user's input + setResponseText(""); // Clear the response text after appending + } }; const handleGenerate = async () => { + if (text.trim() === "") return; + setLoading(true); - setResponseText(""); // Clear previous response + + // Trim the text to the last 400 characters or less + const trimmedText = text.slice(-400); + + const newMessage: GeminiMessage = { + role: "user", + parts: [{ text: trimmedText }], + }; + + const newMessages = [...messages, newMessage]; + setMessages(newMessages); try { const response = await fetch("/api/completion", { @@ -27,57 +66,55 @@ const JournalPage = () => { headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ text }), + body: JSON.stringify({ messages: newMessages }), }); if (!response.ok) { setLoading(false); - throw new Error("Failed to save the entry"); - } - - const data = response.body; - - if (!data) { - return; + throw new Error("Failed to generate the response"); } setLoading(false); setStreaming(true); - const reader = data.getReader(); + const reader = response.body?.getReader(); const decoder = new TextDecoder(); let done = false; - while (!done) { + let systemResponse = ""; + + while (!done && reader) { const { value, done: doneReading } = await reader.read(); done = doneReading; - const chunkValue = decoder.decode(value, { stream: true }); - setResponseText((prev) => prev + chunkValue); + systemResponse += decoder.decode(value, { stream: true }); } + setResponseText(systemResponse); // Update responseText with the bot's response + + const botMessage: GeminiMessage = { + role: "model", + parts: [{ text: systemResponse }], + }; + + setMessages((prev) => [...prev, botMessage]); setStreaming(false); } catch (error) { - console.error("Failed to save the entry:", error); + console.error("Failed to generate the response:", error); + setLoading(false); } }; const handleSave = async () => { - if (text === "") { - console.error("Text is empty"); + if (text === "" && messages.length === 0) { + console.error("No content to save"); return; } setLoading(true); - const contents = [ - { - sender: Sender.USER, - content: text, - }, - { - sender: Sender.BOT, - content: responseText, - }, - ]; + const contents = messages.map((message) => ({ + sender: message.role === "user" ? Sender.USER : Sender.MODEL, + content: message.parts.map((part) => part.text).join(" "), + })); try { const response = await fetch("/api/journal", { @@ -92,43 +129,45 @@ const JournalPage = () => { setLoading(false); throw new Error("Failed to save the entry"); } - setLoading(false); + setLoading(false); router.push("/journals"); } catch (error) { console.error("Failed to save the entry:", error); + setLoading(false); } }; return ( -
-
+
+
-

Entry

+

Entry

- - {streaming && ( -
Streaming response...
- )} - {responseText && ( -
{responseText}
- )} -
- -