Skip to content

Commit 2e967e9

Browse files
committed
init
1 parent 15c2ad7 commit 2e967e9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+14669
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,10 @@ dist
128128
.yarn/build-state.yml
129129
.yarn/install-state.gz
130130
.pnp.*
131+
132+
*/.env
133+
.env
134+
/build
135+
/src-tauri/target/
136+
/src-tauri/bin/
137+
.DS_Store

app/(chat)/layout.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { SidebarDesktop } from '@/components/sidebar-desktop'
2+
3+
interface ChatLayoutProps {
4+
children: React.ReactNode
5+
}
6+
7+
export default async function ChatLayout({ children }: ChatLayoutProps) {
8+
return (
9+
<div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
10+
<SidebarDesktop />
11+
<div className="group w-full overflow-auto pl-0 animate-in duration-300 ease-in-out peer-[[data-state=open]]:lg:pl-[250px] peer-[[data-state=open]]:xl:pl-[300px]">
12+
{children}
13+
</div>
14+
</div>
15+
)
16+
}

app/(chat)/page.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use client'
2+
import { nanoid } from '@/lib/utils'
3+
import { Chat } from '@/components/chat'
4+
import { useSearchParams } from 'next/navigation'
5+
export default function IndexPage() {
6+
const params = useSearchParams()
7+
return <Chat id={params!.get('cid') || undefined}/>
8+
}

app/favicon.ico

25.3 KB
Binary file not shown.

app/globals.css

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
@layer base {
6+
:root {
7+
--background: 0 0% 100%;
8+
--foreground: 240 10% 3.9%;
9+
10+
--muted: 240 4.8% 95.9%;
11+
--muted-foreground: 240 3.8% 46.1%;
12+
13+
--popover: 0 0% 100%;
14+
--popover-foreground: 240 10% 3.9%;
15+
16+
--card: 0 0% 100%;
17+
--card-foreground: 240 10% 3.9%;
18+
19+
--border: 240 5.9% 90%;
20+
--input: 240 5.9% 90%;
21+
22+
--primary: 240 5.9% 10%;
23+
--primary-foreground: 0 0% 98%;
24+
25+
--secondary: 240 4.8% 95.9%;
26+
--secondary-foreground: 240 5.9% 10%;
27+
28+
--accent: 240 4.8% 95.9%;
29+
--accent-foreground: ;
30+
31+
--destructive: 0 84.2% 60.2%;
32+
--destructive-foreground: 0 0% 98%;
33+
34+
--ring: 240 5% 64.9%;
35+
36+
--radius: 0.5rem;
37+
}
38+
39+
.dark {
40+
--background: 240 10% 3.9%;
41+
--foreground: 0 0% 98%;
42+
43+
--muted: 240 3.7% 15.9%;
44+
--muted-foreground: 240 5% 64.9%;
45+
46+
--popover: 240 10% 3.9%;
47+
--popover-foreground: 0 0% 98%;
48+
49+
--card: 240 10% 3.9%;
50+
--card-foreground: 0 0% 98%;
51+
52+
--border: 240 3.7% 15.9%;
53+
--input: 240 3.7% 15.9%;
54+
55+
--primary: 0 0% 98%;
56+
--primary-foreground: 240 5.9% 10%;
57+
58+
--secondary: 240 3.7% 15.9%;
59+
--secondary-foreground: 0 0% 98%;
60+
61+
--accent: 240 3.7% 15.9%;
62+
--accent-foreground: ;
63+
64+
--destructive: 0 62.8% 30.6%;
65+
--destructive-foreground: 0 85.7% 97.3%;
66+
67+
--ring: 240 3.7% 15.9%;
68+
}
69+
}
70+
71+
@layer base {
72+
* {
73+
@apply border-border;
74+
}
75+
body {
76+
@apply bg-background text-foreground;
77+
}
78+
}

