# Project export: lingo llm

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: UC Berkeley AI Hackathon 2025
- Tagline: # lingo llm 🐧 Lingo LLM is a language learning website where users can engage with little anthropomorphic characters to learn Spanish through AI-powered storytelling.
- Devpost: https://devpost.com/software/lingo-llm
- GitHub: https://github.com/mjsipes/lingo-llm
- Team: 1 GitHub contributor(s) — Michael Sipes (85 commits)

## Devpost submission (written by the team)

### Overview

Lingo LLM is a language learning website where users can engage with little anthropomorphic characters to learn Spanish through AI-powered storytelling. TLDR I started learning Spanish this summer, downloaded Duolingo, but it felt like Khan Academy. When I asked Claude to translate a news article into Spanish, our conversation evolved into creating fictional Spanish-English stories. I wanted to expand on Claude's capabilities by adding image generation and creating an experience designed for children with a universe of helpful animal characters.

### What it does

Lingo LLM introduces kids to AI orchestration through language learning. Instead of one monolithic AI, children interact with specialized anthropomorphic characters, each with unique abilities. This teaches them how to coordinate different AI agents while making Spanish learning feel like playing with friends. The core experience revolves around Pingu, the main storyteller who controls the chat interface. Children can summon specialized helpers: Palette the Camel and Panda the Painter turn conversations into illustrated stories Single-purpose agents provide feedback, inspiration, randomness, and translation Each character responds instantly in dialogue format through tooltips Why this matters We might be heading toward a future where children learn from AI or orchestrate AI agents. While we call this "prompt engineering," how do we make it intuitive for kids? Lingo LLM explores this by hiding the complexity of tool calling and agent coordination behind the simple concept: "these are your friends, and they can help you with specific tasks." The app addresses a fundamental question about AI's future: Will we have one super-intelligent agent, or will the most powerful entity be the dynamic duo of human + coordinated AI? This project was my exploration into what our experience with orchestrating and learning from AI agents might look like in the future. How it works Children can interact with characters in two ways: Main conversation with Pingu for natural language learning Quick agent calls using specialized helper buttons - even spam-clicking generates different responses for instant feedback The Spanish learning happens through collaborative storytelling, where AI agents help create multilingual narratives that make language acquisition feel like creative play rather than studying. System Architecture Lingo LLM uses a multi-agent AI system with intelligent context management. The backend connects to three AI services: Claude 4 Sonnet for reasoning, OpenAI for image generation and audio, and Groq for fast responses. Each character is designed with specific capabilities that map to these underlying services. The system maintains conversation context through smart summarization: persistent messages get condensed into background context, while selected text becomes the immediate subject. This allows image-generating agents to create relevant visuals and keeps all characters contextually aware without overwhelming the models. Characters 🐧 Pingu – Main Storyteller A classic, adorable cartoon penguin with a navy-blue blazer and big, curious eyes. Pingu is the explorer of the group — asking questions, trying new things, and learning alongside the child. 🐼 Panda the Painter – Creative Artist A cheerful, wide-eyed panda covered in colorful paint splatters, wearing a slightly oversized navy blazer and red sneakers. Panda the Painter is a beginner artist who's learning to express creativity, holding a paintbrush in one paw and a palette in the other. Always enthusiastic, even when the paint goes outside the lines. 🐫 Palette the Camel – Creative Artist A tall, calm, and confident camel with a painter's smock and a neat navy blazer. Palette the Camel brings years of experience and precision to every canvas. Known for clean strokes, beautiful compositions, and a patient, mentoring energy — the creative foil to Panda's messy enthusiasm. 🦉 Owl the Translator – Pronunciation Coach A wise owl with soft feathers, round glasses, and a gentle expression. Owl the Translator wears a clean navy suit and carries a small microphone, helping learners understand, pronounce, and translate language clearly and calmly. 🦁 Lion the Critic – Constructive Critic A strong, poised lion wearing a sharp navy suit and black tie, with small sunglasses perched on his mane. Lion the Critic offers honest, thoughtful feedback to improve your ideas. He's not mean — just focused on making every story the best it can be. 🐸 Ribbit the Writer – Creative Writing Assistant A quirky green frog with oversized glasses, a blazer, and an endless imagination. Ribbit doesn't write the story for you — he's here to spark ideas, suggest what could happen next, and help brainstorm characters, plot twists, and silly surprises. 🐝 Bee the Multitasker – External Task Coordinator A swarm of 4–5 adorable professional bees, each with a different job: one on the phone, another at a laptop, one flying a clipboard over, another with a coffee cup. Dressed in mini suit accessories, Bee the Multitasker handles all external coordination so you and the creative crew can stay focused on the fun. Design Font: Geist, "Geist Fallback" Colors: Rose: oklch(58.6% 0.253 17.585) Sky: oklch(95.1% 0.026 236.824) White: oklch(98.5% 0 0)

