# Project export: AI

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 10.0
- Tagline: OI
- Devpost: https://devpost.com/software/ai-mq061e
- GitHub: https://github.com/AmeerArsala/ai-clinician-trainer
- Demo: http://www.diversitygpt.xyz/
- Team: 1 GitHub contributor(s) — Ameer Arsala (16 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# AI Clinician Trainer
The frontend of DiversityGPT

## DiversityGPT
...


## Detected evidence (automated analysis)

Indexed codebase: 71 recognized source files, 346 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Vercel AI SDK (technology) — detected in the code
- Python (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (77 of 77)

```
.eslintrc.json
.gitignore
app/actions.ts
app/api/auth/[...nextauth]/route.ts
app/api/chat/route.ts
app/chat/[id]/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/share/[id]/opengraph-image.tsx
app/share/[id]/page.tsx
app/sign-in/page.tsx
auth.ts
components/button-scroll-to-bottom.tsx
components/chat-list.tsx
components/chat-message-actions.tsx
components/chat-message.tsx
components/chat-panel.tsx
components/chat-scroll-anchor.tsx
components/chat.tsx
components/clear-history.tsx
components/empty-screen.tsx
components/external-link.tsx
components/footer.tsx
components/header.tsx
components/login-button.tsx
components/markdown.tsx
components/prompt-form.tsx
components/providers.tsx
components/sidebar-actions.tsx
components/sidebar-footer.tsx
components/sidebar-item.tsx
components/sidebar-list.tsx
components/sidebar.tsx
components/tailwind-indicator.tsx
components/theme-toggle.tsx
components/toaster.tsx
components/ui/alert-dialog.tsx
components/ui/badge.tsx
components/ui/button.tsx
components/ui/codeblock.tsx
components/ui/dialog.tsx
components/ui/dropdown-menu.tsx
components/ui/icons.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/select.tsx
components/ui/separator.tsx
components/ui/sheet.tsx
components/ui/switch.tsx
components/ui/textarea.tsx
components/ui/tooltip.tsx
components/user-menu.tsx
cypress.config.js
cypress/e2e/basic.cy.js
lib/analytics.ts
lib/fonts.ts
lib/hooks/use-at-bottom.tsx
lib/hooks/use-copy-to-clipboard.tsx
lib/hooks/use-enter-submit.tsx
lib/hooks/use-local-storage.ts
lib/types.ts
lib/utils.ts
middleware.ts
netlify.toml
next-env.d.ts
next.config.js
package.json
pages/Input.js
postcss.config.js
prettier.config.cjs
README.md
renovate.json
styles/globals.css
tailwind.config.js
tsconfig.json
types/fix.d.ts
```

### Dependencies

- package.json: @clerk/nextjs@^4.26.1, @radix-ui/react-alert-dialog@^1.0.5, @radix-ui/react-dialog@^1.0.5, @radix-ui/react-dropdown-menu@^2.0.6, @radix-ui/react-label@^2.0.2, @radix-ui/react-select@^1.2.2, @radix-ui/react-separator@^1.0.3, @radix-ui/react-slot@^1.0.2, @radix-ui/react-switch@^1.0.3, @radix-ui/react-tooltip@^1.0.7, @tailwindcss/typography@^0.5.10, @trpc/client@^10.43.0, @trpc/server@^10.43.0, @types/node@^17.0.45, @types/react@^18.2.33, @types/react-dom@^18.2.14, @types/react-syntax-highlighter@^15.5.9, @typescript-eslint/parser@^5.62.0, @vercel/analytics@^1.1.1, @vercel/kv@^0.2.3, @vercel/og@^0.5.20, ai@^2.2.20, autoprefixer@^10.4.16, class-variance-authority@^0.7.0, clsx@^1.2.1, cypress@^10.11.0, eslint@^8.52.0, eslint-config-next@13.4.7-canary.1, eslint-config-prettier@^8.10.0, eslint-plugin-tailwindcss@^3.13.0, focus-trap-react@^10.2.3, nanoid@^4.0.2, next@13.4.7-canary.1, next-auth@0.0.0-manual.83c4ebd1, next-themes@^0.2.1, openai-edge@^0.5.1, postcss@^8.4.31, prettier@^3.0.3, prettier-plugin-tailwindcss@^0.5.6, react@^18.2.0, react-dom@^18.2.0, react-hot-toast@^2.4.1, react-intersection-observer@^9.5.2, react-markdown@^8.0.7, react-syntax-highlighter@^15.5.0, react-textarea-autosize@^8.5.3, remark-gfm@^3.0.1, remark-math@^5.1.1, tailwind-merge@^1.14.0, tailwindcss@^3.3.5, tailwindcss-animate@^1.0.7, typescript@^5.2.2

### Recent commits (newest first)

- Delete backend/base.py
- Update README.md
- updates
- Update README.md
- Delete .env.local
- fixed gitignore
- X
- finished Clerk
- X
- commented out sample test
- cleaned up js
- Update layout.tsx
- fully switched to typescript, resolved some dependency conflicts
- fix build
- bug fix: deleted remnant
- modified gitignore
- Merge is almost done. Now for the cleanup
- unioned everything
- started merge process and changed package/package-lock project name
- added a backend

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "ai-clinician-trainer",
  "version": "0.5.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "export": "next export",
    "lint": "next lint",
    "lint:fix": "next lint --fix",
    "preview": "next build && next start",
    "type-check": "tsc --noEmit",
    "format:write": "prettier --write \"{app,lib,components}/**/*.{ts,tsx,mdx}\" --cache",
    "format:check": "prettier --check \"{app,lib,components}**/*.{ts,tsx,mdx}\" --cache"
  },
  "dependencies": {
    "@clerk/nextjs": "^4.26.1",
    "@radix-ui/react-alert-dialog": "^1.0.5",
    "@radix-ui/react-dialog": "^1.0.5",
    "@radix-ui/react-dropdown-menu": "^2.0.6",
    "@radix-ui/react-label": "^2.0.2",
    "@radix-ui/react-select": "^1.2.2",
    "@radix-ui/react-separator": "^1.0.3",
    "@radix-ui/react-slot": "^1.0.2",
    "@radix-ui/react-switch": "^1.0.3",
    "@radix-ui/react-tooltip": "^1.0.7",
    "@trpc/client": "^10.43.0",
    "@trpc/server": "^10.43.0",
    "@vercel/analytics": "^1.1.1",
    "@vercel/kv": "^0.2.3",
    "@vercel/og": "^0.5.20",
    "ai": "^2.2.20",
    "class-variance-authority": "^0.7.0",
    "clsx": "^1.2.1",
    "cypress": "^10.11.0",
    "focus-trap-react": "^10.2.3",
    "nanoid": "^4.0.2",
    "next": "13.4.7-canary.1",
    "next-auth": "0.0.0-manual.83c4ebd1",
    "next-themes": "^0.2.1",
    "openai-edge": "^0.5.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-hot-toast": "^2.4.1",
    "react-intersection-observer": "^9.5.2",
    "react-markdown": "^8.0.7",
    "react-syntax-highlighter": "^15.5.0",
    "react-textarea-autosize": "^8.5.3",
    "remark-gfm": "^3.0.1",
    "remark-math": "^5.1.1"
  },
  "devDependencies": {
    "@tailwindcss/typography": "^0.5.10",
    "@types/node": "^17.0.45",
    "@types/react": "^18.2.33",
    "@types/react-dom": "^18.2.14",
    "@types/react-syntax-highlighter": "^15.5.9",
    "@typescript-eslint/parser": "^5.62.0",
    "autoprefixer": "^10.4.16",
    "eslint": "^8.52.0",
    "eslint-config-next": "13.4.7-canary.1",
    "eslint-config-prettier": "^8.10.0",
    "eslint-plugin-tailwindcss": "^3.13.0",
    "postcss": "^8.4.31",
    "prettier": "^3.0.3",
    "prettier-plugin-tailwindcss": "^0.5.6",
    "tailwind-merge": "^1.14.0",
    "tailwindcss": "^3.3.5",
    "tailwindcss-animate": "^1.0.7",
    "typescript": "^5.2.2"
  },
  "packageManager": "pnpm@8.6.3"
}
```

### app/page.tsx

```typescript
import { nanoid } from '@/lib/utils'
import { Chat } from '@/components/chat'

