# Project export: GenUIne

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: TreeHacks 2026
- Tagline: We made what AI browsers should be.
- Devpost: https://devpost.com/software/genuine-0pk8vg
- GitHub: https://github.com/bobdethird/genUIne
- Demo: https://genuine-one.vercel.app/
- Video: https://www.youtube.com/embed/lgk4CfOefDc?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Caden Li (30 commits), Heng Yang (11 commits), saniab27 (6 commits), Cursor (4 commits)

## Devpost submission (written by the team)

### Inspiration

When Steve Jobs introduced the iPhone, he explained how having a constant UI for smartphones didn't make sense. Each application requires a slightly different way to interact with information — something that fixed controls don't help with. A great example we like to use is that you'd much prefer a screen than a keyboard on the bottom half of your phone to play Clash Royale. By designing the iPhone to be a full screen, he created a fully flexible UI that could adapt to what each application required. We believe that AI interfaces are currently at a similar inflection point. Despite LLMs being used in various different fields, the way we interact with it has been the same — text in, text out. We want to change the way that people interact with structured and unstructured information. Our project explores how alternative visual outputs, like generative UIs, could help AI convey information in a way text cannot, and as a result, create what we believe what AI-native browsers should be like.

### What it does

GenUIne is our interpretation of what an AI-Native Browser looks like. At the core, GenUIne is an agent that gathers information, and generates a custom interfaces to help convey it as clearly as possible. This often means tables, graphs, maps and interactive diagrams, and customized versions of sites. This allows the user to interact with the rest of the internet in the way they're most comfortable with — whether it be buying headphones from various sites, learning physics with diagrams, or planning the full trip logistics — all without having to leave the window, or our AI. For example, GenUIne can webscrape and query many different storefronts to identify the best match products for users to buy, so that users don't waste time with an array of different websites, filters, and reviews.

### How we built it

GenUIne is a multi-layer application. At the top layer, we have a UI agent that is able to convert information gathered into a visually appealing UI. This is made possible with json-renderer, which essentially takes in a custom JSON file, and is able to compile it into a full react webapp based on a catalog of components that we created for the agent. Behind the UI agent, we have an information gathering agent, which has various tools from Exa, Stagehand, BrightData, Perplexity, and various other apis / functionalities that allow us to gather as much information as we need to display in order for the user to learn. Beyond that, we have many tailor-made interfaces that make data easy to visualize, such as 2D and 3D graphic renderers, dynamic graph display tools, and more.

### Challenges we ran into

We took the longest time to deliberate and finalize what the flow of the UI should look like as the user continues to make new prompts and interact with the agent. We had many different ideas and no formal UI/UX design experience prior to this project. We wanted a UI that would clearly link together related ideas, be appropriately modular for separating different kinds and levels of information, and easy to interact with. We ended up deciding on the current format of our project, which separates each distinct user query into a fresh page to display information clearly, with the ability to trace your own "chain-of-thought" along a conversation history sidebar and easily jump between thoughts, which we preferred over the current infinite-scroll format that most chatbots use. In addition, we made the UI's components able to be altered on the fly by user prompts after a user sees the version that the AI creates.

### Accomplishments we're proud of

We're proud of the UI that we created for GenUIne, especially since none of have prior UI/UX experience; its ability to address complex questions and create detailed UI for users to interact with; its ability to dynamically predict and adjust UI formats based on user queries; extensive web search tool; and how our UI handles unique and vastly different questions. Working on GenUIne as a team was also a great experience; we were able to optimize our workflow together and find different parts of the project that each member could work on simultaneously that best fit their strengths. We're glad that we were able to combine our different takes on optimal UI into one focused tool.

### What we learned

We learned how to implement BrowserUse agents, optimize UI and UX, dynamically load in UI, and create multi-agent workflows.

### What's next

GenUIne can be taken in a lot of directions. We hope that we can optimize the shopping exploration tool to revolutionize the future of commerce and user experience with purchases. We also plan to create a mobile application for GenUIne -- especially useful due to mobile browser difficulties -- and integrate other types of inputs (photo, audio) to make the UI more accessible. In addition, there are many aspects of the UI that can be further optimized; in fact, another future direction would be allowing users to customize their own AI interfaces.

## README (from the GitHub repository)

# GenUIne

**The Autonomous Personal Agent for your Digital Life.**

