Skip to content

Commit

Permalink
Merge pull request #12 from tapas-pasta-tapas/zj
Browse files Browse the repository at this point in the history
feat: add completions effect
  • Loading branch information
leezhengjing authored Aug 11, 2024
2 parents 21a1b97 + 228622a commit 04f2d0d
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 65 deletions.
147 changes: 93 additions & 54 deletions src/app/(journal)/entry/page.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,120 @@
"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<boolean>(false);
const [streaming, setStreaming] = useState<boolean>(false);
const [responseText, setResponseText] = useState<string>("");
const [messages, setMessages] = useState<GeminiMessage[]>([]);
const [responseText, setResponseText] = useState<string>(""); // State for the response text
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
const debounceTimeout = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
if (textareaRef.current) {
textareaRef.current.focus(); // Set focus to the textarea when the component mounts
}
}, []);

const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
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<HTMLTextAreaElement>) => {
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", {
method: "POST",
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", {
Expand All @@ -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 (
<div className="min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]">
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-8">
<div className="min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr] relative">
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6 relative">
<div className="flex items-center">
<h1 className="h1">Entry</h1>
<h1 className="text-lg font-semibold md:text-2xl">Entry</h1>
</div>
<Textarea value={text} onChange={handleTextChange}></Textarea>
{streaming && (
<div className="mt-4 text-gray-600">Streaming response...</div>
)}
{responseText && (
<div className="mt-4 rounded-md bg-gray-100 p-4">{responseText}</div>
)}
<div className="flex items-center space-x-2">
<Button
className="flex flex-grow"
onClick={handleGenerate}
disabled={loading || streaming}
>
{loading ? "Generating..." : "Generate"}
</Button>
<Button
className="flex flex-grow bg-blue-500"
onClick={handleSave}
disabled={loading}

<div className="relative">
<Textarea
ref={textareaRef}
value={text}
onChange={handleTextChange}
onKeyDown={handleKeyDown}
className="relative z-10 bg-transparent min-h-96 resize-none border-transparent border-none outline-none"
style={{ color: "black" }}
/>
<div
className="absolute text-md top-2 left-0 pointer-events-none z-0 whitespace-pre-wrap"
style={{ color: "transparent" }}
>
{text}
{loading && <span className="text-gray-400"></span>}
<span className="text-gray-400">{responseText}</span> {/* Display the response text */}
</div>
</div>
{/* <div className="flex justify-end">
<Button className="w-full md:w-auto" onClick={handleSave} disabled={loading}>
{loading ? "Saving..." : "Save"}
</Button>
</div>
</div> */}
</main>
</div>
);
Expand Down
32 changes: 24 additions & 8 deletions src/app/api/completion/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Content } from "@google/generative-ai";
import { GeminiMessage } from "@/types";

export async function POST(request: Request) {
try {
const { VertexAI } = require("@google-cloud/vertexai");
Expand All @@ -8,8 +11,8 @@ export async function POST(request: Request) {
const model = "gemini-1.5-flash-001";

const json = await request.json();
const { text } = json as {
text: string;
const { messages } = json as {
messages: Content[];
};

const sysInstruction = `
Expand All @@ -29,7 +32,13 @@ Behavior Guidelines:
3. Encouraging Depth and Insight
- Ask open-ended, reflective questions that encourage deeper thinking.
- Help the user explore their feelings, thoughts, and experiences further.`;
- Help the user explore their feelings, thoughts, and experiences further.
4. Avoid repetition.
- Do not repeat your own questions or responses, by checking against your previous messages.
- Do not repeat the user's input verbatim, and do not give redundant suggestions.
- Focus on adding value to the conversation by bringing new perspectives or deepening the reflection.`;


// const vertexAIRetrievalTool = {
// retrieval: {
Expand Down Expand Up @@ -80,11 +89,18 @@ Behavior Guidelines:
// });

// const response = await chat.sendMessageStream(lastMessage.parts);
console.log(text);
const req = {
contents: [{ role: "user", parts: [{ text: text }] }],
};
const response = await generativeModel.generateContentStream(req);
// console.log(text);
// const req = {
// contents: [{ role: "user", parts: [{ text: text }] }],
// };
// const response = await generativeModel.generateContentStream(req);
const lastMessage = messages.pop() as GeminiMessage;

const chat = generativeModel.startChat({
history: messages,
});

const response = await chat.sendMessageStream(lastMessage.parts);
const encoder = new TextEncoder();
const readableStream = new ReadableStream({
async start(controller) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import * as React from "react";
import { cn } from "@/lib/utils";

export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { }

const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"flex min-h-[80px] w-full rounded-md bg-background py-2 text-md ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
Expand Down
2 changes: 1 addition & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type User = {

export enum Sender {
USER = "USER",
BOT = "BOT",
MODEL = "MODEL",
}

// Request Objects **************************************************************
Expand Down
7 changes: 7 additions & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ const config = {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
spacing: {
'0.1': '0.025rem', // Add a custom spacing value
'0.2': '0.05rem', // Add a custom spacing value
'2.3': '0.575rem', // Add a custom spacing value
'2.6': '0.65rem', // Add a custom spacing value
'3.1': '0.775rem', // Add a custom spacing value
},
},
},
plugins: [require("tailwindcss-animate")],
Expand Down

0 comments on commit 04f2d0d

Please sign in to comment.