export const runtime = 'edge'

export default function IndexPage() {
  const id = nanoid()

  return <Chat id={id} />
}

```

### app/layout.tsx

```typescript
import { Metadata } from 'next'

import { Toaster } from 'react-hot-toast'

import '@/app/globals.css'
import { fontMono, fontSans } from '@/lib/fonts'
import { cn } from '@/lib/utils'
import { TailwindIndicator } from '@/components/tailwind-indicator'
import { Providers } from '@/components/providers'
import { Header } from '@/components/header'
import { ClerkProvider } from '@clerk/nextjs'
import { UserButton } from '@clerk/nextjs'

export const metadata: Metadata = {
  title: {
    default: 'MulticulturalGPT',
    template: `%s - Next.js AI Chatbot`
  },
  description: 'An AI-powered chatbot template built with Next.js and Vercel.',
  themeColor: [
    { media: '(prefers-color-scheme: light)', color: 'white' },
    { media: '(prefers-color-scheme: dark)', color: 'black' }
  ],
  icons: {
    icon: '/favicon.ico',
    shortcut: '/favicon-16x16.png',
    apple: '/apple-touch-icon.png'
  }
}

interface RootLayoutProps {
  children: React.ReactNode
}

export default function RootLayout({ children }: RootLayoutProps) {
  // surround with <ClerkProvider> for a login
  return (    
    <ClerkProvider>
      <html lang="en" suppressHydrationWarning>
        <head />
        <body
          className={cn(
            'font-sans antialiased',
            fontSans.variable,
            fontMono.variable
          )}
        >
          <Toaster />
          <Providers attribute="class" defaultTheme="system" enableSystem>
            <div className="flex flex-col min-h-screen">
              {/* @ts-ignore */}
              <Header />
              <main className="flex flex-col flex-1 bg-muted/50">
                {children}
              </main>
            </div>
            <TailwindIndicator />
          </Providers>
        </body>
      </html>
    </ClerkProvider>
  )
}

