Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredpalmer committed May 19, 2023
0 parents commit a047762
Show file tree
Hide file tree
Showing 56 changed files with 6,808 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# editorconfig.org
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
5 changes: 5 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist/*
.cache
public
node_modules
*.esm.js
20 changes: 20 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://json.schemastore.org/eslintrc",
"root": true,
"extends": ["next/core-web-vitals", "prettier"],
"rules": {
"@next/next/no-html-link-for-pages": "off",
"react/jsx-key": "off"
},
"settings": {
"next": {
"rootDir": ["./"]
}
},
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"parser": "@typescript-eslint/parser"
}
]
}
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
node_modules
.pnp
.pnp.js

# testing
coverage

# next.js
.next/
out/
build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# turbo
.turbo

.contentlayer
.env
.vercel
12 changes: 12 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cache
.cache
package.json
package-lock.json
public
CHANGELOG.md
.yarn
dist
node_modules
.next
build
.contentlayer
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"typescript.tsdk": "../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Chat
25 changes: 25 additions & 0 deletions app/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use server";

import { getServerSession } from "next-auth";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";

export async function removeChat({ id, path }: { id: string; path: string }) {
const session = await getServerSession(authOptions);
const userId = session?.user?.email;
if (!userId || !id) {
throw new Error("Unauthorized");
}

await prisma.chat.delete({
where: {
id,
// TODO: Add scoping
// userId,
},
});

revalidatePath("/");
revalidatePath("/chat/[id]");
}
18 changes: 18 additions & 0 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import NextAuth, { NextAuthOptions } from "next-auth";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
import GoogleProvider from "next-auth/providers/google";

export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
}),
],
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };
27 changes: 27 additions & 0 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { OpenAIStream, openai } from "@/lib/openai";
import { getServerSession } from "next-auth";

export const runtime = "edge";

export async function POST(req: Request) {
const json = await req.json();
const session = await getServerSession();

const res = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: json.messages,
temperature: 0.7,
top_p: 1,
frequency_penalty: 1,
max_tokens: 500,
n: 1,
stream: true,
});

const stream = await OpenAIStream(res);

return new Response(stream, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}
28 changes: 28 additions & 0 deletions app/chat-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import { type Message } from "@prisma/client";

import { ChatMessage } from "./chat-message";

export interface ChatList {
messages: Message[];
}

export function ChatList({ messages }: ChatList) {
return (
<div className="relative h-full dark:bg-zinc-900">
<div className="h-full w-full overflow-auto">
{messages.length > 0 ? (
<div className="group w-full text-zinc-900 dark:text-white divide-y dark:divide-zinc-800">
{messages.map((message) => (
<ChatMessage
key={message.id || message.content}
message={message}
/>
))}
</div>
) : null}
</div>
</div>
);
}
119 changes: 119 additions & 0 deletions app/chat-message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { type Message } from "@prisma/client";
import CodeBlock from "./codeblock";
import { MemoizedReactMarkdown } from "./markdown";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { cn } from "@/lib/utils";
import { fontMessage } from "@/lib/fonts";
export interface ChatMessageProps {
message: Message;
}

export function ChatMessage(props: ChatMessageProps) {
return (
<div
className={cn(
{
"bg-zinc-50": props.message.role === "assistant",
},
"pr-0 lg:pr-[260px]",
fontMessage.className
)}
>
<div className="m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-xl xl:max-w-3xl">
<div className="flex w-full gap-4 items-start">
{props.message.role === "user" ? (
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gradient-to-r from-cyan-500 to-blue-500 p-2 select-none">
<div
className="font-medium uppercase text-zinc-100"
style={{ fontSize: 6 }}
>
User
</div>
</div>
) : (
<div className="flex h-8 w-8 shrink-0 items-center justify-center select-none bg-zinc-300 rounded-full">
<IconOpenAI className="h-5 w-5" />
</div>
)}
<MemoizedReactMarkdown
className="prose prose-stone prose-base prose-pre:rounded-md w-full flex-1 leading-6 prose-p:leading-[1.8rem] prose-pre:bg-[#282c34] max-w-full"
remarkPlugins={[remarkGfm, remarkMath]}
components={{
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == "▍") {
return (
<span className="mt-1 animate-pulse cursor-default">
</span>
);
}

children[0] = (children[0] as string).replace("`▍`", "▍");
}

const match = /language-(\w+)/.exec(className || "");

return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ""}
value={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
table({ children }) {
return (
<table className="border-collapse border border-black px-3 py-1 ">
{children}
</table>
);
},
th({ children }) {
return (
<th className="break-words border border-black bg-gray-500 px-3 py-1 text-white ">
{children}
</th>
);
},
td({ children }) {
return (
<td className="break-words border border-black px-3 py-1">
{children}
</td>
);
},
}}
>
{props.message.content}
</MemoizedReactMarkdown>
</div>
</div>
</div>
);
}

ChatMessage.displayName = "ChatMessage";

export function IconOpenAI(props: JSX.IntrinsicElements["svg"]) {
return (
<svg
fill="#000000"
width="800px"
height="800px"
viewBox="0 0 24 24"
role="img"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<title>OpenAI icon</title>
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
</svg>
);
}
47 changes: 47 additions & 0 deletions app/chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { type Message } from "@prisma/client";
import { useRouter } from "next/navigation";
import { ChatList } from "./chat-list";
import { Prompt } from "./prompt";
import { usePrompt } from "./use-prompt";

export interface ChatProps {
// create?: (input: string) => Chat | undefined;
messages?: Message[];
id?: string;
}

export function Chat({
id: _id,
// create,
messages,
}: ChatProps) {
const router = useRouter();

const { isLoading, messageList, appendUserMessage, reloadLastMessage } =
usePrompt({
messages,
_id,
// onCreate: (id: string) => {
// router.push(`/chat/${id}`);
// },
});

return (
<main className="transition-width relative min-h-full w-full flex-1 overflow-y-auto flex flex-col">
<div className="flex-1">
<ChatList messages={messageList} />
</div>
<div className="sticky light-gradient dark:bg-gradient-to-b dark:from-zinc-900 dark:to-zinc-950 bottom-0 left-0 w-full border-t bg-white dark:bg-black md:border-t-0 py-4 md:border-transparent md:!bg-transparent md:dark:border-transparent pr-0 lg:pr-[260px] flex dark:border-transparent items-center justify-center">
<Prompt
onSubmit={appendUserMessage}
onRefresh={messageList.length ? reloadLastMessage : undefined}
isLoading={isLoading}
/>
</div>
</main>
);
}

Chat.displayName = "Chat";
Loading

0 comments on commit a047762

Please sign in to comment.