## README (from the GitHub repository)

# lingo llm 🐧

**Live App: https://lingo-llm.vercel.app/**

Lingo LLM is a language learning website where users can engage with little anthropomorphic characters to learn Spanish through AI-powered storytelling.

## TLDR
I started learning Spanish this summer, downloaded Duolingo, but it felt like Khan Academy. When I asked Claude to translate a news article into Spanish, our conversation evolved into creating fictional Spanish-English stories. I wanted to expand on Claude's capabilities by adding image generation and creating an experience designed for children with a universe of helpful animal characters.

## What it does

Lingo LLM introduces kids to AI orchestration through language learning. Instead of one monolithic AI, children interact with specialized anthropomorphic characters, each with unique abilities. This teaches them how to coordinate different AI agents while making Spanish learning feel like playing with friends.

The core experience revolves around **Pingu**, the main storyteller who controls the chat interface. Children can summon specialized helpers:
- **Palette the Camel** and **Panda the Painter** turn conversations into illustrated stories
- Single-purpose agents provide feedback, inspiration, randomness, and translation
- Each character responds instantly in dialogue format through tooltips

## Why this matters

We might be heading toward a future where children learn from AI or orchestrate AI agents. While we call this "prompt engineering," how do we make it intuitive for kids? Lingo LLM explores this by hiding the complexity of tool calling and agent coordination behind the simple concept: "these are your friends, and they can help you with specific tasks."

The app addresses a fundamental question about AI's future: Will we have one super-intelligent agent, or will the most powerful entity be the dynamic duo of human + coordinated AI? This project was my exploration into what our experience with orchestrating and learning from AI agents might look like in the future.

## How it works

Children can interact with characters in two ways:
1. **Main conversation** with Pingu for natural language learning
2. **Quick agent calls** using specialized helper buttons - even spam-clicking generates different responses for instant feedback

The Spanish learning happens through collaborative storytelling, where AI agents help create multilingual narratives that make language acquisition feel like creative play rather than studying.

![lingo llm](./lingollm.png)

## System Architecture

Lingo LLM uses a multi-agent AI system with intelligent context management. The backend connects to three AI services: Claude 4 Sonnet for reasoning, OpenAI for image generation and audio, and Groq for fast responses. Each character is designed with specific capabilities that map to these underlying services.

The system maintains conversation context through smart summarization: persistent messages get condensed into background context, while selected text becomes the immediate subject. This allows image-generating agents to create relevant visuals and keeps all characters contextually aware without overwhelming the models.

![lingo llm](./lingollmdiagram.png)

## Characters

### 🐧 **Pingu** – *Main Storyteller*
A classic, adorable cartoon penguin with a navy-blue blazer and big, curious eyes. Pingu is the explorer of the group — asking questions, trying new things, and learning alongside the child.

### 🐼 **Panda the Painter** – *Creative Artist*
A cheerful, wide-eyed panda covered in colorful paint splatters, wearing a slightly oversized navy blazer and red sneakers. Panda the Painter is a beginner artist who's learning to express creativity, holding a paintbrush in one paw and a palette in the other. Always enthusiastic, even when the paint goes outside the lines.

### 🐫 **Palette the Camel** – *Creative Artist*
A tall, calm, and confident camel with a painter's smock and a neat navy blazer. Palette the Camel brings years of experience and precision to every canvas. Known for clean strokes, beautiful compositions, and a patient, mentoring energy — the creative foil to Panda's messy enthusiasm.

