Skip to content

Commit 802442a

Browse files
committed
feat: add chat functionality with Google GenAI integration
1 parent c4d18c0 commit 802442a

File tree

5 files changed

+114
-0
lines changed

5 files changed

+114
-0
lines changed

src/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default [
2424
]),
2525
route("/not-found", "./routes/not-found/index.tsx"),
2626
route("/verify-email", "./routes/verify-email/index.tsx"),
27+
route("/chat", "./routes/chat/index.tsx"),
2728
],
2829
},
2930
] satisfies RouteConfig;

src/routes/chat/index.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { sendMessage } from "@/services/chat.service";
2+
3+
import type { Route } from "../root/+types";
4+
5+
export async function action({ request }: Route.ActionArgs) {
6+
const { message, sessionid } = await request.json();
7+
8+
if (typeof message !== "string" || !message.trim()) {
9+
throw new Error("Message cannot be empty");
10+
}
11+
12+
const response = await sendMessage(message, sessionid);
13+
return { message: response };
14+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { useEffect, useState } from "react";
2+
3+
import { Button, Input } from "@/components/ui";
4+
5+
export function ChatBot() {
6+
const [messages, setMessages] = useState<string[]>([
7+
"🤖: Hola! En que puedo ayudarte?",
8+
]);
9+
const [input, setInput] = useState("");
10+
const [sessionid, setSessionId] = useState<null | string>(null);
11+
const [loading, setLoading] = useState(false);
12+
13+
useEffect(() => {
14+
const currentSessionId = sessionStorage.getItem("sessionid");
15+
if (currentSessionId) {
16+
setSessionId(currentSessionId);
17+
} else {
18+
const newSessionId = crypto.randomUUID();
19+
sessionStorage.setItem("sessionid", newSessionId);
20+
setSessionId(newSessionId);
21+
}
22+
}, []);
23+
24+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
25+
e.preventDefault();
26+
setInput("");
27+
setMessages((prev) => [...prev, `👤: ${input}`]);
28+
29+
setLoading(true);
30+
const response = await fetch("/chat", {
31+
method: "POST",
32+
headers: {
33+
"Content-Type": "application/json",
34+
},
35+
body: JSON.stringify({ message: input, sessionid }),
36+
});
37+
38+
const { message } = await response.json();
39+
40+
setMessages((prev) => [...prev, `🤖: ${message}`]);
41+
setLoading(false);
42+
};
43+
44+
return (
45+
<div className="p-4 border rounded shadow-lg bg-background text-foreground w-96 max-h-96">
46+
<div className="overflow-y-auto max-h-60 mb-4">
47+
{messages.map((msg, index) => (
48+
<p className="mb-2" key={index}>
49+
{msg}
50+
</p>
51+
))}
52+
</div>
53+
<form className="flex flex-col gap-2" onSubmit={handleSubmit}>
54+
<Input
55+
type="text"
56+
name="message"
57+
value={input}
58+
onChange={(e) => setInput(e.target.value)}
59+
autoComplete="off"
60+
/>
61+
<Button size="lg" type="submit" disabled={!input.trim() || loading}>
62+
{loading ? "Pensando..." : "Enviar"}
63+
</Button>
64+
</form>
65+
</div>
66+
);
67+
}

src/routes/root/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { createRemoteItems } from "@/services/cart.service";
2323
import { commitSession, getSession } from "@/session.server";
2424

2525
import AuthNav from "./components/auth-nav";
26+
import { ChatBot } from "./components/chatbot";
2627
import HeaderMain from "./components/header-main";
2728

2829
import type { Route } from "./+types";
@@ -186,6 +187,9 @@ export default function Root({ loaderData }: Route.ComponentProps) {
186187
</small>
187188
</Container>
188189
</footer>
190+
<div className="fixed bottom-4 right-4">
191+
<ChatBot />
192+
</div>
189193
<ScrollRestoration />
190194
</div>
191195
);

src/services/chat.service.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { type Chat, GoogleGenAI } from "@google/genai";
2+
import dotenv from "dotenv";
3+
4+
dotenv.config();
5+
6+
const ai = new GoogleGenAI({
7+
apiKey: process.env.GOOGLE_API_KEY || "",
8+
});
9+
10+
const chats: { [key: string]: Chat } = {};
11+
12+
export async function sendMessage(message: string, sessionId: string) {
13+
if (!chats[sessionId]) {
14+
const chat = ai.chats.create({
15+
model: "gemini-2.5-flash",
16+
history: [],
17+
});
18+
19+
chats[sessionId] = chat;
20+
}
21+
22+
const chat = chats[sessionId];
23+
24+
const response = await chat.sendMessage({
25+
message,
26+
});
27+
return response.text;
28+
}

0 commit comments

Comments
 (0)