Skip to content

Commit

Permalink
v1 shipping for testing
Browse files Browse the repository at this point in the history
  • Loading branch information
git-create-devben committed Aug 2, 2024
1 parent b0ff9c1 commit beb50dd
Show file tree
Hide file tree
Showing 18 changed files with 1,184 additions and 957 deletions.
24 changes: 24 additions & 0 deletions app/api/create-session/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminAuth as auth } from '@/lib/firebaseAdmin';

export async function POST(req: NextRequest) {
const { idToken } = await req.json();

try {
const expiresIn = 60 * 60 * 24 * 5 * 1000; // 5 days
const sessionCookie = await auth.createSessionCookie(idToken, { expiresIn });

const response = NextResponse.json({ status: 'success' });
response.cookies.set('session', sessionCookie, {
maxAge: expiresIn,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
path: '/'
});

return response;
} catch (error) {
console.error('Error creating session:', error);
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
}
168 changes: 100 additions & 68 deletions app/api/gemini/route.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,121 @@
import { NextRequest } from "next/server";
import { NextRequest, NextResponse } from "next/server";
import { GoogleGenerativeAI } from "@google/generative-ai";
import { getLocalServices } from "@/lib/getLocationServices";
import { keyword } from "@/lib/keywords";
import { faqs } from "@/app/faq/data";
import { adminAuth, admindb } from "@/lib/firebaseAdmin";

// Initialize the Google Generative AI with the provided API key
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || "");

// Handle POST requests
export async function POST(req: NextRequest) {
// Parse the request body to get userMessage, latitude, and longitude
const { userMessage, latitude, longitude } = await req.json();
try {
// Get the authorization token from the request headers
// Get the session cookie from the request
const sessionCookie = req.cookies.get('session')?.value;

// Validate the required fields
if (!userMessage || !latitude || !longitude) {
return new Response(
JSON.stringify({
error:
"----- Missing required fields: userMessage, latitude, or longitude -----",
}),
{ status: 400 },
);
}
if (!sessionCookie) {
return NextResponse.json({ error: "No session cookie found" }, { status: 401 });
}

// Verify the session cookie
const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true);

// Fetch user data from Firestore
const userDoc = await admindb.collection("users").doc(decodedClaims.uid).get();
const userData = userDoc.data();
const userName = userData?.name || "User";

// Initialize the encoder and stream for sending responses
const encoder = new TextEncoder();
const stream = new TransformStream();
const writer = stream.writable.getWriter();

// Function to write chunks of data to the stream
const writeChunk = async (chunk: { type: string; data: any }) => {
await writer.write(encoder.encode(JSON.stringify(chunk) + "\n"));
};

// Asynchronous function to handle the main logic
(async () => {
try {
// Extract service keywords from the user message
const serviceKeywords = extractServiceKeywords(userMessage);
let services = [];
if (serviceKeywords) {
// Fetch local services based on the extracted keywords and location
services = await getLocalServices(serviceKeywords, latitude, longitude);
await writeChunk({ type: "services", data: services });
}


// Get the generative model from Google Generative AI
const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro" });
// Parse the request body to get userMessage, latitude, and longitude
const { userMessage, latitude, longitude } = await req.json();

// Create a prompt for the AI model
const prompt = `You are Loca, a local AI service finder. ${
services.length > 0
? `Here are some available services: ${JSON.stringify(services)}. Provide a helpful response based on this information, highlighting the best options.`
: `Provide a general response about "${userMessage}". If the user is asking about local services, suggest how they might find them.`
}`;
// Validate the required fields
if (!userMessage || !latitude || !longitude) {
return new Response(
JSON.stringify({
error:
"----- Missing required fields: userMessage, latitude, or longitude -----",
}),
{ status: 400 },
);
}

// Generate content stream from the AI model based on the prompt
const result = await model.generateContentStream(prompt);
// Initialize the encoder and stream for sending responses
const encoder = new TextEncoder();
const stream = new TransformStream();
const writer = stream.writable.getWriter();

// Write the generated content to the stream
for await (const chunk of result.stream) {
const chunkText = chunk.text();
await writeChunk({ type: "text", data: chunkText });
}
// Function to write chunks of data to the stream
const writeChunk = async (chunk: { type: string; data: any }) => {
await writer.write(encoder.encode(JSON.stringify(chunk) + "\n"));
};

// Asynchronous function to handle the main logic
(async () => {
try {
// Extract service keywords from the user message
const serviceKeywords = extractServiceKeywords(userMessage);
let services = [];
if (serviceKeywords) {
// Fetch local services based on the extracted keywords and location
services = await getLocalServices(
serviceKeywords,
latitude,
longitude,
);
await writeChunk({ type: "services", data: services });
}

// Get the generative model from Google Generative AI
const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro" });

// Send services again at the end to ensure they're not missed
if (services.length > 0) {
await writeChunk({ type: "services", data: services });
// Modify the prompt to include the user's name
const prompt = `You are Loca, a local AI service finder. You're talking to ${userName}. This is a FAQ about you: ${faqs}. Use this to improve your response. ${
services.length > 0
? `Here are some available services: ${JSON.stringify(services)}. Provide a helpful response based on this information, highlighting the best options for ${userName}.`
: `Provide a general response about "${userMessage}" for ${userName}. If they are asking about local services, suggest how they might find them.`
}`;

// Generate content stream from the AI model based on the prompt
const result = await model.generateContentStream(prompt);

// Write the generated content to the stream
for await (const chunk of result.stream) {
const chunkText = chunk.text();
await writeChunk({ type: "text", data: chunkText });
}

// Send services again at the end to ensure they're not missed
if (services.length > 0) {
await writeChunk({ type: "services", data: services });
}
} catch (error) {
// Handle errors and write an error message to the stream
console.error(" ---- Server Error:", error);
await writeChunk({
type: "error",
data: "An error occurred while processing your request.",
});
} finally {
// Close the writer
writer.close();
}
} catch (error) {
// Handle errors and write an error message to the stream
console.error(" ---- Server Error:", error);
await writeChunk({
type: "error",
data: "An error occurred while processing your request.",
});
} finally {
// Close the writer
writer.close();
}
})();
})();

// Return the readable stream as the response
return new Response(stream.readable, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
// Return the readable stream as the response
return new Response(stream.readable, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
} catch (error) {
console.error("Error verifying token or fetching user data:", error);
return new Response(
JSON.stringify({ error: "Invalid token or user data not found" }),
{ status: 403 },
);
}
}

// Function to extract service keywords from the input string
Expand Down
13 changes: 13 additions & 0 deletions app/api/verify-session/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminAuth as auth } from '@/lib/firebaseAdmin';

export async function GET(req: NextRequest) {
const sessionCookie = req.cookies.get('session')?.value || '';

try {
const decodedClaims = await auth.verifySessionCookie(sessionCookie, true);
return NextResponse.json({ authenticated: true, user: decodedClaims });
} catch (error) {
return NextResponse.json({ authenticated: false }, { status: 401 });
}
}
70 changes: 59 additions & 11 deletions app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,74 @@ import Image from "next/image";
import FirstVisitPopup from "@/components/firstvisitpopup";
import { SignOut } from "@/lib/signIn";
import { LogOut } from "lucide-react";
import { useRouter } from "next/navigation";
import { doc, getDoc } from "firebase/firestore";
import { db } from "@/lib/firebase";

interface UserData {
name?: string;
email?: string;
photoURL?: string;
}
export default function Chat() {
const [user, setUser] = useState(auth.currentUser);
const image = user?.photoURL || local;
const [user, setUser] = useState<UserData | null>(null);
const [loading, setLoading] = useState(true);
const router = useRouter();

useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
const verifySession = async () => {
try {
const res = await fetch("/api/verify-session");
if (res.ok) {
const data = await res.json();
if (data.authenticated) {
// Fetch user data from Firestore
const userDoc = await getDoc(doc(db, "users", data.user.uid));
if (userDoc.exists()) {
setUser(userDoc.data() as UserData);
} else {
throw new Error("User document not found");
}
} else {
throw new Error("Not authenticated");
}
} else {
throw new Error("Failed to verify session");
}
} catch (error) {
console.error("Error verifying session:", error);
router.push("/");
} finally {
setLoading(false);
}
};

verifySession();
}, [router]);