app/layout.tsx

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Toaster } from 'react-hot-toast'
2+
import { GeistSans } from 'geist/font/sans'
3+
import { GeistMono } from 'geist/font/mono'
4+
5+
import '@/app/globals.css'
6+
import { cn } from '@/lib/utils'
7+
import { TailwindIndicator } from '@/components/tailwind-indicator'
8+
import { Providers } from '@/components/providers'
9+
import { Header } from '@/components/header'
10+
11+
export const metadata = {
12+
metadataBase: new URL(`http://${process.env.VERCEL_URL}`),
13+
title: {
14+
default: 'Next.js AI Chatbot',
15+
template: `%s - Next.js AI Chatbot`
16+
},
17+
description: 'An AI-powered chatbot template built with Next.js and Vercel.',
18+
icons: {
19+
icon: '/favicon.ico',
20+
shortcut: '/favicon-16x16.png',
21+
apple: '/apple-touch-icon.png'
22+
}
23+
}
24+
25+
export const viewport = {
26+
themeColor: [
27+
{ media: '(prefers-color-scheme: light)', color: 'white' },
28+
{ media: '(prefers-color-scheme: dark)', color: 'black' }
29+
]
30+
}
31+
32+
interface RootLayoutProps {
33+
children: React.ReactNode
34+
}
35+
36+
export default function RootLayout({ children }: RootLayoutProps) {
37+
return (
38+
<html lang="en" suppressHydrationWarning>
39+
<body
40+
className={cn(
41+
'font-sans antialiased',
42+
GeistSans.variable,
43+
GeistMono.variable
44+
)}
45+
>
46+
<Toaster />
47+
<Providers
48+
attribute="class"
49+
defaultTheme="system"
50+
enableSystem
51+
disableTransitionOnChange
52+
>
53+
<div className="flex flex-col min-h-screen">
54+
<Header />
55+
<main className="flex flex-col flex-1 bg-muted/50">{children}</main>
56+
</div>
57+
<TailwindIndicator />
58+
</Providers>
59+
</body>
60+
</html>
61+
)
62+
}

app/shared/add.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function add(a: number, b: number) {
2+
return {
3+
data: a + b,
4+
};
5+
}
6+
7+
export type AddedData = ReturnType<typeof add>;

build-server.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as esbuild from 'esbuild';
2+
3+
(async () => {
4+
await esbuild.build({
5+
entryPoints: ['./server/server.ts'],
6+
bundle: true,
7+
platform: 'node',
8+
target: ['node20.0'],
9+
outfile: 'build/server.js',
10+
plugins: [],
11+
});
12+
})();

components/AppLayout/index.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
3+
export type AppLayoutProps = {
4+
children?: React.ReactNode;
5+
}
6+
7+
export default function AppLayout(props: AppLayoutProps) {
8+
return (
9+
<div className='app-layout'>
10+
{props.children}
11+
</div>
12+
)
13+
}

components/AppLayout/style.css

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.app-layout {
2+
display: flex;
3+
flex: 1;
4+
width: 100%;
5+
height: 100%;
6+
}

components/HomeBlock/index.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react';
2+
3+
export type HomeBlockProps = {
4+
url: string,
5+
title: string,
6+
description: string,
7+
};
8+
9+
const HomeBlock = (props: HomeBlockProps) => (
10+
<a target="_blank" rel="noreferrer" className='home-block-anchor' href={props.url}>
11+
<div className='home-block'>
12+
<h4 className='home-block-title'>📚 {props.title}</h4>
13+
<p>{props.description}</p>
14+
</div>
15+
</a>
16+
)
17+
18+
export default HomeBlock;