### 🦉 **Owl the Translator** – *Pronunciation Coach*
A wise owl with soft feathers, round glasses, and a gentle expression. Owl the Translator wears a clean navy suit and carries a small microphone, helping learners understand, pronounce, and translate language clearly and calmly.

### 🦁 **Lion the Critic** – *Constructive Critic*
A strong, poised lion wearing a sharp navy suit and black tie, with small sunglasses perched on his mane. Lion the Critic offers honest, thoughtful feedback to improve your ideas. He's not mean — just focused on making every story the best it can be.

### 🐸 **Ribbit the Writer** – *Creative Writing Assistant*
A quirky green frog with oversized glasses, a blazer, and an endless imagination. Ribbit doesn't write the story for you — he's here to spark ideas, suggest what could happen next, and help brainstorm characters, plot twists, and silly surprises.

### 🐝 **Bee the Multitasker** – *External Task Coordinator*
A swarm of 4–5 adorable professional bees, each with a different job: one on the phone, another at a laptop, one flying a clipboard over, another with a coffee cup. Dressed in mini suit accessories, Bee the Multitasker handles all external coordination so you and the creative crew can stay focused on the fun.

## Design
**Font:** Geist, "Geist Fallback"  
**Colors:**
- Rose: `oklch(58.6% 0.253 17.585)`
- Sky: `oklch(95.1% 0.026 236.824)`  
- White: `oklch(98.5% 0 0)`


## Detected evidence (automated analysis)

