├── src
├── components
│ ├── spinner.tsx
│ ├── ui
│ │ ├── aspect-ratio.tsx
│ │ ├── skeleton.tsx
│ │ ├── collapsible.tsx
│ │ ├── label.tsx
│ │ ├── textarea.tsx
│ │ ├── separator.tsx
│ │ ├── progress.tsx
│ │ ├── input.tsx
│ │ ├── toaster.tsx
│ │ ├── checkbox.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── badge.tsx
│ │ ├── tooltip.tsx
│ │ ├── hover-card.tsx
│ │ ├── popover.tsx
│ │ ├── avatar.tsx
│ │ ├── toggle.tsx
│ │ ├── radio-group.tsx
│ │ ├── alert.tsx
│ │ ├── scroll-area.tsx
│ │ ├── toggle-group.tsx
│ │ ├── button.tsx
│ │ ├── tabs.tsx
│ │ ├── card.tsx
│ │ ├── accordion.tsx
│ │ ├── calendar.tsx
│ │ ├── table.tsx
│ │ ├── dialog.tsx
│ │ ├── use-toast.ts
│ │ ├── form.tsx
│ │ ├── sheet.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── toast.tsx
│ │ ├── command.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── select.tsx
│ │ ├── context-menu.tsx
│ │ ├── dropdown-menu.tsx
│ │ └── menubar.tsx
│ ├── memoized-react-markdown.tsx
│ ├── textarea-autosize.tsx
│ ├── image-card.tsx
│ ├── code-dropdown.tsx
│ ├── icons.tsx
│ ├── code-block.tsx
│ └── markdown.tsx
├── app
│ ├── favicon.ico
│ ├── shared
│ │ ├── file-upload.ts
│ │ ├── process-message-stream.ts
│ │ ├── process-polled-steps.ts
│ │ ├── utils.ts
│ │ └── types.ts
│ ├── layout.tsx
│ ├── globals.css
│ ├── assistant-response.ts
│ ├── hooks
│ │ └── use-assistant.ts
│ ├── page.tsx
│ └── api
│ │ └── assistant
│ │ └── route.ts
└── lib
│ └── utils.ts
├── .eslintrc.json
├── plot.png
├── next.config.js
├── postcss.config.js
├── components.json
├── .vscode
├── settings.json
└── launch.json
├── .gitignore
├── tailwind.config.ts
├── public
├── vercel.svg
└── next.svg
├── tsconfig.json
├── README.md
├── package.json
└── tailwind.config.js
/src/components/spinner.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/plot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sullyo/CodeInterpreter/HEAD/plot.png
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sullyo/CodeInterpreter/HEAD/src/app/favicon.ico
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root
6 |
7 | export { AspectRatio }
8 |
--------------------------------------------------------------------------------
/src/app/shared/file-upload.ts:
--------------------------------------------------------------------------------
1 | import { nanoid } from "@/app/shared/utils";
2 | import { put } from "@vercel/blob";
3 |
4 | export async function uploadBlob(input: ArrayBuffer) {
5 | const blob = await put(`${nanoid}-plot.png`, input, {
6 | access: "public",
7 | });
8 |
9 | return blob.url;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/src/components/memoized-react-markdown.tsx:
--------------------------------------------------------------------------------
1 | import { memo, type FC } from "react";
2 | import ReactMarkdown, { type Options } from "react-markdown";
3 |
4 | export const MemoizedReactMarkdown: FC = memo(
5 | ReactMarkdown,
6 | (prevProps, nextProps) =>
7 | prevProps.children === nextProps.children &&
8 | prevProps.className === nextProps.className
9 | );
10 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "tailwindCSS.includeLanguages": {
3 | "plaintext": "html"
4 | },
5 | "tailwindCSS.experimental.classRegex": [
6 | ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
7 | ["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
8 | ["tw=\"([^\"]*)\""]
9 | ],
10 | "tailwindCSS.classAttributes": [
11 | "class",
12 | "className",
13 | "ngClass",
14 | ".*Class.*",
15 | ".*Classes.*"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 | import { Inter } from 'next/font/google'
3 | import './globals.css'
4 |
5 | const inter = Inter({ subsets: ['latin'] })
6 |
7 | export const metadata: Metadata = {
8 | title: 'Create Next App',
9 | description: 'Generated by create next app',
10 | }
11 |
12 | export default function RootLayout({
13 | children,
14 | }: {
15 | children: React.ReactNode
16 | }) {
17 | return (
18 |
19 | {children}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | content: [
5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/shared/process-message-stream.ts:
--------------------------------------------------------------------------------
1 | export async function processMessageStream(
2 | reader: ReadableStreamDefaultReader,
3 | processMessage: (message: string) => void | Promise
4 | ) {
5 | const decoder = new TextDecoder();
6 | let buffer = "";
7 | while (true) {
8 | const { done, value } = await reader.read();
9 |
10 | if (done) {
11 | if (buffer.length > 0) {
12 | processMessage(buffer);
13 | }
14 | break;
15 | }
16 |
17 | buffer += decoder.decode(value, { stream: true });
18 |
19 | let endIndex: number;
20 | while ((endIndex = buffer.indexOf("\n")) !== -1) {
21 | processMessage(buffer.substring(0, endIndex).trim());
22 | buffer = buffer.substring(endIndex + 1); // Remove the processed instruction + delimiter
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/textarea-autosize.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import ReactTextareaAutosize, {
5 | type TextareaAutosizeProps,
6 | } from "react-textarea-autosize";
7 |
8 | import { cn } from "@/lib/utils";
9 |
10 | const TextareaAutosize = React.forwardRef<
11 | HTMLTextAreaElement,
12 | TextareaAutosizeProps & { skeletonClassName?: string }
13 | >(({ className, skeletonClassName, ...props }, ref) => {
14 | return (
15 |
23 | );
24 | });
25 |
26 | TextareaAutosize.displayName = "TextareaAutosize";
27 |
28 | export { TextareaAutosize };
29 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/src/components/image-card.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | /**
3 | * v0 by Vercel.
4 | * @see https://v0.dev/t/fiw0Sm8ha2x
5 | */
6 | import { CardTitle, CardHeader, CardContent, Card } from "@/components/ui/card";
7 |
8 | interface ImagePlotProps {
9 | src: string;
10 | }
11 | export default function ImagePlot({ src }: ImagePlotProps) {
12 | return (
13 |
14 |
15 | Plot
16 |
17 |
18 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Next.js: debug server-side",
6 | "type": "node-terminal",
7 | "request": "launch",
8 | "command": "npm run dev"
9 | },
10 | {
11 | "name": "Debug embed file",
12 | "type": "node-terminal",
13 | "request": "launch",
14 | "command": "npm run embed"
15 | },
16 | {
17 | "name": "Next.js: debug client-side",
18 | "type": "chrome",
19 | "request": "launch",
20 | "url": "http://localhost:3000"
21 | },
22 | {
23 | "name": "Next.js: debug full stack",
24 | "type": "node-terminal",
25 | "request": "launch",
26 | "command": "npm run dev",
27 | "serverReadyAction": {
28 | "pattern": "started server on .+, url: (https?://.+)",
29 | "uriFormat": "%s",
30 | "action": "debugWithChrome"
31 | }
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "@/components/ui/toast"
11 | import { useToast } from "@/components/ui/use-toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | This Code Interpreter using OpenAI Assistants API, Next.js, Vercel Blob and Vercel AI Package
3 |
4 |
5 |
6 | To run this project, you will need the following API keys:
7 |
8 | 1. OPENAI_API_KEY: This is your OpenAI API key. You can obtain it by creating an account on the OpenAI website.
9 |
10 | 2. ASSISTANT_ID: This is the ID of your OpenAI Assistant. You can find this in your OpenAI dashboard under the Assistants section.
11 |
12 | 3. BLOB_READ_WRITE_TOKEN: This is your Vercel Blob read/write token. You can obtain it by creating a Blob storage on the Vercel website.
13 | Setup
14 |
15 | To setup the project, follow these steps:
16 |
17 | 1. Clone the repository to your local machine.
18 | 2. Create a .env.local file in the root directory of the project.
19 | 3. Add the following lines to the .env.local file, replacing with your actual keys:
20 | "
21 |
22 | 4. Install the project dependencies by running npm install or yarn install.
23 | 5. Start the development server by running npm run dev or yarn dev
24 |
25 | Now, you should be able to access the application at http://localhost:3000.
26 | Usage
27 |
28 | This project is not affiliated with Next.js
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SliderPrimitive from "@radix-ui/react-slider"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Slider = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
21 |
22 |
23 |
24 |
25 | ))
26 | Slider.displayName = SliderPrimitive.Root.displayName
27 |
28 | export { Slider }
29 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const HoverCard = HoverCardPrimitive.Root
9 |
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger
11 |
12 | const HoverCardContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
26 | ))
27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
28 |
29 | export { HoverCard, HoverCardTrigger, HoverCardContent }
30 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/code-dropdown.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 |
5 | import { Button } from "@/components/ui/button";
6 | import {
7 | Collapsible,
8 | CollapsibleContent,
9 | CollapsibleTrigger,
10 | } from "@/components/ui/collapsible";
11 | import { ChevronDownIcon } from "@heroicons/react/24/outline";
12 | import Markdown from "@/components/markdown";
13 |
14 | interface CodeInterpreterCollapsibleProps {
15 | code: string;
16 | }
17 |
18 | export function CodeInterpreterCollapsible({
19 | code,
20 | }: CodeInterpreterCollapsibleProps) {
21 | const [isOpen, setIsOpen] = React.useState(false);
22 |
23 | return (
24 |
29 |
30 |
Code Interpreter
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/src/components/icons.tsx:
--------------------------------------------------------------------------------
1 | export const LoadingCircle = () => {
2 | return (
3 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/src/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TogglePrimitive from "@radix-ui/react-toggle"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const toggleVariants = cva(
10 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
11 | {
12 | variants: {
13 | variant: {
14 | default: "bg-transparent",
15 | outline:
16 | "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
17 | },
18 | size: {
19 | default: "h-10 px-3",
20 | sm: "h-9 px-2.5",
21 | lg: "h-11 px-5",
22 | },
23 | },
24 | defaultVariants: {
25 | variant: "default",
26 | size: "default",
27 | },
28 | }
29 | )
30 |
31 | const Toggle = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef &
34 | VariantProps
35 | >(({ className, variant, size, ...props }, ref) => (
36 |
41 | ))
42 |
43 | Toggle.displayName = TogglePrimitive.Root.displayName
44 |
45 | export { Toggle, toggleVariants }
46 |
--------------------------------------------------------------------------------
/src/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5 | import { Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const RadioGroup = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => {
13 | return (
14 |
19 | )
20 | })
21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
22 |
23 | const RadioGroupItem = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => {
27 | return (
28 |
36 |
37 |
38 |
39 |
40 | )
41 | })
42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
43 |
44 | export { RadioGroup, RadioGroupItem }
45 |
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 222.2 84% 4.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 222.2 84% 4.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 222.2 84% 4.9%;
15 |
16 | --primary: 222.2 47.4% 11.2%;
17 | --primary-foreground: 210 40% 98%;
18 |
19 | --secondary: 210 40% 96.1%;
20 | --secondary-foreground: 222.2 47.4% 11.2%;
21 |
22 | --muted: 210 40% 96.1%;
23 | --muted-foreground: 215.4 16.3% 46.9%;
24 |
25 | --accent: 210 40% 96.1%;
26 | --accent-foreground: 222.2 47.4% 11.2%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 210 40% 98%;
30 |
31 | --border: 214.3 31.8% 91.4%;
32 | --input: 214.3 31.8% 91.4%;
33 | --ring: 222.2 84% 4.9%;
34 |
35 | --radius: 0.5rem;
36 | }
37 |
38 | .dark {
39 | --background: 222.2 84% 4.9%;
40 | --foreground: 210 40% 98%;
41 |
42 | --card: 222.2 84% 4.9%;
43 | --card-foreground: 210 40% 98%;
44 |
45 | --popover: 222.2 84% 4.9%;
46 | --popover-foreground: 210 40% 98%;
47 |
48 | --primary: 210 40% 98%;
49 | --primary-foreground: 222.2 47.4% 11.2%;
50 |
51 | --secondary: 217.2 32.6% 17.5%;
52 | --secondary-foreground: 210 40% 98%;
53 |
54 | --muted: 217.2 32.6% 17.5%;
55 | --muted-foreground: 215 20.2% 65.1%;
56 |
57 | --accent: 217.2 32.6% 17.5%;
58 | --accent-foreground: 210 40% 98%;
59 |
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 210 40% 98%;
62 |
63 | --border: 217.2 32.6% 17.5%;
64 | --input: 217.2 32.6% 17.5%;
65 | --ring: 212.7 26.8% 83.9%;
66 | }
67 | }
68 |
69 | @layer base {
70 | * {
71 | @apply border-border;
72 | }
73 | body {
74 | @apply bg-background text-foreground;
75 | }
76 | }
--------------------------------------------------------------------------------
/src/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
5 | import { VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 | import { toggleVariants } from "@/components/ui/toggle"
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: "default",
14 | variant: "default",
15 | })
16 |
17 | const ToggleGroup = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | VariantProps
21 | >(({ className, variant, size, children, ...props }, ref) => (
22 |
27 |
28 | {children}
29 |
30 |
31 | ))
32 |
33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
34 |
35 | const ToggleGroupItem = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef &
38 | VariantProps
39 | >(({ className, children, variant, size, ...props }, ref) => {
40 | const context = React.useContext(ToggleGroupContext)
41 |
42 | return (
43 |
54 | {children}
55 |
56 | )
57 | })
58 |
59 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
60 |
61 | export { ToggleGroup, ToggleGroupItem }
62 |
--------------------------------------------------------------------------------
/src/app/assistant-response.ts:
--------------------------------------------------------------------------------
1 | import { AssistantMessage, AssistantStep } from "@/app/shared/types";
2 | import { getStreamString } from "@/app/shared/utils";
3 |
4 | export function experimental_AssistantResponse(
5 | { threadId, messageId }: { threadId: string; messageId: string },
6 | process: (stream: {
7 | threadId: string;
8 | messageId: string;
9 | sendMessage: (message: AssistantMessage | AssistantStep) => void;
10 | }) => Promise
11 | ): Response {
12 | const stream = new ReadableStream({
13 | async start(controller) {
14 | const textEncoder = new TextEncoder();
15 |
16 | const sendMessage = (message: AssistantMessage | AssistantStep) => {
17 | if (message.type === "step") {
18 | controller.enqueue(
19 | textEncoder.encode(getStreamString("step", message))
20 | );
21 | } else {
22 | controller.enqueue(
23 | textEncoder.encode(getStreamString("text", message))
24 | );
25 | }
26 | };
27 |
28 | const sendError = (errorMessage: string) => {
29 | controller.enqueue(
30 | textEncoder.encode(getStreamString("error", errorMessage))
31 | );
32 | };
33 |
34 | // send the threadId and messageId as the first message:
35 | controller.enqueue(
36 | textEncoder.encode(
37 | getStreamString("control_data", {
38 | threadId,
39 | messageId,
40 | })
41 | )
42 | );
43 |
44 | try {
45 | await process({
46 | threadId,
47 | messageId,
48 | sendMessage,
49 | });
50 | } catch (error) {
51 | sendError((error as any).message ?? `${error}`);
52 | } finally {
53 | controller.close();
54 | }
55 | },
56 | pull(controller) {},
57 | cancel() {},
58 | });
59 |
60 | return new Response(stream, {
61 | status: 200,
62 | headers: {
63 | "Content-Type": "text/plain; charset=utf-8",
64 | },
65 | });
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TabsPrimitive from "@radix-ui/react-tabs"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
5 | import { ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Accordion = AccordionPrimitive.Root
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
20 | ))
21 | AccordionItem.displayName = "AccordionItem"
22 |
23 | const AccordionTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, children, ...props }, ref) => (
27 |
28 | svg]:rotate-180",
32 | className
33 | )}
34 | {...props}
35 | >
36 | {children}
37 |
38 |
39 |
40 | ))
41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42 |
43 | const AccordionContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, children, ...props }, ref) => (
47 |
52 | {children}
53 |
54 | ))
55 |
56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
57 |
58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
59 |
--------------------------------------------------------------------------------
/src/components/code-block.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
4 | import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
5 |
6 | interface Props {
7 | language: string;
8 | value: string;
9 | }
10 |
11 | interface languageMap {
12 | [key: string]: string | undefined;
13 | }
14 |
15 | export const programmingLanguages: languageMap = {
16 | javascript: ".js",
17 | python: ".py",
18 | java: ".java",
19 | c: ".c",
20 | cpp: ".cpp",
21 | "c++": ".cpp",
22 | "c#": ".cs",
23 | ruby: ".rb",
24 | php: ".php",
25 | swift: ".swift",
26 | "objective-c": ".m",
27 | kotlin: ".kt",
28 | typescript: ".ts",
29 | go: ".go",
30 | perl: ".pl",
31 | rust: ".rs",
32 | scala: ".scala",
33 | haskell: ".hs",
34 | lua: ".lua",
35 | shell: ".sh",
36 | sql: ".sql",
37 | html: ".html",
38 | css: ".css",
39 | // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
40 | };
41 |
42 | export const generateRandomString = (length: number, lowercase = false) => {
43 | const chars = "ABCDEFGHJKLMNPQRSTUVWXY3456789"; // excluding similar looking characters like Z, 2, I, 1, O, 0
44 | let result = "";
45 | for (let i = 0; i < length; i++) {
46 | result += chars.charAt(Math.floor(Math.random() * chars.length));
47 | }
48 | return lowercase ? result.toLowerCase() : result;
49 | };
50 |
51 | const CodeBlock = ({ language, value }: Props) => {
52 | return (
53 |
54 |
55 | {language}
56 |
57 |
74 | {value}
75 |
76 |
77 | );
78 | };
79 |
80 | CodeBlock.displayName = "CodeBlock";
81 |
82 | export { CodeBlock };
83 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stock-assistant",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@heroicons/react": "^2.0.18",
13 | "@hookform/resolvers": "^3.3.2",
14 | "@radix-ui/react-accordion": "^1.1.2",
15 | "@radix-ui/react-alert-dialog": "^1.0.5",
16 | "@radix-ui/react-aspect-ratio": "^1.0.3",
17 | "@radix-ui/react-avatar": "^1.0.4",
18 | "@radix-ui/react-checkbox": "^1.0.4",
19 | "@radix-ui/react-collapsible": "^1.0.3",
20 | "@radix-ui/react-context-menu": "^2.1.5",
21 | "@radix-ui/react-dialog": "^1.0.5",
22 | "@radix-ui/react-dropdown-menu": "^2.0.6",
23 | "@radix-ui/react-hover-card": "^1.0.7",
24 | "@radix-ui/react-label": "^2.0.2",
25 | "@radix-ui/react-menubar": "^1.0.4",
26 | "@radix-ui/react-navigation-menu": "^1.1.4",
27 | "@radix-ui/react-popover": "^1.0.7",
28 | "@radix-ui/react-progress": "^1.0.3",
29 | "@radix-ui/react-radio-group": "^1.1.3",
30 | "@radix-ui/react-scroll-area": "^1.0.5",
31 | "@radix-ui/react-select": "^2.0.0",
32 | "@radix-ui/react-separator": "^1.0.3",
33 | "@radix-ui/react-slider": "^1.1.2",
34 | "@radix-ui/react-slot": "^1.0.2",
35 | "@radix-ui/react-switch": "^1.0.3",
36 | "@radix-ui/react-tabs": "^1.0.4",
37 | "@radix-ui/react-toast": "^1.1.5",
38 | "@radix-ui/react-toggle": "^1.0.3",
39 | "@radix-ui/react-toggle-group": "^1.0.4",
40 | "@radix-ui/react-tooltip": "^1.0.7",
41 | "@vercel/blob": "^0.15.0",
42 | "ai": "^2.2.22",
43 | "class-variance-authority": "^0.7.0",
44 | "clsx": "^2.0.0",
45 | "cmdk": "^0.2.0",
46 | "date-fns": "^2.30.0",
47 | "lucide-react": "^0.292.0",
48 | "next": "14.0.2",
49 | "openai": "^4.17.4",
50 | "react": "^18",
51 | "react-day-picker": "^8.9.1",
52 | "react-dom": "^18",
53 | "react-hook-form": "^7.48.2",
54 | "react-markdown": "^9.0.1",
55 | "react-syntax-highlighter": "^15.5.0",
56 | "react-textarea-autosize": "^8.5.3",
57 | "remark-gfm": "^4.0.0",
58 | "tailwind-merge": "^2.0.0",
59 | "tailwindcss-animate": "^1.0.7",
60 | "zod": "^3.22.4"
61 | },
62 | "devDependencies": {
63 | "@types/node": "^20",
64 | "@types/react": "^18",
65 | "@types/react-dom": "^18",
66 | "@types/react-syntax-highlighter": "^15.5.10",
67 | "autoprefixer": "^10.0.1",
68 | "eslint": "^8",
69 | "eslint-config-next": "14.0.2",
70 | "postcss": "^8",
71 | "tailwindcss": "^3.3.0",
72 | "typescript": "^5"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/markdown.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CodeBlock } from "@/components/code-block";
4 | import { MemoizedReactMarkdown } from "@/components/memoized-react-markdown";
5 | import React from "react";
6 | import SyntaxHighlighter from "react-syntax-highlighter";
7 | import remarkGfm from "remark-gfm";
8 |
9 | interface MarkdownProps {
10 | text: string;
11 | }
12 |
13 | export default function Markdown({ text }: MarkdownProps) {
14 | return (
15 | {children};
21 | },
22 | a({ node, href, children, ...props }) {
23 | const childrenArray = React.Children.toArray(children);
24 | const childrenText = childrenArray
25 |
26 | .map((child) => child?.toString() ?? "")
27 | .join("");
28 |
29 | const cleanedText = childrenText.replace(/\[|\]/g, "");
30 | const isNumber = /^\d+$/.test(cleanedText);
31 |
32 | return isNumber ? (
33 |
40 | {children}
41 |
42 | ) : (
43 |
50 | {children}
51 |
52 | );
53 | },
54 |
55 | code(props) {
56 | const { children, className, node, ...rest } = props;
57 | const match = /language-(\w+)/.exec(className || "");
58 | return match ? (
59 |
65 | ) : (
66 |
67 | {children}
68 |
69 | );
70 | },
71 | }}
72 | >
73 | {text}
74 |
75 | );
76 | }
77 |
--------------------------------------------------------------------------------
/src/app/shared/process-polled-steps.ts:
--------------------------------------------------------------------------------
1 | import { openai } from "@/app/api/assistant/route";
2 | import { uploadBlob } from "@/app/shared/file-upload";
3 |
4 | import { AssistantMessage, AssistantStep } from "@/app/shared/types";
5 |
6 | import OpenAI from "openai";
7 | import {
8 | CodeToolCall,
9 | RunStep,
10 | ToolCallsStepDetails,
11 | } from "openai/resources/beta/threads/runs/steps.mjs";
12 |
13 | export async function processSteps(
14 | steps: OpenAI.Beta.Threads.Runs.Steps.RunStepsPage,
15 | sendMessage: (message: AssistantMessage | AssistantStep) => void,
16 | sentMessages: {
17 | code: boolean;
18 | image: boolean;
19 | }
20 | ) {
21 | for (const step of steps.data) {
22 | if (step.type === "tool_calls") {
23 | const toolCallsDetails = step.step_details as ToolCallsStepDetails;
24 | const codeInterpreterCall = toolCallsDetails.tool_calls.find(
25 | (call) => call.type === "code_interpreter"
26 | ) as CodeToolCall | undefined;
27 |
28 | if (codeInterpreterCall) {
29 | const currentInput = codeInterpreterCall.code_interpreter.input.trim();
30 | const imageOutput = codeInterpreterCall.code_interpreter.outputs.find(
31 | (output) => output.type === "image"
32 | ) as CodeToolCall.CodeInterpreter.Image | undefined;
33 |
34 | if (currentInput !== "" && !sentMessages.code) {
35 | handleCodeInterpreterInputChange(step, currentInput, sendMessage);
36 | sentMessages.code = true;
37 | }
38 |
39 | if (imageOutput && !sentMessages.image) {
40 | await handleImageOutputChange(
41 | step,
42 | imageOutput.image.file_id,
43 | sendMessage
44 | );
45 | sentMessages.image = true;
46 | }
47 | }
48 | }
49 | }
50 | return sentMessages;
51 | }
52 |
53 | function handleCodeInterpreterInputChange(
54 | step: RunStep,
55 | input: string,
56 | sendMessage: (message: AssistantMessage | AssistantStep) => void
57 | ): void {
58 | sendMessage({
59 | id: step.id,
60 | type: "step",
61 | content: "```python\n" + input + "\n```",
62 | ui: "code-input",
63 | });
64 | }
65 |
66 | async function handleImageOutputChange(
67 | step: RunStep,
68 | imageFileId: string,
69 | sendMessage: (message: AssistantMessage | AssistantStep) => void
70 | ): Promise {
71 | const response = await openai.files.content(imageFileId);
72 | const data = await response.arrayBuffer();
73 | const url = await uploadBlob(Buffer.from(data));
74 | sendMessage({
75 | id: step.id,
76 | type: "step",
77 | content: url,
78 | ui: "code-output",
79 | });
80 | }
81 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | "./src/pages/**/*.{ts,tsx}",
6 | "./src/components/**/*.{ts,tsx}",
7 | "./src/app/**/*.{ts,tsx}",
8 | "./src/**/*.{ts,tsx}",
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: "2rem",
14 | screens: {
15 | "2xl": "1400px",
16 | },
17 | extend: {
18 | backgroundImage: {
19 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
20 | "gradient-conic":
21 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
22 | },
23 | },
24 | },
25 | extend: {
26 | colors: {
27 | border: "hsl(var(--border))",
28 | input: "hsl(var(--input))",
29 | ring: "hsl(var(--ring))",
30 | background: "hsl(var(--background))",
31 | foreground: "hsl(var(--foreground))",
32 | primary: {
33 | DEFAULT: "hsl(var(--primary))",
34 | foreground: "hsl(var(--primary-foreground))",
35 | },
36 | secondary: {
37 | DEFAULT: "hsl(var(--secondary))",
38 | foreground: "hsl(var(--secondary-foreground))",
39 | },
40 | destructive: {
41 | DEFAULT: "hsl(var(--destructive))",
42 | foreground: "hsl(var(--destructive-foreground))",
43 | },
44 | muted: {
45 | DEFAULT: "hsl(var(--muted))",
46 | foreground: "hsl(var(--muted-foreground))",
47 | },
48 | accent: {
49 | DEFAULT: "hsl(var(--accent))",
50 | foreground: "hsl(var(--accent-foreground))",
51 | },
52 | popover: {
53 | DEFAULT: "hsl(var(--popover))",
54 | foreground: "hsl(var(--popover-foreground))",
55 | },
56 | card: {
57 | DEFAULT: "hsl(var(--card))",
58 | foreground: "hsl(var(--card-foreground))",
59 | },
60 | },
61 | borderRadius: {
62 | lg: "var(--radius)",
63 | md: "calc(var(--radius) - 2px)",
64 | sm: "calc(var(--radius) - 4px)",
65 | },
66 | keyframes: {
67 | "accordion-down": {
68 | from: { height: 0 },
69 | to: { height: "var(--radix-accordion-content-height)" },
70 | },
71 | "accordion-up": {
72 | from: { height: "var(--radix-accordion-content-height)" },
73 | to: { height: 0 },
74 | },
75 | },
76 | animation: {
77 | "accordion-down": "accordion-down 0.2s ease-out",
78 | "accordion-up": "accordion-up 0.2s ease-out",
79 | },
80 | },
81 | },
82 | plugins: [require("tailwindcss-animate")],
83 | };
84 |
--------------------------------------------------------------------------------
/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ChevronLeft, ChevronRight } from "lucide-react"
5 | import { DayPicker } from "react-day-picker"
6 |
7 | import { cn } from "@/lib/utils"
8 | import { buttonVariants } from "@/components/ui/button"
9 |
10 | export type CalendarProps = React.ComponentProps
11 |
12 | function Calendar({
13 | className,
14 | classNames,
15 | showOutsideDays = true,
16 | ...props
17 | }: CalendarProps) {
18 | return (
19 | ,
58 | IconRight: ({ ...props }) => ,
59 | }}
60 | {...props}
61 | />
62 | )
63 | }
64 | Calendar.displayName = "Calendar"
65 |
66 | export { Calendar }
67 |
--------------------------------------------------------------------------------
/src/components/ui/table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Table = React.forwardRef<
6 | HTMLTableElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
16 | ))
17 | Table.displayName = "Table"
18 |
19 | const TableHeader = React.forwardRef<
20 | HTMLTableSectionElement,
21 | React.HTMLAttributes
22 | >(({ className, ...props }, ref) => (
23 |
24 | ))
25 | TableHeader.displayName = "TableHeader"
26 |
27 | const TableBody = React.forwardRef<
28 | HTMLTableSectionElement,
29 | React.HTMLAttributes
30 | >(({ className, ...props }, ref) => (
31 |
36 | ))
37 | TableBody.displayName = "TableBody"
38 |
39 | const TableFooter = React.forwardRef<
40 | HTMLTableSectionElement,
41 | React.HTMLAttributes
42 | >(({ className, ...props }, ref) => (
43 | tr]:last:border-b-0",
47 | className
48 | )}
49 | {...props}
50 | />
51 | ))
52 | TableFooter.displayName = "TableFooter"
53 |
54 | const TableRow = React.forwardRef<
55 | HTMLTableRowElement,
56 | React.HTMLAttributes
57 | >(({ className, ...props }, ref) => (
58 |
66 | ))
67 | TableRow.displayName = "TableRow"
68 |
69 | const TableHead = React.forwardRef<
70 | HTMLTableCellElement,
71 | React.ThHTMLAttributes
72 | >(({ className, ...props }, ref) => (
73 | |
81 | ))
82 | TableHead.displayName = "TableHead"
83 |
84 | const TableCell = React.forwardRef<
85 | HTMLTableCellElement,
86 | React.TdHTMLAttributes
87 | >(({ className, ...props }, ref) => (
88 | |
93 | ))
94 | TableCell.displayName = "TableCell"
95 |
96 | const TableCaption = React.forwardRef<
97 | HTMLTableCaptionElement,
98 | React.HTMLAttributes
99 | >(({ className, ...props }, ref) => (
100 |
105 | ))
106 | TableCaption.displayName = "TableCaption"
107 |
108 | export {
109 | Table,
110 | TableHeader,
111 | TableBody,
112 | TableFooter,
113 | TableHead,
114 | TableRow,
115 | TableCell,
116 | TableCaption,
117 | }
118 |
--------------------------------------------------------------------------------
/src/app/hooks/use-assistant.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | import { Message } from "../shared/types";
4 | import { getStreamStringTypeAndValue } from "@/app/shared/utils";
5 | import { processMessageStream } from "@/app/shared/process-message-stream";
6 |
7 | export type AssistantStatus = "in_progress" | "awaiting_message";
8 |
9 | export function useAssistant_experimental({
10 | api,
11 | threadId: threadIdParam,
12 | }: {
13 | api: string;
14 | threadId?: string | undefined;
15 | }) {
16 | const [messages, setMessages] = useState([]);
17 | const [input, setInput] = useState("");
18 | const [threadId, setThreadId] = useState(undefined);
19 | const [status, setStatus] = useState("awaiting_message");
20 | const [error, setError] = useState(undefined);
21 |
22 | const handleInputChange = (e: any) => {
23 | setInput(e.target.value);
24 | };
25 |
26 | const submitMessage = async (e: any) => {
27 | e.preventDefault();
28 |
29 | if (input === "") {
30 | return;
31 | }
32 |
33 | setStatus("in_progress");
34 |
35 | setMessages((messages) => [
36 | ...messages,
37 | { id: "", role: "user", content: input },
38 | ]);
39 |
40 | setInput("");
41 |
42 | const result = await fetch(api, {
43 | method: "POST",
44 | headers: { "Content-Type": "application/json" },
45 | body: JSON.stringify({
46 | // always use user-provided threadId when available:
47 | threadId: threadIdParam ?? threadId ?? null,
48 | message: input,
49 | }),
50 | });
51 |
52 | if (result.body == null) {
53 | throw new Error("The response body is empty.");
54 | }
55 |
56 | await processMessageStream(result.body.getReader(), (message: string) => {
57 | try {
58 | const { type, value } = getStreamStringTypeAndValue(message);
59 | const messageContent = value as any;
60 |
61 | switch (type) {
62 | case "text": {
63 | // append message:
64 | setMessages((messages) => [
65 | ...messages,
66 | {
67 | id: messageContent.id,
68 | role: messageContent.role,
69 | content: messageContent.content[0].text.value,
70 | },
71 | ]);
72 |
73 | break;
74 | }
75 | case "step": {
76 | // append message:
77 | console.log(value);
78 | setMessages((messages) => [
79 | ...messages,
80 | {
81 | id: messageContent.id,
82 | role: messageContent.role,
83 | content: messageContent.content,
84 | ui: messageContent.ui,
85 | },
86 | ]);
87 |
88 | break;
89 | }
90 |
91 | case "error": {
92 | setError(messageContent);
93 | break;
94 | }
95 | case "control_data": {
96 | setThreadId(messageContent.threadId);
97 |
98 | // set id of last message:
99 | setMessages((messages) => {
100 | const lastMessage = messages[messages.length - 1];
101 | lastMessage.id = messageContent.messageId;
102 | return [...messages.slice(0, messages.length - 1), lastMessage];
103 | });
104 |
105 | break;
106 | }
107 | }
108 | } catch (error) {
109 | setError(error);
110 | }
111 | });
112 |
113 | setStatus("awaiting_message");
114 | };
115 |
116 | return {
117 | messages,
118 | input,
119 | handleInputChange,
120 | submitMessage,
121 | status,
122 | error,
123 | };
124 | }
125 |
--------------------------------------------------------------------------------
/src/app/shared/utils.ts:
--------------------------------------------------------------------------------
1 | import { customAlphabet } from "nanoid/non-secure";
2 | import { JSONValue } from "./types";
3 |
4 | // 7-character random string
5 | export const nanoid = customAlphabet(
6 | "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
7 | 7
8 | );
9 |
10 | // simple decoder signatures:
11 | function createChunkDecoder(): (chunk: Uint8Array | undefined) => string;
12 | function createChunkDecoder(
13 | complex: false
14 | ): (chunk: Uint8Array | undefined) => string;
15 | // complex decoder signature:
16 | function createChunkDecoder(complex: true): (chunk: Uint8Array | undefined) => {
17 | type: keyof typeof StreamStringPrefixes;
18 | value: string;
19 | }[];
20 | // combined signature for when the client calls this function with a boolean:
21 | function createChunkDecoder(complex?: boolean): (
22 | chunk: Uint8Array | undefined
23 | ) =>
24 | | {
25 | type: keyof typeof StreamStringPrefixes;
26 | value: string;
27 | }[]
28 | | string;
29 | function createChunkDecoder(complex?: boolean) {
30 | const decoder = new TextDecoder();
31 |
32 | if (!complex) {
33 | return function (chunk: Uint8Array | undefined): string {
34 | if (!chunk) return "";
35 | return decoder.decode(chunk, { stream: true });
36 | };
37 | }
38 |
39 | return function (chunk: Uint8Array | undefined) {
40 | const decoded = decoder
41 | .decode(chunk, { stream: true })
42 | .split("\n")
43 | .filter((line) => line !== ""); // splitting leaves an empty string at the end
44 |
45 | return decoded.map(getStreamStringTypeAndValue).filter(Boolean) as any;
46 | };
47 | }
48 |
49 | export { createChunkDecoder };
50 |
51 | /**
52 | * The map of prefixes for data in the stream
53 | *
54 | * - 0: Text from the LLM response
55 | * - 1: (OpenAI) function_call responses
56 | * - 2: custom JSON added by the user using `Data`
57 | *
58 | * Example:
59 | * ```
60 | * 0:Vercel
61 | * 0:'s
62 | * 0: AI
63 | * 0: AI
64 | * 0: SDK
65 | * 0: is great
66 | * 0:!
67 | * 2: { "someJson": "value" }
68 | * 1: {"function_call": {"name": "get_current_weather", "arguments": "{\\n\\"location\\": \\"Charlottesville, Virginia\\",\\n\\"format\\": \\"celsius\\"\\n}"}}
69 | *```
70 | */
71 | export const StreamStringPrefixes = {
72 | text: 0,
73 | function_call: 1,
74 | data: 2,
75 | error: 3,
76 | control_data: 4,
77 | step: 5,
78 | } as const;
79 |
80 | export const isStreamStringEqualToType = (
81 | type: keyof typeof StreamStringPrefixes,
82 | value: string
83 | ): value is StreamString =>
84 | value.startsWith(`${StreamStringPrefixes[type]}:`) && value.endsWith("\n");
85 |
86 | /**
87 | * Prepends a string with a prefix from the `StreamChunkPrefixes`, JSON-ifies it, and appends a new line.
88 | */
89 | export const getStreamString = (
90 | type: keyof typeof StreamStringPrefixes,
91 | value: JSONValue
92 | ): StreamString => `${StreamStringPrefixes[type]}:${JSON.stringify(value)}\n`;
93 |
94 | export type StreamString =
95 | `${(typeof StreamStringPrefixes)[keyof typeof StreamStringPrefixes]}:${string}\n`;
96 |
97 | export const getStreamStringTypeAndValue = (
98 | line: string
99 | ): { type: keyof typeof StreamStringPrefixes; value: JSONValue } => {
100 | // const split = line.split(':', 2)
101 | const firstSeperatorIndex = line.indexOf(":");
102 | const prefix = line.slice(0, firstSeperatorIndex);
103 | const type = Object.keys(StreamStringPrefixes).find(
104 | (key) =>
105 | StreamStringPrefixes[key as keyof typeof StreamStringPrefixes] ===
106 | Number(prefix)
107 | ) as keyof typeof StreamStringPrefixes;
108 |
109 | const val = line.slice(firstSeperatorIndex + 1);
110 |
111 | let parsedVal = val;
112 |
113 | if (!val) {
114 | return { type, value: "" };
115 | }
116 |
117 | try {
118 | parsedVal = JSON.parse(val);
119 | } catch (e) {
120 | console.error("Failed to parse JSON value:", val);
121 | }
122 |
123 | return { type, value: parsedVal };
124 | };
125 |
126 | /**
127 | * A header sent to the client so it knows how to handle parsing the stream (as a deprecated text response or using the new prefixed protocol)
128 | */
129 | export const COMPLEX_HEADER = "X-Experimental-Stream-Data";
130 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogClose,
116 | DialogTrigger,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import ReactMarkdown from "react-markdown";
3 | import remarkGfm from "remark-gfm";
4 | import { useAssistant_experimental } from "@/app/hooks/use-assistant";
5 | import { Message } from "ai/react";
6 | import { useEffect, useRef } from "react";
7 | import { PaperAirplaneIcon } from "@heroicons/react/24/outline";
8 | import { cn } from "@/lib/utils";
9 | import { TextareaAutosize } from "@/components/textarea-autosize";
10 | import { ScrollArea } from "@/components/ui/scroll-area";
11 | import { CodeInterpreterCollapsible } from "@/components/code-dropdown";
12 | import ImagePlot from "@/components/image-card";
13 | import { Bot, User } from "lucide-react";
14 | import { LoadingCircle } from "@/components/icons";
15 |
16 | const roleToColorMap: Record = {
17 | system: "red",
18 | user: "black",
19 | function: "blue",
20 | assistant: "green",
21 | };
22 |
23 | export default function Chat() {
24 | const { status, messages, input, submitMessage, handleInputChange, error } =
25 | useAssistant_experimental({
26 | api: "/api/assistant",
27 | });
28 |
29 | const formRef = useRef(null);
30 | // When status changes to accepting messages, focus the input:
31 | const inputRef = useRef(null);
32 | useEffect(() => {
33 | if (status === "awaiting_message") {
34 | inputRef.current?.focus();
35 | }
36 | }, [status]);
37 |
38 | return (
39 |
40 | {error != null && (
41 |
42 |
43 | Error: {(error as any).toString()}
44 |
45 |
46 | )}
47 |
48 | {messages.map((m: Message) => (
49 |
56 |
57 |
63 | {m.role === "user" ? : }
64 |
65 |
66 | {m.ui && m.ui === "code-input" ? (
67 |
68 | ) : m.ui && m.ui === "code-output" ? (
69 |
70 | ) : (
71 |
(
76 |
77 | ),
78 | }}
79 | >
80 | {m.content}
81 |
82 | )}
83 |
84 |
85 | ))}
86 |
87 |
127 |
128 | );
129 | }
130 |
--------------------------------------------------------------------------------
/src/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | // Inspired by react-hot-toast library
2 | import * as React from "react"
3 |
4 | import type {
5 | ToastActionElement,
6 | ToastProps,
7 | } from "@/components/ui/toast"
8 |
9 | const TOAST_LIMIT = 1
10 | const TOAST_REMOVE_DELAY = 1000000
11 |
12 | type ToasterToast = ToastProps & {
13 | id: string
14 | title?: React.ReactNode
15 | description?: React.ReactNode
16 | action?: ToastActionElement
17 | }
18 |
19 | const actionTypes = {
20 | ADD_TOAST: "ADD_TOAST",
21 | UPDATE_TOAST: "UPDATE_TOAST",
22 | DISMISS_TOAST: "DISMISS_TOAST",
23 | REMOVE_TOAST: "REMOVE_TOAST",
24 | } as const
25 |
26 | let count = 0
27 |
28 | function genId() {
29 | count = (count + 1) % Number.MAX_VALUE
30 | return count.toString()
31 | }
32 |
33 | type ActionType = typeof actionTypes
34 |
35 | type Action =
36 | | {
37 | type: ActionType["ADD_TOAST"]
38 | toast: ToasterToast
39 | }
40 | | {
41 | type: ActionType["UPDATE_TOAST"]
42 | toast: Partial
43 | }
44 | | {
45 | type: ActionType["DISMISS_TOAST"]
46 | toastId?: ToasterToast["id"]
47 | }
48 | | {
49 | type: ActionType["REMOVE_TOAST"]
50 | toastId?: ToasterToast["id"]
51 | }
52 |
53 | interface State {
54 | toasts: ToasterToast[]
55 | }
56 |
57 | const toastTimeouts = new Map>()
58 |
59 | const addToRemoveQueue = (toastId: string) => {
60 | if (toastTimeouts.has(toastId)) {
61 | return
62 | }
63 |
64 | const timeout = setTimeout(() => {
65 | toastTimeouts.delete(toastId)
66 | dispatch({
67 | type: "REMOVE_TOAST",
68 | toastId: toastId,
69 | })
70 | }, TOAST_REMOVE_DELAY)
71 |
72 | toastTimeouts.set(toastId, timeout)
73 | }
74 |
75 | export const reducer = (state: State, action: Action): State => {
76 | switch (action.type) {
77 | case "ADD_TOAST":
78 | return {
79 | ...state,
80 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
81 | }
82 |
83 | case "UPDATE_TOAST":
84 | return {
85 | ...state,
86 | toasts: state.toasts.map((t) =>
87 | t.id === action.toast.id ? { ...t, ...action.toast } : t
88 | ),
89 | }
90 |
91 | case "DISMISS_TOAST": {
92 | const { toastId } = action
93 |
94 | // ! Side effects ! - This could be extracted into a dismissToast() action,
95 | // but I'll keep it here for simplicity
96 | if (toastId) {
97 | addToRemoveQueue(toastId)
98 | } else {
99 | state.toasts.forEach((toast) => {
100 | addToRemoveQueue(toast.id)
101 | })
102 | }
103 |
104 | return {
105 | ...state,
106 | toasts: state.toasts.map((t) =>
107 | t.id === toastId || toastId === undefined
108 | ? {
109 | ...t,
110 | open: false,
111 | }
112 | : t
113 | ),
114 | }
115 | }
116 | case "REMOVE_TOAST":
117 | if (action.toastId === undefined) {
118 | return {
119 | ...state,
120 | toasts: [],
121 | }
122 | }
123 | return {
124 | ...state,
125 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
126 | }
127 | }
128 | }
129 |
130 | const listeners: Array<(state: State) => void> = []
131 |
132 | let memoryState: State = { toasts: [] }
133 |
134 | function dispatch(action: Action) {
135 | memoryState = reducer(memoryState, action)
136 | listeners.forEach((listener) => {
137 | listener(memoryState)
138 | })
139 | }
140 |
141 | type Toast = Omit
142 |
143 | function toast({ ...props }: Toast) {
144 | const id = genId()
145 |
146 | const update = (props: ToasterToast) =>
147 | dispatch({
148 | type: "UPDATE_TOAST",
149 | toast: { ...props, id },
150 | })
151 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
152 |
153 | dispatch({
154 | type: "ADD_TOAST",
155 | toast: {
156 | ...props,
157 | id,
158 | open: true,
159 | onOpenChange: (open) => {
160 | if (!open) dismiss()
161 | },
162 | },
163 | })
164 |
165 | return {
166 | id: id,
167 | dismiss,
168 | update,
169 | }
170 | }
171 |
172 | function useToast() {
173 | const [state, setState] = React.useState(memoryState)
174 |
175 | React.useEffect(() => {
176 | listeners.push(setState)
177 | return () => {
178 | const index = listeners.indexOf(setState)
179 | if (index > -1) {
180 | listeners.splice(index, 1)
181 | }
182 | }
183 | }, [state])
184 |
185 | return {
186 | ...state,
187 | toast,
188 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
189 | }
190 | }
191 |
192 | export { useToast, toast }
193 |
--------------------------------------------------------------------------------
/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/src/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 | import { X } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const Sheet = SheetPrimitive.Root
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger
13 |
14 | const SheetClose = SheetPrimitive.Close
15 |
16 | const SheetPortal = SheetPrimitive.Portal
17 |
18 | const SheetOverlay = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, ...props }, ref) => (
22 |
30 | ))
31 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
32 |
33 | const sheetVariants = cva(
34 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
35 | {
36 | variants: {
37 | side: {
38 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39 | bottom:
40 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
42 | right:
43 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
44 | },
45 | },
46 | defaultVariants: {
47 | side: "right",
48 | },
49 | }
50 | )
51 |
52 | interface SheetContentProps
53 | extends React.ComponentPropsWithoutRef,
54 | VariantProps {}
55 |
56 | const SheetContent = React.forwardRef<
57 | React.ElementRef,
58 | SheetContentProps
59 | >(({ side = "right", className, children, ...props }, ref) => (
60 |
61 |
62 |
67 | {children}
68 |
69 |
70 | Close
71 |
72 |
73 |
74 | ))
75 | SheetContent.displayName = SheetPrimitive.Content.displayName
76 |
77 | const SheetHeader = ({
78 | className,
79 | ...props
80 | }: React.HTMLAttributes) => (
81 |
88 | )
89 | SheetHeader.displayName = "SheetHeader"
90 |
91 | const SheetFooter = ({
92 | className,
93 | ...props
94 | }: React.HTMLAttributes) => (
95 |
102 | )
103 | SheetFooter.displayName = "SheetFooter"
104 |
105 | const SheetTitle = React.forwardRef<
106 | React.ElementRef,
107 | React.ComponentPropsWithoutRef
108 | >(({ className, ...props }, ref) => (
109 |
114 | ))
115 | SheetTitle.displayName = SheetPrimitive.Title.displayName
116 |
117 | const SheetDescription = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, ...props }, ref) => (
121 |
126 | ))
127 | SheetDescription.displayName = SheetPrimitive.Description.displayName
128 |
129 | export {
130 | Sheet,
131 | SheetPortal,
132 | SheetOverlay,
133 | SheetTrigger,
134 | SheetClose,
135 | SheetContent,
136 | SheetHeader,
137 | SheetFooter,
138 | SheetTitle,
139 | SheetDescription,
140 | }
141 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5 |
6 | import { cn } from "@/lib/utils"
7 | import { buttonVariants } from "@/components/ui/button"
8 |
9 | const AlertDialog = AlertDialogPrimitive.Root
10 |
11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12 |
13 | const AlertDialogPortal = AlertDialogPrimitive.Portal
14 |
15 | const AlertDialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29 |
30 | const AlertDialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, ...props }, ref) => (
34 |
35 |
36 |
44 |
45 | ))
46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47 |
48 | const AlertDialogHeader = ({
49 | className,
50 | ...props
51 | }: React.HTMLAttributes) => (
52 |
59 | )
60 | AlertDialogHeader.displayName = "AlertDialogHeader"
61 |
62 | const AlertDialogFooter = ({
63 | className,
64 | ...props
65 | }: React.HTMLAttributes) => (
66 |
73 | )
74 | AlertDialogFooter.displayName = "AlertDialogFooter"
75 |
76 | const AlertDialogTitle = React.forwardRef<
77 | React.ElementRef,
78 | React.ComponentPropsWithoutRef
79 | >(({ className, ...props }, ref) => (
80 |
85 | ))
86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87 |
88 | const AlertDialogDescription = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
97 | ))
98 | AlertDialogDescription.displayName =
99 | AlertDialogPrimitive.Description.displayName
100 |
101 | const AlertDialogAction = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112 |
113 | const AlertDialogCancel = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
126 | ))
127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
128 |
129 | export {
130 | AlertDialog,
131 | AlertDialogPortal,
132 | AlertDialogOverlay,
133 | AlertDialogTrigger,
134 | AlertDialogContent,
135 | AlertDialogHeader,
136 | AlertDialogFooter,
137 | AlertDialogTitle,
138 | AlertDialogDescription,
139 | AlertDialogAction,
140 | AlertDialogCancel,
141 | }
142 |
--------------------------------------------------------------------------------
/src/app/api/assistant/route.ts:
--------------------------------------------------------------------------------
1 | import { experimental_AssistantResponse } from "@/app/assistant-response";
2 | import { processSteps } from "@/app/shared/process-polled-steps";
3 | import OpenAI from "openai";
4 | import { MessageContentText } from "openai/resources/beta/threads/messages/messages";
5 |
6 | export const openai = new OpenAI({
7 | apiKey: process.env.OPENAI_API_KEY || "",
8 | });
9 |
10 | const homeTemperatures = {
11 | bedroom: 20,
12 | "home office": 21,
13 | "living room": 21,
14 | kitchen: 22,
15 | bathroom: 23,
16 | };
17 |
18 | export async function POST(req: Request) {
19 | // Parse the request body
20 | const input: {
21 | threadId: string | null;
22 | message: string;
23 | } = await req.json();
24 |
25 | const sentMessages = {
26 | code: false,
27 | image: false,
28 | };
29 |
30 | // Create a thread if needed
31 | const threadId = input.threadId ?? (await openai.beta.threads.create({})).id;
32 |
33 | // Add a message to the thread
34 | const createdMessage = await openai.beta.threads.messages.create(threadId, {
35 | role: "user",
36 | content: input.message,
37 | });
38 |
39 | return experimental_AssistantResponse(
40 | { threadId, messageId: createdMessage.id },
41 | async ({ threadId, sendMessage }) => {
42 | // Run the assistant on the thread
43 | const run = await openai.beta.threads.runs.create(threadId, {
44 | assistant_id:
45 | process.env.ASSISTANT_ID ??
46 | (() => {
47 | throw new Error("ASSISTANT_ID is not set");
48 | })(),
49 | });
50 |
51 | async function waitForRun(run: OpenAI.Beta.Threads.Runs.Run) {
52 | // Poll for status change
53 | while (run.status === "queued" || run.status === "in_progress") {
54 | // delay for 500ms:
55 | await new Promise((resolve) => setTimeout(resolve, 500));
56 |
57 | run = await openai.beta.threads.runs.retrieve(threadId!, run.id);
58 | const step = await openai.beta.threads.runs.steps.list(
59 | threadId!,
60 | run.id
61 | );
62 | const messagesSent = await processSteps(
63 | step,
64 | sendMessage,
65 | sentMessages
66 | );
67 | sentMessages.code = messagesSent.code;
68 | sentMessages.image = messagesSent.image;
69 | }
70 |
71 | // Check the run status
72 | if (
73 | run.status === "cancelled" ||
74 | run.status === "cancelling" ||
75 | run.status === "failed" ||
76 | run.status === "expired"
77 | ) {
78 | throw new Error(run.status);
79 | }
80 |
81 | if (run.status === "requires_action") {
82 | if (run.required_action?.type === "submit_tool_outputs") {
83 | const tool_outputs =
84 | run.required_action.submit_tool_outputs.tool_calls.map(
85 | (toolCall) => {
86 | const parameters = JSON.parse(toolCall.function.arguments);
87 |
88 | switch (toolCall.function.name) {
89 | case "getRoomTemperature": {
90 | const temperature =
91 | homeTemperatures[
92 | parameters.room as keyof typeof homeTemperatures
93 | ];
94 |
95 | return {
96 | tool_call_id: toolCall.id,
97 | output: temperature.toString(),
98 | };
99 | }
100 |
101 | case "setRoomTemperature": {
102 | homeTemperatures[
103 | parameters.room as keyof typeof homeTemperatures
104 | ] = parameters.temperature;
105 |
106 | return {
107 | tool_call_id: toolCall.id,
108 | output: `temperature set successfully`,
109 | };
110 | }
111 |
112 | default:
113 | throw new Error(
114 | `Unknown tool call function: ${toolCall.function.name}`
115 | );
116 | }
117 | }
118 | );
119 |
120 | run = await openai.beta.threads.runs.submitToolOutputs(
121 | threadId!,
122 | run.id,
123 | { tool_outputs }
124 | );
125 |
126 | await waitForRun(run);
127 | }
128 | }
129 | }
130 |
131 | await waitForRun(run);
132 |
133 | // Get new thread messages (after our message)
134 | const responseMessages = (
135 | await openai.beta.threads.messages.list(threadId, {
136 | after: createdMessage.id,
137 | order: "asc",
138 | })
139 | ).data;
140 |
141 | // Send the messages
142 | for (const message of responseMessages) {
143 | sendMessage({
144 | id: message.id,
145 | type: "message",
146 | role: "assistant",
147 | content: message.content.filter(
148 | (content) => content.type === "text"
149 | ) as Array,
150 | });
151 | }
152 | }
153 | );
154 | }
155 |
--------------------------------------------------------------------------------
/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ToastPrimitives from "@radix-ui/react-toast"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 | import { X } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ToastProvider = ToastPrimitives.Provider
9 |
10 | const ToastViewport = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
24 |
25 | const toastVariants = cva(
26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
27 | {
28 | variants: {
29 | variant: {
30 | default: "border bg-background text-foreground",
31 | destructive:
32 | "destructive group border-destructive bg-destructive text-destructive-foreground",
33 | },
34 | },
35 | defaultVariants: {
36 | variant: "default",
37 | },
38 | }
39 | )
40 |
41 | const Toast = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef &
44 | VariantProps
45 | >(({ className, variant, ...props }, ref) => {
46 | return (
47 |
52 | )
53 | })
54 | Toast.displayName = ToastPrimitives.Root.displayName
55 |
56 | const ToastAction = React.forwardRef<
57 | React.ElementRef,
58 | React.ComponentPropsWithoutRef
59 | >(({ className, ...props }, ref) => (
60 |
68 | ))
69 | ToastAction.displayName = ToastPrimitives.Action.displayName
70 |
71 | const ToastClose = React.forwardRef<
72 | React.ElementRef,
73 | React.ComponentPropsWithoutRef
74 | >(({ className, ...props }, ref) => (
75 |
84 |
85 |
86 | ))
87 | ToastClose.displayName = ToastPrimitives.Close.displayName
88 |
89 | const ToastTitle = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => (
93 |
98 | ))
99 | ToastTitle.displayName = ToastPrimitives.Title.displayName
100 |
101 | const ToastDescription = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | ToastDescription.displayName = ToastPrimitives.Description.displayName
112 |
113 | type ToastProps = React.ComponentPropsWithoutRef
114 |
115 | type ToastActionElement = React.ReactElement
116 |
117 | export {
118 | type ToastProps,
119 | type ToastActionElement,
120 | ToastProvider,
121 | ToastViewport,
122 | Toast,
123 | ToastTitle,
124 | ToastDescription,
125 | ToastClose,
126 | ToastAction,
127 | }
128 |
--------------------------------------------------------------------------------
/src/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { type DialogProps } from "@radix-ui/react-dialog"
5 | import { Command as CommandPrimitive } from "cmdk"
6 | import { Search } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 | import { Dialog, DialogContent } from "@/components/ui/dialog"
10 |
11 | const Command = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
23 | ))
24 | Command.displayName = CommandPrimitive.displayName
25 |
26 | interface CommandDialogProps extends DialogProps {}
27 |
28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
29 | return (
30 |
37 | )
38 | }
39 |
40 | const CommandInput = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
45 |
46 |
54 |
55 | ))
56 |
57 | CommandInput.displayName = CommandPrimitive.Input.displayName
58 |
59 | const CommandList = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, ...props }, ref) => (
63 |
68 | ))
69 |
70 | CommandList.displayName = CommandPrimitive.List.displayName
71 |
72 | const CommandEmpty = React.forwardRef<
73 | React.ElementRef,
74 | React.ComponentPropsWithoutRef
75 | >((props, ref) => (
76 |
81 | ))
82 |
83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
84 |
85 | const CommandGroup = React.forwardRef<
86 | React.ElementRef,
87 | React.ComponentPropsWithoutRef
88 | >(({ className, ...props }, ref) => (
89 |
97 | ))
98 |
99 | CommandGroup.displayName = CommandPrimitive.Group.displayName
100 |
101 | const CommandSeparator = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
112 |
113 | const CommandItem = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
125 | ))
126 |
127 | CommandItem.displayName = CommandPrimitive.Item.displayName
128 |
129 | const CommandShortcut = ({
130 | className,
131 | ...props
132 | }: React.HTMLAttributes) => {
133 | return (
134 |
141 | )
142 | }
143 | CommandShortcut.displayName = "CommandShortcut"
144 |
145 | export {
146 | Command,
147 | CommandDialog,
148 | CommandInput,
149 | CommandList,
150 | CommandEmpty,
151 | CommandGroup,
152 | CommandItem,
153 | CommandShortcut,
154 | CommandSeparator,
155 | }
156 |
--------------------------------------------------------------------------------
/src/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
3 | import { cva } from "class-variance-authority"
4 | import { ChevronDown } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const NavigationMenu = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
20 | {children}
21 |
22 |
23 | ))
24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
25 |
26 | const NavigationMenuList = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, ...props }, ref) => (
30 |
38 | ))
39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
40 |
41 | const NavigationMenuItem = NavigationMenuPrimitive.Item
42 |
43 | const navigationMenuTriggerStyle = cva(
44 | "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
45 | )
46 |
47 | const NavigationMenuTrigger = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, children, ...props }, ref) => (
51 |
56 | {children}{" "}
57 |
61 |
62 | ))
63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
64 |
65 | const NavigationMenuContent = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
77 | ))
78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
79 |
80 | const NavigationMenuLink = NavigationMenuPrimitive.Link
81 |
82 | const NavigationMenuViewport = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
87 |
95 |
96 | ))
97 | NavigationMenuViewport.displayName =
98 | NavigationMenuPrimitive.Viewport.displayName
99 |
100 | const NavigationMenuIndicator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
112 |
113 |
114 | ))
115 | NavigationMenuIndicator.displayName =
116 | NavigationMenuPrimitive.Indicator.displayName
117 |
118 | export {
119 | navigationMenuTriggerStyle,
120 | NavigationMenu,
121 | NavigationMenuList,
122 | NavigationMenuItem,
123 | NavigationMenuContent,
124 | NavigationMenuTrigger,
125 | NavigationMenuLink,
126 | NavigationMenuIndicator,
127 | NavigationMenuViewport,
128 | }
129 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SelectPrimitive from "@radix-ui/react-select"
5 | import { Check, ChevronDown, ChevronUp } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Select = SelectPrimitive.Root
10 |
11 | const SelectGroup = SelectPrimitive.Group
12 |
13 | const SelectValue = SelectPrimitive.Value
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 | span]:line-clamp-1",
23 | className
24 | )}
25 | {...props}
26 | >
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectScrollUpButton = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 |
48 |
49 | ))
50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51 |
52 | const SelectScrollDownButton = React.forwardRef<
53 | React.ElementRef,
54 | React.ComponentPropsWithoutRef
55 | >(({ className, ...props }, ref) => (
56 |
64 |
65 |
66 | ))
67 | SelectScrollDownButton.displayName =
68 | SelectPrimitive.ScrollDownButton.displayName
69 |
70 | const SelectContent = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >(({ className, children, position = "popper", ...props }, ref) => (
74 |
75 |
86 |
87 |
94 | {children}
95 |
96 |
97 |
98 |
99 | ))
100 | SelectContent.displayName = SelectPrimitive.Content.displayName
101 |
102 | const SelectLabel = React.forwardRef<
103 | React.ElementRef,
104 | React.ComponentPropsWithoutRef
105 | >(({ className, ...props }, ref) => (
106 |
111 | ))
112 | SelectLabel.displayName = SelectPrimitive.Label.displayName
113 |
114 | const SelectItem = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, children, ...props }, ref) => (
118 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | {children}
133 |
134 | ))
135 | SelectItem.displayName = SelectPrimitive.Item.displayName
136 |
137 | const SelectSeparator = React.forwardRef<
138 | React.ElementRef,
139 | React.ComponentPropsWithoutRef
140 | >(({ className, ...props }, ref) => (
141 |
146 | ))
147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
148 |
149 | export {
150 | Select,
151 | SelectGroup,
152 | SelectValue,
153 | SelectTrigger,
154 | SelectContent,
155 | SelectLabel,
156 | SelectItem,
157 | SelectSeparator,
158 | SelectScrollUpButton,
159 | SelectScrollDownButton,
160 | }
161 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const ContextMenu = ContextMenuPrimitive.Root
10 |
11 | const ContextMenuTrigger = ContextMenuPrimitive.Trigger
12 |
13 | const ContextMenuGroup = ContextMenuPrimitive.Group
14 |
15 | const ContextMenuPortal = ContextMenuPrimitive.Portal
16 |
17 | const ContextMenuSub = ContextMenuPrimitive.Sub
18 |
19 | const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
20 |
21 | const ContextMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
41 |
42 | const ContextMenuSubContent = React.forwardRef<
43 | React.ElementRef,
44 | React.ComponentPropsWithoutRef
45 | >(({ className, ...props }, ref) => (
46 |
54 | ))
55 | ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
56 |
57 | const ContextMenuContent = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
62 |
70 |
71 | ))
72 | ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
73 |
74 | const ContextMenuItem = React.forwardRef<
75 | React.ElementRef,
76 | React.ComponentPropsWithoutRef & {
77 | inset?: boolean
78 | }
79 | >(({ className, inset, ...props }, ref) => (
80 |
89 | ))
90 | ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
91 |
92 | const ContextMenuCheckboxItem = React.forwardRef<
93 | React.ElementRef,
94 | React.ComponentPropsWithoutRef
95 | >(({ className, children, checked, ...props }, ref) => (
96 |
105 |
106 |
107 |
108 |
109 |
110 | {children}
111 |
112 | ))
113 | ContextMenuCheckboxItem.displayName =
114 | ContextMenuPrimitive.CheckboxItem.displayName
115 |
116 | const ContextMenuRadioItem = React.forwardRef<
117 | React.ElementRef,
118 | React.ComponentPropsWithoutRef
119 | >(({ className, children, ...props }, ref) => (
120 |
128 |
129 |
130 |
131 |
132 |
133 | {children}
134 |
135 | ))
136 | ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
137 |
138 | const ContextMenuLabel = React.forwardRef<
139 | React.ElementRef,
140 | React.ComponentPropsWithoutRef & {
141 | inset?: boolean
142 | }
143 | >(({ className, inset, ...props }, ref) => (
144 |
153 | ))
154 | ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
155 |
156 | const ContextMenuSeparator = React.forwardRef<
157 | React.ElementRef,
158 | React.ComponentPropsWithoutRef
159 | >(({ className, ...props }, ref) => (
160 |
165 | ))
166 | ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
167 |
168 | const ContextMenuShortcut = ({
169 | className,
170 | ...props
171 | }: React.HTMLAttributes) => {
172 | return (
173 |
180 | )
181 | }
182 | ContextMenuShortcut.displayName = "ContextMenuShortcut"
183 |
184 | export {
185 | ContextMenu,
186 | ContextMenuTrigger,
187 | ContextMenuContent,
188 | ContextMenuItem,
189 | ContextMenuCheckboxItem,
190 | ContextMenuRadioItem,
191 | ContextMenuLabel,
192 | ContextMenuSeparator,
193 | ContextMenuShortcut,
194 | ContextMenuGroup,
195 | ContextMenuPortal,
196 | ContextMenuSub,
197 | ContextMenuSubContent,
198 | ContextMenuSubTrigger,
199 | ContextMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/src/app/shared/types.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/openai/openai-node/blob/07b3504e1c40fd929f4aae1651b83afc19e3baf8/src/resources/chat/completions.ts#L146-L159
2 | export interface FunctionCall {
3 | /**
4 | * The arguments to call the function with, as generated by the model in JSON
5 | * format. Note that the model does not always generate valid JSON, and may
6 | * hallucinate parameters not defined by your function schema. Validate the
7 | * arguments in your code before calling your function.
8 | */
9 | arguments?: string;
10 |
11 | /**
12 | * The name of the function to call.
13 | */
14 | name?: string;
15 | }
16 |
17 | //
18 | interface Function {
19 | /**
20 | * The name of the function to be called. Must be a-z, A-Z, 0-9, or contain
21 | * underscores and dashes, with a maximum length of 64.
22 | */
23 | name: string;
24 |
25 | /**
26 | * The parameters the functions accepts, described as a JSON Schema object. See the
27 | * [guide](/docs/guides/gpt/function-calling) for examples, and the
28 | * [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for
29 | * documentation about the format.
30 | *
31 | * To describe a function that accepts no parameters, provide the value
32 | * `{"type": "object", "properties": {}}`.
33 | */
34 | parameters: Record;
35 |
36 | /**
37 | * A description of what the function does, used by the model to choose when and
38 | * how to call the function.
39 | */
40 | description?: string;
41 | }
42 |
43 | /**
44 | * Shared types between the API and UI packages.
45 | */
46 | export interface Message {
47 | id: string;
48 | createdAt?: Date;
49 | content: string;
50 | ui?: string | JSX.Element | JSX.Element[] | null | undefined;
51 | role: "system" | "user" | "assistant" | "function";
52 | /**
53 | * If the message has a role of `function`, the `name` field is the name of the function.
54 | * Otherwise, the name field should not be set.
55 | */
56 | name?: string;
57 | /**
58 | * If the assistant role makes a function call, the `function_call` field
59 | * contains the function call name and arguments. Otherwise, the field should
60 | * not be set.
61 | */
62 | function_call?: string | FunctionCall;
63 | }
64 |
65 | export type CreateMessage = Omit & {
66 | id?: Message["id"];
67 | };
68 |
69 | export type ChatRequest = {
70 | messages: Message[];
71 | options?: RequestOptions;
72 | functions?: Array;
73 | function_call?: FunctionCall;
74 | };
75 |
76 | export type FunctionCallHandler = (
77 | chatMessages: Message[],
78 | functionCall: FunctionCall
79 | ) => Promise;
80 |
81 | export type RequestOptions = {
82 | headers?: Record | Headers;
83 | body?: object;
84 | };
85 |
86 | export type ChatRequestOptions = {
87 | options?: RequestOptions;
88 | functions?: Array;
89 | function_call?: FunctionCall;
90 | };
91 |
92 | export type UseChatOptions = {
93 | /**
94 | * The API endpoint that accepts a `{ messages: Message[] }` object and returns
95 | * a stream of tokens of the AI chat response. Defaults to `/api/chat`.
96 | */
97 | api?: string;
98 |
99 | /**
100 | * A unique identifier for the chat. If not provided, a random one will be
101 | * generated. When provided, the `useChat` hook with the same `id` will
102 | * have shared states across components.
103 | */
104 | id?: string;
105 |
106 | /**
107 | * Initial messages of the chat. Useful to load an existing chat history.
108 | */
109 | initialMessages?: Message[];
110 |
111 | /**
112 | * Initial input of the chat.
113 | */
114 | initialInput?: string;
115 |
116 | /**
117 | * Callback function to be called when a function call is received.
118 | * If the function returns a `ChatRequest` object, the request will be sent
119 | * automatically to the API and will be used to update the chat.
120 | */
121 | experimental_onFunctionCall?: FunctionCallHandler;
122 |
123 | /**
124 | * Callback function to be called when the API response is received.
125 | */
126 | onResponse?: (response: Response) => void | Promise;
127 |
128 | /**
129 | * Callback function to be called when the chat is finished streaming.
130 | */
131 | onFinish?: (message: Message) => void;
132 |
133 | /**
134 | * Callback function to be called when an error is encountered.
135 | */
136 | onError?: (error: Error) => void;
137 |
138 | /**
139 | * The credentials mode to be used for the fetch request.
140 | * Possible values are: 'omit', 'same-origin', 'include'.
141 | * Defaults to 'same-origin'.
142 | */
143 | credentials?: RequestCredentials;
144 |
145 | /**
146 | * HTTP headers to be sent with the API request.
147 | */
148 | headers?: Record | Headers;
149 |
150 | /**
151 | * Extra body object to be sent with the API request.
152 | * @example
153 | * Send a `sessionId` to the API along with the messages.
154 | * ```js
155 | * useChat({
156 | * body: {
157 | * sessionId: '123',
158 | * }
159 | * })
160 | * ```
161 | */
162 | body?: object;
163 |
164 | /**
165 | * Whether to send extra message fields such as `message.id` and `message.createdAt` to the API.
166 | * Defaults to `false`. When set to `true`, the API endpoint might need to
167 | * handle the extra fields before forwarding the request to the AI service.
168 | */
169 | sendExtraMessageFields?: boolean;
170 | };
171 |
172 | export type UseCompletionOptions = {
173 | /**
174 | * The API endpoint that accepts a `{ prompt: string }` object and returns
175 | * a stream of tokens of the AI completion response. Defaults to `/api/completion`.
176 | */
177 | api?: string;
178 | /**
179 | * An unique identifier for the chat. If not provided, a random one will be
180 | * generated. When provided, the `useChat` hook with the same `id` will
181 | * have shared states across components.
182 | */
183 | id?: string;
184 |
185 | /**
186 | * Initial prompt input of the completion.
187 | */
188 | initialInput?: string;
189 |
190 | /**
191 | * Initial completion result. Useful to load an existing history.
192 | */
193 | initialCompletion?: string;
194 |
195 | /**
196 | * Callback function to be called when the API response is received.
197 | */
198 | onResponse?: (response: Response) => void | Promise;
199 |
200 | /**
201 | * Callback function to be called when the completion is finished streaming.
202 | */
203 | onFinish?: (prompt: string, completion: string) => void;
204 |
205 | /**
206 | * Callback function to be called when an error is encountered.
207 | */
208 | onError?: (error: Error) => void;
209 |
210 | /**
211 | * The credentials mode to be used for the fetch request.
212 | * Possible values are: 'omit', 'same-origin', 'include'.
213 | * Defaults to 'same-origin'.
214 | */
215 | credentials?: RequestCredentials;
216 |
217 | /**
218 | * HTTP headers to be sent with the API request.
219 | */
220 | headers?: Record | Headers;
221 |
222 | /**
223 | * Extra body object to be sent with the API request.
224 | * @example
225 | * Send a `sessionId` to the API along with the prompt.
226 | * ```js
227 | * useChat({
228 | * body: {
229 | * sessionId: '123',
230 | * }
231 | * })
232 | * ```
233 | */
234 | body?: object;
235 | };
236 |
237 | export type JSONValue =
238 | | null
239 | | string
240 | | number
241 | | boolean
242 | | { [x: string]: JSONValue }
243 | | Array;
244 |
245 | export type AssistantMessage = {
246 | id: string;
247 | type: "message";
248 | role: "assistant";
249 | content: Array<{
250 | type: "text";
251 | text: {
252 | value: string;
253 | };
254 | }>;
255 | };
256 |
257 | export interface StepDetails {
258 | type: string;
259 | message_creation?: {
260 | message_id: string;
261 | };
262 | tool_calls?: Array<{
263 | id: string;
264 | type: string;
265 | code_interpreter?: {
266 | input: string;
267 | outputs: Array<{
268 | type: string;
269 | image?: {
270 | file_id: string;
271 | };
272 | }>;
273 | };
274 | }>;
275 | }
276 |
277 | export interface AssistantStep {
278 | id: string;
279 | [key: string]: any;
280 | type: "step";
281 | content: string;
282 | ui?: string | JSX.Element | JSX.Element[] | null | undefined;
283 | }
284 |
--------------------------------------------------------------------------------
/src/components/ui/menubar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as MenubarPrimitive from "@radix-ui/react-menubar"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const MenubarMenu = MenubarPrimitive.Menu
10 |
11 | const MenubarGroup = MenubarPrimitive.Group
12 |
13 | const MenubarPortal = MenubarPrimitive.Portal
14 |
15 | const MenubarSub = MenubarPrimitive.Sub
16 |
17 | const MenubarRadioGroup = MenubarPrimitive.RadioGroup
18 |
19 | const Menubar = React.forwardRef<
20 | React.ElementRef,
21 | React.ComponentPropsWithoutRef
22 | >(({ className, ...props }, ref) => (
23 |
31 | ))
32 | Menubar.displayName = MenubarPrimitive.Root.displayName
33 |
34 | const MenubarTrigger = React.forwardRef<
35 | React.ElementRef,
36 | React.ComponentPropsWithoutRef
37 | >(({ className, ...props }, ref) => (
38 |
46 | ))
47 | MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
48 |
49 | const MenubarSubTrigger = React.forwardRef<
50 | React.ElementRef,
51 | React.ComponentPropsWithoutRef & {
52 | inset?: boolean
53 | }
54 | >(({ className, inset, children, ...props }, ref) => (
55 |
64 | {children}
65 |
66 |
67 | ))
68 | MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
69 |
70 | const MenubarSubContent = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >(({ className, ...props }, ref) => (
74 |
82 | ))
83 | MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
84 |
85 | const MenubarContent = React.forwardRef<
86 | React.ElementRef,
87 | React.ComponentPropsWithoutRef
88 | >(
89 | (
90 | { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
91 | ref
92 | ) => (
93 |
94 |
105 |
106 | )
107 | )
108 | MenubarContent.displayName = MenubarPrimitive.Content.displayName
109 |
110 | const MenubarItem = React.forwardRef<
111 | React.ElementRef,
112 | React.ComponentPropsWithoutRef & {
113 | inset?: boolean
114 | }
115 | >(({ className, inset, ...props }, ref) => (
116 |
125 | ))
126 | MenubarItem.displayName = MenubarPrimitive.Item.displayName
127 |
128 | const MenubarCheckboxItem = React.forwardRef<
129 | React.ElementRef,
130 | React.ComponentPropsWithoutRef
131 | >(({ className, children, checked, ...props }, ref) => (
132 |
141 |
142 |
143 |
144 |
145 |
146 | {children}
147 |
148 | ))
149 | MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
150 |
151 | const MenubarRadioItem = React.forwardRef<
152 | React.ElementRef,
153 | React.ComponentPropsWithoutRef
154 | >(({ className, children, ...props }, ref) => (
155 |
163 |
164 |
165 |
166 |
167 |
168 | {children}
169 |
170 | ))
171 | MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
172 |
173 | const MenubarLabel = React.forwardRef<
174 | React.ElementRef,
175 | React.ComponentPropsWithoutRef & {
176 | inset?: boolean
177 | }
178 | >(({ className, inset, ...props }, ref) => (
179 |
188 | ))
189 | MenubarLabel.displayName = MenubarPrimitive.Label.displayName
190 |
191 | const MenubarSeparator = React.forwardRef<
192 | React.ElementRef,
193 | React.ComponentPropsWithoutRef
194 | >(({ className, ...props }, ref) => (
195 |
200 | ))
201 | MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
202 |
203 | const MenubarShortcut = ({
204 | className,
205 | ...props
206 | }: React.HTMLAttributes) => {
207 | return (
208 |
215 | )
216 | }
217 | MenubarShortcut.displayname = "MenubarShortcut"
218 |
219 | export {
220 | Menubar,
221 | MenubarMenu,
222 | MenubarTrigger,
223 | MenubarContent,
224 | MenubarItem,
225 | MenubarSeparator,
226 | MenubarLabel,
227 | MenubarCheckboxItem,
228 | MenubarRadioGroup,
229 | MenubarRadioItem,
230 | MenubarPortal,
231 | MenubarSubContent,
232 | MenubarSubTrigger,
233 | MenubarGroup,
234 | MenubarSub,
235 | MenubarShortcut,
236 | }
237 |
--------------------------------------------------------------------------------