From beb50dd7fd20487164dfb030af166a3a22535c88 Mon Sep 17 00:00:00 2001 From: git-create-devben Date: Fri, 2 Aug 2024 18:43:00 +0100 Subject: [PATCH] v1 shipping for testing --- app/api/create-session/route.ts | 24 + app/api/gemini/route.ts | 168 ++-- app/api/verify-session/route.ts | 13 + app/chat/page.tsx | 70 +- components/Deafultchatpage.tsx | 2 +- components/booking.tsx | 4 +- components/bookingForm.tsx | 110 +-- components/chatInbox.tsx | 2 +- components/chatpage.tsx | 2 +- components/loading.tsx | 2 +- components/main.tsx | 87 +-- lib/firebaseAdmin.ts | 22 +- lib/keywords.ts | 1288 +++++++++++++++---------------- lib/signIn.tsx | 55 +- middleware.ts | 22 +- package-lock.json | 233 ++++-- package.json | 30 +- tsconfig.json | 7 +- 18 files changed, 1184 insertions(+), 957 deletions(-) create mode 100644 app/api/create-session/route.ts create mode 100644 app/api/verify-session/route.ts diff --git a/app/api/create-session/route.ts b/app/api/create-session/route.ts new file mode 100644 index 0000000..8d805f2 --- /dev/null +++ b/app/api/create-session/route.ts @@ -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 }); + } +} \ No newline at end of file diff --git a/app/api/gemini/route.ts b/app/api/gemini/route.ts index a98d48b..e8f7ecf 100644 --- a/app/api/gemini/route.ts +++ b/app/api/gemini/route.ts @@ -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 diff --git a/app/api/verify-session/route.ts b/app/api/verify-session/route.ts new file mode 100644 index 0000000..db1dd7a --- /dev/null +++ b/app/api/verify-session/route.ts @@ -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 }); + } +} \ No newline at end of file diff --git a/app/chat/page.tsx b/app/chat/page.tsx index 5ae46f6..1ae916d 100644 --- a/app/chat/page.tsx +++ b/app/chat/page.tsx @@ -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(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 ( +
+
+

Loading...

+
+ ); + } - return () => unsubscribe(); - }); + if (!user) { + return router.push("/"); + } + // @ts-ignore + const image = user?.photoURL as string; return ( -
+
-
-
+
+
-
+
diff --git a/components/Deafultchatpage.tsx b/components/Deafultchatpage.tsx index efad4d2..6e28bb2 100644 --- a/components/Deafultchatpage.tsx +++ b/components/Deafultchatpage.tsx @@ -3,7 +3,7 @@ import { CardCarousel } from "./CardCarousel"; export const DefaultChatPage = ({ user }: { user: string }) => { return (
-
+

Hello {user}

diff --git a/components/booking.tsx b/components/booking.tsx index 1c3ce6c..70538d7 100644 --- a/components/booking.tsx +++ b/components/booking.tsx @@ -189,7 +189,7 @@ export function Booking({ {/* */} - + ReadMore on How we use Loca to Book you a service provider @@ -332,7 +332,7 @@ export function Booking({ {/* */} - + -
-
-
- - -