-
Notifications
You must be signed in to change notification settings - Fork 320
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6600bcc
commit 7a6a909
Showing
157 changed files
with
10,102 additions
and
10,037 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,5 @@ | ||
# Then get your Google Gemini API Key here: https://cloud.google.com/vertex-ai | ||
GOOGLE_GENERATIVE_AI_API_KEY=XXXXXXXX | ||
# Create an API key here https://platform.openai.com/account/api-keys | ||
OPENAI_API_KEY=**** | ||
|
||
# Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32` | ||
AUTH_SECRET=XXXXXXXX | ||
|
||
# Instructions to create kv database here: https://vercel.com/docs/storage/vercel-kv/quickstart and | ||
KV_URL=XXXXXXXX | ||
KV_REST_API_URL=XXXXXXXX | ||
KV_REST_API_TOKEN=XXXXXXXX | ||
KV_REST_API_READ_ONLY_TOKEN=XXXXXXXX | ||
|
||
# Get your kasada configurations here: https://kasada.io | ||
KASADA_API_ENDPOINT=XXXXXXXX | ||
KASADA_API_VERSION=XXXXXXXX | ||
KASADA_HEADER_HOST=XXXXXXXX | ||
# Create an access token here https://supabase.com/dashboard/account/tokens | ||
SUPABASE_ACCESS_TOKEN=**** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// import { openai } from "@ai-sdk/openai"; | ||
import { experimental_wrapLanguageModel as wrapLanguageModel } from "ai"; | ||
import { ragMiddleware } from "./rag-middleware"; | ||
import { google } from "@ai-sdk/google"; | ||
|
||
export const customModel = wrapLanguageModel({ | ||
model: google("gemini-1.5-pro-002"), | ||
middleware: ragMiddleware, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Experimental_LanguageModelV1Middleware } from "ai"; | ||
|
||
export const ragMiddleware: Experimental_LanguageModelV1Middleware = {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
"use server"; | ||
|
||
import { redirect } from "next/navigation"; | ||
import { revalidatePath } from "next/cache"; | ||
import { createClient } from "@/utils/supabase/server"; | ||
|
||
export interface LoginActionState { | ||
status: "idle" | "in_progress" | "success" | "failed"; | ||
} | ||
|
||
export const login = async ( | ||
_: LoginActionState, | ||
formData: FormData, | ||
): Promise<LoginActionState> => { | ||
const supabase = createClient(); | ||
|
||
const { error } = await supabase.auth.signInWithPassword({ | ||
email: formData.get("email") as string, | ||
password: formData.get("password") as string, | ||
}); | ||
|
||
if (error) { | ||
return { status: "failed" } as LoginActionState; | ||
} | ||
|
||
revalidatePath("/", "layout"); | ||
redirect("/"); | ||
}; | ||
|
||
export interface RegisterActionState { | ||
status: "idle" | "in_progress" | "success" | "failed" | "user_exists"; | ||
} | ||
|
||
export const register = async (_: RegisterActionState, formData: FormData) => { | ||
const supabase = createClient(); | ||
|
||
let email = formData.get("email") as string; | ||
let password = formData.get("password") as string; | ||
|
||
const { data, error } = await supabase.auth.signUp({ email, password }); | ||
|
||
if (error) { | ||
if (error.code === "user_already_exists") { | ||
return { status: "user_exists" } as RegisterActionState; | ||
} | ||
} | ||
|
||
const { user, session } = data; | ||
|
||
if (user && session) { | ||
const { error } = await supabase.auth.signInWithPassword({ | ||
email: formData.get("email") as string, | ||
password: formData.get("password") as string, | ||
}); | ||
|
||
if (error) { | ||
return { status: "failed" } as LoginActionState; | ||
} | ||
|
||
revalidatePath("/", "layout"); | ||
redirect("/"); | ||
} else { | ||
return { status: "failed" } as RegisterActionState; | ||
} | ||
}; | ||
|
||
export const getUserFromSession = async () => { | ||
const supabase = createClient(); | ||
const { data } = await supabase.auth.getUser(); | ||
return data.user; | ||
}; | ||
|
||
export const signOut = async () => { | ||
const supabase = createClient(); | ||
const { error } = await supabase.auth.signOut(); | ||
|
||
if (!error) { | ||
redirect("/login"); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
"use client"; | ||
|
||
import Link from "next/link"; | ||
import { Form } from "@/components/form"; | ||
import { SubmitButton } from "@/components/submit-button"; | ||
import { useActionState, useEffect } from "react"; | ||
import { login, LoginActionState } from "../actions"; | ||
import { toast } from "sonner"; | ||
import { useRouter } from "next/navigation"; | ||
|
||
export default function Page() { | ||
const router = useRouter(); | ||
|
||
const [state, formAction] = useActionState<LoginActionState, FormData>( | ||
login, | ||
{ | ||
status: "idle", | ||
}, | ||
); | ||
|
||
useEffect(() => { | ||
if (state.status === "failed") { | ||
toast.error("Invalid credentials!"); | ||
} else if (state.status === "success") { | ||
router.refresh(); | ||
} | ||
}, [state.status, router]); | ||
|
||
return ( | ||
<div className="flex h-screen w-screen items-center justify-center bg-background"> | ||
<div className="w-full max-w-md overflow-hidden rounded-2xl flex flex-col gap-12"> | ||
<div className="flex flex-col items-center justify-center gap-2 px-4 text-center sm:px-16"> | ||
<h3 className="text-xl font-semibold dark:text-zinc-50">Sign In</h3> | ||
<p className="text-sm text-gray-500 dark:text-zinc-400"> | ||
Use your email and password to sign in | ||
</p> | ||
</div> | ||
<Form action={formAction}> | ||
<SubmitButton>Sign in</SubmitButton> | ||
<p className="text-center text-sm text-gray-600 mt-4 dark:text-zinc-400"> | ||
{"Don't have an account? "} | ||
<Link | ||
href="/register" | ||
className="font-semibold text-gray-800 hover:underline dark:text-zinc-200" | ||
> | ||
Sign up | ||
</Link> | ||
{" for free."} | ||
</p> | ||
</Form> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
"use client"; | ||
|
||
import Link from "next/link"; | ||
import { Form } from "@/components/form"; | ||
import { SubmitButton } from "@/components/submit-button"; | ||
import { register, RegisterActionState } from "../actions"; | ||
import { useActionState, useEffect } from "react"; | ||
import { toast } from "sonner"; | ||
import { useRouter } from "next/navigation"; | ||
|
||
export default function Page() { | ||
const router = useRouter(); | ||
const [state, formAction] = useActionState<RegisterActionState, FormData>( | ||
register, | ||
{ | ||
status: "idle", | ||
}, | ||
); | ||
|
||
useEffect(() => { | ||
if (state.status === "user_exists") { | ||
toast.error("Account already exists"); | ||
} else if (state.status === "failed") { | ||
toast.error("Failed to create account"); | ||
} else if (state.status === "success") { | ||
toast.success("Account created successfully"); | ||
router.refresh(); | ||
} | ||
}, [state, router]); | ||
|
||
return ( | ||
<div className="flex h-screen w-screen items-center justify-center bg-background"> | ||
<div className="w-full max-w-md overflow-hidden rounded-2xl gap-12 flex flex-col"> | ||
<div className="flex flex-col items-center justify-center gap-2 px-4 text-center sm:px-16"> | ||
<h3 className="text-xl font-semibold dark:text-zinc-50">Sign Up</h3> | ||
<p className="text-sm text-gray-500 dark:text-zinc-400"> | ||
Create an account with your email and password | ||
</p> | ||
</div> | ||
<Form action={formAction}> | ||
<SubmitButton>Sign Up</SubmitButton> | ||
<p className="text-center text-sm text-gray-600 mt-4 dark:text-zinc-400"> | ||
{"Already have an account? "} | ||
<Link | ||
href="/login" | ||
className="font-semibold text-gray-800 hover:underline dark:text-zinc-200" | ||
> | ||
Sign in | ||
</Link> | ||
{" instead."} | ||
</p> | ||
</Form> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Message } from "ai"; | ||
import { Chat } from "@/utils/supabase/schema"; | ||
import { getChatById } from "../actions"; | ||
import { notFound } from "next/navigation"; | ||
import { Chat as PreviewChat } from "@/components/chat"; | ||
|
||
export default async function Page({ params }: { params: any }) { | ||
const { id } = params; | ||
const chatFromDb = await getChatById({ id }); | ||
|
||
if (!chatFromDb) { | ||
notFound(); | ||
} | ||
|
||
// type casting | ||
const chat: Chat = { | ||
...chatFromDb, | ||
messages: chatFromDb.messages as Message[], | ||
}; | ||
|
||
return <PreviewChat id={chat.id} initialMessages={chat.messages} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { createClient } from "@/utils/supabase/server"; | ||
|
||
export async function saveChat({ | ||
id, | ||
messages, | ||
userId, | ||
}: { | ||
id: string; | ||
messages: any; | ||
userId: string; | ||
}) { | ||
const supabase = createClient(); | ||
|
||
await supabase.from("chat").upsert({ | ||
id, | ||
messages, | ||
userId, | ||
}); | ||
} | ||
|
||
export async function getChatById({ id }: { id: string }) { | ||
const supabase = createClient(); | ||
|
||
const { data: chat } = await supabase | ||
.from("chat") | ||
.select("*") | ||
.eq("id", id) | ||
.single(); | ||
|
||
return chat; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { customModel } from "@/ai"; | ||
import { saveChat } from "@/app/(chat)/actions"; | ||
import { convertToCoreMessages, streamText } from "ai"; | ||
import { getUserFromSession } from "@/app/(auth)/actions"; | ||
import { createClient } from "@/utils/supabase/server"; | ||
|
||
export async function POST(request: Request) { | ||
const { id, messages, selectedFilePathnames } = await request.json(); | ||
|
||
const user = await getUserFromSession(); | ||
|
||
if (!user) { | ||
return new Response("Unauthorized", { status: 401 }); | ||
} | ||
|
||
const result = await streamText({ | ||
model: customModel, | ||
system: | ||
"you are a friendly assistant! keep your responses concise and helpful.", | ||
messages: convertToCoreMessages(messages), | ||
experimental_providerMetadata: { | ||
files: { | ||
selection: selectedFilePathnames, | ||
}, | ||
}, | ||
onFinish: async ({ text }) => { | ||
await saveChat({ | ||
id, | ||
messages: [...messages, { role: "assistant", content: text }], | ||
userId: user.id, | ||
}); | ||
}, | ||
experimental_telemetry: { | ||
isEnabled: true, | ||
functionId: "stream-text", | ||
}, | ||
}); | ||
|
||
return result.toDataStreamResponse({}); | ||
} | ||
|
||
export async function DELETE(request: Request) { | ||
const { searchParams } = new URL(request.url); | ||
const id = searchParams.get("id"); | ||
|
||
if (!id) { | ||
return new Response("Not Found", { status: 404 }); | ||
} | ||
|
||
const supabase = createClient(); | ||
|
||
try { | ||
const { data, error } = await supabase.from("chat").delete().eq("id", id); | ||
|
||
if (error) throw error; | ||
|
||
return Response.json(data); | ||
} catch (error) { | ||
return new Response("An error occurred while processing your request", { | ||
status: 500, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { getUserFromSession } from "@/app/(auth)/actions"; | ||
import { createClient } from "@/utils/supabase/server"; | ||
import { NextResponse } from "next/server"; | ||
|
||
export async function POST(request: Request) { | ||
const user = await getUserFromSession(); | ||
|
||
if (!user) { | ||
return Response.redirect("/login"); | ||
} | ||
|
||
if (request.body === null) { | ||
return new Response("Request body is empty", { status: 400 }); | ||
} | ||
|
||
const supabase = createClient(); | ||
|
||
try { | ||
const formData = await request.formData(); | ||
const file = formData.get("file") as File; | ||
|
||
if (!file) { | ||
return NextResponse.json({ error: "No file uploaded" }, { status: 400 }); | ||
} | ||
|
||
const filename = file.name; | ||
const fileBuffer = await file.arrayBuffer(); | ||
|
||
const { data, error } = await supabase.storage | ||
.from("attachments") | ||
.upload(filename, fileBuffer, { | ||
contentType: file.type, | ||
upsert: true, | ||
}); | ||
|
||
if (error) { | ||
return NextResponse.json({ error: "Upload failed" }, { status: 500 }); | ||
} | ||
|
||
return NextResponse.json({ data }); | ||
} catch (error) { | ||
return NextResponse.json( | ||
{ error: "Failed to process request" }, | ||
{ status: 500 }, | ||
); | ||
} | ||
} |
Oops, something went wrong.