Indexed codebase: 58 recognized source files, 141 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- Supabase (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
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (64 of 64)

```
.gitignore
app/api/audio/route.ts
app/api/chat/route.ts
app/api/chat/system.ts
app/api/groq/route.ts
app/api/image/route.ts
app/globals.css
app/layout.tsx
app/page.tsx
components.json
components/AgentCard.tsx
components/AgentCardFrog.tsx
components/AgentCardOwl.tsx
components/auth-button.tsx
components/Chat.tsx
components/clientSystemPrompts.tsx
components/CopyClick.tsx
components/deploy-button.tsx
components/env-var-warning.tsx
components/forgot-password-form.tsx
components/hero.tsx
components/ImageAgent.tsx
components/login-form.tsx
components/logout-button.tsx
components/next-logo.tsx
components/sign-up-form.tsx
components/supabase-logo.tsx
components/theme-switcher.tsx
components/tutorial/code-block.tsx
components/tutorial/connect-supabase-steps.tsx
components/tutorial/fetch-data-steps.tsx
components/tutorial/sign-up-user-steps.tsx
components/tutorial/tutorial-step.tsx
components/ui/avatar.tsx
components/ui/badge.tsx
components/ui/button.tsx
components/ui/card.tsx
components/ui/checkbox.tsx
components/ui/dialog.tsx
components/ui/dropdown-menu.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/popover.tsx
components/ui/resizable.tsx
components/ui/scroll-area.tsx
components/ui/skeleton.tsx
components/ui/sonner.tsx
components/ui/textarea.tsx
components/ui/tooltip.tsx
components/update-password-form.tsx
eslint.config.mjs
lib/supabase/AuthProvider.tsx
lib/supabase/client.ts
lib/supabase/middleware.ts
lib/supabase/server.ts
lib/utils.ts
middleware.ts
next.config.ts
notes.md
package.json
postcss.config.mjs
README.md
tailwind.config.ts
tsconfig.json
```

### Dependencies

- package.json: @ai-sdk/anthropic@^1.2.12, @ai-sdk/groq@^1.2.9, @eslint/eslintrc@^3, @radix-ui/react-avatar@^1.1.10, @radix-ui/react-checkbox@^1.3.1, @radix-ui/react-dialog@^1.1.14, @radix-ui/react-dropdown-menu@^2.1.14, @radix-ui/react-label@^2.1.6, @radix-ui/react-popover@^1.1.14, @radix-ui/react-scroll-area@^1.2.9, @radix-ui/react-slot@^1.2.2, @radix-ui/react-tooltip@^1.2.7, @supabase/ssr@latest, @supabase/supabase-js@latest, @types/node@^20, @types/react@^19, @types/react-dom@^19, ai@^4.3.16, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9, eslint-config-next@15.3.1, groq-sdk@^0.25.0, lucide-react@^0.511.0, next@latest, next-themes@^0.4.6, openai@^5.6.0, postcss@^8, react@^19.0.0, react-dom@^19.0.0, react-markdown@^10.1.0, react-resizable-panels@^3.0.3, sonner@^2.0.5, tailwind-merge@^3.3.0, tailwindcss@^3.4.1, tailwindcss-animate@^1.0.7, typescript@^5

### Recent commits (newest first)

- default to light mode, not system
- omg tooltip omg
- added live app to readme
- copilot pulled some quick ones on me
- remove clientauthwrapper because it is an unnessary file
- fix lint errors
- fix lint errors
- fix linting errors
- give edward and major 20 credits
- credit system is good enough for me
- image credits working
- sets user credits to 20
- added anonymous user
- Merge branch 'main' of https://github.com/mjsipes/lingo-llm
- about to set up 20 credit limit
- switched to claude 3.7 sonnet for faster streaming
- Add files via upload
- Update README.md
- Add files via upload
- Add files via upload

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

### notes.md

```markdown
write me a story. start with a boy who gets lost in the desert. include the words trees, leaves, branches, and bark



next steps
add caching to the owl response
add functionality for beez
further purse rthe image generation to create story books. To prove how good the image generation could be I should turn the Bible into a picturbook. i can implement rolling averages of summarizations of the text to get background context and immediate subject. there should always be style guide, i should do research on how best to do this. the biggest challenge will be locking down character attributes so characters look the same through out the story. i will have to research how to do this more.

internal notes
i have a really solid idea of vercel and supabase now which is exciting, and the tools of a lot of these ai hackathon companies. i am actually really exciting about hwo that hackathon did for me. i need to make this project better, than go through my investigations, engange in my little smalller projects, and then i will be ready for my next hackathon. 

i think that everythign in the world is possible to change.
Even smaller if a human coordinates, when will it be able to do the stirring of a cake, for thing people don’t want to be involved with, when will it just be able to do the dishes
can ai robot bake a cake itself.
computers have stressed people out

https://claude.ai/share/c6052ce0-58f0-46b2-8ffd-bbb6e3416991


okay i should share all my links to this project somewhere. just so everything about this proejct is documented. now with my idea of rolling averages, rag sounds very unique, rag is like a hashmap over data, whereas my idea of rolling averages is like convolutions summing/ condensing context to feed back into the llm. . they are kind of like the datastructures of ai. this is ata least unti we understand how to put the datastructures into the ai, that will be a whole nother thing, but right now we are in an age of combinging classical computing with ai to make something poweferul, until ai does all of classical computing as well. that is a good qeustion, when will ai do all of classical computing as well?

like when will the ai itself be able to use those datastructures? and these should be datastructures, not full on products.




addd agent for conjugation table and definiont.

i think it is possible for anything to change in the universe. the the questions is if you like it so much do you want ot build it or go acquisition route.  better file system for claude. so you first look to the toptwo competitors. are they doing that? i get that as a new person to a company you get seen as ineperiences and i done know if you guys accept or dont acpet input from newer people but i would probeably have pretty good input after a few months. that is just one of my positive predictions about my self. that is not . perplexity is a good idea, it is the ability to plug n play any ai models with one outlet. so is perplexity the outlet, or are they searc
[truncated — 4685 more characters]
```

### package.json

```
{
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@ai-sdk/anthropic": "^1.2.12",
    "@ai-sdk/groq": "^1.2.9",
    "@radix-ui/react-avatar": "^1.1.10",
    "@radix-ui/react-checkbox": "^1.3.1",
    "@radix-ui/react-dialog": "^1.1.14",
    "@radix-ui/react-dropdown-menu": "^2.1.14",
    "@radix-ui/react-label": "^2.1.6",
    "@radix-ui/react-popover": "^1.1.14",
    "@radix-ui/react-scroll-area": "^1.2.9",
    "@radix-ui/react-slot": "^1.2.2",
    "@radix-ui/react-tooltip": "^1.2.7",
    "@supabase/ssr": "latest",
    "@supabase/supabase-js": "latest",
    "ai": "^4.3.16",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "groq-sdk": "^0.25.0",
    "lucide-react": "^0.511.0",
    "next": "latest",
    "next-themes": "^0.4.6",
    "openai": "^5.6.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-markdown": "^10.1.0",
    "react-resizable-panels": "^3.0.3",
    "sonner": "^2.0.5",
    "tailwind-merge": "^3.3.0"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "autoprefixer": "^10.4.20",
    "eslint": "^9",
    "eslint-config-next": "15.3.1",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "tailwindcss-animate": "^1.0.7",
    "typescript": "^5"
  }
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist } from "next/font/google";
import { ThemeProvider } from "next-themes";
import { Toaster } from "sonner";
import { AuthProvider } from "@/lib/supabase/AuthProvider";
import "./globals.css";

const defaultUrl = process.env.VERCEL_URL
  ? `https://${process.env.VERCEL_URL}`
  : "http://localhost:3000";

export const metadata: Metadata = {
  metadataBase: new URL(defaultUrl),
  title: "Next.js and Supabase Starter Kit",
  description: "The fastest way to build apps with Next.js and Supabase",
};

const geistSans = Geist({
  variable: "--font-geist-sans",
  display: "swap",
  subsets: ["latin"],
});

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={`${geistSans.className} antialiased`}>
        <ThemeProvider
          attribute="class"
          defaultTheme="light"
          enableSystem
          disableTransitionOnChange
        >
          <AuthProvider>
            {children}
          </AuthProvider>
          <Toaster />
        </ThemeProvider>
      </body>
    </html>
  );
}