if (loading) {
return (
<div className="flex justify-center items-center h-screen bg-black">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-blue-500"></div>
<p className="text-white ml-4 text-xl">Loading...</p>
</div>
);
}

return () => unsubscribe();
});
if (!user) {
return router.push("/");
}
// @ts-ignore
const image = user?.photoURL as string;
return (
<main className=" flex bg-black">
<main className=" flex bg-black h-screen overflow-hidden">
<FirstVisitPopup />
<div className=" hidden lg:block">
<Sidebar />
</div>
<div className=" bg-[#1212] min-h-[100vh] pb-[15vh] relative flex-1 ">
<div className="flex flex-col max-h-[830px] overflow-y-scroll scroll-m-1">
<div className="sticky top-0 w-full shadow-lg">
<div className="flex flex-col max-h-[830px] overflow-y-auto scroll-m-1">
<div className="sticky z-10 top-0 w-full shadow-lg">
<nav className="flex justify-between p-4 ">
<span
className="text-[#caccce] font-medium text-3xl cursor-pointer"
Expand All @@ -55,7 +103,7 @@ export default function Chat() {
</nav>
</div>

<div className="self-center max-w-[900px] m-auto h-screen px-4">
<div className="h-screen px-5 ">
<Main />
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/Deafultchatpage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CardCarousel } from "./CardCarousel";
export const DefaultChatPage = ({ user }: { user: string }) => {
return (
<main>
<div className="text-[#c4c7c556] lg:text-6xl text-4xl font-semibold flex flex-col self-auto">
<div className="text-[#c4c7c556] lg:text-6xl text-4xl font-semibold flex flex-col xl:ml-24">
<h1 className="bg-clip-text text-transparent bg-gradient-to-r from-[#4b90ff] from-1% via-blue-600 via-5% to-15% to-[#ff5546]">
Hello {user}
</h1>
Expand Down
4 changes: 2 additions & 2 deletions components/booking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export function Booking({
{/* <Button className="bg-blue-400 rounded-full p-6 hover:bg-blue-300 text-black border-none outline-none">
<Link href="/chat/booking">Book by Loca</Link>
</Button> */}
<BookingForm/>
<BookingForm />
<span className="text-xs underline text-center">
ReadMore on How we use Loca to Book you a service provider
</span>
Expand Down Expand Up @@ -332,7 +332,7 @@ export function Booking({
{/* <Button className="bg-blue-400 rounded-full p-6 hover:bg-blue-300 text-black border-none outline-none">
<Link href="/chat/booking">Book by Loca</Link>
</Button> */}
<BookingForm/>
<BookingForm />
<Link
href="/faqs"
className="text-xs underline text-center cursor-pointer"
Expand Down
Loading

0 comments on commit beb50dd

Please sign in to comment.