GenUIne is a next-generation AI interface that combines specific **Generative UI** capabilities with a powerful **Autonomous Swarm** backend. It doesn't just chat; it renders interactive interfaces and performs complex actions across the web and your private workspace.

## Features

### 1. Generative UI (The Front-End)
The AI agent responds with rich, interactive UI components defined by JSONL specs, not just plain text.
-   **Interactive Dashboards**: Live data visualization using Recharts.
-   **3D Rendering**: Immersive 3D scenes using React Three Fiber (e.g., Solar System, Molecules).
-   **Dynamic Components**: Cards, tables, and forms generated on the fly.

### 2. Autonomous Swarm (The Back-End)
Powered by **LangGraph**, the Swarm Agent ("The Brain") orchestrates a suite of powerful tools to accomplish multi-step checks and tasks.

#### 🧠 LangGraph Architecture
The agent uses a cyclic state graph to plan, execute, and refine its actions. It thinks before it acts, ensuring higher reliability for complex requests.

#### 🌍 Web Intelligence (The Eyes) -> Exa
-   Instead of generic searches, the agent uses **Exa.ai** to find the *exact* information or URL needed.
-   Capable of finding specific products, reviews, and detailed answers without hallucination.

#### 🖐️ Web Interaction (The Hands) -> Stagehand
-   **Stagehand Browser Agent** allows GenUIne to interact with live webpages.
-   It can click buttons, extract hidden pricing, and navigate complex sites autonomously.

#### 🏢 Google Workspace (The Office)
-   **Gmail**: Read and send emails.
-   **Calendar**: Check availability and schedule meetings.
-   **Drive**: Search for files, read documents, and include file links in emails.

## Tech Stack