```

### app/sign-in/page.tsx

```typescript
import { auth } from '@/auth'
import { LoginButton } from '@/components/login-button'
import { redirect } from 'next/navigation'

export default async function SignInPage() {
  const session = await auth()
  // redirect to home if user is already logged in
  if (session?.user) {
    redirect('/')
  }
  return (
    <div className="flex h-[calc(100vh-theme(spacing.16))] items-center justify-center py-10">
      <LoginButton />
    </div>
  )
}

```

### app/chat/[id]/page.tsx

```typescript
import { type Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'

import { auth } from '@/auth'
import { getChat } from '@/app/actions'
import { Chat } from '@/components/chat'

export const runtime = 'edge'
export const preferredRegion = 'home'

export interface ChatPageProps {
  params: {
    id: string
  }
}

export async function generateMetadata({
  params
}: ChatPageProps): Promise<Metadata> {
  const session = await auth()

  if (!session?.user) {
    return {}
  }

  const chat = await getChat(params.id, session.user.id)
  return {
    title: chat?.title.toString().slice(0, 50) ?? 'Chat'
  }
}

export default async function ChatPage({ params }: ChatPageProps) {
  const session = await auth()

  if (!session?.user) {
    redirect(`/sign-in?next=/chat/${params.id}`)
  }

  const chat = await getChat(params.id, session.user.id)

  if (!chat) {
    notFound()
  }

  if (chat?.userId !== session?.user?.id) {
    notFound()
  }

  return <Chat id={chat.id} initialMessages={chat.messages} />
}

```

### app/share/[id]/page.tsx

```typescript
import { type Metadata } from 'next'
import { notFound } from 'next/navigation'

import { formatDate } from '@/lib/utils'
import { getSharedChat } from '@/app/actions'
import { ChatList } from '@/components/chat-list'
import { FooterText } from '@/components/footer'

export const runtime = 'edge'
export const preferredRegion = 'home'

interface SharePageProps {
  params: {
    id: string
  }
}

export async function generateMetadata({
  params
}: SharePageProps): Promise<Metadata> {
  const chat = await getSharedChat(params.id)

  return {
    title: chat?.title.slice(0, 50) ?? 'Chat'
  }
}

export default async function SharePage({ params }: SharePageProps) {
  const chat = await getSharedChat(params.id)

  if (!chat || !chat?.sharePath) {
    notFound()
  }

  return (
    <>
      <div className="flex-1 space-y-6">
        <div className="px-4 py-6 border-b bg-background md:px-6 md:py-8">
          <div className="max-w-2xl mx-auto md:px-6">
            <div className="space-y-1 md:-mx-8">
              <h1 className="text-2xl font-bold">{chat.title}</h1>
              <div className="text-sm text-muted-foreground">
                {formatDate(chat.createdAt)} · {chat.messages.length} messages
              </div>
            </div>
          </div>
        </div>
        <ChatList messages={chat.messages} />
      </div>
      <FooterText className="py-8" />
    </>
  )
}

```

### app/api/chat/route.ts

```typescript
import { kv } from '@vercel/kv'
import { OpenAIStream, StreamingTextResponse } from 'ai'
import { Configuration, OpenAIApi } from 'openai-edge'

import { auth } from '@/auth'
import { nanoid } from '@/lib/utils'

export const runtime = 'edge'

const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY
})

const openai = new OpenAIApi(configuration)

export async function POST(req: Request) {
  const json = await req.json()
  const { messages, previewToken } = json
  const userId = (await auth())?.user.id

  if (!userId) {
    return new Response('Unauthorized', {
      status: 401
    })
  }

  if (previewToken) {
    configuration.apiKey = previewToken
  }

  const res = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    messages,
    temperature: 0.7,
    stream: true
  })

  const stream = OpenAIStream(res, {
    async onCompletion(completion) {
      const title = json.messages[0].content.substring(0, 100)
      const id = json.id ?? nanoid()
      const createdAt = Date.now()
      const path = `/chat/${id}`
      const payload = {
        id,
        title,
        userId,
        createdAt,
        path,
        messages: [
          ...messages,
          {
            content: completion,
            role: 'assistant'
          }
        ]
      }
      await kv.hmset(`chat:${id}`, payload)
      await kv.zadd(`user:chat:${userId}`, {
        score: createdAt,
        member: `chat:${id}`
      })
    }
  })

  return new StreamingTextResponse(stream)
}

```

### app/api/auth/[...nextauth]/route.ts

```typescript
export { GET, POST } from '@/auth'
export const runtime = 'edge'

```

### postcss.config.js

```javascript
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### cypress.config.js

```javascript
const { defineConfig } = require("cypress");

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:8888/',
    supportFile: false,
  },
});

```

[60 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]