```

### app/page.tsx

```typescript
"use client";
import { useState, useEffect } from "react";
import { useChat } from "@ai-sdk/react";
import { Chat } from "@/components/Chat";
import AgentCard from "@/components/AgentCard";
import ImageAgent from "@/components/ImageAgent";
import AgentCardOwl from "@/components/AgentCardOwl";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { Coins } from "lucide-react";
import Image from "next/image";
import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable";
import { useTextSelection, SelectionPopover } from "@/components/CopyClick";
import { useAuth } from "@/lib/supabase/AuthProvider";
import {
  agentPandaSystemPrompt,
  agentBeeSystemPrompt,
  agentLionSystemPrompt,
  agentOwlSystemPrompt,
  agentFrogSystemPrompt,
  camelSelfPortraitPrompt,
  pandaSelfPortraitPrompt,
  styleGuidelines,
} from "@/components/clientSystemPrompts";

export default function Home() {
  const [images, setImages] = useState<string[]>([]);
  const { user, refreshUser } = useAuth();

  // Debugging useEffect to display user info
  useEffect(() => {
    console.log("=== USER DEBUG INFO ===");
    console.log("Full user object:", user);
    if (user) {
      console.log("User ID:", user.id);
      console.log("User metadata:", user.user_metadata);
      console.log("User app metadata:", user.app_metadata);
      console.log("Is anonymous:", user.is_anonymous);
      console.log("Created at:", user.created_at);
    } else {
      console.log("No user found");
    }
    console.log("======================");
  }, [user]);

  const {
    messages,
    input,
    handleInputChange,
    handleSubmit,
    isLoading,
  } = useChat();

  const selectionProps = useTextSelection();
  const [imageAgentUserPrompt, setImageAgentUserPrompt] = useState<string>();
  const [pandaAgentUserPrompt, setPandaAgentUserPrompt] = useState<string>();
  const [backgroundContext, setBackgroundContext] = useState<string>("");
  const [immediateSubject, setImmediateSubject] = useState<string>("");
  // Only trigger when streaming completes

  const analyzeBackgroundContext = async () => {
    try {
      const formattedMessages = messages
        .map((msg) => `${msg.role}: ${msg.content}`)
        .join("\n");
      const response = await fetch("/api/groq", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          systemPrompt:
            "You summarize stories with great detail and imagery. Take the following story imput and output a brief 3-4 sentance background of the story at hand. Capture the stories setting, mood and atmosphere. Specifically output only the summary.",
          userPrompt: formattedMessages,
        }),
      });
      const data = await response.json();
      const contextResult = data.content || "";
      console.log(`backgroundContext set to: ${contextResult}...`);
      setBackgroundContext(contextResult);
    } catch (error) {
      console.error("Error analyzing background context:", error);
      console.log("backgroundContext set to: (error - empty)");
      setBackgroundContext("");
    }
  };

  // useEffect for backgroundContext - analyzes messages for story context (only when streaming completes)
  useEffect(() => {
    console.log("backgroundContext useEffect called");
    if (isLoading) {
      console.log("backgroundContext - still loading, skip");
      return;
    }
    if (messages.length === 0) {
      console.log("backgroundContext set to: (empty)");
      setBackgroundContext("");
      return;
    }
    analyzeBackgroundContext();
  }, [isLoading]);

  const analyzeImmediateSubject = async () => {
    console.log(
      "analyzeImmediateSubject. sending following selectionProps.selectedText to groq for analysis: ",
      selectionProps.selectedText.slice(0, 100)
    );
    try {
      const response = await fetch("/api/groq", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          systemPrompt:
            "You are an expert at analyzing text content and creating immediate subject descriptions for image generation. Based on the selected text, create a concise description of what should be the main subject/focus of an image.",
          userPrompt: `Analyze the following selected text and create an appropriate immediate subject description for image generation that captures the main focus, characters, or objects that should be prominently featured in the image:\n\n${selectionProps.selectedText}`,
        }),
      });
      const data = await response.json();
      const subjectResult = data.content || "";
      console.log(
        `   setting immediateSubject to groq result: ${subjectResult.slice(
          0,
          100
        )}...`
      );
      setImmediateSubject(subjectResult);
    } catch (error) {
      console.error("Error analyzing immediate subject:", error);
      console.log("immediateSubject set to: (error - empty)");
      setImmediateSubject("");
    }
  };
  // useEffect for immediateSubject - analyzes selectedText (only when not empty/null)
  useEffect(() => {
    console.log("selectionProps.selectedText useEffect called");
    if (
      !selectionProps.selectedText ||
      selectionProps.selectedText.trim() === ""
    ) {
      console.log("immediateSubject set to: (empty)");
      setImmediateSubject("");
      return;
    }
    analyzeImmediateSubject();
  }, [selectionProps.selectedText]);
  // useEffect to combine backgroundContext and immediateSubject into imageAgentUserPrompt
  useEffect(() => {
    console.log("backgroundContext immediateSubject useEffect called");
    if (backgroundContext || immediateSubject) {
      let prompt = "Generate an image";
 
[truncated — 10072 more characters]
```

### lib/supabase/server.ts

```typescript
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options),
            );
          } catch {
            // The `setAll` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    },
  );
}

