diff --git a/.env.example b/.env.example
index 04dc1787..3db95ec1 100644
--- a/.env.example
+++ b/.env.example
@@ -47,7 +47,3 @@ STRIPE_WEBHOOK_SECRET="whsec_"
# found at https://dashboard.stripe.com/test/products
STRIPE_STD_MONTHLY_PRICE_ID="price_"
STRIPE_PRO_MONTHLY_PRICE_ID="price_"
-
-# Optional
-# OpenAI API Key
-OPENAI_API_KEY="sk-"
diff --git a/src/app/(experimental)/ai/_components/chat-message.tsx b/src/app/(experimental)/ai/_components/chat-message.tsx
deleted file mode 100644
index 36093dde..00000000
--- a/src/app/(experimental)/ai/_components/chat-message.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { AvatarIcon } from "@radix-ui/react-icons"
-import { type Message } from "ai"
-import remarkGfm from "remark-gfm"
-import remarkMath from "remark-math"
-
-import { cn } from "@/lib/utils"
-import { Icons } from "@/components/icons"
-
-import { MemoizedReactMarkdown } from "./markdown"
-
-export interface ChatMessageProps {
- message: Message
-}
-
-export function ChatMessage({ message }: ChatMessageProps) {
- return (
-
-
- {message.role === "user" ? (
-
- ) : (
-
- )}
-
-
- {children}
- },
- }}
- >
- {message.content}
-
-
-
- )
-}
diff --git a/src/app/(experimental)/ai/_components/chat-panel.tsx b/src/app/(experimental)/ai/_components/chat-panel.tsx
deleted file mode 100644
index d35a95cc..00000000
--- a/src/app/(experimental)/ai/_components/chat-panel.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { ReloadIcon, StopIcon } from "@radix-ui/react-icons"
-import { type UseChatHelpers } from "ai/react"
-
-import { Button } from "@/components/ui/button"
-
-import { PromptForm } from "./prompt-form"
-import { ScrollToBottomButton } from "./scroll-to-bottom-button"
-
-export interface ChatPanelProps
- extends Pick<
- UseChatHelpers,
- | "append"
- | "isLoading"
- | "reload"
- | "messages"
- | "stop"
- | "input"
- | "setInput"
- > {
- id?: string
-}
-
-export function ChatPanel({
- id,
- isLoading,
- stop,
- append,
- reload,
- input,
- setInput,
- messages,
-}: ChatPanelProps) {
- return (
-
-
-
-
- {isLoading ? (
-
- ) : (
- messages?.length > 0 && (
-
- )
- )}
-
-
{
- await append({
- id,
- content: value,
- role: "user",
- })
- }}
- input={input}
- setInput={setInput}
- isLoading={isLoading}
- />
-
-
- )
-}
diff --git a/src/app/(experimental)/ai/_components/chat-scroll-anchor.tsx b/src/app/(experimental)/ai/_components/chat-scroll-anchor.tsx
deleted file mode 100644
index d3437ced..00000000
--- a/src/app/(experimental)/ai/_components/chat-scroll-anchor.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { useInView } from "react-intersection-observer"
-
-import { useAtBottom } from "@/hooks/use-at-bottom"
-
-interface ChatScrollAnchorProps {
- trackVisibility?: boolean
-}
-
-export function ChatScrollAnchor({ trackVisibility }: ChatScrollAnchorProps) {
- const isAtBottom = useAtBottom()
- const { ref, entry, inView } = useInView({
- trackVisibility,
- delay: 100,
- rootMargin: "0px 0px -150px 0px",
- })
-
- React.useEffect(() => {
- if (isAtBottom && trackVisibility && !inView) {
- entry?.target.scrollIntoView({
- block: "start",
- })
- }
- }, [inView, entry, isAtBottom, trackVisibility])
-
- return
-}
diff --git a/src/app/(experimental)/ai/_components/chat.tsx b/src/app/(experimental)/ai/_components/chat.tsx
deleted file mode 100644
index 0dfbd686..00000000
--- a/src/app/(experimental)/ai/_components/chat.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-"use client"
-
-// Original source: https://github.com/vercel-labs/ai-chatbot/blob/main/lib/hooks/use-at-bottom.tsx
-import { useChat } from "ai/react"
-import { toast } from "sonner"
-
-import { Separator } from "@/components/ui/separator"
-
-import { ChatMessage } from "./chat-message"
-import { ChatPanel } from "./chat-panel"
-import { ChatScrollAnchor } from "./chat-scroll-anchor"
-import { EmptyChatScreen } from "./empty-chat-screen"
-
-export function Chat() {
- const { messages, append, reload, stop, isLoading, input, setInput } =
- useChat({
- onResponse: (response) => {
- console.log(response)
- },
- onError: (error) => {
- toast.error(error.message)
- },
- })
-
- return (
- <>
-
- {messages.length ? (
- <>
- {messages.map((message, i) => (
-
-
- {i < messages.length - 1 ? (
-
- ) : null}
-
- ))}
-
- >
- ) : (
-
- )}
-
-
- >
- )
-}
diff --git a/src/app/(experimental)/ai/_components/code-block.tsx b/src/app/(experimental)/ai/_components/code-block.tsx
deleted file mode 100644
index 4de7dff8..00000000
--- a/src/app/(experimental)/ai/_components/code-block.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-// Original source: https://github.com/vercel-labs/ai-chatbot/blob/main/components/ui/codeblock.tsx
-
-"use client"
-
-import * as React from "react"
-import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
-import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism"
-
-interface Props {
- language: string
- value: string
-}
-
-interface languageMap {
- [key: string]: string | undefined
-}
-
-export const programmingLanguages: languageMap = {
- javascript: ".js",
- python: ".py",
- java: ".java",
- c: ".c",
- cpp: ".cpp",
- "c++": ".cpp",
- "c#": ".cs",
- ruby: ".rb",
- php: ".php",
- swift: ".swift",
- "objective-c": ".m",
- kotlin: ".kt",
- typescript: ".ts",
- go: ".go",
- perl: ".pl",
- rust: ".rs",
- scala: ".scala",
- haskell: ".hs",
- lua: ".lua",
- shell: ".sh",
- sql: ".sql",
- html: ".html",
- css: ".css",
- // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
-}
-
-export const generateRandomString = (length: number, lowercase = false) => {
- const chars = "ABCDEFGHJKLMNPQRSTUVWXY3456789" // excluding similar looking characters like Z, 2, I, 1, O, 0
- let result = ""
- for (let i = 0; i < length; i++) {
- result += chars.charAt(Math.floor(Math.random() * chars.length))
- }
- return lowercase ? result.toLowerCase() : result
-}
-
-const CodeBlock: React.FC = React.memo(({ language, value }) => {
- return (
-
-
- {language}
-
-
- {value}
-
-
- )
-})
-CodeBlock.displayName = "CodeBlock"
-
-export { CodeBlock }
diff --git a/src/app/(experimental)/ai/_components/empty-chat-screen.tsx b/src/app/(experimental)/ai/_components/empty-chat-screen.tsx
deleted file mode 100644
index b009a304..00000000
--- a/src/app/(experimental)/ai/_components/empty-chat-screen.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { type UseChatHelpers } from "ai/react"
-
-import { Button } from "@/components/ui/button"
-import { Card } from "@/components/ui/card"
-import { Shell } from "@/components/shell"
-
-const examples = [
- "Get me the top 5 stories on Hacker News in markdown table format. Use columns like title, link, score, and comments.",
- "Summarize the comments in the top hacker news story.",
- "What is the top story on Hacker News right now?",
-]
-
-type EmptyChatScreenProps = Pick
-
-export function EmptyChatScreen({ setInput }: EmptyChatScreenProps) {
- return (
-
-
- {examples.map((example, i) => (
-
- ))}
-
-
- )
-}
diff --git a/src/app/(experimental)/ai/_components/markdown.tsx b/src/app/(experimental)/ai/_components/markdown.tsx
deleted file mode 100644
index a6edc2f1..00000000
--- a/src/app/(experimental)/ai/_components/markdown.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import * as React from "react"
-import ReactMarkdown, { type Options } from "react-markdown"
-
-export const MemoizedReactMarkdown: React.FC = React.memo(
- ReactMarkdown,
- (prevProps, nextProps) =>
- prevProps.children === nextProps.children &&
- prevProps.className === nextProps.className
-)
diff --git a/src/app/(experimental)/ai/_components/prompt-form.tsx b/src/app/(experimental)/ai/_components/prompt-form.tsx
deleted file mode 100644
index 542a4c22..00000000
--- a/src/app/(experimental)/ai/_components/prompt-form.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import * as React from "react"
-import { PaperPlaneIcon } from "@radix-ui/react-icons"
-import { type UseChatHelpers } from "ai/react"
-
-import { showErrorToast } from "@/lib/handle-error"
-import { useEnterSubmit } from "@/hooks/use-enter-submit"
-import { Button } from "@/components/ui/button"
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip"
-import { Icons } from "@/components/icons"
-import { TextareaAutosize } from "@/components/textarea-autosize"
-
-export interface PromptFormProps
- extends Pick {
- onSubmit: (value: string) => Promise
- isLoading: boolean
-}
-
-export function PromptForm({
- onSubmit,
- input,
- setInput,
- isLoading,
-}: PromptFormProps) {
- const { formRef, onKeyDown } = useEnterSubmit()
- const inputRef = React.useRef(null)
-
- React.useEffect(() => {
- if (!inputRef.current) return
- inputRef.current.focus()
- }, [])
-
- async function onFormSubmit(e: React.FormEvent) {
- try {
- e.preventDefault()
- if (!input?.trim()) return
- setInput("")
- await onSubmit(input)
- } catch (err) {
- showErrorToast(err)
- }
- }
-
- return (
-
- )
-}
diff --git a/src/app/(experimental)/ai/_components/scroll-to-bottom-button.tsx b/src/app/(experimental)/ai/_components/scroll-to-bottom-button.tsx
deleted file mode 100644
index 836871e1..00000000
--- a/src/app/(experimental)/ai/_components/scroll-to-bottom-button.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-"use client"
-
-import { ArrowDownIcon } from "@radix-ui/react-icons"
-
-import { cn } from "@/lib/utils"
-import { useAtBottom } from "@/hooks/use-at-bottom"
-import { Button, type ButtonProps } from "@/components/ui/button"
-
-export function ScrollToBottomButton({ className, ...props }: ButtonProps) {
- const isAtBottom = useAtBottom()
-
- function scrollToBottom(behavior: ScrollBehavior = "auto") {
- window.scrollTo({
- top: document.body.offsetHeight,
- behavior: behavior,
- })
- }
-
- return (
-
- )
-}
diff --git a/src/app/(experimental)/ai/page.tsx b/src/app/(experimental)/ai/page.tsx
deleted file mode 100644
index 9791cc0e..00000000
--- a/src/app/(experimental)/ai/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Chat } from "./_components/chat"
-
-export default function AIPage() {
- return (
-
-
-
- )
-}
diff --git a/src/app/(experimental)/layout.tsx b/src/app/(experimental)/layout.tsx
deleted file mode 100644
index d5d69096..00000000
--- a/src/app/(experimental)/layout.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { getCachedUser } from "@/lib/queries/user"
-import { SiteHeader } from "@/components/layouts/site-header"
-
-export default async function ExperimentalLayout({
- children,
-}: React.PropsWithChildren) {
- const user = await getCachedUser()
-
- return (
-
-
- {children}
-
- )
-}
diff --git a/src/app/icon.png b/src/app/icon.png
new file mode 100644
index 00000000..de496353
Binary files /dev/null and b/src/app/icon.png differ
diff --git a/src/app/icon.tsx b/src/app/icon.tsx
deleted file mode 100644
index 2082357d..00000000
--- a/src/app/icon.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { ImageResponse } from "next/og"
-
-// Route segment config
-export const runtime = "edge"
-
-// Image metadata
-export const size = {
- width: 32,
- height: 32,
-}
-export const contentType = "image/png"
-
-// Image generation
-export default function Icon() {
- return new ImageResponse(
- (
- // ImageResponse JSX element
-
- S
-
- ),
- // ImageResponse options
- {
- // For convenience, we can re-use the exported icons size metadata
- // config to also set the ImageResponse's width and height.
- ...size,
- }
- )
-}
diff --git a/src/env.js b/src/env.js
index 6969396e..2f29d50c 100644
--- a/src/env.js
+++ b/src/env.js
@@ -20,7 +20,6 @@ export const env = createEnv({
STRIPE_WEBHOOK_SECRET: z.string().min(1),
STRIPE_STD_MONTHLY_PRICE_ID: z.string().min(1),
STRIPE_PRO_MONTHLY_PRICE_ID: z.string().min(1),
- OPENAI_API_KEY: z.string().optional(),
},
/**
@@ -59,7 +58,6 @@ export const env = createEnv({
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
STRIPE_STD_MONTHLY_PRICE_ID: process.env.STRIPE_STD_MONTHLY_PRICE_ID,
STRIPE_PRO_MONTHLY_PRICE_ID: process.env.STRIPE_PRO_MONTHLY_PRICE_ID,
- OPENAI_API_KEY: process.env.OPENAI_API_KEY,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially