-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🥕 v0.17.0-b20 🌱 ThreeD: Home Design OpenAI
- Loading branch information
1 parent
a089c33
commit d7a7365
Showing
74 changed files
with
4,946 additions
and
3 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
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,65 @@ | ||
import { type Metadata } from 'next' | ||
import { notFound, redirect } from 'next/navigation' | ||
|
||
import { auth } from '@/auth' | ||
import { getChat, getMissingKeys } from '@/app/actions' | ||
import { Chat } from '~/src/lib/ai/chat' | ||
import { AI } from '~/src/lib/threed/threed.ai/chat/actions' | ||
import { Session } from '@/lib/types' | ||
|
||
export interface ChatPageProps { | ||
params: { | ||
id: string | ||
} | ||
} | ||
|
||
export async function generateMetadata({ | ||
params | ||
}: ChatPageProps): Promise<Metadata> { | ||
const session = await auth() | ||
|
||
if (!session?.user) { | ||
return {} | ||
} | ||
|
||
const chat = await getChat(params.id, session.user.id) | ||
|
||
if (!chat || 'error' in chat) { | ||
redirect('/') | ||
} else { | ||
return { | ||
title: chat?.title.toString().slice(0, 50) ?? 'Chat' | ||
} | ||
} | ||
} | ||
|
||
export default async function ChatPage({ params }: ChatPageProps) { | ||
const session = (await auth()) as Session | ||
const missingKeys = await getMissingKeys() | ||
|
||
if (!session?.user) { | ||
redirect(`/login?next=/chat/${params.id}`) | ||
} | ||
|
||
const userId = session.user.id as string | ||
const chat = await getChat(params.id, userId) | ||
|
||
if (!chat || 'error' in chat) { | ||
redirect('/') | ||
} else { | ||
if (chat?.userId !== session?.user?.id) { | ||
notFound() | ||
} | ||
|
||
return ( | ||
<AI initialAIState={{ chatId: chat.id, messages: chat.messages }}> | ||
<Chat | ||
id={chat.id} | ||
session={session} | ||
initialMessages={chat.messages} | ||
missingKeys={missingKeys} | ||
/> | ||
</AI> | ||
) | ||
} | ||
} |
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,14 @@ | ||
import { SidebarDesktop } from '~/src/lib/ai/sidebar-desktop' | ||
|
||
interface ChatLayoutProps { | ||
children: React.ReactNode | ||
} | ||
|
||
export default async function ChatLayout({ children }: ChatLayoutProps) { | ||
return ( | ||
<div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden"> | ||
<SidebarDesktop /> | ||
{children} | ||
</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 { nanoid } from '@/lib/utils' | ||
import { Chat } from '~/src/lib/ai/chat' | ||
import { AI } from '~/src/lib/threed/threed.ai/chat/actions' | ||
import { auth } from '@/auth' | ||
import { Session } from '@/lib/types' | ||
import { getMissingKeys } from '@/app/actions' | ||
|
||
export const metadata = { | ||
title: 'Next.js AI Chatbot' | ||
} | ||
|
||
export default async function IndexPage() { | ||
const id = nanoid() | ||
const session = (await auth()) as Session | ||
const missingKeys = await getMissingKeys() | ||
|
||
return ( | ||
<AI initialAIState={{ chatId: id, messages: [] }}> | ||
<Chat id={id} session={session} missingKeys={missingKeys} /> | ||
</AI> | ||
) | ||
} |
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,172 @@ | ||
'use server' | ||
|
||
import { revalidatePath } from 'next/cache' | ||
import { redirect } from 'next/navigation' | ||
import { kv } from '@vercel/kv' | ||
|
||
import { auth } from '@/auth' | ||
import { type Chat } from '@/lib/types' | ||
|
||
export async function getChats(userId?: string | null) { | ||
const session = await auth() | ||
|
||
if (!userId) { | ||
return [] | ||
} | ||
|
||
if (userId !== session?.user?.id) { | ||
return { | ||
error: 'Unauthorized' | ||
} | ||
} | ||
|
||
try { | ||
const pipeline = kv.pipeline() | ||
const chats: string[] = await kv.zrange(`user:chat:${userId}`, 0, -1, { | ||
rev: true | ||
}) | ||
|
||
for (const chat of chats) { | ||
pipeline.hgetall(chat) | ||
} | ||
|
||
const results = await pipeline.exec() | ||
|
||
return results as Chat[] | ||
} catch (error) { | ||
return [] | ||
} | ||
} | ||
|
||
export async function getChat(id: string, userId: string) { | ||
const session = await auth() | ||
|
||
if (userId !== session?.user?.id) { | ||
return { | ||
error: 'Unauthorized' | ||
} | ||
} | ||
|
||
const chat = await kv.hgetall<Chat>(`chat:${id}`) | ||
|
||
if (!chat || (userId && chat.userId !== userId)) { | ||
return null | ||
} | ||
|
||
return chat | ||
} | ||
|
||
export async function removeChat({ id, path }: { id: string; path: string }) { | ||
const session = await auth() | ||
|
||
if (!session) { | ||
return { | ||
error: 'Unauthorized' | ||
} | ||
} | ||
|
||
// Convert uid to string for consistent comparison with session.user.id | ||
const uid = String(await kv.hget(`chat:${id}`, 'userId')) | ||
|
||
if (uid !== session?.user?.id) { | ||
return { | ||
error: 'Unauthorized' | ||
} | ||
} | ||
|
||
await kv.del(`chat:${id}`) | ||
await kv.zrem(`user:chat:${session.user.id}`, `chat:${id}`) | ||
|
||
revalidatePath('/') | ||
return revalidatePath(path) | ||
} | ||
|
||
export async function clearChats() { | ||
const session = await auth() | ||
|
||
if (!session?.user?.id) { | ||
return { | ||
error: 'Unauthorized' | ||
} | ||
} | ||
|
||
const chats: string[] = await kv.zrange(`user:chat:${session.user.id}`, 0, -1) | ||
if (!chats.length) { | ||
return redirect('/') | ||
} | ||
const pipeline = kv.pipeline() | ||
|
||
for (const chat of chats) { | ||
pipeline.del(chat) | ||
pipeline.zrem(`user:chat:${session.user.id}`, chat) | ||
} | ||
|
||
await pipeline.exec() | ||
|
||
revalidatePath('/') | ||
return redirect('/') | ||
} | ||
|
||
export async function getSharedChat(id: string) { | ||
const chat = await kv.hgetall<Chat>(`chat:${id}`) | ||
|
||
if (!chat || !chat.sharePath) { | ||
return null | ||
} | ||
|
||
return chat | ||
} | ||
|
||
export async function shareChat(id: string) { | ||
const session = await auth() | ||
|
||
if (!session?.user?.id) { | ||
return { | ||
error: 'Unauthorized' | ||
} | ||
} | ||
|
||
const chat = await kv.hgetall<Chat>(`chat:${id}`) | ||
|
||
if (!chat || chat.userId !== session.user.id) { | ||
return { | ||
error: 'Something went wrong' | ||
} | ||
} | ||
|
||
const payload = { | ||
...chat, | ||
sharePath: `/share/${chat.id}` | ||
} | ||
|
||
await kv.hmset(`chat:${chat.id}`, payload) | ||
|
||
return payload | ||
} | ||
|
||
export async function saveChat(chat: Chat) { | ||
const session = await auth() | ||
|
||
if (session && session.user) { | ||
const pipeline = kv.pipeline() | ||
pipeline.hmset(`chat:${chat.id}`, chat) | ||
pipeline.zadd(`user:chat:${chat.userId}`, { | ||
score: Date.now(), | ||
member: `chat:${chat.id}` | ||
}) | ||
await pipeline.exec() | ||
} else { | ||
return | ||
} | ||
} | ||
|
||
export async function refreshHistory(path: string) { | ||
redirect(path) | ||
} | ||
|
||
export async function getMissingKeys() { | ||
const keysRequired = ['OPENAI_API_KEY'] | ||
return keysRequired | ||
.map(key => (process.env[key] ? '' : key)) | ||
.filter(key => key !== '') | ||
} |
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,76 @@ | ||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
|
||
@layer base { | ||
:root { | ||
--background: 0 0% 100%; | ||
--foreground: 240 10% 3.9%; | ||
|
||
--card: 0 0% 100%; | ||
--card-foreground: 240 10% 3.9%; | ||
|
||
--popover: 0 0% 100%; | ||
--popover-foreground: 240 10% 3.9%; | ||
|
||
--primary: 240 5.9% 10%; | ||
--primary-foreground: 0 0% 98%; | ||
|
||
--secondary: 240 4.8% 95.9%; | ||
--secondary-foreground: 240 5.9% 10%; | ||
|
||
--muted: 240 4.8% 95.9%; | ||
--muted-foreground: 240 3.8% 46.1%; | ||
|
||
--accent: 240 4.8% 95.9%; | ||
--accent-foreground: 240 5.9% 10%; | ||
|
||
--destructive: 0 84.2% 60.2%; | ||
--destructive-foreground: 0 0% 98%; | ||
|
||
--border: 240 5.9% 90%; | ||
--input: 240 5.9% 90%; | ||
--ring: 240 10% 3.9%; | ||
|
||
--radius: 0.5rem; | ||
} | ||
|
||
.dark { | ||
--background: 240 10% 3.9%; | ||
--foreground: 0 0% 98%; | ||
|
||
--card: 240 10% 3.9%; | ||
--card-foreground: 0 0% 98%; | ||
|
||
--popover: 240 10% 3.9%; | ||
--popover-foreground: 0 0% 98%; | ||
|
||
--primary: 0 0% 98%; | ||
--primary-foreground: 240 5.9% 10%; | ||
|
||
--secondary: 240 3.7% 15.9%; | ||
--secondary-foreground: 0 0% 98%; | ||
|
||
--muted: 240 3.7% 15.9%; | ||
--muted-foreground: 240 5% 64.9%; | ||
|
||
--accent: 240 3.7% 15.9%; | ||
--accent-foreground: 0 0% 98%; | ||
|
||
--destructive: 0 62.8% 30.6%; | ||
--destructive-foreground: 0 0% 98%; | ||
|
||
--border: 240 3.7% 15.9%; | ||
--input: 240 3.7% 15.9%; | ||
--ring: 240 4.9% 83.9%; | ||
} | ||
} | ||
|
||
@layer base { | ||
* { | ||
@apply border-border; | ||
} | ||
body { | ||
@apply bg-background text-foreground; | ||
} | ||
} |
Oops, something went wrong.