diff --git a/app/chat/page.tsx b/app/chat/page.tsx index 66921f6..2c71dda 100644 --- a/app/chat/page.tsx +++ b/app/chat/page.tsx @@ -1,6 +1,5 @@ import { Chat } from "@/components/chat"; import { Container } from "@/components/container"; -import { Prose } from "@/components/prose"; export default function ChatPage() { return ( diff --git a/app/globals.css b/app/globals.css index c7357b9..e066805 100644 --- a/app/globals.css +++ b/app/globals.css @@ -6,125 +6,125 @@ @plugin "@tailwindcss/typography"; @theme inline { - --font-heading: var(--font-sans); - --font-sans: var(--font-sans); - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); - --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); - --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); - --color-foreground: var(--foreground); - --color-background: var(--background); - --radius-sm: calc(var(--radius) * 0.6); - --radius-md: calc(var(--radius) * 0.8); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) * 1.4); - --radius-2xl: calc(var(--radius) * 1.8); - --radius-3xl: calc(var(--radius) * 2.2); - --radius-4xl: calc(var(--radius) * 2.6); + --font-heading: var(--font-sans); + --font-sans: var(--font-sans); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); } :root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.514 0.222 16.935); - --primary-foreground: oklch(0.969 0.015 12.422); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.81 0.117 11.638); - --chart-2: oklch(0.645 0.246 16.439); - --chart-3: oklch(0.586 0.253 17.585); - --chart-4: oklch(0.514 0.222 16.935); - --chart-5: oklch(0.455 0.188 13.697); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.586 0.253 17.585); - --sidebar-primary-foreground: oklch(0.969 0.015 12.422); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.514 0.222 16.935); + --primary-foreground: oklch(0.969 0.015 12.422); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.81 0.117 11.638); + --chart-2: oklch(0.645 0.246 16.439); + --chart-3: oklch(0.586 0.253 17.585); + --chart-4: oklch(0.514 0.222 16.935); + --chart-5: oklch(0.455 0.188 13.697); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.586 0.253 17.585); + --sidebar-primary-foreground: oklch(0.969 0.015 12.422); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); } .dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.455 0.188 13.697); - --primary-foreground: oklch(0.969 0.015 12.422); - --secondary: oklch(0.274 0.006 286.033); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.81 0.117 11.638); - --chart-2: oklch(0.645 0.246 16.439); - --chart-3: oklch(0.586 0.253 17.585); - --chart-4: oklch(0.514 0.222 16.935); - --chart-5: oklch(0.455 0.188 13.697); - --sidebar: oklch(0.21 0.006 285.885); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.645 0.246 16.439); - --sidebar-primary-foreground: oklch(0.969 0.015 12.422); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.455 0.188 13.697); + --primary-foreground: oklch(0.969 0.015 12.422); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.81 0.117 11.638); + --chart-2: oklch(0.645 0.246 16.439); + --chart-3: oklch(0.586 0.253 17.585); + --chart-4: oklch(0.514 0.222 16.935); + --chart-5: oklch(0.455 0.188 13.697); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.645 0.246 16.439); + --sidebar-primary-foreground: oklch(0.969 0.015 12.422); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); } @layer base { * { @apply border-border outline-ring/50; - } + } body { @apply bg-background text-foreground; - } + } html { @apply font-sans; - } -} \ No newline at end of file + } +} diff --git a/biome.json b/biome.json index d2ead4c..a1ae92a 100644 --- a/biome.json +++ b/biome.json @@ -19,7 +19,8 @@ "rules": { "recommended": true, "suspicious": { - "noUnknownAtRules": "off" + "noUnknownAtRules": "off", + "noArrayIndexKey": "off" }, "complexity": { "noStaticOnlyClass": "off" diff --git a/components/chat/chat-error.tsx b/components/chat/chat-error.tsx new file mode 100644 index 0000000..a9edcf4 --- /dev/null +++ b/components/chat/chat-error.tsx @@ -0,0 +1,14 @@ +import { AlertTriangleIcon } from "lucide-react"; +import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; + +export function ChatError({ error }: { error?: Error }) { + if (!error) return null; + + return ( + + + {error.name} + {error.message} + + ); +} diff --git a/components/chat/chat-form.tsx b/components/chat/chat-form.tsx index 1af29b4..c127d00 100644 --- a/components/chat/chat-form.tsx +++ b/components/chat/chat-form.tsx @@ -1,7 +1,7 @@ -import { useState, type SubmitEvent } from "react"; +import { ArrowUpIcon } from "lucide-react"; +import { type SubmitEvent, useState } from "react"; import { Button } from "../ui/button"; import { Textarea } from "../ui/textarea"; -import { ArrowUpIcon, SendIcon } from "lucide-react"; interface ChatFormProps { onMessage: (message: { text: string }) => void; diff --git a/components/chat/chat-messages.tsx b/components/chat/chat-messages.tsx index def90fe..1cbd099 100644 --- a/components/chat/chat-messages.tsx +++ b/components/chat/chat-messages.tsx @@ -1,5 +1,5 @@ +import type { UIMessage } from "ai"; import { cn } from "@/lib/utils"; -import { UIMessage } from "ai"; interface ChatMessagesProps { messages: UIMessage[]; diff --git a/components/chat/index.tsx b/components/chat/index.tsx index 07082ef..566c38d 100644 --- a/components/chat/index.tsx +++ b/components/chat/index.tsx @@ -1,9 +1,10 @@ "use client"; -import { ChatMessages } from "./chat-messages"; -import { ChatForm } from "./chat-form"; -import { DefaultChatTransport } from "ai"; import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; +import { ChatError } from "./chat-error"; +import { ChatForm } from "./chat-form"; +import { ChatMessages } from "./chat-messages"; export function Chat() { const { messages, sendMessage, status, error } = useChat({ @@ -13,7 +14,8 @@ export function Chat() { }); return ( -
+
+ svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground", + className, + )} + {...props} + /> + ); +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Alert, AlertTitle, AlertDescription, AlertAction }; diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 6138844..188ca1f 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -1,8 +1,8 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { Slot } from "radix-ui" +import { cva, type VariantProps } from "class-variance-authority"; +import { Slot } from "radix-ui"; +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", @@ -38,8 +38,8 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); function Button({ className, @@ -49,9 +49,9 @@ function Button({ ...props }: React.ComponentProps<"button"> & VariantProps & { - asChild?: boolean + asChild?: boolean; }) { - const Comp = asChild ? Slot.Root : "button" + const Comp = asChild ? Slot.Root : "button"; return ( - ) + ); } -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/components/ui/textarea.tsx b/components/ui/textarea.tsx index 04d27f7..9da9eab 100644 --- a/components/ui/textarea.tsx +++ b/components/ui/textarea.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { return ( @@ -8,11 +8,11 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { data-slot="textarea" className={cn( "flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", - className + className, )} {...props} /> - ) + ); } -export { Textarea } +export { Textarea }; diff --git a/lib/utils.ts b/lib/utils.ts index bd0c391..365058c 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1,6 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); } diff --git a/server/index.ts b/server/index.ts index 83e67ac..e3894f0 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,5 +1,5 @@ import Elysia from "elysia"; -import { health } from "./modules/health"; import { chat } from "./modules/chat"; +import { health } from "./modules/health"; export const app = new Elysia({ prefix: "/api" }).use(health).use(chat); diff --git a/server/modules/chat/service.ts b/server/modules/chat/service.ts index 9c56836..acb8826 100644 --- a/server/modules/chat/service.ts +++ b/server/modules/chat/service.ts @@ -1,6 +1,6 @@ +import { openai } from "@ai-sdk/openai"; import { convertToModelMessages, streamText } from "ai"; import type { ChatModel } from "./model"; -import { openai } from "@ai-sdk/openai"; export abstract class Chat { static async streamText(chat: ChatModel["chat"]) {