-   **Framework**: [Next.js 16 (App Router)](https://nextjs.org)
-   **AI Orchestration**: [LangGraph](https://langchain-ai.github.io/langgraphjs/) & [Vercel AI SDK](https://sdk.vercel.ai/docs)
-   **Browser Automation**: [Stagehand](https://stagehand.dev)
-   **Search**: [Exa.ai](https://exa.ai)
-   **UI Library**: [React 19](https://react.dev), [Tailwind CSS v4](https://tailwindcss.com), [Shadcn UI](https://ui.shadcn.com)
-   **3D Rendering**: [React Three Fiber](https://r3f.docs.pmnd.rs/)

## Getting Started

### Prerequisites

-   Node.js 18+ installed
-   `npm`, `pnpm`, or `yarn`

### Installation

1.  Clone the repository:
    ```bash
    git clone https://github.com/yourusername/genuine.git
    cd genuine
    ```

2.  Install dependencies:
    ```bash
    npm install
    ```

3.  Configure Environment Variables:
    Create a `.env.local` file in the root directory.
    ```env
    # AI Keys
    OPENAI_API_KEY=sk-...
    EXA_API_KEY=...
    
    # Google Workspace (OAuth)
    GOOGLE_CLIENT_ID=...
    GOOGLE_CLIENT_SECRET=...
    
    # Optional
    AI_GATEWAY_MODEL=anthropic/claude-haiku-4.5
    ```

4.  Run the development server:
    ```bash
    npm run dev
    ```

5.  Open [http://localhost:3000](http://localhost:3000) with your browser.

## Project Structure

-   `app/`: Next.js App Router pages and API routes.
-   `lib/swarm/`: **The Autonomous Agent Core**.
    -   `graph.ts`: LangGraph state machine definition.
    -   `tools.ts`: Tool definitions (Exa, Stagehand, Google).
    -   `runner.ts`: Swarm execution logic.
-   `components/`: Reusable UI components.


## Detected evidence (automated analysis)

Indexed codebase: 83 recognized source files, 839 KB.
- CSS (language) — detected in the code
- LangChain (technology) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (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
- JavaScript (language) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Cursor — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (89 of 89)

```
.gitignore
app/api/chat/route.ts
app/api/debug/swarm/route.ts
app/api/planet-info/route.ts
app/api/title/route.ts
app/globals.css
app/layout.tsx
app/page.tsx
components.json
components/prompt-pill.tsx
components/theme-provider.tsx
components/theme-toggle.tsx
components/ui/accordion.tsx
components/ui/alert.tsx
components/ui/avatar.tsx
components/ui/badge.tsx
components/ui/button.tsx
components/ui/card.tsx
components/ui/chart.tsx
components/ui/dropdown-menu.tsx
components/ui/hover-card.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/popover.tsx
components/ui/progress.tsx
components/ui/radio-group.tsx
components/ui/scroll-area.tsx
components/ui/select.tsx
components/ui/separator.tsx
components/ui/sheet.tsx
components/ui/sidebar.tsx
components/ui/skeleton.tsx
components/ui/sonner.tsx
components/ui/table.tsx
components/ui/tabs.tsx
components/ui/textarea.tsx
components/ui/tooltip.tsx
docs/PLAN-follow-up-choices-flow.md
eslint.config.mjs
example-use.ts
hooks/use-mobile.ts
lib/agent.ts
lib/follow-up-choices/catalog.ts
lib/follow-up-choices/context.tsx
lib/follow-up-choices/FollowUpChoices.tsx
lib/follow-up-choices/index.ts
lib/gist-title.ts
lib/hooks/use-local-chat.ts
lib/mcp/brightdata.ts
lib/render/catalog.ts
lib/render/lazy/map-inner.tsx
lib/render/lazy/scene3d-inner.tsx
lib/render/lightbox.tsx
lib/render/registry.tsx
lib/render/renderer.tsx
lib/solar-system-spec.ts
lib/swarm/buffers.ts
lib/swarm/google-auth.ts
lib/swarm/graph.ts
lib/swarm/runner.ts
lib/swarm/tools.ts
lib/tools/crypto.ts
lib/tools/geocode.ts
lib/tools/github.ts
lib/tools/hackernews.ts
lib/tools/search.ts
lib/tools/stock.ts
lib/tools/swarm-reader.ts
lib/tools/weather.ts
lib/utils.ts
next.config.ts
package.json
pnpm-workspace.yaml
postcss.config.mjs
README.md
scripts/get-cookies.ts
scripts/get-google-token.ts
scripts/test-swarm-reader.ts
scripts/verify-autonomous.ts
scripts/verify-exa.ts
scripts/verify-google-agent.ts
scripts/verify-integrated-session.ts
scripts/verify-stagehand-cookies.ts
scripts/verify-stagehand.ts
scripts/verify-swarm-integrated.ts
scripts/verify-swarm.ts
scripts/watch-swarm.ts
tsconfig.json
verify_log.txt
```

### Dependencies

- package.json: @ai-sdk/gateway@^3.0.46, @ai-sdk/mcp@^1.0.21, @ai-sdk/openai@^3.0.29, @ai-sdk/react@^3.0.88, @browserbasehq/stagehand@^3.0.8, @json-render/core@^0.6.0, @json-render/react@^0.6.0, @langchain/core@^1.1.24, @langchain/langgraph@^1.1.4, @langchain/openai@^1.2.7, @react-three/drei@^10.7.7, @react-three/fiber@^9.5.0, @streamdown/code@^1.0.2, @tailwindcss/postcss@^4, @types/mapbox-gl@^3.4.1, @types/node@^20.19.33, @types/react@^19, @types/react-dom@^19, @types/three@^0.182.0, accordian@^0.0.1, ai@^6.0.86, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9, eslint-config-next@16.1.6, exa-js@^2.4.0, googleapis@^171.4.0, langchain@^1.2.24, lucide-react@^0.564.0, mapbox-gl@^3.18.1, next@16.1.6, next-themes@^0.4.6, open@^11.0.0, radix-ui@^1.4.3, react@19.2.3, react-dom@19.2.3, react-map-gl@^8.1.0, recharts@2.15.4, shadcn@^3.8.4, sonner@^2.0.7, streamdown@^2.2.0, tailwind-merge@^3.4.0, tailwindcss@^4, three@^0.182.0, tsx@^4.21.0, tw-animate-css@^1.4.0, typescript@^5, yahoo-finance2@^3.13.0, zod@^3.23.8, zod-v3@npm:zod@^3.25.76, zod-v4@npm:zod@^4.3.6

### Recent commits (newest first)

- Merge branch 'main' of https://github.com/bobdethird/chatgpt-v2
- Merge branch 'gsd-swarm' into main with conflict resolution and updated README
- halfway merge?
- v1 of valentine's demo
- complete backend gsd swarm
- fixed timeline
- aoeu
- Merge branch 'generating-buttons'
- multiple choice buttons
- integrated tests are chill but overall doesn't work
- thinking placeholder fix
- wtf
- final scrollable history ui changes
- tiny scrollable history changes
- Merge scrollable-history into main
- agents work fine, need system prompt redesign
- correct thinking status
- changed back to haiku and thinking shimmer
- projectile sim
- memoized lightbox

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

### docs/PLAN-follow-up-choices-flow.md

```markdown
# Plan: Interactive Follow-Up Choices Flow (e.g. Valentine’s Dinner)

## Goal

When the user asks for suggestions (e.g. “I’m planning to go somewhere with my partner for Valentine’s day dinner, can you help suggest?”):

1. **AI** determines relevant **categories** of follow-up (e.g. Dinner, Activity, Drinks).
2. **UI** shows these as **multi-select buttons** the user can tap.
3. The user’s **selections become the next prompt** (shortcut): e.g. “I’d like: dinner and drinks” is sent automatically as the next user message so the chatbot continues with that context.

---

## Current Behavior (for context)

- **Chat**: `app/page.tsx` uses `useChat`; `handleSubmit(text?)` sends a message (optionally with a string, e.g. from suggestion pills).
- **Rendering**: Assistant content is rendered via `ExplorerRenderer` (spec-driven UI from `lib/render/`). The renderer has no callback to the page today.
- **Suggestions**: Empty state has `SUGGESTIONS` pills that call `handleSubmit(s.prompt)` on click.

---

## Proposed Flow (end-to-end)

1. User sends: *“I’m planning to go somewhere with my partner for Valentine’s day dinner, can you help suggest?”*
2. **Backend (agent)** responds with a **spec** that includes:
   - A short intro (e.g. Card + Text).
   - A **FollowUpChoices** component with categories like: `Dinner`, `Activity`, `Drinks`, and optionally `Other`.
3. **Frontend** renders the spec; **FollowUpChoices** shows:
   - One button per category (multi-select toggles).
   - A **“Continue”** (or “Send”) button.
4. User selects e.g. **Dinner** and **Drinks**, then clicks **Continue**.
5. Frontend **builds a shortcut message** from the selected labels (e.g. “I’m interested in: dinner and drinks”) and calls the same **send** path as the normal input (e.g. `handleSubmit(shortcut)`).
6. That message is appended as the **next user message** and the AI continues with that context (no extra click or typing).

---

## Implementation Outline

### 1. New component: `FollowUpChoices`

- **Catalog** (`lib/render/catalog.ts`): Define a component with props, e.g.:
  - `categories`: array of `{ id: string, label: string }` (e.g. `[{ id: "dinner", label: "Dinner" }, ...]`).
  - `multiSelect`: boolean (default true).
  - `confirmLabel`: string (e.g. `"Continue"`).
  - Optional: `intro` or `title` for a short line above the buttons.
- **Registry** (`lib/render/registry.tsx`): Implement the component:
  - Render a row of toggle buttons (or pill-style toggles) per category.
  - “Continue” button that:
    - Builds a single string from the selected labels (e.g. “I’m interested in: dinner and drinks”).
    - Calls an **app-level callback** to send that string as the next user message (see below).
- **Callback from renderer to page**: The component must be able to call something like `sendFollowUp(text)`. Options:
  - **Recommended**: Pass an optional callback from the page into the renderer (e.g. `onSendFollowUp`) and provide it to the component via **React context** inside the r
[truncated — 4915 more characters]
```

### package.json

```
{
  "name": "chatgpt-v2",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint",
    "verify-swarm": "tsx scripts/verify-swarm.ts",
    "setup-google": "tsx scripts/get-google-token.ts",
    "verify-google-agent": "tsx scripts/verify-google-agent.ts"
  },
  "dependencies": {
    "@ai-sdk/gateway": "^3.0.46",
    "@ai-sdk/mcp": "^1.0.21",
    "@ai-sdk/openai": "^3.0.29",
    "@ai-sdk/react": "^3.0.88",
    "@browserbasehq/stagehand": "^3.0.8",
    "@json-render/core": "^0.6.0",
    "@json-render/react": "^0.6.0",
    "@langchain/core": "^1.1.24",
    "@langchain/langgraph": "^1.1.4",
    "@langchain/openai": "^1.2.7",
    "@react-three/drei": "^10.7.7",
    "@react-three/fiber": "^9.5.0",
    "@streamdown/code": "^1.0.2",
    "accordian": "^0.0.1",
    "ai": "^6.0.86",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "exa-js": "^2.4.0",
    "googleapis": "^171.4.0",
    "langchain": "^1.2.24",
    "lucide-react": "^0.564.0",
    "mapbox-gl": "^3.18.1",
    "next": "16.1.6",
    "next-themes": "^0.4.6",
    "open": "^11.0.0",
    "radix-ui": "^1.4.3",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "react-map-gl": "^8.1.0",
    "recharts": "2.15.4",
    "sonner": "^2.0.7",
    "streamdown": "^2.2.0",
    "tailwind-merge": "^3.4.0",
    "three": "^0.182.0",
    "yahoo-finance2": "^3.13.0",
    "zod": "^3.23.8",
    "zod-v3": "npm:zod@^3.25.76",
    "zod-v4": "npm:zod@^4.3.6"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/mapbox-gl": "^3.4.1",
    "@types/node": "^20.19.33",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "@types/three": "^0.182.0",
    "eslint": "^9",
    "eslint-config-next": "16.1.6",
    "shadcn": "^3.8.4",
    "tailwindcss": "^4",
    "tsx": "^4.21.0",
    "tw-animate-css": "^1.4.0",
    "typescript": "^5"
  }
}
```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Toaster } from "sonner";
import { ThemeProvider } from "@/components/theme-provider";
import "streamdown/styles.css";
import "./globals.css";

export const metadata: Metadata = {
  title: "ChatGPT V2",
  description: "AI-powered data explorer using ToolLoopAgent and json-render",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className="font-sans antialiased">
        <ThemeProvider>
          {children}
          <Toaster />
        </ThemeProvider>
      </body>
    </html>
  );
}
```

### lib/follow-up-choices/index.ts

```typescript
export { followUpChoicesCatalogEntry } from "./catalog";
export { FollowUpChoicesComponent } from "./FollowUpChoices";
export {
  FollowUpProvider,
  useSendFollowUp,
  type SendFollowUp,
} from "./context";

```

### app/api/planet-info/route.ts

```typescript
import { generateText } from "ai";
import { gateway } from "@ai-sdk/gateway";

const MODEL = process.env.AI_GATEWAY_MODEL || "anthropic/claude-haiku-4.5";

const PLANET_SYSTEM = `You are a concise astronomy assistant. Given a planet (or Sun/Moon), output 4–5 short factual bullets.

RULES:
- Start with distance from Sun in km or AU (for Moon: distance from Earth).
- Include: orbital period or day length, radius/size, one distinctive fact.
- One line per bullet. No headers, no numbering, no markdown.
- Max 80 characters per line. Be accurate and scannable.`;

export const maxDuration = 10;

export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const planet = searchParams.get("planet")?.trim();
  if (!planet) {
    return Response.json({ error: "planet query required" }, { status: 400 });
  }

  try {
    const { text } = await generateText({
      model: gateway(MODEL),
      system: PLANET_SYSTEM,
      prompt: `Planet (or body): ${planet}\n\nBullet facts:`,
      temperature: 0.2,
    });

    const facts = text
      .split("\n")
      .map((s) => s.trim())
      .filter(Boolean)
      .slice(0, 5);

    return Response.json({ name: planet, facts });
  } catch (error) {
    return Response.json(
      {
        error:
          error instanceof Error ? error.message : "Failed to generate planet info",
      },
      { status: 500 }
    );
  }
}

```

### app/api/title/route.ts

```typescript
import { generateText } from "ai";
import { gateway } from "@ai-sdk/gateway";

const MODEL = process.env.AI_GATEWAY_TITLE_MODEL || "google/gemini-2.5-flash";

const TITLE_SYSTEM = `Generate a clean, short topic title for a user prompt.

RULES:
- 5–8 words. Be specific, not generic. Include the main verb + object.
- No filler words (a, the, please, could you, etc.).
- No quotes, punctuation, or markdown.
- Must be readable out of context.

Output ONLY the title, nothing else.`;

export const maxDuration = 10;

export async function POST(req: Request) {
  try {
    const body = await req.json();
    const prompt = typeof body?.prompt === "string" ? body.prompt.trim() : "";
    if (!prompt) {
      return Response.json(
        { error: "prompt is required" },
        { status: 400 }
      );
    }

    const { text } = await generateText({
      model: gateway(MODEL),
      system: TITLE_SYSTEM,
      prompt: `User prompt:\n"${prompt}"\n\nTitle:`,
      temperature: 0.2,
    });

    const title = sanitizeTitle(text);
    return Response.json({ title });
  } catch (error) {
    return Response.json(
      { error: error instanceof Error ? error.message : "Failed to generate title" },
      { status: 500 }
    );
  }
}

function sanitizeTitle(raw: string): string {
  let s = raw
    .trim()
    .replace(/^["'`]+|["'`]+$/g, "")
    .replace(/[#*_`\[\](){}]/g, "")
    .replace(/\.+$/, "")
    .replace(/\s+/g, " ");
  if (s.length > 60) s = s.slice(0, 60).trim();
  return toTitleCase(s);
}

function toTitleCase(s: string): string {
  return s.replace(
    /\b\w+/g,
    (w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()
  );
}

```

### app/page.tsx

```typescript
"use client";

import { useState, useCallback, useRef, useEffect, memo, Fragment } from "react";
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport, type UIMessage } from "ai";
import {
  SPEC_DATA_PART,
  SPEC_DATA_PART_TYPE,
  type SpecDataPart,
} from "@json-render/core";
import { useJsonRenderMessage } from "@json-render/react";
import { ExplorerRenderer } from "@/lib/render/renderer";
import { ThemeToggle } from "@/components/theme-toggle";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Alert, AlertDescription } from "@/components/ui/alert";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import {
  ArrowUp,
  ChevronRight,
  Code2,
  Loader2,
  Sparkles,
  AlertCircle,
  MoreVertical,
  Clock,
  MessageSquarePlus,
} from "lucide-react";
import { Streamdown } from "streamdown";
import { code } from "@streamdown/code";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar";
import { useLocalChat } from "@/lib/hooks/use-local-chat";
import { Plus } from "lucide-react";
import { PromptPill } from "@/components/prompt-pill";
import { FollowUpProvider } from "@/lib/follow-up-choices";


// =============================================================================
// Types
// =============================================================================

type AppDataParts = { [SPEC_DATA_PART]: SpecDataPart };
type AppMessage = UIMessage<unknown, AppDataParts>;

// =============================================================================
// Transport
// =============================================================================

/**
 * Strip heavy spec-data parts and tool output from messages before sending to
 * the API. The LLM generated those specs — it doesn't need to see them again —
 * and tool outputs are already summarized in the assistant text. This alone
 * can cut payload size by 10-50x on long conversations.
 *
 * We also cap history to the last MAX_HISTORY_MESSAGES messages to prevent
 * unbounded growth.
 */
const MAX_HISTORY_MESSAGES = 20;

function stripHeavyParts(messages: UIMessage[]): UIMessage[] {
  // Take only the last N messages to cap context size
  const capped =
    messages.length > MAX_HISTORY_MESSAGES
      ? messages.slice(-MAX_HISTORY_MESSAGES)
      : messages;

  return capped.map((m) => {
    if (m.role !== "assistant" || !Array.isArray(m.parts)) return m;

    // Filter out spec data parts and strip tool result output
    const lightParts = m.parts
      .filter((p: any) => p.type !== SPEC_DATA_PART_TYPE)
      .map((p: any) => {
        // Strip large tool output data — keep the tool call metadata
        if (p.type?.startsWith("tool-") && p.output != null) {
          return { ...p, output: "[stripped]" };
        }
        return p;
      });

    return { ...m, parts: lightParts };
  });
}

const transport = new DefaultChatTransport({
  api: "/api/chat",
  prepareSendMessagesRequest: ({ id, messages }) => ({
    body: { id, messages: stripHeavyParts(messages as UIMessage[]) },
  }),
});

// =============================================================================
// Helpers
// =============================================================================

function extractPromptFromMessage(m: AppMessage): string {
  const msg = m as { content?: string; parts?: Array<{ type?: string; text?: string }> };
  if (typeof msg.content === "string") return msg.content;
  if (Array.isArray(msg.parts)) {
    const textPart = msg.parts.find((p) => p.type === "text");
    if (textPart && typeof textPart.text === "string") return textPart.text;
  }
  return "";
}

// =============================================================================
// Suggestions (shown in empty state)
// =============================================================================

const SUGGESTIONS = [
  {
    label: "Weather comparison",
    prompt: "Compare the weather in New York, London, and Tokyo",
  },
  {
    label: "GitHub repo stats",
    prompt: "Show me stats for the vercel/next.js and vercel/ai GitHub repos",
  },
  {
    label: "Stock prices",
    prompt: "Check the stock price of TQQQ, Nvidia, and Apple",
  },
  {
    label: "Hacker News top stories",
    prompt: "Show me the top 15 Hacker News stories right now",
  },
];

// =============================================================================
// Tool Call Display
// =============================================================================

/** Readable labels for tool names: [loading, done] */
const TOOL_LABELS: Record<string, [string, string]> = {
  getWeather: ["Getting weather data", "Got weather data"],
  getGitHubRepo: ["Fetching GitHub repo", "Fetched GitHub repo"],
  getGitHubPullRequests: ["Fetching pull requests", "Fetched pull requests"],
  getCryptoPrice: ["Looking up crypto price", "Looked up crypto price"],
  getCryptoPriceHistory: ["Fetching price history", "Fetched price history"],
  getHackerNewsTop: ["Loading Hacker News", "Loaded Hacker News"],
  webSearch: ["Searching the web", "Searched the web"],
};

const SpecWithDebug = memo(function SpecWithDebug({
  spec,
  loading,
}: {
  spec: Parameters<typeof ExplorerRenderer>[0]["spec"];
  loading: boolean;
}) {
  const [showJson, setShowJson] = useState(false);
  return (
    <div className="w-full flex flex-col gap-2">
      <ExplorerRenderer spec={spec} loading={loading} />
      <button
        type="button"
        className="self-end inline-flex items-center gap-1 text-xs text-muted-foreground/50 hover:text-muted-foreground transition-colors"
        onClick={() => setShowJson((v) => !v)}
      >
        <Code2 className="h-3 w-3" />
        {showJson ? "Hide JSON" : "Show JSON"}
      </butto
[truncated — 37161 more characters]
```

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

```typescript
import { createAgent } from "@/lib/agent";
import { startSwarm } from "@/lib/swarm/runner";
import { getSolarSystemSpec } from "@/lib/solar-system-spec";
import {
  convertToModelMessages,
  createUIMessageStream,
  createUIMessageStreamResponse,
  type UIMessage,
} from "ai";
import { pipeJsonRender } from "@json-render/core";

// Increase timeout for full stall
export const maxDuration = 300; // 5 minutes

const SOLAR_SYSTEM_INTENT =
  /\b(solar\s+system|diagram\s+of\s+(the\s+)?solar\s+system|show\s+me\s+.*solar|solar\s+system\s+diagram)\b/i;

function getLastUserMessageText(messages: UIMessage[]): string {
  const userMessages = messages.filter((m) => m.role === "user");
  const last = userMessages[userMessages.length - 1];
  if (!last) return "";
  const parts = (last as { parts?: Array<{ type?: string; text?: string }> }).parts;
  if (Array.isArray(parts)) {
    const textPart = parts.find((p) => p.type === "text");
    if (textPart && typeof textPart.text === "string") return textPart.text;
  }
  return "";
}

export async function POST(req: Request) {
  const body = await req.json();
  const uiMessages: UIMessage[] = body.messages;

  if (!uiMessages || !Array.isArray(uiMessages) || uiMessages.length === 0) {
    return new Response(
      JSON.stringify({ error: "messages array is required" }),
      {
        status: 400,
        headers: { "Content-Type": "application/json" },
      },
    );
  }

  // 1. Force Fixed Session
  const sessionId = "demo-heng-yang";

  // 2. Handle Swarm Triggers
  // Extract the latest user query to start/update the swarm
  // DEBUG: Log the raw messages length
  console.log(`[Route] Received ${uiMessages.length} messages.`);

  // Find the last message that is strictly from the user
  const lastUserMessage = [...uiMessages].reverse().find(m => m.role === 'user');

  // LOG THE MESSAGE OBJECT TO SEE STRUCTURE
  if (lastUserMessage) {
    console.log(`[Route] Last User Message Object:`, JSON.stringify(lastUserMessage).slice(0, 200));
  } else {
    console.log(`[Route] No user message found in history.`);
  }

  // Handle both possible structures (direct content property or nested parts)
  let query: string | null = null;

  if (lastUserMessage) {
    const messageDetails = lastUserMessage as any;
    if (typeof messageDetails.content === 'string') {
      query = messageDetails.content;
    } else if (Array.isArray(messageDetails.content)) {
      // Vercel AI SDK Core Message format
      query = messageDetails.content.map((c: any) => c.type === 'text' ? c.text : '').join('');
    } else if ('parts' in messageDetails && Array.isArray(messageDetails.parts)) {
      // Vercel AI SDK UI Message format (sometimes)
      query = messageDetails.parts.map((p: any) => p.text || '').join('');
    } else {
      // Fallback
      query = JSON.stringify(messageDetails.content || messageDetails);
    }
  }

  console.log(`[Route] Extracted Query: "${query}"`);

  // FULL STALL MODE: Trigger Swarm and WAIT for completion
  if (query) {
    console.log(`[Route] Triggering Swarm for ${sessionId} and WAITING for completion...`);
    try {
      // Deconstruct the execution promise from the runner
      // explicit cast to any as the runner was updated in a separate turn
      const swarmResult = startSwarm(sessionId, query as string) as any;
      const executionPromise = swarmResult.executionPromise;

      if (executionPromise) {
        console.log(`[Route] Execution promise found. Awaiting...`);
        await executionPromise;
        console.log(`[Route] Swarm finished execution. Proceeding to UI Agent.`);
      } else {
        console.warn(`[Route] Swarm started but no execution promise returned.`);
      }

    } catch (e) {
      console.error(`[Route] FAILED to run swarm:`, e);
    }
  } else {
    console.warn(`[Route] No user query found, skipping swarm trigger.`);
  }

  // 3. CONTEXT INJECTION
  // Read the swarm buffer to get the latest status
  const { getSwarmBuffer } = await import("@/lib/swarm/buffers");
  const buffer = getSwarmBuffer(sessionId);

  // We inject a SYSTEM message at the end of the history (before the new user message ideally, or just as context)
  // AI SDK Core doesn't strictly enforce message order for 'system' messages, but usually they go first.
  // BUT we want this to be "fresh" context. So we can add it as a 'system' message right before the last user message.

  let injectedSystemMessage: any = null;
  if (buffer) {
    const recentLogs = buffer.logs.slice(-5).join("\n");
    const recentArtifacts = buffer.artifacts.slice(-3).map(a =>
      `TYPE: ${a.type}\nTITLE: ${a.title}\nCONTENT: ${JSON.stringify(a.content).slice(0, 500)}...`
    ).join("\n---\n");

    const statusMsg = `
[BACKGROUND SWARM STATUS]
Status: ${buffer.status}
Recent Activity:
${recentLogs}

[FOUND DATA/ARTIFACTS]
${recentArtifacts}

INSTRUCTION: Uses the above data to answer the user's request. If there are structured artifacts (products, lists), RENDER THEM AS UI COMPONENTS (Tables, Cards).
`;
    injectedSystemMessage = { role: "system", content: statusMsg };
  }

  // 4. Create the Agent
  const agent = createAgent(sessionId);

  let modelMessages = await convertToModelMessages(uiMessages);

  // Inject the context
  if (injectedSystemMessage) {
    modelMessages = [...modelMessages, injectedSystemMessage];
  }

  const lastUserText = getLastUserMessageText(uiMessages);
  const useSolarShortcut = SOLAR_SYSTEM_INTENT.test(lastUserText.trim());

  if (useSolarShortcut) {
    const flatSpec = getSolarSystemSpec();
    const textId = crypto.randomUUID?.() ?? `text-${Date.now()}`;
    const intro =
      "Here's an interactive solar system diagram. Hover over planets to see info.\n\n";

    const stream = createUIMessageStream({
      originalMessages: uiMessages,
      execute: async ({ writer }) => {
        writer.write({ type: "text-start", id: textId });
        writer.write({ type: "text-delta", id: textId, delta: intro });
        writer.write(
[truncated — 537 more characters]
```

### app/api/debug/swarm/route.ts

```typescript
import { getSwarmBuffer } from "@/lib/swarm/buffers";
import { NextResponse } from "next/server";

export async function GET() {
    // Hardcoded session ID as per our hackathon "Single Swarm" architecture
    const sessionId = "demo-heng-yang";
    const buffer = getSwarmBuffer(sessionId);

    // Return empty state if no buffer yet
    return NextResponse.json(buffer || {
        status: "idle",
        logs: ["No active swarm session found yet."],
        artifacts: []
    });
}

```

### pnpm-workspace.yaml

```yaml
ignoredBuiltDependencies:
  - sharp
  - unrs-resolver

```

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