components/HomeBlock/style.css

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.home-block-anchor {
2+
text-decoration: none;
3+
border-radius: 8px;
4+
border: 1px solid #E9EBEE;
5+
padding: 0px 8px;
6+
width: 200px;
7+
height: fit-content;
8+
color: black;
9+
}
10+
11+
.home-block-anchor:hover {
12+
transform: scale(1.1);
13+
background-color: #E5E7EB;
14+
}
15+
16+
.home-block {
17+
display: flex;
18+
flex-direction: column;
19+
}
20+
21+
.home-block-title {
22+
font-size: 18px;
23+
margin: 8px 0px 0px 0px;
24+
}
25+
26+
.home-block-description {
27+
font-size: 16px;
28+
}
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use client'
2+
3+
import * as React from 'react'
4+
5+
import { cn } from '@/lib/utils'
6+
import { useAtBottom } from '@/lib/hooks/use-at-bottom'
7+
import { Button, type ButtonProps } from '@/components/ui/button'
8+
import { IconArrowDown } from '@/components/ui/icons'
9+
10+
export function ButtonScrollToBottom({ className, ...props }: ButtonProps) {
11+
const isAtBottom = useAtBottom()
12+
13+
return (
14+
<Button
15+
variant="outline"
16+
size="icon"
17+
className={cn(
18+
'absolute right-4 top-1 z-10 bg-background transition-opacity duration-300 sm:right-8 md:top-2',
19+
isAtBottom ? 'opacity-0' : 'opacity-100',
20+
className
21+
)}
22+
onClick={() =>
23+
window.scrollTo({
24+
top: document.body.offsetHeight,
25+
behavior: 'smooth'
26+
})
27+
}
28+
{...props}
29+
>
30+
<IconArrowDown />
31+
<span className="sr-only">Scroll to bottom</span>
32+
</Button>
33+
)
34+
}

components/chat-history.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as React from 'react'
2+
3+
import Link from 'next/link'
4+
5+
import { cn } from '@/lib/utils'
6+
import { SidebarList } from '@/components/sidebar-list'
7+
import { buttonVariants } from '@/components/ui/button'
8+
import { IconPlus } from '@/components/ui/icons'
9+
10+
interface ChatHistoryProps {
11+
userId?: string
12+
}
13+
14+
export async function ChatHistory({ userId }: ChatHistoryProps) {
15+
return (
16+
<div className="flex flex-col h-full">
17+
<div className="px-2 my-4">
18+
<Link
19+
href="/"
20+
className={cn(
21+
buttonVariants({ variant: 'outline' }),
22+
'h-10 w-full justify-start bg-zinc-50 px-4 shadow-none transition-colors hover:bg-zinc-200/40 dark:bg-zinc-900 dark:hover:bg-zinc-300/10'
23+
)}
24+
>
25+
<IconPlus className="-translate-x-2 stroke-2" />
26+
New Chat
27+
</Link>
28+
</div>
29+
<React.Suspense
30+
fallback={
31+
<div className="flex flex-col flex-1 px-4 space-y-4 overflow-auto">
32+
{Array.from({ length: 10 }).map((_, i) => (
33+
<div
34+
key={i}
35+
className="w-full h-6 rounded-md shrink-0 animate-pulse bg-zinc-200 dark:bg-zinc-800"
36+
/>
37+
))}
38+
</div>
39+
}
40+
>
41+
{/* @ts-ignore */}
42+
<SidebarList userId={userId} />
43+
</React.Suspense>
44+
</div>
45+
)
46+
}

components/chat-list.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { type Message } from 'ai'
2+
3+
import { Separator } from '@/components/ui/separator'
4+
import { ChatMessage } from '@/components/chat-message'
5+
6+
export interface ChatList {
7+
messages: Message[]
8+
}
9+
10+
export function ChatList({ messages }: ChatList) {
11+
if (!messages.length) {
12+
return null
13+
}
14+
15+
return (
16+
<div className="relative mx-auto max-w-2xl px-4">
17+
{messages.map((message, index) => (
18+
<div key={index}>
19+
<ChatMessage message={message} />
20+
{index < messages.length - 1 && (
21+
<Separator className="my-4 md:my-8" />
22+
)}
23+
</div>
24+
))}
25+
</div>
26+
)
27+
}

0 commit comments

Comments
 (0)