```

### app/api/audio/route.ts

```typescript
import OpenAI from "openai";
import { NextRequest } from "next/server";

const openai = new OpenAI();

export async function POST(req: NextRequest) {
  const { text, voice = "coral" } = await req.json();

  const mp3 = await openai.audio.speech.create({
    model: "tts-1",
    voice: voice,
    input: text,
  });

  const buffer = Buffer.from(await mp3.arrayBuffer());
  
  return new Response(buffer, {
    headers: {
      "Content-Type": "audio/mpeg",
      "Content-Length": buffer.length.toString(),
    },
  });
}
```

### app/api/groq/route.ts

```typescript
import Groq from "groq-sdk";
import { NextResponse } from "next/server";

const groq = new Groq({
  apiKey: process.env.GROQ_API_KEY,
});

export async function POST(req: Request) {
    console.log("hitting groq endpoint")
  const { systemPrompt, userPrompt, model = "llama-3.3-70b-versatile" } = await req.json();

  const completion = await groq.chat.completions.create({
    messages: [
      {
        role: "system",
        content: systemPrompt,
      },
      {
        role: "user",
        content: userPrompt,
      },
    ],
    model,
  });

  return NextResponse.json({
    content: completion.choices[0]?.message?.content || "",
  });
}
```

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

```typescript
import { anthropic, AnthropicProviderOptions } from '@ai-sdk/anthropic';
import { groq } from '@ai-sdk/groq';
import { streamText } from 'ai';
import system from './system';

