├── app ├── api │ ├── auth │ │ ├── [...nextauth] │ │ │ └── route.ts │ │ └── signup │ │ │ └── route.ts │ ├── updateUser │ │ └── route.ts │ ├── history │ │ └── route.ts │ ├── vote │ │ └── route.ts │ └── files │ │ └── upload │ │ └── route.ts ├── [locale] │ ├── not-found.tsx │ ├── page.tsx │ ├── chat │ │ ├── layout.tsx │ │ └── [model] │ │ │ ├── [id] │ │ │ └── page.tsx │ │ │ └── page.tsx │ └── layout.tsx ├── (chat) │ └── actions.ts └── (auth) │ ├── actions.ts │ └── auth.ts ├── public ├── fonts │ ├── geist.woff2 │ └── geist-mono.woff2 ├── hero-image-2.png ├── models │ ├── gpt-icon.png │ └── gemini-icon.webp ├── globe.svg └── circles.svg ├── assets └── fonts │ ├── GeistVF.woff2 │ ├── Inter-Bold.ttf │ ├── Inter-Regular.ttf │ ├── CalSans-SemiBold.ttf │ ├── CalSans-SemiBold.woff2 │ ├── satoshi-variable.woff2 │ └── index.ts ├── .eslintrc.json ├── postcss.config.mjs ├── config ├── footer-navs.ts └── site.ts ├── hooks ├── use-mounted.ts ├── use-user-message-id.ts ├── use-scroll-to-bottom.ts ├── use-mobile.tsx ├── use-copy-to-clipboard.tsx └── use-media-query.ts ├── lib ├── navigation.ts ├── s3.ts ├── chat-utils.ts └── utils.ts ├── ai ├── prompts.ts ├── index.ts └── models.ts ├── components ├── theme-provider.tsx ├── ui │ ├── skeleton.tsx │ ├── collapsible.tsx │ ├── textarea.tsx │ ├── label.tsx │ ├── button-loading.tsx │ ├── button-copy.tsx │ ├── input.tsx │ ├── separator.tsx │ ├── progress.tsx │ ├── switch.tsx │ ├── badge.tsx │ ├── popover.tsx │ ├── avatar.tsx │ ├── toggle.tsx │ ├── scroll-area.tsx │ ├── tooltip.tsx │ ├── toggle-group.tsx │ ├── card.tsx │ ├── accordion.tsx │ ├── button2.tsx │ ├── button.tsx │ ├── modal.tsx │ ├── breadcrumb.tsx │ ├── drawer.tsx │ ├── dialog.tsx │ ├── sheet.tsx │ ├── form.tsx │ ├── alert-dialog.tsx │ ├── navigation-menu.tsx │ └── select.tsx ├── layout │ ├── theme-provider.tsx │ ├── toogle-theme.tsx │ ├── mode-toggle.tsx │ └── footer.tsx ├── shared │ ├── ai-model-icon.tsx │ ├── section.tsx │ ├── max-width-wrapper.tsx │ ├── card-skeleton.tsx │ ├── blur-image.tsx │ ├── user-avatar.tsx │ ├── section-skeleton.tsx │ ├── copy-button.tsx │ ├── breadcrumbs.tsx │ ├── mdx-components.tsx │ └── card-model.tsx ├── social-link.tsx ├── modals │ ├── providers.tsx │ └── sign-in-modal.tsx ├── data-stream-handler.tsx ├── sidebar-toggle.tsx ├── app-nav-projects.tsx ├── chat │ ├── overview.tsx │ ├── message-codeblock.tsx │ ├── chat-header.tsx │ ├── preview-attachment.tsx │ ├── messages.tsx │ └── chat-ui.tsx ├── auth-form.tsx ├── locale-switcher.tsx ├── model-selector.tsx ├── app-sidebar.tsx └── markdown-react.tsx ├── drizzle.config.ts ├── i18n ├── routing.ts └── request.ts ├── components.json ├── global.d.ts ├── next.config.ts ├── .gitignore ├── tsconfig.json ├── styles ├── spinner1.css ├── loader.css └── globals.css ├── LICENSE ├── middleware.ts ├── messages └── en.json ├── README.md ├── package.json ├── tailwind.config.ts └── db ├── schema.ts └── queries.ts /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | export { GET, POST } from '@/app/(auth)/auth'; 2 | -------------------------------------------------------------------------------- /public/fonts/geist.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/public/fonts/geist.woff2 -------------------------------------------------------------------------------- /public/hero-image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/public/hero-image-2.png -------------------------------------------------------------------------------- /assets/fonts/GeistVF.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/assets/fonts/GeistVF.woff2 -------------------------------------------------------------------------------- /public/models/gpt-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/public/models/gpt-icon.png -------------------------------------------------------------------------------- /assets/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/assets/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/geist-mono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/public/fonts/geist-mono.woff2 -------------------------------------------------------------------------------- /assets/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/assets/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /public/models/gemini-icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/public/models/gemini-icon.webp -------------------------------------------------------------------------------- /assets/fonts/CalSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/assets/fonts/CalSans-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/CalSans-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/assets/fonts/CalSans-SemiBold.woff2 -------------------------------------------------------------------------------- /assets/fonts/satoshi-variable.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuilenren/chooat-chat-ai/main/assets/fonts/satoshi-variable.woff2 -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals"], 3 | "rules": { 4 | "@next/next/no-html-link-for-pages": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /config/footer-navs.ts: -------------------------------------------------------------------------------- 1 | const Navs: any[] = [ 2 | { 3 | label: "Resource", 4 | items: [ 5 | { title: "Home", href: "/" }, 6 | { title: "AI Chat", href: "/chat" }, 7 | ], 8 | }, 9 | ]; 10 | 11 | export default Navs; 12 | -------------------------------------------------------------------------------- /hooks/use-mounted.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | export function useMounted() { 4 | const [mounted, setMounted] = React.useState(false) 5 | 6 | React.useEffect(() => { 7 | setMounted(true) 8 | }, []) 9 | 10 | return mounted 11 | } 12 | -------------------------------------------------------------------------------- /lib/navigation.ts: -------------------------------------------------------------------------------- 1 | import { getModelById } from "./utils"; 2 | import { DEFAULT_MODEL_NAME, models } from "@/ai/models"; 3 | 4 | export const navigation = [ 5 | { title: "Home", href: "/" }, 6 | { title: "AI Chat", href: `/chat/${getModelById(DEFAULT_MODEL_NAME).path}` }, 7 | ]; 8 | -------------------------------------------------------------------------------- /ai/prompts.ts: -------------------------------------------------------------------------------- 1 | export const systemPrompt = "You are a friendly assistant! Keep your responses concise and helpful."; 2 | 3 | // export const systemPrompt = "do not respond on markdown or lists, keep your responses brief, you can ask the user to upload images or documents if it could help you understand the problem better"; -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 4 | import { type ThemeProviderProps } from "next-themes/dist/types"; 5 | 6 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 7 | return {children}; 8 | } 9 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /app/[locale]/not-found.tsx: -------------------------------------------------------------------------------- 1 | 2 | export default async function NotFound() { 3 | // const data = await getSiteData(domain) 4 | return ( 5 |
6 |

