diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts
index fef1de2..07a21f4 100644
--- a/app/api/chat/route.ts
+++ b/app/api/chat/route.ts
@@ -1,10 +1,20 @@
import { auth } from "@/auth";
import { chats, db } from "@/lib/db/schema";
-import { openai } from "@/lib/openai";
import { OpenAIStream, StreamingTextResponse } from "ai-connector";
+import { Configuration, OpenAIApi } from "openai-edge";
export const runtime = "edge";
+const configuration = new Configuration({
+ apiKey: process.env.OPENAI_API_KEY,
+});
+
+const openai = new OpenAIApi(configuration);
+
+if (!process.env.OPENAI_API_KEY) {
+ throw new Error("Missing env var from OpenAI");
+}
+
export const POST = auth(async function POST(req: Request) {
const json = await req.json();
// @ts-ignore
diff --git a/app/chat-message.tsx b/app/chat-message.tsx
index 7713293..8b84e4a 100644
--- a/app/chat-message.tsx
+++ b/app/chat-message.tsx
@@ -1,10 +1,10 @@
-import { type Message } from "@prisma/client";
import CodeBlock from "@/components/ui/codeblock";
import { MemoizedReactMarkdown } from "@/components/markdown";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { cn } from "@/lib/utils";
import { fontMessage } from "@/lib/fonts";
+import { Message } from "ai-connector";
export interface ChatMessageProps {
message: Message;
}
diff --git a/lib/db/index.ts b/lib/db/index.ts
deleted file mode 100644
index 60dd07d..0000000
--- a/lib/db/index.ts
+++ /dev/null
@@ -1,268 +0,0 @@
-/**
- *
- *
- * ## Installation
- *
- * ```bash npm2yarn2pnpm
- * npm install next-auth drizzle-orm @auth/drizzle-adapter
- * npm install drizzle-kit --save-dev
- * ```
- *
- * @module @auth/drizzle-adapter
- */
-import type { Adapter } from "@auth/core/adapters";
-import { and, eq } from "drizzle-orm";
-import type { DbClient, Schema } from "./schema";
-
-/**
- * ## Setup
- *
- * Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object:
- *
- * ```js title="pages/api/auth/[...nextauth].js"
- * import NextAuth from "next-auth"
- * import GoogleProvider from "next-auth/providers/google"
- * import { DrizzleAdapter } from "@auth/drizzle-adapter"
- * import { db } from "./db-schema"
- *
- * export default NextAuth({
- * adapter: DrizzleAdapter(db),
- * providers: [
- * GoogleProvider({
- * clientId: process.env.GOOGLE_CLIENT_ID,
- * clientSecret: process.env.GOOGLE_CLIENT_SECRET,
- * }),
- * ],
- * })
- * ```
- *
- * ## Advanced usage
- *
- * ### Create the Drizzle schema from scratch
- *
- * You'll need to create a database schema that includes the minimal schema for a `next-auth` adapter.
- * Be sure to use the Drizzle driver version that you're using for your project.
- *
- * > This schema is adapted for use in Drizzle and based upon our main [schema](https://authjs.dev/reference/adapters#models)
- *
- *
- * ```json title="db-schema.ts"
- *
- * import { integer, pgTable, text, primaryKey } from 'drizzle-orm/pg-core';
- * import { drizzle } from 'drizzle-orm/node-postgres';
- * import { migrate } from 'drizzle-orm/node-postgres/migrator';
- * import { Pool } from 'pg'
- * import { ProviderType } from 'next-auth/providers';
- *
- * export const users = pgTable('users', {
- * id: text('id').notNull().primaryKey(),
- * name: text('name'),
- * email: text("email").notNull(),
- * emailVerified: integer("emailVerified"),
- * image: text("image"),
- * });
- *
- * export const accounts = pgTable("accounts", {
- * userId: text("userId").notNull().references(() => users.id, { onDelete: "cascade" }),
- * type: text("type").$type().notNull(),
- * provider: text("provider").notNull(),
- * providerAccountId: text("providerAccountId").notNull(),
- * refresh_token: text("refresh_token"),
- * access_token: text("access_token"),
- * expires_at: integer("expires_at"),
- * token_type: text("token_type"),
- * scope: text("scope"),
- * id_token: text("id_token"),
- * session_state: text("session_state"),
- * }, (account) => ({
- * _: primaryKey(account.provider, account.providerAccountId)
- * }))
- *
- * export const sessions = pgTable("sessions", {
- * userId: text("userId").notNull().references(() => users.id, { onDelete: "cascade" }),
- * sessionToken: text("sessionToken").notNull().primaryKey(),
- * expires: integer("expires").notNull(),
- * })
- *
- * export const verificationTokens = pgTable("verificationToken", {
- * identifier: text("identifier").notNull(),
- * token: text("token").notNull(),
- * expires: integer("expires").notNull()
- * }, (vt) => ({
- * _: primaryKey(vt.identifier, vt.token)
- * }))
- *
- * const pool = new Pool({
- * connectionString: "YOUR_CONNECTION_STRING"
- * });
- *
- * export const db = drizzle(pool);
- *
- * migrate(db, { migrationsFolder: "./drizzle" })
- *
- * ```
- *
- **/
-export function PgAdapter(
- client: DbClient,
- { users, sessions, verificationTokens, accounts }: Schema
-): Adapter {
- return {
- createUser: async (data) => {
- return client
- .insert(users)
- .values({ ...data, id: crypto.randomUUID() as string })
- .returning()
- .then((res) => res[0]);
- },
- getUser: async (data) => {
- return (
- client
- .select()
- .from(users)
- .where(eq(users.id, data))
- .then((res) => res[0]) ?? null
- );
- },
- getUserByEmail: async (data) => {
- return (
- client
- .select()
- .from(users)
- .where(eq(users.email, data))
- .then((res) => res[0]) ?? null
- );
- },
- createSession: async (data) => {
- return client
- .insert(sessions)
- .values(data)
- .returning()
- .then((res) => res[0]);
- },
- getSessionAndUser: async (data) => {
- return (
- client
- .select({
- session: sessions,
- user: users,
- })
- .from(sessions)
- .where(eq(sessions.sessionToken, data))
- .innerJoin(users, eq(users.id, sessions.userId))
- .then((res) => res[0]) ?? null
- );
- },
- updateUser: async (data) => {
- if (!data.id) {
- throw new Error("No user id.");
- }
-
- return client
- .update(users)
- .set(data)
- .where(eq(users.id, data.id))
- .returning()
- .then((res) => res[0]);
- },
- updateSession: async (data) => {
- return client
- .update(sessions)
- .set(data)
- .where(eq(sessions.sessionToken, data.sessionToken))
- .returning()
- .then((res) => res[0]);
- },
- linkAccount: async (rawAccount) => {
- const updatedAccount = await client
- .insert(accounts)
- .values(rawAccount)
- .returning()
- .then((res) => res[0]);
-
- // Drizzle will return `null` for fields that are not defined.
- // However, the return type is expecting `undefined`.
- const account = {
- ...updatedAccount,
- access_token: updatedAccount.access_token ?? undefined,
- token_type: updatedAccount.token_type ?? undefined,
- id_token: updatedAccount.id_token ?? undefined,
- refresh_token: updatedAccount.refresh_token ?? undefined,
- scope: updatedAccount.scope ?? undefined,
- expires_at: updatedAccount.expires_at ?? undefined,
- session_state: updatedAccount.session_state ?? undefined,
- };
-
- return account;
- },
- getUserByAccount: async (account) => {
- const dbAccount = await client
- .select()
- .from(accounts)
- .where(
- and(
- eq(accounts.providerAccountId, account.providerAccountId),
- eq(accounts.provider, account.provider)
- )
- )
- .leftJoin(users, eq(accounts.userId, users.id))
- .then((res) => res[0]);
-
- return dbAccount.users;
- },
- deleteSession: async (sessionToken) => {
- await client
- .delete(sessions)
- .where(eq(sessions.sessionToken, sessionToken));
- },
- createVerificationToken: async (token) => {
- return client
- .insert(verificationTokens)
- .values(token)
- .returning()
- .then((res) => res[0]);
- },
- useVerificationToken: async (token) => {
- try {
- return (
- client
- .delete(verificationTokens)
- .where(
- and(
- eq(verificationTokens.identifier, token.identifier),
- eq(verificationTokens.token, token.token)
- )
- )
- .returning()
- .then((res) => res[0]) ?? null
- );
- } catch (err) {
- throw new Error("No verification token found.");
- }
- },
- deleteUser: async (id) => {
- await client
- .delete(users)
- .where(eq(users.id, id))
- .returning()
- .then((res) => res[0]);
- },
- unlinkAccount: async (account) => {
- await client
- .delete(accounts)
- .where(
- and(
- eq(accounts.providerAccountId, account.providerAccountId),
- eq(accounts.provider, account.provider)
- )
- );
-
- return undefined;
- },
- };
-}
diff --git a/lib/hooks/is-modifier-pressed.tsx b/lib/hooks/is-modifier-pressed.tsx
deleted file mode 100644
index 10e3180..0000000
--- a/lib/hooks/is-modifier-pressed.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { isAppleDevice } from "@/lib/tinykeys";
-
-/**
- * Uses the Meta key on macOS, and the Ctrl key on other platforms.
- */
-export const isModifierPressed = (
- event: KeyboardEvent | React.KeyboardEvent
-): boolean => {
- return isAppleDevice() ? event.metaKey : event.ctrlKey;
-};
diff --git a/lib/hooks/use-command-enter-submit.tsx b/lib/hooks/use-command-enter-submit.tsx
index 5c5d75c..bf178c7 100644
--- a/lib/hooks/use-command-enter-submit.tsx
+++ b/lib/hooks/use-command-enter-submit.tsx
@@ -1,6 +1,5 @@
import type { RefObject } from "react";
import { useRef } from "react";
-import { isModifierPressed } from "./is-modifier-pressed";
export function useCmdEnterSubmit(): {
formRef: RefObject;
@@ -11,8 +10,7 @@ export function useCmdEnterSubmit(): {
const handleKeyDown = (
event: React.KeyboardEvent
): void => {
- // Capture `⌘`|Ctrl + Enter
- if (isModifierPressed(event) && event.key === "Enter") {
+ if (event.key === "Enter") {
formRef.current?.requestSubmit();
}
};
diff --git a/lib/openai.ts b/lib/openai.ts
deleted file mode 100644
index a25ea2f..0000000
--- a/lib/openai.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import {
- createParser,
- type ParsedEvent,
- type ReconnectInterval,
-} from "eventsource-parser";
-import { Configuration, OpenAIApi } from "openai-edge";
-
-const configuration = new Configuration({
- apiKey: process.env.OPENAI_API_KEY,
-});
-
-export const openai = new OpenAIApi(configuration);
-
-if (!process.env.OPENAI_API_KEY) {
- throw new Error("Missing env var from OpenAI");
-}
-
-export async function OpenAIStream(res: Response) {
- const encoder = new TextEncoder();
- const decoder = new TextDecoder();
-
- let counter = 0;
-
- const stream = new ReadableStream({
- async start(controller) {
- function onParse(event: ParsedEvent | ReconnectInterval) {
- if (event.type === "event") {
- const data = event.data;
- if (data === "[DONE]") {
- controller.close();
- return;
- }
- try {
- const json = JSON.parse(data) as {
- choices: any;
- };
- const text =
- json.choices[0]?.delta?.content ?? json.choices[0]?.text ?? "";
-
- if (counter < 2 && (text.match(/\n/) || []).length) {
- return;
- }
-
- const queue = encoder.encode(JSON.stringify(text) + "\n");
- controller.enqueue(queue);
- counter++;
- } catch (e) {
- controller.error(e);
- }
- }
- }
-
- const parser = createParser(onParse);
-
- for await (const chunk of res.body as any) {
- parser.feed(decoder.decode(chunk));
- }
- },
- });
-
- return stream;
-}
diff --git a/lib/tinykeys.tsx b/lib/tinykeys.tsx
deleted file mode 100644
index 820d8cb..0000000
--- a/lib/tinykeys.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-// forked from https://github.com/jamiebuilds/tinykeys
-// to fix navigator not being defined in SSR context
-// import { type ModifierKey } from "react";
-
-type ModifierKey = any;
-/*
-
-MIT License
-
-Copyright (c) 2020 Jamie Kyle
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
-*/
-
-type KeyBindingPress = [string[], string];
-
-/**
- * A map of keybinding strings to event handlers.
- */
-export interface KeyBindingMap {
- [keybinding: string]: (event: React.KeyboardEvent) => void;
-}
-
-export interface Options {
- ignoreFocus?: boolean;
-}
-
-/**
- * These are the modifier keys that change the meaning of keybindings.
- *
- * Note: Ignoring "AltGraph" because it is covered by the others.
- */
-let KEYBINDING_MODIFIER_KEYS = ["Shift", "Meta", "Alt", "Control"];
-
-/**
- * Keybinding sequences should timeout if individual key presses are more than
- * 1s apart.
- */
-let TIMEOUT = 1000;
-
-/**
- * When focus is on these elements, ignore the keydown event.
- */
-let inputs = ["select", "textarea", "input"];
-
-export const isAppleDevice = (): boolean => {
- return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
-};
-
-/**
- * Parses a "Key Binding String" into its parts
- *
- * grammar = ``
- * = ` ...`
- * = `` or `+`
- * = `++...`
- */
-function parse(str: string): KeyBindingPress[] {
- const modifierKey = (isAppleDevice() ? "Meta" : "Control") as ModifierKey;
- return str
- .trim()
- .split(" ")
- .map((press) => {
- let mods = press.split("+");
-
- let key = mods.pop()!;
- mods = mods.map((mod) => (mod === "$mod" ? modifierKey : mod));
- return [mods, key];
- });
-}
-
-/**
- * This tells us if a series of events matches a key binding sequence either
- * partially or exactly.
- */
-function match(event: React.KeyboardEvent, press: KeyBindingPress): boolean {
- // prettier-ignore
- return !(
- // Allow either the `event.key` or the `event.code`
- // MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
- // MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
- (
- press[1].toUpperCase() !== event.key.toUpperCase() &&
- press[1] !== event.code
- ) ||
-
- // Ensure all the modifiers in the keybinding are pressed.
- press[0].find((mod) => {
- return !event.getModifierState(mod as ModifierKey)
- }) ||
-
- // KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a
- // keybinding. So if they are pressed but aren't part of this keybinding,
- // then we don't have a match.
- KEYBINDING_MODIFIER_KEYS.find(mod => {
- return !press[0].includes(mod) && event.getModifierState(mod as ModifierKey)
- })
- )
-}
-
-/**
- * Subscribes to keybindings.
- *
- * Returns an unsubscribe method.
- *
- * @example
- * ```js
- * import keybindings from "../src/keybindings"
- *
- * keybindings(window, {
- * "Shift+d": () => {
- * alert("The 'Shift' and 'd' keys were pressed at the same time")
- * },
- * "y e e t": () => {
- * alert("The keys 'y', 'e', 'e', and 't' were pressed in order")
- * },
- * "$mod+d": () => {
- * alert("Either 'Control+d' or 'Meta+d' were pressed")
- * },
- * })
- * ```
- */
-export default function keybindings(
- target: Window | HTMLElement,
- keyBindingMap: KeyBindingMap,
- options: Options = {}
-): () => void {
- const keyBindings = Object.keys(keyBindingMap).map((key) => {
- return [parse(key), keyBindingMap[key]] as const;
- });
-
- const possibleMatches = new Map();
-
- let timer: any = null;
-
- const onKeyDown = (event: React.KeyboardEvent): void => {
- // Ignore modifier keydown events
- // Note: This works because:
- // - non-modifiers will always return false
- // - if the current keypress is a modifier then it will return true when we check its state
- // MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState
- if (
- event.getModifierState &&
- event.getModifierState(event.key as ModifierKey)
- ) {
- return;
- }
-
- // Ignore event when a focusable item is focused
- if (options.ignoreFocus) {
- if (document.activeElement) {
- if (
- inputs.indexOf(document.activeElement.tagName.toLowerCase()) !== -1 ||
- (document.activeElement as HTMLElement).contentEditable === "true"
- ) {
- return;
- }
- }
- }
-
- keyBindings.forEach((keyBinding) => {
- let sequence = keyBinding[0];
-
- let callback = keyBinding[1];
-
- let prev = possibleMatches.get(sequence);
-
- let remainingExpectedPresses = prev ? prev : sequence;
-
- let currentExpectedPress = remainingExpectedPresses[0];
-
- let matches = match(event, currentExpectedPress);
-
- if (!matches) {
- possibleMatches.delete(sequence);
- } else if (remainingExpectedPresses.length > 1) {
- possibleMatches.set(sequence, remainingExpectedPresses.slice(1));
- } else {
- possibleMatches.delete(sequence);
- callback(event);
- }
- });
-
- clearTimeout(timer);
- timer = setTimeout(possibleMatches.clear.bind(possibleMatches), TIMEOUT);
- };
-
- target.addEventListener("keydown", onKeyDown as any);
- return (): void => {
- target.removeEventListener("keydown", onKeyDown as any);
- };
-}
diff --git a/package.json b/package.json
index 52b1db2..f571296 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,6 @@
"common-tags": "^1.8.2",
"cookie": "^0.5.0",
"drizzle-orm": "^0.26.0",
- "eventsource-parser": "^1.0.0",
"focus-trap-react": "^10.1.1",
"framer-motion": "^10.12.4",
"jose": "^4.14.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 02191d7..463b48e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -43,9 +43,6 @@ dependencies:
drizzle-orm:
specifier: ^0.26.0
version: 0.26.0(@vercel/postgres@0.3.0)
- eventsource-parser:
- specifier: ^1.0.0
- version: 1.0.0
focus-trap-react:
specifier: ^10.1.1
version: 10.1.1(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)