export async function POST(req: Request) {
  console.log("hitting chat endpoint");
  const { messages, model = 'claude', systemPrompt = system } = await req.json();

  let modelConfig;
  let providerOptions;
  let headers;

  if (model === 'groq') {
    console.log("hitting groq through chat");
    modelConfig = groq('llama-3.3-70b-versatile'); // or whichever Groq model you prefer
    providerOptions = undefined;
    headers = undefined;
  } else {
    // Default to Claude
    console.log("hitting claude through chat");
    modelConfig = anthropic('claude-3-7-sonnet-20250219');
    providerOptions = {
      anthropic: {
        thinking: { type: 'enabled', budgetTokens: 15000 },
      } satisfies AnthropicProviderOptions,
    };
    headers = {
      'anthropic-beta': 'interleaved-thinking-2025-05-14',
    };
  }

  const result = streamText({
    model: modelConfig,
    system: systemPrompt,
    messages,
    headers,
    providerOptions,
  });

  return result.toDataStreamResponse({
    sendReasoning: model !== 'groq', // Only send reasoning for Claude
  });
}
```

### app/api/image/route.ts

```typescript
import { createClient } from "@/lib/supabase/server";

export async function POST(req: Request) {
  const { prompt, model } = await req.json();
  console.log("\n\nimage api hit for ", model);

  // Check user credits
  const supabase = await createClient();
  const { data: { user }, error: authError } = await supabase.auth.getUser();
  
  if (authError || !user) {
    return Response.json({ error: "Authentication required" }, { status: 401 });
  }

  const currentCredits = user.user_metadata?.credits || 0;
  console.log(`User ${user.id} has ${currentCredits} credits`);

  if (currentCredits < 1) {
    return Response.json({ 
      error: "Insufficient credits. You need 1 credit to generate an image." 
    }, { status: 402 });
  }

  try {
    let processedPrompt = prompt;
    if (model === "dall-e-2") {
      processedPrompt = prompt.slice(-800);
    }

    console.log(`Generating image with prompt: ${processedPrompt}\n\n`);

    const response = await fetch(
      "https://api.openai.com/v1/images/generations",
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          model: model,
          prompt: processedPrompt,
          n: 1,
          size: "1024x1024",
        }),
      }
    );

    if (!response.ok) {
      const errorData = await response.json();
      console.log("OpenAI API error:", errorData);
      throw new Error(`OpenAI API error: ${response.status}`);
    }

    const data = await response.json();
    console.log("RESPONSE: api/image ... ");
    const imageUrl = data.data[0].url;

    // Fetch fresh credit count right before decrementing to avoid race conditions
    const { data: { user: freshUser }, error: freshUserError } = await supabase.auth.getUser();
    
    if (freshUserError || !freshUser) {
      console.error("Error fetching fresh user data:", freshUserError);
      return Response.json({ imageUrl }); // Still return the image, but don't update credits
    }

    const freshCredits = freshUser.user_metadata?.credits || 0;
    console.log(`Fresh credit check: ${freshCredits} credits remaining`);

    if (freshCredits < 1) {
      console.log("No credits remaining after fresh check, skipping decrement");
      return Response.json({ imageUrl }); // Still return the image, but don't update credits
    }

    // Deduct 1 credit after successful image generation
    const newCredits = freshCredits - 1;
    const { error: updateError } = await supabase.auth.updateUser({
      data: { 
        ...freshUser.user_metadata,
        credits: newCredits 
      }
    });

    if (updateError) {
      console.error("Error updating credits:", updateError);
    } else {
      console.log(`Credits updated: ${freshCredits} -> ${newCredits}`);
    }

    return Response.json({ imageUrl });
  } catch (error) {
    console.error("Error generating image:", error);
    return Response.json(
      { error: "Failed to generate image" },
      { status: 500 }
    );
  }
}

```

### next.config.ts

```typescript
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    domains: [
      'oaidalleapiprodscus.blob.core.windows.net',
    ],
  },
  devIndicators: false,
};

export default nextConfig;
```

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