Not Found

7 |

Could not find requested resource

8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | import { defineConfig } from "drizzle-kit"; 3 | 4 | config({ 5 | path: ".env.local" 6 | }); 7 | 8 | export default defineConfig({ 9 | schema: "./db/schema.ts", 10 | out: "./lib/drizzle", 11 | dialect: "postgresql", 12 | dbCredentials: { 13 | url: process.env.POSTGRES_URL!, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /hooks/use-user-message-id.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import useSWR from "swr"; 4 | 5 | export function useUserMessageId() { 6 | const { 7 | data: userMessageIdFromServer, 8 | mutate: setUserMessageIdFromServer, 9 | } = useSWR("userMessageIdFromServer", null); 10 | 11 | return { userMessageIdFromServer, setUserMessageIdFromServer }; 12 | } 13 | -------------------------------------------------------------------------------- /components/layout/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { type ThemeProviderProps } from "next-themes/dist/types"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /components/shared/ai-model-icon.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | "use client"; 3 | 4 | import { Model } from "@/ai/models"; 5 | import { useTheme } from "next-themes"; 6 | 7 | export function AIModelIcon({ model }: { model: Model }) { 8 | const { theme } = useTheme(); 9 | 10 | return ( 11 | {model.label} 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /components/shared/section.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export default function Section({ 6 | className, 7 | children, 8 | }: { 9 | className?: string; 10 | children: ReactNode; 11 | }) { 12 | return ( 13 |
16 | {children} 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /config/site.ts: -------------------------------------------------------------------------------- 1 | export const siteConfig: any = { 2 | name: "Chooat", 3 | url: process.env.NEXT_PUBLIC_URL, 4 | domain: process.env.NEXT_PUBLIC_DOMAIN, 5 | }; 6 | 7 | export const defaultLocale = "en" as const; 8 | export const languages = [ 9 | { lang: "en", label: "English", hrefLang: "en-US" }, 10 | ] as const; 11 | 12 | export const locales = languages.map((lang) => lang.lang); 13 | 14 | export const localePrefix: any = "as-needed"; 15 | export const localeDetection: boolean = false; -------------------------------------------------------------------------------- /i18n/routing.ts: -------------------------------------------------------------------------------- 1 | import { createNavigation, createSharedPathnamesNavigation } from "next-intl/navigation"; 2 | import { defineRouting } from "next-intl/routing"; 3 | import { locales, localePrefix } from "@/config/site"; 4 | 5 | export const routing = defineRouting({ 6 | locales, 7 | defaultLocale: "en", 8 | localePrefix 9 | }); 10 | 11 | export type Locale = (typeof routing.locales)[number]; 12 | 13 | export const { Link, getPathname, redirect, usePathname, useRouter } = 14 | createNavigation(routing); 15 | -------------------------------------------------------------------------------- /app/api/updateUser/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@/app/(auth)/auth"; 2 | import { updateUserById } from "@/db/queries"; 3 | 4 | export async function POST(request: Request) { 5 | const { userData }: { userData: any } = await request.json(); 6 | const session = await auth(); 7 | 8 | if (!session || !session.user) { 9 | return Response.json("Unauthorized!", { status: 401 }); 10 | } 11 | 12 | const userInfo = await updateUserById(session.user.id || "", userData); 13 | return Response.json({user: userInfo}); 14 | } 15 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'bcrypt'; 2 | declare module "mime-types"; 3 | declare module "next-intl"; 4 | declare module 'typography'; 5 | declare module 'react-typography'; 6 | declare module 'md5'; 7 | 8 | declare module globalThis { 9 | var prisma: any; 10 | } 11 | 12 | export type NavItem = { 13 | title: string; 14 | href: string; 15 | badge?: number; 16 | disabled?: boolean; 17 | external?: boolean; 18 | icon?: any; 19 | }; 20 | 21 | export type SidebarNavItem = { 22 | title: string; 23 | items: NavItem[]; 24 | icon?: any; 25 | }; -------------------------------------------------------------------------------- /components/shared/max-width-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export default function MaxWidthWrapper({ 6 | className, 7 | children, 8 | large = false, 9 | }: { 10 | className?: string; 11 | large?: boolean; 12 | children: ReactNode; 13 | }) { 14 | return ( 15 |
22 | {children} 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /app/api/history/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@/app/(auth)/auth"; 2 | import { getChatsByUserId } from "@/db/queries"; 3 | 4 | export async function GET(request: Request) { 5 | const session = await auth(); 6 | const { searchParams } = new URL(request.url); 7 | const app = searchParams.get("app") || ""; 8 | 9 | if (!session || !session.user) { 10 | return Response.json("Unauthorized!", { status: 401 }); 11 | } 12 | 13 | const chats = await getChatsByUserId({ id: session.user.id! }); 14 | return Response.json(chats); 15 | } 16 | -------------------------------------------------------------------------------- /hooks/use-scroll-to-bottom.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "ai"; 2 | import { useEffect, useRef, RefObject } from "react"; 3 | 4 | export function useScrollToBottom(messages: Array): [ 5 | RefObject, 6 | RefObject, 7 | ] { 8 | const containerRef = useRef(null); 9 | const endRef = useRef(null); 10 | 11 | useEffect(() => { 12 | if (messages.length <= 1) return; 13 | endRef.current?.scrollIntoView({ behavior: "smooth", block: "end" }); 14 | }, [messages]); 15 | 16 | return [containerRef, endRef]; 17 | } 18 | -------------------------------------------------------------------------------- /app/[locale]/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | import HeroSection from "@/components/marketing/hero"; 3 | import { getTranslations } from "next-intl/server"; 4 | 5 | export async function generateMetadata({ params }: any) { 6 | const { locale } = await params; 7 | const t = await getTranslations("Home"); 8 | 9 | return { 10 | title: t("Metadata.title"), 11 | description: t("Metadata.description"), 12 | }; 13 | } 14 | 15 | export default async function IndexPage() { 16 | return ( 17 |
18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /components/shared/card-skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Card, 3 | CardContent, 4 | CardFooter, 5 | CardHeader, 6 | } from "@/components/ui/card"; 7 | import { Skeleton } from "@/components/ui/skeleton"; 8 | 9 | export function CardSkeleton() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | import createNextIntlPlugin from 'next-intl/plugin'; 3 | 4 | const withNextIntl = createNextIntlPlugin(); 5 | 6 | const nextConfig: NextConfig = { 7 | /* config options here */ 8 | reactStrictMode: false, 9 | images: { 10 | unoptimized: true, 11 | }, 12 | typescript: { 13 | ignoreBuildErrors: true, 14 | }, 15 | // Configure `pageExtensions` to include MDX files 16 | pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"], 17 | transpilePackages: ['next-mdx-remote'], 18 | }; 19 | 20 | export default withNextIntl(nextConfig); 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /components/social-link.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | const SocialLink = ({ children, url }: any) => { 4 | return ( 5 | 11 | {children} 12 | 13 | ); 14 | }; 15 | 16 | export default SocialLink; 17 | -------------------------------------------------------------------------------- /components/shared/blur-image.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import type { ComponentProps } from "react"; 5 | import Image from "next/image"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | export default function BlurImage(props: ComponentProps) { 10 | const [isLoading, setLoading] = useState(true); 11 | 12 | return ( 13 | {props.alt} setLoading(false)} 22 | /> 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/layout/toogle-theme.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes"; 2 | import { Button } from "@/components/ui/button"; 3 | import { Moon, Sun } from "lucide-react"; 4 | 5 | export const ToggleTheme = () => { 6 | const { theme, setTheme } = useTheme(); 7 | return ( 8 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /ai/index.ts: -------------------------------------------------------------------------------- 1 | import { createGoogleGenerativeAI } from '@ai-sdk/google'; 2 | import { createOpenAI } from '@ai-sdk/openai'; 3 | 4 | const openrouter = createOpenAI({ 5 | name: 'openrouter', 6 | apiKey: process.env.OPENROUTER_API_KEY, 7 | baseURL: "https://openrouter.ai/api/v1", 8 | fetch: async (url, options) => { 9 | return await fetch(url, options); 10 | } 11 | }); 12 | 13 | const google = createGoogleGenerativeAI({ 14 | // custom settings 15 | apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY 16 | }); 17 | 18 | export const customModel = (apiIdentifier: string) => { 19 | if (apiIdentifier.includes("gemini")) { 20 | return google(apiIdentifier); 21 | } else { 22 | return openrouter(apiIdentifier); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /components/modals/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createContext, Dispatch, ReactNode, SetStateAction } from "react"; 4 | 5 | import { useSignInModal } from "@/components/modals/sign-in-modal"; 6 | 7 | export const ModalContext = createContext<{ 8 | setShowSignInModal: Dispatch>; 9 | }>({ 10 | setShowSignInModal: () => {}, 11 | }); 12 | 13 | export default function ModalProvider({ children }: { children: ReactNode }) { 14 | const { SignInModal, setShowSignInModal } = useSignInModal(); 15 | 16 | return ( 17 | 22 | 23 | {children} 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/[locale]/chat/layout.tsx: -------------------------------------------------------------------------------- 1 | import { cookies } from "next/headers"; 2 | import { AppSidebar } from "@/components/app-sidebar"; 3 | import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; 4 | 5 | export const experimental_ppr = true; 6 | 7 | export default async function Layout({ 8 | children, 9 | params, 10 | }: { 11 | children: React.ReactNode; 12 | params: any; 13 | }) { 14 | const cookieStore = await cookies() 15 | const isCollapsed = cookieStore.get("sidebar:state")?.value !== "true"; 16 | 17 | return ( 18 | 19 | 20 | {children} 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |