Full Code of vercel/chatbot for AI

main d83238b29b87 cached
225 files
1.3 MB
433.6k tokens
597 symbols
1 requests
Download .txt
Showing preview only (1,352K chars total). Download the full file or copy to clipboard to get everything.
Repository: vercel/chatbot
Branch: main
Commit: d83238b29b87
Files: 225
Total size: 1.3 MB

Directory structure:
gitextract_y5pipq23/

├── .changeset/
│   └── config.json
├── .cursor/
│   └── rules/
│       └── ultracite.mdc
├── .github/
│   ├── CONTRIBUTING.md
│   └── workflows/
│       ├── lint.yml
│       ├── playwright.yml
│       └── release.yml
├── .gitignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── LICENSE
├── README.md
├── app/
│   ├── (auth)/
│   │   ├── actions.ts
│   │   ├── api/
│   │   │   └── auth/
│   │   │       ├── [...nextauth]/
│   │   │       │   └── route.ts
│   │   │       └── guest/
│   │   │           └── route.ts
│   │   ├── auth.config.ts
│   │   ├── auth.ts
│   │   ├── login/
│   │   │   └── page.tsx
│   │   └── register/
│   │       └── page.tsx
│   ├── (chat)/
│   │   ├── actions.ts
│   │   ├── api/
│   │   │   ├── chat/
│   │   │   │   ├── [id]/
│   │   │   │   │   └── stream/
│   │   │   │   │       └── route.ts
│   │   │   │   ├── route.ts
│   │   │   │   └── schema.ts
│   │   │   ├── document/
│   │   │   │   └── route.ts
│   │   │   ├── files/
│   │   │   │   └── upload/
│   │   │   │       └── route.ts
│   │   │   ├── history/
│   │   │   │   └── route.ts
│   │   │   ├── suggestions/
│   │   │   │   └── route.ts
│   │   │   └── vote/
│   │   │       └── route.ts
│   │   ├── chat/
│   │   │   └── [id]/
│   │   │       └── page.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── globals.css
│   └── layout.tsx
├── artifacts/
│   ├── actions.ts
│   ├── code/
│   │   ├── client.tsx
│   │   └── server.ts
│   ├── image/
│   │   └── client.tsx
│   ├── sheet/
│   │   ├── client.tsx
│   │   └── server.ts
│   └── text/
│       ├── client.tsx
│       └── server.ts
├── biome.jsonc
├── components/
│   ├── ai-elements/
│   │   ├── artifact.tsx
│   │   ├── canvas.tsx
│   │   ├── chain-of-thought.tsx
│   │   ├── checkpoint.tsx
│   │   ├── confirmation.tsx
│   │   ├── connection.tsx
│   │   ├── controls.tsx
│   │   ├── conversation.tsx
│   │   ├── edge.tsx
│   │   ├── image.tsx
│   │   ├── inline-citation.tsx
│   │   ├── loader.tsx
│   │   ├── message.tsx
│   │   ├── model-selector.tsx
│   │   ├── node.tsx
│   │   ├── open-in-chat.tsx
│   │   ├── panel.tsx
│   │   ├── plan.tsx
│   │   ├── prompt-input.tsx
│   │   ├── queue.tsx
│   │   ├── reasoning.tsx
│   │   ├── shimmer.tsx
│   │   ├── sources.tsx
│   │   ├── suggestion.tsx
│   │   ├── task.tsx
│   │   ├── tool.tsx
│   │   ├── toolbar.tsx
│   │   └── web-preview.tsx
│   ├── app-sidebar.tsx
│   ├── artifact-actions.tsx
│   ├── artifact-close-button.tsx
│   ├── artifact-messages.tsx
│   ├── artifact.tsx
│   ├── auth-form.tsx
│   ├── chat-header.tsx
│   ├── chat.tsx
│   ├── code-editor.tsx
│   ├── console.tsx
│   ├── create-artifact.tsx
│   ├── data-stream-handler.tsx
│   ├── data-stream-provider.tsx
│   ├── diffview.tsx
│   ├── document-preview.tsx
│   ├── document-skeleton.tsx
│   ├── document.tsx
│   ├── elements/
│   │   ├── actions.tsx
│   │   ├── branch.tsx
│   │   ├── conversation.tsx
│   │   ├── image.tsx
│   │   ├── inline-citation.tsx
│   │   ├── loader.tsx
│   │   ├── message.tsx
│   │   ├── prompt-input.tsx
│   │   ├── reasoning.tsx
│   │   ├── response.tsx
│   │   ├── source.tsx
│   │   ├── suggestion.tsx
│   │   ├── task.tsx
│   │   ├── tool.tsx
│   │   └── web-preview.tsx
│   ├── greeting.tsx
│   ├── icons.tsx
│   ├── image-editor.tsx
│   ├── message-actions.tsx
│   ├── message-editor.tsx
│   ├── message-reasoning.tsx
│   ├── message.tsx
│   ├── messages.tsx
│   ├── multimodal-input.tsx
│   ├── preview-attachment.tsx
│   ├── sheet-editor.tsx
│   ├── sidebar-history-item.tsx
│   ├── sidebar-history.tsx
│   ├── sidebar-toggle.tsx
│   ├── sidebar-user-nav.tsx
│   ├── sign-out-form.tsx
│   ├── submit-button.tsx
│   ├── suggested-actions.tsx
│   ├── suggestion.tsx
│   ├── text-editor.tsx
│   ├── theme-provider.tsx
│   ├── toast.tsx
│   ├── toolbar.tsx
│   ├── ui/
│   │   ├── alert-dialog.tsx
│   │   ├── alert.tsx
│   │   ├── avatar.tsx
│   │   ├── badge.tsx
│   │   ├── button-group.tsx
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   ├── carousel.tsx
│   │   ├── collapsible.tsx
│   │   ├── command.tsx
│   │   ├── dialog.tsx
│   │   ├── dropdown-menu.tsx
│   │   ├── hover-card.tsx
│   │   ├── input-group.tsx
│   │   ├── input.tsx
│   │   ├── label.tsx
│   │   ├── progress.tsx
│   │   ├── scroll-area.tsx
│   │   ├── select.tsx
│   │   ├── separator.tsx
│   │   ├── sheet.tsx
│   │   ├── sidebar.tsx
│   │   ├── skeleton.tsx
│   │   ├── textarea.tsx
│   │   └── tooltip.tsx
│   ├── version-footer.tsx
│   ├── visibility-selector.tsx
│   └── weather.tsx
├── components.json
├── drizzle.config.ts
├── hooks/
│   ├── use-artifact.ts
│   ├── use-auto-resume.ts
│   ├── use-chat-visibility.ts
│   ├── use-messages.tsx
│   ├── use-mobile.ts
│   └── use-scroll-to-bottom.tsx
├── instrumentation-client.ts
├── instrumentation.ts
├── lib/
│   ├── ai/
│   │   ├── entitlements.ts
│   │   ├── models.mock.ts
│   │   ├── models.test.ts
│   │   ├── models.ts
│   │   ├── prompts.ts
│   │   ├── providers.ts
│   │   └── tools/
│   │       ├── create-document.ts
│   │       ├── get-weather.ts
│   │       ├── request-suggestions.ts
│   │       └── update-document.ts
│   ├── artifacts/
│   │   └── server.ts
│   ├── constants.ts
│   ├── db/
│   │   ├── helpers/
│   │   │   └── 01-core-to-parts.ts
│   │   ├── migrate.ts
│   │   ├── migrations/
│   │   │   ├── 0000_keen_devos.sql
│   │   │   ├── 0001_sparkling_blue_marvel.sql
│   │   │   ├── 0002_wandering_riptide.sql
│   │   │   ├── 0003_cloudy_glorian.sql
│   │   │   ├── 0004_odd_slayback.sql
│   │   │   ├── 0005_wooden_whistler.sql
│   │   │   ├── 0006_marvelous_frog_thor.sql
│   │   │   ├── 0007_flowery_ben_parker.sql
│   │   │   ├── 0008_flat_forgotten_one.sql
│   │   │   └── meta/
│   │   │       ├── 0000_snapshot.json
│   │   │       ├── 0001_snapshot.json
│   │   │       ├── 0002_snapshot.json
│   │   │       ├── 0003_snapshot.json
│   │   │       ├── 0004_snapshot.json
│   │   │       ├── 0005_snapshot.json
│   │   │       ├── 0006_snapshot.json
│   │   │       ├── 0007_snapshot.json
│   │   │       ├── 0008_snapshot.json
│   │   │       └── _journal.json
│   │   ├── queries.ts
│   │   ├── schema.ts
│   │   └── utils.ts
│   ├── editor/
│   │   ├── config.ts
│   │   ├── diff.js
│   │   ├── functions.tsx
│   │   ├── react-renderer.tsx
│   │   └── suggestions.tsx
│   ├── errors.ts
│   ├── ratelimit.ts
│   ├── types.ts
│   └── utils.ts
├── next-env.d.ts
├── next.config.ts
├── package.json
├── playwright.config.ts
├── postcss.config.mjs
├── proxy.ts
├── tests/
│   ├── e2e/
│   │   ├── api.test.ts
│   │   ├── auth.test.ts
│   │   ├── chat.test.ts
│   │   └── model-selector.test.ts
│   ├── fixtures.ts
│   ├── helpers.ts
│   ├── pages/
│   │   └── chat.ts
│   └── prompts/
│       └── utils.ts
├── tsconfig.json
├── tsconfig.tsbuildinfo
├── vercel-template.json
└── vercel.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .changeset/config.json
================================================
{
  "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
  "changelog": [
    "@changesets/changelog-github",
    { "repo": "vercel-labs/chatbot" }
  ],
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}


================================================
FILE: .cursor/rules/ultracite.mdc
================================================
---
description: Ultracite Rules - AI-Ready Formatter and Linter
globs: "**/*.{ts,tsx,js,jsx}"
alwaysApply: true
---

# Project Context
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.

## Key Principles
- Zero configuration required
- Subsecond performance
- Maximum type safety
- AI-friendly code generation

## Before Writing Code
1. Analyze existing patterns in the codebase
2. Consider edge cases and error scenarios
3. Follow the rules below strictly
4. Validate accessibility requirements

## Rules

### Accessibility (a11y)
- Don't use `accessKey` attribute on any HTML element.
- Don't set `aria-hidden="true"` on focusable elements.
- Don't add ARIA roles, states, and properties to elements that don't support them.
- Don't use distracting elements like `<marquee>` or `<blink>`.
- Only use the `scope` prop on `<th>` elements.
- Don't assign non-interactive ARIA roles to interactive HTML elements.
- Make sure label elements have text content and are associated with an input.
- Don't assign interactive ARIA roles to non-interactive HTML elements.
- Don't assign `tabIndex` to non-interactive HTML elements.
- Don't use positive integers for `tabIndex` property.
- Don't include "image", "picture", or "photo" in img alt prop.
- Don't use explicit role property that's the same as the implicit/default role.
- Make static elements with click handlers use a valid role attribute.
- Always include a `title` element for SVG elements.
- Give all elements requiring alt text meaningful information for screen readers.
- Make sure anchors have content that's accessible to screen readers.
- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`.
- Include all required ARIA attributes for elements with ARIA roles.
- Make sure ARIA properties are valid for the element's supported roles.
- Always include a `type` attribute for button elements.
- Make elements with interactive roles and handlers focusable.
- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`).
- Always include a `lang` attribute on the html element.
- Always include a `title` attribute for iframe elements.
- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
- Include caption tracks for audio and video elements.
- Use semantic elements instead of role attributes in JSX.
- Make sure all anchors are valid and navigable.
- Ensure all ARIA properties (`aria-*`) are valid.
- Use valid, non-abstract ARIA roles for elements with ARIA roles.
- Use valid ARIA state and property values.
- Use valid values for the `autocomplete` attribute on input elements.
- Use correct ISO language/country codes for the `lang` attribute.

### Code Complexity and Quality
- Don't use consecutive spaces in regular expression literals.
- Don't use the `arguments` object.
- Don't use primitive type aliases or misleading types.
- Don't use the comma operator.
- Don't use empty type parameters in type aliases and interfaces.
- Don't write functions that exceed a given Cognitive Complexity score.
- Don't nest describe() blocks too deeply in test files.
- Don't use unnecessary boolean casts.
- Don't use unnecessary callbacks with flatMap.
- Use for...of statements instead of Array.forEach.
- Don't create classes that only have static members (like a static namespace).
- Don't use this and super in static contexts.
- Don't use unnecessary catch clauses.
- Don't use unnecessary constructors.
- Don't use unnecessary continue statements.
- Don't export empty modules that don't change anything.
- Don't use unnecessary escape sequences in regular expression literals.
- Don't use unnecessary fragments.
- Don't use unnecessary labels.
- Don't use unnecessary nested block statements.
- Don't rename imports, exports, and destructured assignments to the same name.
- Don't use unnecessary string or template literal concatenation.
- Don't use String.raw in template literals when there are no escape sequences.
- Don't use useless case statements in switch statements.
- Don't use ternary operators when simpler alternatives exist.
- Don't use useless `this` aliasing.
- Don't use any or unknown as type constraints.
- Don't initialize variables to undefined.
- Don't use the void operators (they're not familiar).
- Use arrow functions instead of function expressions.
- Use Date.now() to get milliseconds since the Unix Epoch.
- Use .flatMap() instead of map().flat() when possible.
- Use literal property access instead of computed property access.
- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
- Use concise optional chaining instead of chained logical expressions.
- Use regular expression literals instead of the RegExp constructor when possible.
- Don't use number literal object member names that aren't base 10 or use underscore separators.
- Remove redundant terms from logical expressions.
- Use while loops instead of for loops when you don't need initializer and update expressions.
- Don't pass children as props.
- Don't reassign const variables.
- Don't use constant expressions in conditions.
- Don't use `Math.min` and `Math.max` to clamp values when the result is constant.
- Don't return a value from a constructor.
- Don't use empty character classes in regular expression literals.
- Don't use empty destructuring patterns.
- Don't call global object properties as functions.
- Don't declare functions and vars that are accessible outside their block.
- Make sure builtins are correctly instantiated.
- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors.
- Don't use variables and function parameters before they're declared.
- Don't use 8 and 9 escape sequences in string literals.
- Don't use literal numbers that lose precision.

### React and JSX Best Practices
- Don't use the return value of React.render.
- Make sure all dependencies are correctly specified in React hooks.
- Make sure all React hooks are called from the top level of component functions.
- Don't forget key props in iterators and collection literals.
- Don't destructure props inside JSX components in Solid projects.
- Don't define React components inside other components.
- Don't use event handlers on non-interactive elements.
- Don't assign to React component props.
- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
- Don't use dangerous JSX props.
- Don't use Array index in keys.
- Don't insert comments as text nodes.
- Don't assign JSX properties multiple times.
- Don't add extra closing tags for components without children.
- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
- Watch out for possible "wrong" semicolons inside JSX elements.

### Correctness and Safety
- Don't assign a value to itself.
- Don't return a value from a setter.
- Don't compare expressions that modify string case with non-compliant values.
- Don't use lexical declarations in switch clauses.
- Don't use variables that haven't been declared in the document.
- Don't write unreachable code.
- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass.
- Don't use control flow statements in finally blocks.
- Don't use optional chaining where undefined values aren't allowed.
- Don't have unused function parameters.
- Don't have unused imports.
- Don't have unused labels.
- Don't have unused private class members.
- Don't have unused variables.
- Make sure void (self-closing) elements don't have children.
- Don't return a value from a function with the return type 'void'
- Use isNaN() when checking for NaN.
- Make sure "for" loop update clauses move the counter in the right direction.
- Make sure typeof expressions are compared to valid values.
- Make sure generator functions contain yield.
- Don't use await inside loops.
- Don't use bitwise operators.
- Don't use expressions where the operation doesn't change the value.
- Make sure Promise-like statements are handled appropriately.
- Don't use __dirname and __filename in the global scope.
- Prevent import cycles.
- Don't use configured elements.
- Don't hardcode sensitive data like API keys and tokens.
- Don't let variable declarations shadow variables from outer scopes.
- Don't use the TypeScript directive @ts-ignore.
- Prevent duplicate polyfills from Polyfill.io.
- Don't use useless backreferences in regular expressions that always match empty strings.
- Don't use unnecessary escapes in string literals.
- Don't use useless undefined.
- Make sure getters and setters for the same property are next to each other in class and object definitions.
- Make sure object literals are declared consistently (defaults to explicit definitions).
- Use static Response methods instead of new Response() constructor when possible.
- Make sure switch-case statements are exhaustive.
- Make sure the `preconnect` attribute is used when using Google Fonts.
- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item.
- Make sure iterable callbacks return consistent values.
- Use `with { type: "json" }` for JSON module imports.
- Use numeric separators in numeric literals.
- Use object spread instead of `Object.assign()` when constructing new objects.
- Always use the radix argument when using `parseInt()`.
- Make sure JSDoc comment lines start with a single asterisk, except for the first one.
- Include a description parameter for `Symbol()`.
- Don't use spread (`...`) syntax on accumulators.
- Don't use the `delete` operator.
- Don't access namespace imports dynamically.
- Don't use namespace imports.
- Declare regex literals at the top level.
- Don't use `target="_blank"` without `rel="noopener"`.

### TypeScript Best Practices
- Don't use TypeScript enums.
- Don't export imported variables.
- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
- Don't use TypeScript namespaces.
- Don't use non-null assertions with the `!` postfix operator.
- Don't use parameter properties in class constructors.
- Don't use user-defined types.
- Use `as const` instead of literal types and type annotations.
- Use either `T[]` or `Array<T>` consistently.
- Initialize each enum member value explicitly.
- Use `export type` for types.
- Use `import type` for types.
- Make sure all enum members are literal values.
- Don't use TypeScript const enum.
- Don't declare empty interfaces.
- Don't let variables evolve into any type through reassignments.
- Don't use the any type.
- Don't misuse the non-null assertion operator (!) in TypeScript files.
- Don't use implicit any type on variable declarations.
- Don't merge interfaces and classes unsafely.
- Don't use overload signatures that aren't next to each other.
- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.

### Style and Consistency
- Don't use global `eval()`.
- Don't use callbacks in asynchronous tests and hooks.
- Don't use negation in `if` statements that have `else` clauses.
- Don't use nested ternary expressions.
- Don't reassign function parameters.
- This rule lets you specify global variable names you don't want to use in your application.
- Don't use specified modules when loaded by import or require.
- Don't use constants whose value is the upper-case version of their name.
- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
- Don't use template literals if you don't need interpolation or special-character handling.
- Don't use `else` blocks when the `if` block breaks early.
- Don't use yoda expressions.
- Don't use Array constructors.
- Use `at()` instead of integer index access.
- Follow curly brace conventions.
- Use `else if` instead of nested `if` statements in `else` clauses.
- Use single `if` statements instead of nested `if` clauses.
- Use `new` for all builtins except `String`, `Number`, and `Boolean`.
- Use consistent accessibility modifiers on class properties and methods.
- Use `const` declarations for variables that are only assigned once.
- Put default function parameters and optional function parameters last.
- Include a `default` clause in switch statements.
- Use the `**` operator instead of `Math.pow`.
- Use `for-of` loops when you need the index to extract an item from the iterated array.
- Use `node:assert/strict` over `node:assert`.
- Use the `node:` protocol for Node.js builtin modules.
- Use Number properties instead of global ones.
- Use assignment operator shorthand where possible.
- Use function types instead of object types with call signatures.
- Use template literals over string concatenation.
- Use `new` when throwing an error.
- Don't throw non-Error values.
- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
- Use standard constants instead of approximated literals.
- Don't assign values in expressions.
- Don't use async functions as Promise executors.
- Don't reassign exceptions in catch clauses.
- Don't reassign class members.
- Don't compare against -0.
- Don't use labeled statements that aren't loops.
- Don't use void type outside of generic or return types.
- Don't use console.
- Don't use control characters and escape sequences that match control characters in regular expression literals.
- Don't use debugger.
- Don't assign directly to document.cookie.
- Use `===` and `!==`.
- Don't use duplicate case labels.
- Don't use duplicate class members.
- Don't use duplicate conditions in if-else-if chains.
- Don't use two keys with the same name inside objects.
- Don't use duplicate function parameter names.
- Don't have duplicate hooks in describe blocks.
- Don't use empty block statements and static blocks.
- Don't let switch clauses fall through.
- Don't reassign function declarations.
- Don't allow assignments to native objects and read-only global variables.
- Use Number.isFinite instead of global isFinite.
- Use Number.isNaN instead of global isNaN.
- Don't assign to imported bindings.
- Don't use irregular whitespace characters.
- Don't use labels that share a name with a variable.
- Don't use characters made with multiple code points in character class syntax.
- Make sure to use new and constructor properly.
- Don't use shorthand assign when the variable appears on both sides.
- Don't use octal escape sequences in string literals.
- Don't use Object.prototype builtins directly.
- Don't redeclare variables, functions, classes, and types in the same scope.
- Don't have redundant "use strict".
- Don't compare things where both sides are exactly the same.
- Don't let identifiers shadow restricted names.
- Don't use sparse arrays (arrays with holes).
- Don't use template literal placeholder syntax in regular strings.
- Don't use the then property.
- Don't use unsafe negation.
- Don't use var.
- Don't use with statements in non-strict contexts.
- Make sure async functions actually use await.
- Make sure default clauses in switch statements come last.
- Make sure to pass a message value when creating a built-in error.
- Make sure get methods always return a value.
- Use a recommended display strategy with Google Fonts.
- Make sure for-in loops include an if statement.
- Use Array.isArray() instead of instanceof Array.
- Make sure to use the digits argument with Number#toFixed().
- Make sure to use the "use strict" directive in script files.

### Next.js Specific Rules
- Don't use `<img>` elements in Next.js projects.
- Don't use `<head>` elements in Next.js projects.
- Don't import next/document outside of pages/_document.jsx in Next.js projects.
- Don't use the next/head module in pages/_document.js on Next.js projects.

### Testing Best Practices
- Don't use export or module.exports in test files.
- Don't use focused tests.
- Make sure the assertion function, like expect, is placed inside an it() function call.
- Don't use disabled tests.

## Common Tasks
- `npx ultracite init` - Initialize Ultracite in your project
- `npx ultracite fix` - Format and fix code automatically
- `npx ultracite check` - Check for issues without fixing

## Example: Error Handling
```typescript
// ✅ Good: Comprehensive error handling
try {
  const result = await fetchData();
  return { success: true, data: result };
} catch (error) {
  console.error('API call failed:', error);
  return { success: false, error: error.message };
}

// ❌ Bad: Swallowing errors
try {
  return await fetchData();
} catch (e) {
  console.log(e);
}
```

================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing

Thanks for your interest in contributing to the chatbot template! Here's how to get started.

## Development

1. Fork and clone the repository
2. Install dependencies with `pnpm install`
3. Create a new branch from `demo` for your work

## Changesets

This project uses [Changesets](https://github.com/changesets/changesets) to manage versioning and releases. When you make a change that should be included in the next release, you need to add a changeset.

### Adding a changeset

Run the following command from the root of the repository:

```bash
pnpm changeset
```

You'll be prompted to:

1. **Select the bump type** — `patch` for bug fixes, `minor` for new features, `major` for breaking changes
2. **Write a summary** — a short description of your change that will appear in the changelog

This creates a Markdown file in the `.changeset` directory. Commit this file along with your code changes.

### When to add a changeset

- Bug fixes, new features, breaking changes, dependency updates, and other user-facing changes should include a changeset
- Internal refactors, test-only changes, and documentation updates typically don't need one

### What happens next

When your changes land on `main`, the release workflow picks up any changeset files and opens a "Version Package" PR. Merging that PR bumps the version, updates the changelog, and creates a GitHub Release.

## Pull requests

- Open PRs against the `demo` branch
- Include a changeset if your change affects the release
- Keep PRs focused — one feature or fix per PR


================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint
on:
  push:

jobs:
  build:
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        node-version: [20]
    steps:
      - uses: actions/checkout@v4
      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.12.3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "pnpm"
      - name: Install dependencies
        run: pnpm install
      - name: Run check
        run: pnpm check


================================================
FILE: .github/workflows/playwright.yml
================================================
name: Playwright Tests
on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  test:
    timeout-minutes: 30
    runs-on: ubuntu-latest
    env:
      AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
      POSTGRES_URL: ${{ secrets.POSTGRES_URL }}
      BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
      REDIS_URL: ${{ secrets.REDIS_URL }}

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - uses: actions/setup-node@v4
        with:
          node-version: lts/*

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: latest
          run_install: false

      - name: Get pnpm store directory
        id: pnpm-cache
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v3
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Cache Playwright browsers
        uses: actions/cache@v3
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}

      - name: Install Playwright Browsers
        if: steps.playwright-cache.outputs.cache-hit != 'true'
        run: pnpm exec playwright install --with-deps chromium

      - name: Run Playwright tests
        run: pnpm test

      - uses: actions/upload-artifact@v4
        if: always() && !cancelled()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: write
  pull-requests: write

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Create Release Pull Request or Tag
        id: changesets
        uses: changesets/action@v1
        with:
          title: 'chore: version package'
          commit: 'chore: version package'
          version: pnpm version
          publish: pnpm release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create GitHub Release
        if: steps.changesets.outputs.published == 'true'
        uses: actions/github-script@v7
        env:
          PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }}
        with:
          script: |
            const packages = JSON.parse(process.env.PUBLISHED_PACKAGES);
            for (const pkg of packages) {
              const tag = `v${pkg.version}`;
              await github.rest.repos.createRelease({
                owner: context.repo.owner,
                repo: context.repo.repo,
                tag_name: tag,
                name: tag,
                generate_release_notes: true,
              });
            }


================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
node_modules
.pnp
.pnp.js

# testing
coverage

# next.js
.next/
out/
build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*


# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# turbo
.turbo

.env
.vercel
.env*.local

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/*


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": ["biomejs.biome"]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[javascript]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[typescript]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[json]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[css]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[graphql]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "typescript.tsdk": "node_modules/typescript/lib",
  "editor.formatOnSave": true,
  "editor.formatOnPaste": true,
  "emmet.showExpandedAbbreviation": "never",
  "editor.codeActionsOnSave": {
    "source.fixAll.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  },
  "[html]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[vue]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[svelte]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[yaml]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[markdown]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[mdx]": {
    "editor.defaultFormatter": "biomejs.biome"
  }
}


================================================
FILE: LICENSE
================================================
Copyright 2024 Vercel, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

================================================
FILE: README.md
================================================
<a href="https://chat.vercel.ai/">
  <img alt="Chatbot" src="app/(chat)/opengraph-image.png">
  <h1 align="center">Chatbot</h1>
</a>

<p align="center">
    Chatbot (formerly AI Chatbot) is a free, open-source template built with Next.js and the AI SDK that helps you quickly build powerful chatbot applications.
</p>

<p align="center">
  <a href="https://chatbot.dev"><strong>Read Docs</strong></a> ·
  <a href="#features"><strong>Features</strong></a> ·
  <a href="#model-providers"><strong>Model Providers</strong></a> ·
  <a href="#deploy-your-own"><strong>Deploy Your Own</strong></a> ·
  <a href="#running-locally"><strong>Running locally</strong></a>
</p>
<br/>

## Features

- [Next.js](https://nextjs.org) App Router
  - Advanced routing for seamless navigation and performance
  - React Server Components (RSCs) and Server Actions for server-side rendering and increased performance
- [AI SDK](https://ai-sdk.dev/docs/introduction)
  - Unified API for generating text, structured objects, and tool calls with LLMs
  - Hooks for building dynamic chat and generative user interfaces
  - Supports OpenAI, Anthropic, Google, xAI, and other model providers via AI Gateway
- [shadcn/ui](https://ui.shadcn.com)
  - Styling with [Tailwind CSS](https://tailwindcss.com)
  - Component primitives from [Radix UI](https://radix-ui.com) for accessibility and flexibility
- Data Persistence
  - [Neon Serverless Postgres](https://vercel.com/marketplace/neon) for saving chat history and user data
  - [Vercel Blob](https://vercel.com/storage/blob) for efficient file storage
- [Auth.js](https://authjs.dev)
  - Simple and secure authentication

## Model Providers

This template uses the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) to access multiple AI models through a unified interface. The default model is [OpenAI](https://openai.com) GPT-4.1 Mini, with support for Anthropic, Google, and xAI models.

### AI Gateway Authentication

**For Vercel deployments**: Authentication is handled automatically via OIDC tokens.

**For non-Vercel deployments**: You need to provide an AI Gateway API key by setting the `AI_GATEWAY_API_KEY` environment variable in your `.env.local` file.

With the [AI SDK](https://ai-sdk.dev/docs/introduction), you can also switch to direct LLM providers like [OpenAI](https://openai.com), [Anthropic](https://anthropic.com), [Cohere](https://cohere.com/), and [many more](https://ai-sdk.dev/providers/ai-sdk-providers) with just a few lines of code.

## Deploy Your Own

You can deploy your own version of Chatbot to Vercel with one click:

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/templates/next.js/chatbot)

## Running locally

You will need to use the environment variables [defined in `.env.example`](.env.example) to run Chatbot. It's recommended you use [Vercel Environment Variables](https://vercel.com/docs/projects/environment-variables) for this, but a `.env` file is all that is necessary.

> Note: You should not commit your `.env` file or it will expose secrets that will allow others to control access to your various AI and authentication provider accounts.

1. Install Vercel CLI: `npm i -g vercel`
2. Link local instance with Vercel and GitHub accounts (creates `.vercel` directory): `vercel link`
3. Download your environment variables: `vercel env pull`

```bash
pnpm install
pnpm db:migrate # Setup database or apply latest database changes
pnpm dev
```

Your app template should now be running on [localhost:3000](http://localhost:3000).


================================================
FILE: app/(auth)/actions.ts
================================================
"use server";

import { z } from "zod";

import { createUser, getUser } from "@/lib/db/queries";

import { signIn } from "./auth";

const authFormSchema = z.object({
  email: z.string().email(),
  password: z.string().min(6),
});

export type LoginActionState = {
  status: "idle" | "in_progress" | "success" | "failed" | "invalid_data";
};

export const login = async (
  _: LoginActionState,
  formData: FormData
): Promise<LoginActionState> => {
  try {
    const validatedData = authFormSchema.parse({
      email: formData.get("email"),
      password: formData.get("password"),
    });

    await signIn("credentials", {
      email: validatedData.email,
      password: validatedData.password,
      redirect: false,
    });

    return { status: "success" };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return { status: "invalid_data" };
    }

    return { status: "failed" };
  }
};

export type RegisterActionState = {
  status:
    | "idle"
    | "in_progress"
    | "success"
    | "failed"
    | "user_exists"
    | "invalid_data";
};

export const register = async (
  _: RegisterActionState,
  formData: FormData
): Promise<RegisterActionState> => {
  try {
    const validatedData = authFormSchema.parse({
      email: formData.get("email"),
      password: formData.get("password"),
    });

    const [user] = await getUser(validatedData.email);

    if (user) {
      return { status: "user_exists" } as RegisterActionState;
    }
    await createUser(validatedData.email, validatedData.password);
    await signIn("credentials", {
      email: validatedData.email,
      password: validatedData.password,
      redirect: false,
    });

    return { status: "success" };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return { status: "invalid_data" };
    }

    return { status: "failed" };
  }
};


================================================
FILE: app/(auth)/api/auth/[...nextauth]/route.ts
================================================
export { GET, POST } from "@/app/(auth)/auth";


================================================
FILE: app/(auth)/api/auth/guest/route.ts
================================================
import { NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";
import { signIn } from "@/app/(auth)/auth";
import { isDevelopmentEnvironment } from "@/lib/constants";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const redirectUrl = searchParams.get("redirectUrl") || "/";

  const token = await getToken({
    req: request,
    secret: process.env.AUTH_SECRET,
    secureCookie: !isDevelopmentEnvironment,
  });

  if (token) {
    return NextResponse.redirect(new URL("/", request.url));
  }

  return signIn("guest", { redirect: true, redirectTo: redirectUrl });
}


================================================
FILE: app/(auth)/auth.config.ts
================================================
import type { NextAuthConfig } from "next-auth";

export const authConfig = {
  pages: {
    signIn: "/login",
    newUser: "/",
  },
  providers: [
    // added later in auth.ts since it requires bcrypt which is only compatible with Node.js
    // while this file is also used in non-Node.js environments
  ],
  callbacks: {},
} satisfies NextAuthConfig;


================================================
FILE: app/(auth)/auth.ts
================================================
import { compare } from "bcrypt-ts";
import NextAuth, { type DefaultSession } from "next-auth";
import type { DefaultJWT } from "next-auth/jwt";
import Credentials from "next-auth/providers/credentials";
import { DUMMY_PASSWORD } from "@/lib/constants";
import { createGuestUser, getUser } from "@/lib/db/queries";
import { authConfig } from "./auth.config";

export type UserType = "guest" | "regular";

declare module "next-auth" {
  interface Session extends DefaultSession {
    user: {
      id: string;
      type: UserType;
    } & DefaultSession["user"];
  }

  interface User {
    id?: string;
    email?: string | null;
    type: UserType;
  }
}

declare module "next-auth/jwt" {
  interface JWT extends DefaultJWT {
    id: string;
    type: UserType;
  }
}

export const {
  handlers: { GET, POST },
  auth,
  signIn,
  signOut,
} = NextAuth({
  ...authConfig,
  providers: [
    Credentials({
      credentials: {},
      async authorize({ email, password }: any) {
        const users = await getUser(email);

        if (users.length === 0) {
          await compare(password, DUMMY_PASSWORD);
          return null;
        }

        const [user] = users;

        if (!user.password) {
          await compare(password, DUMMY_PASSWORD);
          return null;
        }

        const passwordsMatch = await compare(password, user.password);

        if (!passwordsMatch) {
          return null;
        }

        return { ...user, type: "regular" };
      },
    }),
    Credentials({
      id: "guest",
      credentials: {},
      async authorize() {
        const [guestUser] = await createGuestUser();
        return { ...guestUser, type: "guest" };
      },
    }),
  ],
  callbacks: {
    jwt({ token, user }) {
      if (user) {
        token.id = user.id as string;
        token.type = user.type;
      }

      return token;
    },
    session({ session, token }) {
      if (session.user) {
        session.user.id = token.id;
        session.user.type = token.type;
      }

      return session;
    },
  },
});


================================================
FILE: app/(auth)/login/page.tsx
================================================
"use client";

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { useActionState, useEffect, useState } from "react";

import { AuthForm } from "@/components/auth-form";
import { SubmitButton } from "@/components/submit-button";
import { toast } from "@/components/toast";
import { type LoginActionState, login } from "../actions";

export default function Page() {
  const router = useRouter();

  const [email, setEmail] = useState("");
  const [isSuccessful, setIsSuccessful] = useState(false);

  const [state, formAction] = useActionState<LoginActionState, FormData>(
    login,
    {
      status: "idle",
    }
  );

  const { update: updateSession } = useSession();

  // biome-ignore lint/correctness/useExhaustiveDependencies: router and updateSession are stable refs
  useEffect(() => {
    if (state.status === "failed") {
      toast({
        type: "error",
        description: "Invalid credentials!",
      });
    } else if (state.status === "invalid_data") {
      toast({
        type: "error",
        description: "Failed validating your submission!",
      });
    } else if (state.status === "success") {
      setIsSuccessful(true);
      updateSession();
      router.refresh();
    }
  }, [state.status]);

  const handleSubmit = (formData: FormData) => {
    setEmail(formData.get("email") as string);
    formAction(formData);
  };

  return (
    <div className="flex h-dvh w-screen items-start justify-center bg-background pt-12 md:items-center md:pt-0">
      <div className="flex w-full max-w-md flex-col gap-12 overflow-hidden rounded-2xl">
        <div className="flex flex-col items-center justify-center gap-2 px-4 text-center sm:px-16">
          <h3 className="font-semibold text-xl dark:text-zinc-50">Sign In</h3>
          <p className="text-gray-500 text-sm dark:text-zinc-400">
            Use your email and password to sign in
          </p>
        </div>
        <AuthForm action={handleSubmit} defaultEmail={email}>
          <SubmitButton isSuccessful={isSuccessful}>Sign in</SubmitButton>
          <p className="mt-4 text-center text-gray-600 text-sm dark:text-zinc-400">
            {"Don't have an account? "}
            <Link
              className="font-semibold text-gray-800 hover:underline dark:text-zinc-200"
              href="/register"
            >
              Sign up
            </Link>
            {" for free."}
          </p>
        </AuthForm>
      </div>
    </div>
  );
}


================================================
FILE: app/(auth)/register/page.tsx
================================================
"use client";

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { useActionState, useEffect, useState } from "react";
import { AuthForm } from "@/components/auth-form";
import { SubmitButton } from "@/components/submit-button";
import { toast } from "@/components/toast";
import { type RegisterActionState, register } from "../actions";

export default function Page() {
  const router = useRouter();

  const [email, setEmail] = useState("");
  const [isSuccessful, setIsSuccessful] = useState(false);

  const [state, formAction] = useActionState<RegisterActionState, FormData>(
    register,
    {
      status: "idle",
    }
  );

  const { update: updateSession } = useSession();

  // biome-ignore lint/correctness/useExhaustiveDependencies: router and updateSession are stable refs
  useEffect(() => {
    if (state.status === "user_exists") {
      toast({ type: "error", description: "Account already exists!" });
    } else if (state.status === "failed") {
      toast({ type: "error", description: "Failed to create account!" });
    } else if (state.status === "invalid_data") {
      toast({
        type: "error",
        description: "Failed validating your submission!",
      });
    } else if (state.status === "success") {
      toast({ type: "success", description: "Account created successfully!" });

      setIsSuccessful(true);
      updateSession();
      router.refresh();
    }
  }, [state.status]);

  const handleSubmit = (formData: FormData) => {
    setEmail(formData.get("email") as string);
    formAction(formData);
  };

  return (
    <div className="flex h-dvh w-screen items-start justify-center bg-background pt-12 md:items-center md:pt-0">
      <div className="flex w-full max-w-md flex-col gap-12 overflow-hidden rounded-2xl">
        <div className="flex flex-col items-center justify-center gap-2 px-4 text-center sm:px-16">
          <h3 className="font-semibold text-xl dark:text-zinc-50">Sign Up</h3>
          <p className="text-gray-500 text-sm dark:text-zinc-400">
            Create an account with your email and password
          </p>
        </div>
        <AuthForm action={handleSubmit} defaultEmail={email}>
          <SubmitButton isSuccessful={isSuccessful}>Sign Up</SubmitButton>
          <p className="mt-4 text-center text-gray-600 text-sm dark:text-zinc-400">
            {"Already have an account? "}
            <Link
              className="font-semibold text-gray-800 hover:underline dark:text-zinc-200"
              href="/login"
            >
              Sign in
            </Link>
            {" instead."}
          </p>
        </AuthForm>
      </div>
    </div>
  );
}


================================================
FILE: app/(chat)/actions.ts
================================================
"use server";

import { generateText, type UIMessage } from "ai";
import { cookies } from "next/headers";
import type { VisibilityType } from "@/components/visibility-selector";
import { titlePrompt } from "@/lib/ai/prompts";
import { getTitleModel } from "@/lib/ai/providers";
import {
  deleteMessagesByChatIdAfterTimestamp,
  getMessageById,
  updateChatVisibilityById,
} from "@/lib/db/queries";
import { getTextFromMessage } from "@/lib/utils";

export async function saveChatModelAsCookie(model: string) {
  const cookieStore = await cookies();
  cookieStore.set("chat-model", model);
}

export async function generateTitleFromUserMessage({
  message,
}: {
  message: UIMessage;
}) {
  const { text } = await generateText({
    model: getTitleModel(),
    system: titlePrompt,
    prompt: getTextFromMessage(message),
  });
  return text
    .replace(/^[#*"\s]+/, "")
    .replace(/["]+$/, "")
    .trim();
}

export async function deleteTrailingMessages({ id }: { id: string }) {
  const [message] = await getMessageById({ id });

  await deleteMessagesByChatIdAfterTimestamp({
    chatId: message.chatId,
    timestamp: message.createdAt,
  });
}

export async function updateChatVisibility({
  chatId,
  visibility,
}: {
  chatId: string;
  visibility: VisibilityType;
}) {
  await updateChatVisibilityById({ chatId, visibility });
}


================================================
FILE: app/(chat)/api/chat/[id]/stream/route.ts
================================================
export function GET() {
  return new Response(null, { status: 204 });
}


================================================
FILE: app/(chat)/api/chat/route.ts
================================================
import { geolocation, ipAddress } from "@vercel/functions";
import {
  convertToModelMessages,
  createUIMessageStream,
  createUIMessageStreamResponse,
  generateId,
  stepCountIs,
  streamText,
} from "ai";
import { checkBotId } from "botid/server";
import { after } from "next/server";
import { createResumableStreamContext } from "resumable-stream";
import { auth, type UserType } from "@/app/(auth)/auth";
import { entitlementsByUserType } from "@/lib/ai/entitlements";
import { allowedModelIds } from "@/lib/ai/models";
import { type RequestHints, systemPrompt } from "@/lib/ai/prompts";
import { getLanguageModel } from "@/lib/ai/providers";
import { createDocument } from "@/lib/ai/tools/create-document";
import { getWeather } from "@/lib/ai/tools/get-weather";
import { requestSuggestions } from "@/lib/ai/tools/request-suggestions";
import { updateDocument } from "@/lib/ai/tools/update-document";
import { isProductionEnvironment } from "@/lib/constants";
import {
  createStreamId,
  deleteChatById,
  getChatById,
  getMessageCountByUserId,
  getMessagesByChatId,
  saveChat,
  saveMessages,
  updateChatTitleById,
  updateMessage,
} from "@/lib/db/queries";
import type { DBMessage } from "@/lib/db/schema";
import { ChatbotError } from "@/lib/errors";
import { checkIpRateLimit } from "@/lib/ratelimit";
import type { ChatMessage } from "@/lib/types";
import { convertToUIMessages, generateUUID } from "@/lib/utils";
import { generateTitleFromUserMessage } from "../../actions";
import { type PostRequestBody, postRequestBodySchema } from "./schema";

export const maxDuration = 60;

function getStreamContext() {
  try {
    return createResumableStreamContext({ waitUntil: after });
  } catch (_) {
    return null;
  }
}

export { getStreamContext };

export async function POST(request: Request) {
  let requestBody: PostRequestBody;

  try {
    const json = await request.json();
    requestBody = postRequestBodySchema.parse(json);
  } catch (_) {
    return new ChatbotError("bad_request:api").toResponse();
  }

  try {
    const { id, message, messages, selectedChatModel, selectedVisibilityType } =
      requestBody;

    const [botResult, session] = await Promise.all([checkBotId(), auth()]);

    if (botResult.isBot) {
      return new ChatbotError("unauthorized:chat").toResponse();
    }

    if (!session?.user) {
      return new ChatbotError("unauthorized:chat").toResponse();
    }

    if (!allowedModelIds.has(selectedChatModel)) {
      return new ChatbotError("bad_request:api").toResponse();
    }

    await checkIpRateLimit(ipAddress(request));

    const userType: UserType = session.user.type;

    const messageCount = await getMessageCountByUserId({
      id: session.user.id,
      differenceInHours: 1,
    });

    if (messageCount > entitlementsByUserType[userType].maxMessagesPerHour) {
      return new ChatbotError("rate_limit:chat").toResponse();
    }

    const isToolApprovalFlow = Boolean(messages);

    const chat = await getChatById({ id });
    let messagesFromDb: DBMessage[] = [];
    let titlePromise: Promise<string> | null = null;

    if (chat) {
      if (chat.userId !== session.user.id) {
        return new ChatbotError("forbidden:chat").toResponse();
      }
      if (!isToolApprovalFlow) {
        messagesFromDb = await getMessagesByChatId({ id });
      }
    } else if (message?.role === "user") {
      await saveChat({
        id,
        userId: session.user.id,
        title: "New chat",
        visibility: selectedVisibilityType,
      });
      titlePromise = generateTitleFromUserMessage({ message });
    }

    const uiMessages = isToolApprovalFlow
      ? (messages as ChatMessage[])
      : [...convertToUIMessages(messagesFromDb), message as ChatMessage];

    const { longitude, latitude, city, country } = geolocation(request);

    const requestHints: RequestHints = {
      longitude,
      latitude,
      city,
      country,
    };

    if (message?.role === "user") {
      await saveMessages({
        messages: [
          {
            chatId: id,
            id: message.id,
            role: "user",
            parts: message.parts,
            attachments: [],
            createdAt: new Date(),
          },
        ],
      });
    }

    const isReasoningModel =
      selectedChatModel.endsWith("-thinking") ||
      (selectedChatModel.includes("reasoning") &&
        !selectedChatModel.includes("non-reasoning"));

    const modelMessages = await convertToModelMessages(uiMessages);

    const stream = createUIMessageStream({
      originalMessages: isToolApprovalFlow ? uiMessages : undefined,
      execute: async ({ writer: dataStream }) => {
        const result = streamText({
          model: getLanguageModel(selectedChatModel),
          system: systemPrompt({ selectedChatModel, requestHints }),
          messages: modelMessages,
          stopWhen: stepCountIs(5),
          experimental_activeTools: isReasoningModel
            ? []
            : [
                "getWeather",
                "createDocument",
                "updateDocument",
                "requestSuggestions",
              ],
          providerOptions: isReasoningModel
            ? {
                anthropic: {
                  thinking: { type: "enabled", budgetTokens: 10_000 },
                },
              }
            : undefined,
          tools: {
            getWeather,
            createDocument: createDocument({ session, dataStream }),
            updateDocument: updateDocument({ session, dataStream }),
            requestSuggestions: requestSuggestions({ session, dataStream }),
          },
          experimental_telemetry: {
            isEnabled: isProductionEnvironment,
            functionId: "stream-text",
          },
        });

        dataStream.merge(
          result.toUIMessageStream({ sendReasoning: isReasoningModel })
        );

        if (titlePromise) {
          const title = await titlePromise;
          dataStream.write({ type: "data-chat-title", data: title });
          updateChatTitleById({ chatId: id, title });
        }
      },
      generateId: generateUUID,
      onFinish: async ({ messages: finishedMessages }) => {
        if (isToolApprovalFlow) {
          for (const finishedMsg of finishedMessages) {
            const existingMsg = uiMessages.find((m) => m.id === finishedMsg.id);
            if (existingMsg) {
              await updateMessage({
                id: finishedMsg.id,
                parts: finishedMsg.parts,
              });
            } else {
              await saveMessages({
                messages: [
                  {
                    id: finishedMsg.id,
                    role: finishedMsg.role,
                    parts: finishedMsg.parts,
                    createdAt: new Date(),
                    attachments: [],
                    chatId: id,
                  },
                ],
              });
            }
          }
        } else if (finishedMessages.length > 0) {
          await saveMessages({
            messages: finishedMessages.map((currentMessage) => ({
              id: currentMessage.id,
              role: currentMessage.role,
              parts: currentMessage.parts,
              createdAt: new Date(),
              attachments: [],
              chatId: id,
            })),
          });
        }
      },
      onError: (error) => {
        if (
          error instanceof Error &&
          error.message?.includes(
            "AI Gateway requires a valid credit card on file to service requests"
          )
        ) {
          return "AI Gateway requires a valid credit card on file to service requests. Please visit https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dadd-credit-card to add a card and unlock your free credits.";
        }
        return "Oops, an error occurred!";
      },
    });

    return createUIMessageStreamResponse({
      stream,
      async consumeSseStream({ stream: sseStream }) {
        if (!process.env.REDIS_URL) {
          return;
        }
        try {
          const streamContext = getStreamContext();
          if (streamContext) {
            const streamId = generateId();
            await createStreamId({ streamId, chatId: id });
            await streamContext.createNewResumableStream(
              streamId,
              () => sseStream
            );
          }
        } catch (_) {
          // ignore redis errors
        }
      },
    });
  } catch (error) {
    const vercelId = request.headers.get("x-vercel-id");

    if (error instanceof ChatbotError) {
      return error.toResponse();
    }

    if (
      error instanceof Error &&
      error.message?.includes(
        "AI Gateway requires a valid credit card on file to service requests"
      )
    ) {
      return new ChatbotError("bad_request:activate_gateway").toResponse();
    }

    console.error("Unhandled error in chat API:", error, { vercelId });
    return new ChatbotError("offline:chat").toResponse();
  }
}

export async function DELETE(request: Request) {
  const { searchParams } = new URL(request.url);
  const id = searchParams.get("id");

  if (!id) {
    return new ChatbotError("bad_request:api").toResponse();
  }

  const session = await auth();

  if (!session?.user) {
    return new ChatbotError("unauthorized:chat").toResponse();
  }

  const chat = await getChatById({ id });

  if (chat?.userId !== session.user.id) {
    return new ChatbotError("forbidden:chat").toResponse();
  }

  const deletedChat = await deleteChatById({ id });

  return Response.json(deletedChat, { status: 200 });
}


================================================
FILE: app/(chat)/api/chat/schema.ts
================================================
import { z } from "zod";

const textPartSchema = z.object({
  type: z.enum(["text"]),
  text: z.string().min(1).max(2000),
});

const filePartSchema = z.object({
  type: z.enum(["file"]),
  mediaType: z.enum(["image/jpeg", "image/png"]),
  name: z.string().min(1).max(100),
  url: z.string().url(),
});

const partSchema = z.union([textPartSchema, filePartSchema]);

const userMessageSchema = z.object({
  id: z.string().uuid(),
  role: z.enum(["user"]),
  parts: z.array(partSchema),
});

// For tool approval flows, we accept all messages (more permissive schema)
const messageSchema = z.object({
  id: z.string(),
  role: z.string(),
  parts: z.array(z.any()),
});

export const postRequestBodySchema = z.object({
  id: z.string().uuid(),
  // Either a single new message or all messages (for tool approvals)
  message: userMessageSchema.optional(),
  messages: z.array(messageSchema).optional(),
  selectedChatModel: z.string(),
  selectedVisibilityType: z.enum(["public", "private"]),
});

export type PostRequestBody = z.infer<typeof postRequestBodySchema>;


================================================
FILE: app/(chat)/api/document/route.ts
================================================
import { auth } from "@/app/(auth)/auth";
import type { ArtifactKind } from "@/components/artifact";
import {
  deleteDocumentsByIdAfterTimestamp,
  getDocumentsById,
  saveDocument,
} from "@/lib/db/queries";
import { ChatbotError } from "@/lib/errors";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const id = searchParams.get("id");

  if (!id) {
    return new ChatbotError(
      "bad_request:api",
      "Parameter id is missing"
    ).toResponse();
  }

  const session = await auth();

  if (!session?.user) {
    return new ChatbotError("unauthorized:document").toResponse();
  }

  const documents = await getDocumentsById({ id });

  const [document] = documents;

  if (!document) {
    return new ChatbotError("not_found:document").toResponse();
  }

  if (document.userId !== session.user.id) {
    return new ChatbotError("forbidden:document").toResponse();
  }

  return Response.json(documents, { status: 200 });
}

export async function POST(request: Request) {
  const { searchParams } = new URL(request.url);
  const id = searchParams.get("id");

  if (!id) {
    return new ChatbotError(
      "bad_request:api",
      "Parameter id is required."
    ).toResponse();
  }

  const session = await auth();

  if (!session?.user) {
    return new ChatbotError("not_found:document").toResponse();
  }

  const {
    content,
    title,
    kind,
  }: { content: string; title: string; kind: ArtifactKind } =
    await request.json();

  const documents = await getDocumentsById({ id });

  if (documents.length > 0) {
    const [doc] = documents;

    if (doc.userId !== session.user.id) {
      return new ChatbotError("forbidden:document").toResponse();
    }
  }

  const document = await saveDocument({
    id,
    content,
    title,
    kind,
    userId: session.user.id,
  });

  return Response.json(document, { status: 200 });
}

export async function DELETE(request: Request) {
  const { searchParams } = new URL(request.url);
  const id = searchParams.get("id");
  const timestamp = searchParams.get("timestamp");

  if (!id) {
    return new ChatbotError(
      "bad_request:api",
      "Parameter id is required."
    ).toResponse();
  }

  if (!timestamp) {
    return new ChatbotError(
      "bad_request:api",
      "Parameter timestamp is required."
    ).toResponse();
  }

  const session = await auth();

  if (!session?.user) {
    return new ChatbotError("unauthorized:document").toResponse();
  }

  const documents = await getDocumentsById({ id });

  const [document] = documents;

  if (document.userId !== session.user.id) {
    return new ChatbotError("forbidden:document").toResponse();
  }

  const documentsDeleted = await deleteDocumentsByIdAfterTimestamp({
    id,
    timestamp: new Date(timestamp),
  });

  return Response.json(documentsDeleted, { status: 200 });
}


================================================
FILE: app/(chat)/api/files/upload/route.ts
================================================
import { put } from "@vercel/blob";
import { NextResponse } from "next/server";
import { z } from "zod";

import { auth } from "@/app/(auth)/auth";

// Use Blob instead of File since File is not available in Node.js environment
const FileSchema = z.object({
  file: z
    .instanceof(Blob)
    .refine((file) => file.size <= 5 * 1024 * 1024, {
      message: "File size should be less than 5MB",
    })
    // Update the file type based on the kind of files you want to accept
    .refine((file) => ["image/jpeg", "image/png"].includes(file.type), {
      message: "File type should be JPEG or PNG",
    }),
});

export async function POST(request: Request) {
  const session = await auth();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  if (request.body === null) {
    return new Response("Request body is empty", { status: 400 });
  }

  try {
    const formData = await request.formData();
    const file = formData.get("file") as Blob;

    if (!file) {
      return NextResponse.json({ error: "No file uploaded" }, { status: 400 });
    }

    const validatedFile = FileSchema.safeParse({ file });

    if (!validatedFile.success) {
      const errorMessage = validatedFile.error.errors
        .map((error) => error.message)
        .join(", ");

      return NextResponse.json({ error: errorMessage }, { status: 400 });
    }

    // Get filename from formData since Blob doesn't have name property
    const filename = (formData.get("file") as File).name;
    const fileBuffer = await file.arrayBuffer();

    try {
      const data = await put(`${filename}`, fileBuffer, {
        access: "public",
      });

      return NextResponse.json(data);
    } catch (_error) {
      return NextResponse.json({ error: "Upload failed" }, { status: 500 });
    }
  } catch (_error) {
    return NextResponse.json(
      { error: "Failed to process request" },
      { status: 500 }
    );
  }
}


================================================
FILE: app/(chat)/api/history/route.ts
================================================
import type { NextRequest } from "next/server";
import { auth } from "@/app/(auth)/auth";
import { deleteAllChatsByUserId, getChatsByUserId } from "@/lib/db/queries";
import { ChatbotError } from "@/lib/errors";

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl;

  const limit = Number.parseInt(searchParams.get("limit") || "10", 10);
  const startingAfter = searchParams.get("starting_after");
  const endingBefore = searchParams.get("ending_before");

  if (startingAfter && endingBefore) {
    return new ChatbotError(
      "bad_request:api",
      "Only one of starting_after or ending_before can be provided."
    ).toResponse();
  }

  const session = await auth();

  if (!session?.user) {
    return new ChatbotError("unauthorized:chat").toResponse();
  }

  const chats = await getChatsByUserId({
    id: session.user.id,
    limit,
    startingAfter,
    endingBefore,
  });

  return Response.json(chats);
}

export async function DELETE() {
  const session = await auth();

  if (!session?.user) {
    return new ChatbotError("unauthorized:chat").toResponse();
  }

  const result = await deleteAllChatsByUserId({ userId: session.user.id });

  return Response.json(result, { status: 200 });
}


================================================
FILE: app/(chat)/api/suggestions/route.ts
================================================
import { auth } from "@/app/(auth)/auth";
import { getSuggestionsByDocumentId } from "@/lib/db/queries";
import { ChatbotError } from "@/lib/errors";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const documentId = searchParams.get("documentId");

  if (!documentId) {
    return new ChatbotError(
      "bad_request:api",
      "Parameter documentId is required."
    ).toResponse();
  }

  const session = await auth();

  if (!session?.user) {
    return new ChatbotError("unauthorized:suggestions").toResponse();
  }

  const suggestions = await getSuggestionsByDocumentId({
    documentId,
  });

  const [suggestion] = suggestions;

  if (!suggestion) {
    return Response.json([], { status: 200 });
  }

  if (suggestion.userId !== session.user.id) {
    return new ChatbotError("forbidden:api").toResponse();
  }

  return Response.json(suggestions, { status: 200 });
}


================================================
FILE: app/(chat)/api/vote/route.ts
================================================
import { auth } from "@/app/(auth)/auth";
import { getChatById, getVotesByChatId, voteMessage } from "@/lib/db/queries";
import { ChatbotError } from "@/lib/errors";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const chatId = searchParams.get("chatId");

  if (!chatId) {
    return new ChatbotError(
      "bad_request:api",
      "Parameter chatId is required."
    ).toResponse();
  }

  const session = await auth();

  if (!session?.user) {
    return new ChatbotError("unauthorized:vote").toResponse();
  }

  const chat = await getChatById({ id: chatId });

  if (!chat) {
    return new ChatbotError("not_found:chat").toResponse();
  }

  if (chat.userId !== session.user.id) {
    return new ChatbotError("forbidden:vote").toResponse();
  }

  const votes = await getVotesByChatId({ id: chatId });

  return Response.json(votes, { status: 200 });
}

export async function PATCH(request: Request) {
  const {
    chatId,
    messageId,
    type,
  }: { chatId: string; messageId: string; type: "up" | "down" } =
    await request.json();

  if (!chatId || !messageId || !type) {
    return new ChatbotError(
      "bad_request:api",
      "Parameters chatId, messageId, and type are required."
    ).toResponse();
  }

  const session = await auth();

  if (!session?.user) {
    return new ChatbotError("unauthorized:vote").toResponse();
  }

  const chat = await getChatById({ id: chatId });

  if (!chat) {
    return new ChatbotError("not_found:vote").toResponse();
  }

  if (chat.userId !== session.user.id) {
    return new ChatbotError("forbidden:vote").toResponse();
  }

  await voteMessage({
    chatId,
    messageId,
    type,
  });

  return new Response("Message voted", { status: 200 });
}


================================================
FILE: app/(chat)/chat/[id]/page.tsx
================================================
import { cookies } from "next/headers";
import { notFound, redirect } from "next/navigation";
import { Suspense } from "react";

import { auth } from "@/app/(auth)/auth";
import { Chat } from "@/components/chat";
import { DataStreamHandler } from "@/components/data-stream-handler";
import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models";
import { getChatById, getMessagesByChatId } from "@/lib/db/queries";
import { convertToUIMessages } from "@/lib/utils";

export default function Page(props: { params: Promise<{ id: string }> }) {
  return (
    <Suspense fallback={<div className="flex h-dvh" />}>
      <ChatPage params={props.params} />
    </Suspense>
  );
}

async function ChatPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const chat = await getChatById({ id });

  if (!chat) {
    redirect("/");
  }

  const session = await auth();

  if (!session) {
    redirect("/api/auth/guest");
  }

  if (chat.visibility === "private") {
    if (!session.user) {
      return notFound();
    }

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

  const messagesFromDb = await getMessagesByChatId({
    id,
  });

  const uiMessages = convertToUIMessages(messagesFromDb);

  const cookieStore = await cookies();
  const chatModelFromCookie = cookieStore.get("chat-model");

  if (!chatModelFromCookie) {
    return (
      <>
        <Chat
          autoResume={true}
          id={chat.id}
          initialChatModel={DEFAULT_CHAT_MODEL}
          initialMessages={uiMessages}
          initialVisibilityType={chat.visibility}
          isReadonly={session?.user?.id !== chat.userId}
        />
        <DataStreamHandler />
      </>
    );
  }

  return (
    <>
      <Chat
        autoResume={true}
        id={chat.id}
        initialChatModel={chatModelFromCookie.value}
        initialMessages={uiMessages}
        initialVisibilityType={chat.visibility}
        isReadonly={session?.user?.id !== chat.userId}
      />
      <DataStreamHandler />
    </>
  );
}


================================================
FILE: app/(chat)/layout.tsx
================================================
import { cookies } from "next/headers";
import Script from "next/script";
import { Suspense } from "react";
import { AppSidebar } from "@/components/app-sidebar";
import { DataStreamProvider } from "@/components/data-stream-provider";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { auth } from "../(auth)/auth";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Script
        src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"
        strategy="beforeInteractive"
      />
      <DataStreamProvider>
        <Suspense fallback={<div className="flex h-dvh" />}>
          <SidebarWrapper>{children}</SidebarWrapper>
        </Suspense>
      </DataStreamProvider>
    </>
  );
}

async function SidebarWrapper({ children }: { children: React.ReactNode }) {
  const [session, cookieStore] = await Promise.all([auth(), cookies()]);
  const isCollapsed = cookieStore.get("sidebar_state")?.value !== "true";

  return (
    <SidebarProvider defaultOpen={!isCollapsed}>
      <AppSidebar user={session?.user} />
      <SidebarInset>{children}</SidebarInset>
    </SidebarProvider>
  );
}


================================================
FILE: app/(chat)/page.tsx
================================================
import { cookies } from "next/headers";
import { Suspense } from "react";
import { Chat } from "@/components/chat";
import { DataStreamHandler } from "@/components/data-stream-handler";
import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models";
import { generateUUID } from "@/lib/utils";

export default function Page() {
  return (
    <Suspense fallback={<div className="flex h-dvh" />}>
      <NewChatPage />
    </Suspense>
  );
}

async function NewChatPage() {
  const cookieStore = await cookies();
  const modelIdFromCookie = cookieStore.get("chat-model");
  const id = generateUUID();

  if (!modelIdFromCookie) {
    return (
      <>
        <Chat
          autoResume={false}
          id={id}
          initialChatModel={DEFAULT_CHAT_MODEL}
          initialMessages={[]}
          initialVisibilityType="private"
          isReadonly={false}
          key={id}
        />
        <DataStreamHandler />
      </>
    );
  }

  return (
    <>
      <Chat
        autoResume={false}
        id={id}
        initialChatModel={modelIdFromCookie.value}
        initialMessages={[]}
        initialVisibilityType="private"
        isReadonly={false}
        key={id}
      />
      <DataStreamHandler />
    </>
  );
}


================================================
FILE: app/globals.css
================================================
@import "tailwindcss";
@import "katex/dist/katex.min.css";

/* include utility classes in streamdown */
@source "../node_modules/streamdown/dist/index.js";

/* custom variant for setting dark mode programmatically */
@custom-variant dark (&:is(.dark, .dark *));

/* include plugins */
@plugin "tailwindcss-animate";
@plugin "@tailwindcss/typography";

/* define design tokens (light mode) */
:root {
  --background: hsl(0 0% 100%);
  --foreground: hsl(240 10% 3.9%);
  --card: hsl(0 0% 100%);
  --card-foreground: hsl(240 10% 3.9%);
  --popover: hsl(0 0% 100%);
  --popover-foreground: hsl(240 10% 3.9%);
  --primary: hsl(240 5.9% 10%);
  --primary-foreground: hsl(0 0% 98%);
  --secondary: hsl(240 4.8% 95.9%);
  --secondary-foreground: hsl(240 5.9% 10%);
  --muted: hsl(240 4.8% 95.9%);
  --muted-foreground: hsl(240 3.8% 46.1%);
  --accent: hsl(240 4.8% 95.9%);
  --accent-foreground: hsl(240 5.9% 10%);
  --destructive: hsl(0 84.2% 60.2%);
  --destructive-foreground: hsl(0 0% 98%);
  --border: hsl(240 5.9% 90%);
  --input: hsl(240 5.9% 90%);
  --ring: hsl(240 10% 3.9%);
  --chart-1: hsl(12 76% 61%);
  --chart-2: hsl(173 58% 39%);
  --chart-3: hsl(197 37% 24%);
  --chart-4: hsl(43 74% 66%);
  --chart-5: hsl(27 87% 67%);
  --sidebar-background: hsl(0 0% 98%);
  --sidebar-foreground: hsl(240 5.3% 26.1%);
  --sidebar-primary: hsl(240 5.9% 10%);
  --sidebar-primary-foreground: hsl(0 0% 98%);
  --sidebar-accent: hsl(240 4.8% 95.9%);
  --sidebar-accent-foreground: hsl(240 5.9% 10%);
  --sidebar-border: hsl(220 13% 91%);
  --sidebar-ring: hsl(217.2 91.2% 59.8%);
  /* border radius unit */
  --radius: 0.5rem;
  --sidebar: hsl(0 0% 98%);
}

/* define design tokens (dark mode) */
.dark {
  --background: hsl(240 10% 3.9%);
  --foreground: hsl(0 0% 98%);
  --card: hsl(240 10% 3.9%);
  --card-foreground: hsl(0 0% 98%);
  --popover: hsl(240 10% 3.9%);
  --popover-foreground: hsl(0 0% 98%);
  --primary: hsl(0 0% 98%);
  --primary-foreground: hsl(240 5.9% 10%);
  --secondary: hsl(240 3.7% 15.9%);
  --secondary-foreground: hsl(0 0% 98%);
  --muted: hsl(240 3.7% 15.9%);
  --muted-foreground: hsl(240 5% 64.9%);
  --accent: hsl(240 3.7% 15.9%);
  --accent-foreground: hsl(0 0% 98%);
  --destructive: hsl(0 62.8% 30.6%);
  --destructive-foreground: hsl(0 0% 98%);
  --border: hsl(240 3.7% 15.9%);
  --input: hsl(240 3.7% 15.9%);
  --ring: hsl(240 4.9% 83.9%);
  --chart-1: hsl(220 70% 50%);
  --chart-2: hsl(160 60% 45%);
  --chart-3: hsl(30 80% 55%);
  --chart-4: hsl(280 65% 60%);
  --chart-5: hsl(340 75% 55%);
  --sidebar-background: hsl(240 5.9% 10%);
  --sidebar-foreground: hsl(240 4.8% 95.9%);
  --sidebar-primary: hsl(224.3 76.3% 48%);
  --sidebar-primary-foreground: hsl(0 0% 100%);
  --sidebar-accent: hsl(240 3.7% 15.9%);
  --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
  --sidebar-border: hsl(240 3.7% 15.9%);
  --sidebar-ring: hsl(217.2 91.2% 59.8%);
  --sidebar: hsl(240 5.9% 10%);
}

/* define theme */
@theme {
  --font-sans: var(--font-geist);
  --font-mono: var(--font-geist-mono);

  --breakpoint-toast-mobile: 600px;

  --radius-lg: var(--radius);
  --radius-md: calc(var(--radius) - 2px);
  --radius-sm: calc(var(--radius) - 4px);

  --color-background: var(--background);
  --color-foreground: var(--foreground);

  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);

  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);

  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);

  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);

  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);

  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);

  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);

  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);

  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);

  --color-sidebar: var(--sidebar-background);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);
}

/*
  The default border color has changed to `currentcolor` in Tailwind CSS v4,
  so we've added these compatibility styles to make sure everything still
  looks the same as it did with Tailwind CSS v3.

  If we ever want to remove these styles, we need to add an explicit border
  color utility to any element that depends on these defaults.
*/
@layer base {
  *,
  ::after,
  ::before,
  ::backdrop,
  ::file-selector-button {
    border-color: var(--color-gray-200, currentcolor);
  }
}

@utility text-balance {
  text-wrap: balance;
}

@utility -webkit-overflow-scrolling-touch {
  -webkit-overflow-scrolling: touch;
}

@utility touch-pan-y {
  touch-action: pan-y;
}

@utility overscroll-behavior-contain {
  overscroll-behavior: contain;
}

@layer utilities {
  :root {
    --foreground-rgb: 0, 0, 0;
    --background-start-rgb: 214, 219, 220;
    --background-end-rgb: 255, 255, 255;
  }

  @media (prefers-color-scheme: dark) {
    :root {
      --foreground-rgb: 255, 255, 255;
      --background-start-rgb: 0, 0, 0;
      --background-end-rgb: 0, 0, 0;
    }
  }
}

@layer base {
  * {
    @apply border-border;
  }

  body {
    @apply bg-background text-foreground;
    overflow-x: hidden;
    position: relative;
  }

  html {
    overflow-x: hidden;
  }
}

.skeleton {
  * {
    pointer-events: none !important;
  }

  *[class^="text-"] {
    color: transparent;
    @apply rounded-md bg-foreground/20 select-none animate-pulse;
  }

  .skeleton-bg {
    @apply bg-foreground/10;
  }

  .skeleton-div {
    @apply bg-foreground/20 animate-pulse;
  }
}

.ProseMirror {
  outline: none;
}

.cm-editor,
.cm-gutters {
  @apply bg-background! dark:bg-zinc-800! outline-hidden! selection:bg-zinc-900!;
}

.ͼo.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground,
.ͼo.cm-selectionBackground,
.ͼo.cm-content::selection {
  @apply bg-zinc-200! dark:bg-zinc-900!;
}

.cm-activeLine,
.cm-activeLineGutter {
  @apply bg-transparent!;
}

.cm-activeLine {
  @apply rounded-r-sm!;
}

.cm-lineNumbers {
  @apply min-w-7;
}

.cm-foldGutter {
  @apply min-w-3;
}

.cm-lineNumbers .cm-activeLineGutter {
  @apply rounded-l-sm!;
}

.suggestion-highlight {
  @apply bg-blue-200 hover:bg-blue-300 dark:hover:bg-blue-400/50 dark:text-blue-50 dark:bg-blue-500/40;
}

/* minimal scrollbar styling */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 3px;
  transition: background 0.2s ease;
}

::-webkit-scrollbar-thumb:hover {
  background: --alpha(var(--muted-foreground) / 0.5);
}

::-webkit-scrollbar-corner {
  background: transparent;
}

/* firefox scrollbar styling */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

@theme inline {
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
}


================================================
FILE: app/layout.tsx
================================================
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Toaster } from "sonner";
import { ThemeProvider } from "@/components/theme-provider";

import "./globals.css";
import { SessionProvider } from "next-auth/react";

export const metadata: Metadata = {
  metadataBase: new URL("https://chat.vercel.ai"),
  title: "Next.js Chatbot Template",
  description: "Next.js chatbot template using the AI SDK.",
};

export const viewport = {
  maximumScale: 1, // Disable auto-zoom on mobile Safari
};

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

const geistMono = Geist_Mono({
  subsets: ["latin"],
  display: "swap",
  variable: "--font-geist-mono",
});

const LIGHT_THEME_COLOR = "hsl(0 0% 100%)";
const DARK_THEME_COLOR = "hsl(240deg 10% 3.92%)";
const THEME_COLOR_SCRIPT = `\
(function() {
  var html = document.documentElement;
  var meta = document.querySelector('meta[name="theme-color"]');
  if (!meta) {
    meta = document.createElement('meta');
    meta.setAttribute('name', 'theme-color');
    document.head.appendChild(meta);
  }
  function updateThemeColor() {
    var isDark = html.classList.contains('dark');
    meta.setAttribute('content', isDark ? '${DARK_THEME_COLOR}' : '${LIGHT_THEME_COLOR}');
  }
  var observer = new MutationObserver(updateThemeColor);
  observer.observe(html, { attributes: true, attributeFilter: ['class'] });
  updateThemeColor();
})();`;

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html
      className={`${geist.variable} ${geistMono.variable}`}
      // `next-themes` injects an extra classname to the body element to avoid
      // visual flicker before hydration. Hence the `suppressHydrationWarning`
      // prop is necessary to avoid the React hydration mismatch warning.
      // https://github.com/pacocoursey/next-themes?tab=readme-ov-file#with-app
      lang="en"
      suppressHydrationWarning
    >
      <head>
        <script
          // biome-ignore lint/security/noDangerouslySetInnerHtml: "Required"
          dangerouslySetInnerHTML={{
            __html: THEME_COLOR_SCRIPT,
          }}
        />
      </head>
      <body className="antialiased">
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          disableTransitionOnChange
          enableSystem
        >
          <Toaster position="top-center" />
          <SessionProvider>{children}</SessionProvider>
        </ThemeProvider>
      </body>
    </html>
  );
}


================================================
FILE: artifacts/actions.ts
================================================
"use server";

import { getSuggestionsByDocumentId } from "@/lib/db/queries";

export async function getSuggestions({ documentId }: { documentId: string }) {
  const suggestions = await getSuggestionsByDocumentId({ documentId });
  return suggestions ?? [];
}


================================================
FILE: artifacts/code/client.tsx
================================================
import { toast } from "sonner";
import { CodeEditor } from "@/components/code-editor";
import {
  Console,
  type ConsoleOutput,
  type ConsoleOutputContent,
} from "@/components/console";
import { Artifact } from "@/components/create-artifact";
import {
  CopyIcon,
  LogsIcon,
  MessageIcon,
  PlayIcon,
  RedoIcon,
  UndoIcon,
} from "@/components/icons";
import { generateUUID } from "@/lib/utils";

const OUTPUT_HANDLERS = {
  matplotlib: `
    import io
    import base64
    from matplotlib import pyplot as plt

    # Clear any existing plots
    plt.clf()
    plt.close('all')

    # Switch to agg backend
    plt.switch_backend('agg')

    def setup_matplotlib_output():
        def custom_show():
            if plt.gcf().get_size_inches().prod() * plt.gcf().dpi ** 2 > 25_000_000:
                print("Warning: Plot size too large, reducing quality")
                plt.gcf().set_dpi(100)

            png_buf = io.BytesIO()
            plt.savefig(png_buf, format='png')
            png_buf.seek(0)
            png_base64 = base64.b64encode(png_buf.read()).decode('utf-8')
            print(f'data:image/png;base64,{png_base64}')
            png_buf.close()

            plt.clf()
            plt.close('all')

        plt.show = custom_show
  `,
  basic: `
    # Basic output capture setup
  `,
};

function detectRequiredHandlers(code: string): string[] {
  const handlers: string[] = ["basic"];

  if (code.includes("matplotlib") || code.includes("plt.")) {
    handlers.push("matplotlib");
  }

  return handlers;
}

type Metadata = {
  outputs: ConsoleOutput[];
};

export const codeArtifact = new Artifact<"code", Metadata>({
  kind: "code",
  description:
    "Useful for code generation; Code execution is only available for python code.",
  initialize: ({ setMetadata }) => {
    setMetadata({
      outputs: [],
    });
  },
  onStreamPart: ({ streamPart, setArtifact }) => {
    if (streamPart.type === "data-codeDelta") {
      setArtifact((draftArtifact) => ({
        ...draftArtifact,
        content: streamPart.data,
        isVisible:
          draftArtifact.status === "streaming" &&
          draftArtifact.content.length > 300 &&
          draftArtifact.content.length < 310
            ? true
            : draftArtifact.isVisible,
        status: "streaming",
      }));
    }
  },
  content: ({ metadata, setMetadata, ...props }) => {
    return (
      <>
        <div className="px-1">
          <CodeEditor {...props} />
        </div>

        {metadata?.outputs && (
          <Console
            consoleOutputs={metadata.outputs}
            setConsoleOutputs={() => {
              setMetadata({
                ...metadata,
                outputs: [],
              });
            }}
          />
        )}
      </>
    );
  },
  actions: [
    {
      icon: <PlayIcon size={18} />,
      label: "Run",
      description: "Execute code",
      onClick: async ({ content, setMetadata }) => {
        const runId = generateUUID();
        const outputContent: ConsoleOutputContent[] = [];

        setMetadata((metadata) => ({
          ...metadata,
          outputs: [
            ...metadata.outputs,
            {
              id: runId,
              contents: [],
              status: "in_progress",
            },
          ],
        }));

        try {
          // @ts-expect-error - loadPyodide is not defined
          const currentPyodideInstance = await globalThis.loadPyodide({
            indexURL: "https://cdn.jsdelivr.net/pyodide/v0.23.4/full/",
          });

          currentPyodideInstance.setStdout({
            batched: (output: string) => {
              outputContent.push({
                type: output.startsWith("data:image/png;base64")
                  ? "image"
                  : "text",
                value: output,
              });
            },
          });

          await currentPyodideInstance.loadPackagesFromImports(content, {
            messageCallback: (message: string) => {
              setMetadata((metadata) => ({
                ...metadata,
                outputs: [
                  ...metadata.outputs.filter((output) => output.id !== runId),
                  {
                    id: runId,
                    contents: [{ type: "text", value: message }],
                    status: "loading_packages",
                  },
                ],
              }));
            },
          });

          const requiredHandlers = detectRequiredHandlers(content);
          for (const handler of requiredHandlers) {
            if (OUTPUT_HANDLERS[handler as keyof typeof OUTPUT_HANDLERS]) {
              await currentPyodideInstance.runPythonAsync(
                OUTPUT_HANDLERS[handler as keyof typeof OUTPUT_HANDLERS]
              );

              if (handler === "matplotlib") {
                await currentPyodideInstance.runPythonAsync(
                  "setup_matplotlib_output()"
                );
              }
            }
          }

          await currentPyodideInstance.runPythonAsync(content);

          setMetadata((metadata) => ({
            ...metadata,
            outputs: [
              ...metadata.outputs.filter((output) => output.id !== runId),
              {
                id: runId,
                contents: outputContent,
                status: "completed",
              },
            ],
          }));
        } catch (error: any) {
          setMetadata((metadata) => ({
            ...metadata,
            outputs: [
              ...metadata.outputs.filter((output) => output.id !== runId),
              {
                id: runId,
                contents: [{ type: "text", value: error.message }],
                status: "failed",
              },
            ],
          }));
        }
      },
    },
    {
      icon: <UndoIcon size={18} />,
      description: "View Previous version",
      onClick: ({ handleVersionChange }) => {
        handleVersionChange("prev");
      },
      isDisabled: ({ currentVersionIndex }) => {
        if (currentVersionIndex === 0) {
          return true;
        }

        return false;
      },
    },
    {
      icon: <RedoIcon size={18} />,
      description: "View Next version",
      onClick: ({ handleVersionChange }) => {
        handleVersionChange("next");
      },
      isDisabled: ({ isCurrentVersion }) => {
        if (isCurrentVersion) {
          return true;
        }

        return false;
      },
    },
    {
      icon: <CopyIcon size={18} />,
      description: "Copy code to clipboard",
      onClick: ({ content }) => {
        navigator.clipboard.writeText(content);
        toast.success("Copied to clipboard!");
      },
    },
  ],
  toolbar: [
    {
      icon: <MessageIcon />,
      description: "Add comments",
      onClick: ({ sendMessage }) => {
        sendMessage({
          role: "user",
          parts: [
            {
              type: "text",
              text: "Add comments to the code snippet for understanding",
            },
          ],
        });
      },
    },
    {
      icon: <LogsIcon />,
      description: "Add logs",
      onClick: ({ sendMessage }) => {
        sendMessage({
          role: "user",
          parts: [
            {
              type: "text",
              text: "Add logs to the code snippet for debugging",
            },
          ],
        });
      },
    },
  ],
});


================================================
FILE: artifacts/code/server.ts
================================================
import { streamObject } from "ai";
import { z } from "zod";
import { codePrompt, updateDocumentPrompt } from "@/lib/ai/prompts";
import { getArtifactModel } from "@/lib/ai/providers";
import { createDocumentHandler } from "@/lib/artifacts/server";

export const codeDocumentHandler = createDocumentHandler<"code">({
  kind: "code",
  onCreateDocument: async ({ title, dataStream }) => {
    let draftContent = "";

    const { fullStream } = streamObject({
      model: getArtifactModel(),
      system: codePrompt,
      prompt: title,
      schema: z.object({
        code: z.string(),
      }),
    });

    for await (const delta of fullStream) {
      const { type } = delta;

      if (type === "object") {
        const { object } = delta;
        const { code } = object;

        if (code) {
          dataStream.write({
            type: "data-codeDelta",
            data: code ?? "",
            transient: true,
          });

          draftContent = code;
        }
      }
    }

    return draftContent;
  },
  onUpdateDocument: async ({ document, description, dataStream }) => {
    let draftContent = "";

    const { fullStream } = streamObject({
      model: getArtifactModel(),
      system: updateDocumentPrompt(document.content, "code"),
      prompt: description,
      schema: z.object({
        code: z.string(),
      }),
    });

    for await (const delta of fullStream) {
      const { type } = delta;

      if (type === "object") {
        const { object } = delta;
        const { code } = object;

        if (code) {
          dataStream.write({
            type: "data-codeDelta",
            data: code ?? "",
            transient: true,
          });

          draftContent = code;
        }
      }
    }

    return draftContent;
  },
});


================================================
FILE: artifacts/image/client.tsx
================================================
import { toast } from "sonner";
import { Artifact } from "@/components/create-artifact";
import { CopyIcon, RedoIcon, UndoIcon } from "@/components/icons";
import { ImageEditor } from "@/components/image-editor";

export const imageArtifact = new Artifact({
  kind: "image",
  description: "Useful for image generation",
  onStreamPart: ({ streamPart, setArtifact }) => {
    if (streamPart.type === "data-imageDelta") {
      setArtifact((draftArtifact) => ({
        ...draftArtifact,
        content: streamPart.data,
        isVisible: true,
        status: "streaming",
      }));
    }
  },
  content: ImageEditor,
  actions: [
    {
      icon: <UndoIcon size={18} />,
      description: "View Previous version",
      onClick: ({ handleVersionChange }) => {
        handleVersionChange("prev");
      },
      isDisabled: ({ currentVersionIndex }) => {
        if (currentVersionIndex === 0) {
          return true;
        }

        return false;
      },
    },
    {
      icon: <RedoIcon size={18} />,
      description: "View Next version",
      onClick: ({ handleVersionChange }) => {
        handleVersionChange("next");
      },
      isDisabled: ({ isCurrentVersion }) => {
        if (isCurrentVersion) {
          return true;
        }

        return false;
      },
    },
    {
      icon: <CopyIcon size={18} />,
      description: "Copy image to clipboard",
      onClick: ({ content }) => {
        const img = new Image();
        img.src = `data:image/png;base64,${content}`;

        img.onload = () => {
          const canvas = document.createElement("canvas");
          canvas.width = img.width;
          canvas.height = img.height;
          const ctx = canvas.getContext("2d");
          ctx?.drawImage(img, 0, 0);
          canvas.toBlob((blob) => {
            if (blob) {
              navigator.clipboard.write([
                new ClipboardItem({ "image/png": blob }),
              ]);
            }
          }, "image/png");
        };

        toast.success("Copied image to clipboard!");
      },
    },
  ],
  toolbar: [],
});


================================================
FILE: artifacts/sheet/client.tsx
================================================
import { parse, unparse } from "papaparse";
import { toast } from "sonner";
import { Artifact } from "@/components/create-artifact";
import {
  CopyIcon,
  LineChartIcon,
  RedoIcon,
  SparklesIcon,
  UndoIcon,
} from "@/components/icons";
import { SpreadsheetEditor } from "@/components/sheet-editor";

type Metadata = any;

export const sheetArtifact = new Artifact<"sheet", Metadata>({
  kind: "sheet",
  description: "Useful for working with spreadsheets",
  initialize: () => null,
  onStreamPart: ({ setArtifact, streamPart }) => {
    if (streamPart.type === "data-sheetDelta") {
      setArtifact((draftArtifact) => ({
        ...draftArtifact,
        content: streamPart.data,
        isVisible: true,
        status: "streaming",
      }));
    }
  },
  content: ({ content, currentVersionIndex, onSaveContent, status }) => {
    return (
      <SpreadsheetEditor
        content={content}
        currentVersionIndex={currentVersionIndex}
        isCurrentVersion={true}
        saveContent={onSaveContent}
        status={status}
      />
    );
  },
  actions: [
    {
      icon: <UndoIcon size={18} />,
      description: "View Previous version",
      onClick: ({ handleVersionChange }) => {
        handleVersionChange("prev");
      },
      isDisabled: ({ currentVersionIndex }) => {
        if (currentVersionIndex === 0) {
          return true;
        }

        return false;
      },
    },
    {
      icon: <RedoIcon size={18} />,
      description: "View Next version",
      onClick: ({ handleVersionChange }) => {
        handleVersionChange("next");
      },
      isDisabled: ({ isCurrentVersion }) => {
        if (isCurrentVersion) {
          return true;
        }

        return false;
      },
    },
    {
      icon: <CopyIcon />,
      description: "Copy as .csv",
      onClick: ({ content }) => {
        const parsed = parse<string[]>(content, { skipEmptyLines: true });

        const nonEmptyRows = parsed.data.filter((row) =>
          row.some((cell) => cell.trim() !== "")
        );

        const cleanedCsv = unparse(nonEmptyRows);

        navigator.clipboard.writeText(cleanedCsv);
        toast.success("Copied csv to clipboard!");
      },
    },
  ],
  toolbar: [
    {
      description: "Format and clean data",
      icon: <SparklesIcon />,
      onClick: ({ sendMessage }) => {
        sendMessage({
          role: "user",
          parts: [
            { type: "text", text: "Can you please format and clean the data?" },
          ],
        });
      },
    },
    {
      description: "Analyze and visualize data",
      icon: <LineChartIcon />,
      onClick: ({ sendMessage }) => {
        sendMessage({
          role: "user",
          parts: [
            {
              type: "text",
              text: "Can you please analyze and visualize the data by creating a new code artifact in python?",
            },
          ],
        });
      },
    },
  ],
});


================================================
FILE: artifacts/sheet/server.ts
================================================
import { streamObject } from "ai";
import { z } from "zod";
import { sheetPrompt, updateDocumentPrompt } from "@/lib/ai/prompts";
import { getArtifactModel } from "@/lib/ai/providers";
import { createDocumentHandler } from "@/lib/artifacts/server";

export const sheetDocumentHandler = createDocumentHandler<"sheet">({
  kind: "sheet",
  onCreateDocument: async ({ title, dataStream }) => {
    let draftContent = "";

    const { fullStream } = streamObject({
      model: getArtifactModel(),
      system: sheetPrompt,
      prompt: title,
      schema: z.object({
        csv: z.string().describe("CSV data"),
      }),
    });

    for await (const delta of fullStream) {
      const { type } = delta;

      if (type === "object") {
        const { object } = delta;
        const { csv } = object;

        if (csv) {
          dataStream.write({
            type: "data-sheetDelta",
            data: csv,
            transient: true,
          });

          draftContent = csv;
        }
      }
    }

    dataStream.write({
      type: "data-sheetDelta",
      data: draftContent,
      transient: true,
    });

    return draftContent;
  },
  onUpdateDocument: async ({ document, description, dataStream }) => {
    let draftContent = "";

    const { fullStream } = streamObject({
      model: getArtifactModel(),
      system: updateDocumentPrompt(document.content, "sheet"),
      prompt: description,
      schema: z.object({
        csv: z.string(),
      }),
    });

    for await (const delta of fullStream) {
      const { type } = delta;

      if (type === "object") {
        const { object } = delta;
        const { csv } = object;

        if (csv) {
          dataStream.write({
            type: "data-sheetDelta",
            data: csv,
            transient: true,
          });

          draftContent = csv;
        }
      }
    }

    return draftContent;
  },
});


================================================
FILE: artifacts/text/client.tsx
================================================
import { toast } from "sonner";
import { Artifact } from "@/components/create-artifact";
import { DiffView } from "@/components/diffview";
import { DocumentSkeleton } from "@/components/document-skeleton";
import {
  ClockRewind,
  CopyIcon,
  MessageIcon,
  PenIcon,
  RedoIcon,
  UndoIcon,
} from "@/components/icons";
import { Editor } from "@/components/text-editor";
import type { Suggestion } from "@/lib/db/schema";
import { getSuggestions } from "../actions";

type TextArtifactMetadata = {
  suggestions: Suggestion[];
};

export const textArtifact = new Artifact<"text", TextArtifactMetadata>({
  kind: "text",
  description: "Useful for text content, like drafting essays and emails.",
  initialize: async ({ documentId, setMetadata }) => {
    const suggestions = await getSuggestions({ documentId });

    setMetadata({
      suggestions,
    });
  },
  onStreamPart: ({ streamPart, setMetadata, setArtifact }) => {
    if (streamPart.type === "data-suggestion") {
      setMetadata((metadata) => {
        return {
          suggestions: [...metadata.suggestions, streamPart.data],
        };
      });
    }

    if (streamPart.type === "data-textDelta") {
      setArtifact((draftArtifact) => {
        return {
          ...draftArtifact,
          content: draftArtifact.content + streamPart.data,
          isVisible:
            draftArtifact.status === "streaming" &&
            draftArtifact.content.length > 400 &&
            draftArtifact.content.length < 450
              ? true
              : draftArtifact.isVisible,
          status: "streaming",
        };
      });
    }
  },
  content: ({
    mode,
    status,
    content,
    isCurrentVersion,
    currentVersionIndex,
    onSaveContent,
    getDocumentContentById,
    isLoading,
    metadata,
  }) => {
    if (isLoading) {
      return <DocumentSkeleton artifactKind="text" />;
    }

    if (mode === "diff") {
      const oldContent = getDocumentContentById(currentVersionIndex - 1);
      const newContent = getDocumentContentById(currentVersionIndex);

      return <DiffView newContent={newContent} oldContent={oldContent} />;
    }

    return (
      <div className="flex flex-row px-4 py-8 md:p-20">
        <Editor
          content={content}
          currentVersionIndex={currentVersionIndex}
          isCurrentVersion={isCurrentVersion}
          onSaveContent={onSaveContent}
          status={status}
          suggestions={metadata ? metadata.suggestions : []}
        />

        {metadata?.suggestions && metadata.suggestions.length > 0 ? (
          <div className="h-dvh w-12 shrink-0 md:hidden" />
        ) : null}
      </div>
    );
  },
  actions: [
    {
      icon: <ClockRewind size={18} />,
      description: "View changes",
      onClick: ({ handleVersionChange }) => {
        handleVersionChange("toggle");
      },
      isDisabled: ({ currentVersionIndex }) => {
        if (currentVersionIndex === 0) {
          return true;
        }

        return false;
      },
    },
    {
      icon: <UndoIcon size={18} />,
      description: "View Previous version",
      onClick: ({ handleVersionChange }) => {
        handleVersionChange("prev");
      },
      isDisabled: ({ currentVersionIndex }) => {
        if (currentVersionIndex === 0) {
          return true;
        }

        return false;
      },
    },
    {
      icon: <RedoIcon size={18} />,
      description: "View Next version",
      onClick: ({ handleVersionChange }) => {
        handleVersionChange("next");
      },
      isDisabled: ({ isCurrentVersion }) => {
        if (isCurrentVersion) {
          return true;
        }

        return false;
      },
    },
    {
      icon: <CopyIcon size={18} />,
      description: "Copy to clipboard",
      onClick: ({ content }) => {
        navigator.clipboard.writeText(content);
        toast.success("Copied to clipboard!");
      },
    },
  ],
  toolbar: [
    {
      icon: <PenIcon />,
      description: "Add final polish",
      onClick: ({ sendMessage }) => {
        sendMessage({
          role: "user",
          parts: [
            {
              type: "text",
              text: "Please add final polish and check for grammar, add section titles for better structure, and ensure everything reads smoothly.",
            },
          ],
        });
      },
    },
    {
      icon: <MessageIcon />,
      description: "Request suggestions",
      onClick: ({ sendMessage }) => {
        sendMessage({
          role: "user",
          parts: [
            {
              type: "text",
              text: "Please add suggestions you have that could improve the writing.",
            },
          ],
        });
      },
    },
  ],
});


================================================
FILE: artifacts/text/server.ts
================================================
import { smoothStream, streamText } from "ai";
import { updateDocumentPrompt } from "@/lib/ai/prompts";
import { getArtifactModel } from "@/lib/ai/providers";
import { createDocumentHandler } from "@/lib/artifacts/server";

export const textDocumentHandler = createDocumentHandler<"text">({
  kind: "text",
  onCreateDocument: async ({ title, dataStream }) => {
    let draftContent = "";

    const { fullStream } = streamText({
      model: getArtifactModel(),
      system:
        "Write about the given topic. Markdown is supported. Use headings wherever appropriate.",
      experimental_transform: smoothStream({ chunking: "word" }),
      prompt: title,
    });

    for await (const delta of fullStream) {
      const { type } = delta;

      if (type === "text-delta") {
        const { text } = delta;

        draftContent += text;

        dataStream.write({
          type: "data-textDelta",
          data: text,
          transient: true,
        });
      }
    }

    return draftContent;
  },
  onUpdateDocument: async ({ document, description, dataStream }) => {
    let draftContent = "";

    const { fullStream } = streamText({
      model: getArtifactModel(),
      system: updateDocumentPrompt(document.content, "text"),
      experimental_transform: smoothStream({ chunking: "word" }),
      prompt: description,
      providerOptions: {
        openai: {
          prediction: {
            type: "content",
            content: document.content,
          },
        },
      },
    });

    for await (const delta of fullStream) {
      const { type } = delta;

      if (type === "text-delta") {
        const { text } = delta;

        draftContent += text;

        dataStream.write({
          type: "data-textDelta",
          data: text,
          transient: true,
        });
      }
    }

    return draftContent;
  },
});


================================================
FILE: biome.jsonc
================================================
{
  "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
  "extends": [
    "ultracite/biome/core",
    "ultracite/biome/next",
    "ultracite/biome/react"
  ],
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2
  },
  "files": {
    "includes": [
      "**/*.ts",
      "**/*.tsx",
      "**/*.js",
      "**/*.jsx",
      "!node_modules",
      "!.next",
      "!ai-sdk",
      "!components/ui",
      "!lib/utils.ts",
      "!hooks/use-mobile.ts"
    ]
  },
  "linter": {
    "rules": {
      "suspicious": {
        "noExplicitAny": "off",
        "noUnknownAtRules": "off",
        "noConsole": "off",
        "noBitwiseOperators": "off"
      },
      "style": {
        "noMagicNumbers": "off",
        "noNestedTernary": "off",
        "useConsistentTypeDefinitions": "off"
      },
      "nursery": {
        "noUnnecessaryConditions": "off",
        "useSortedClasses": "off"
      },
      "complexity": {
        "noExcessiveCognitiveComplexity": "off",
        "useSimplifiedLogicExpression": "off"
      },
      "a11y": {
        "noSvgWithoutTitle": "off"
      },
      "correctness": {
        "useUniqueElementIds": "off",
        "useImageSize": "off"
      },
      "performance": {
        "noBarrelFile": "off",
        "useTopLevelRegex": "off"
      }
    }
  }
}


================================================
FILE: components/ai-elements/artifact.tsx
================================================
"use client";

import { type LucideIcon, XIcon } from "lucide-react";
import type { ComponentProps, HTMLAttributes } from "react";
import { Button } from "@/components/ui/button";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";

export type ArtifactProps = HTMLAttributes<HTMLDivElement>;

export const Artifact = ({ className, ...props }: ArtifactProps) => (
  <div
    className={cn(
      "flex flex-col overflow-hidden rounded-lg border bg-background shadow-sm",
      className
    )}
    {...props}
  />
);

export type ArtifactHeaderProps = HTMLAttributes<HTMLDivElement>;

export const ArtifactHeader = ({
  className,
  ...props
}: ArtifactHeaderProps) => (
  <div
    className={cn(
      "flex items-center justify-between border-b bg-muted/50 px-4 py-3",
      className
    )}
    {...props}
  />
);

export type ArtifactCloseProps = ComponentProps<typeof Button>;

export const ArtifactClose = ({
  className,
  children,
  size = "sm",
  variant = "ghost",
  ...props
}: ArtifactCloseProps) => (
  <Button
    className={cn(
      "size-8 p-0 text-muted-foreground hover:text-foreground",
      className
    )}
    size={size}
    type="button"
    variant={variant}
    {...props}
  >
    {children ?? <XIcon className="size-4" />}
    <span className="sr-only">Close</span>
  </Button>
);

export type ArtifactTitleProps = HTMLAttributes<HTMLParagraphElement>;

export const ArtifactTitle = ({ className, ...props }: ArtifactTitleProps) => (
  <p
    className={cn("font-medium text-foreground text-sm", className)}
    {...props}
  />
);

export type ArtifactDescriptionProps = HTMLAttributes<HTMLParagraphElement>;

export const ArtifactDescription = ({
  className,
  ...props
}: ArtifactDescriptionProps) => (
  <p className={cn("text-muted-foreground text-sm", className)} {...props} />
);

export type ArtifactActionsProps = HTMLAttributes<HTMLDivElement>;

export const ArtifactActions = ({
  className,
  ...props
}: ArtifactActionsProps) => (
  <div className={cn("flex items-center gap-1", className)} {...props} />
);

export type ArtifactActionProps = ComponentProps<typeof Button> & {
  tooltip?: string;
  label?: string;
  icon?: LucideIcon;
};

export const ArtifactAction = ({
  tooltip,
  label,
  icon: Icon,
  children,
  className,
  size = "sm",
  variant = "ghost",
  ...props
}: ArtifactActionProps) => {
  const button = (
    <Button
      className={cn(
        "size-8 p-0 text-muted-foreground hover:text-foreground",
        className
      )}
      size={size}
      type="button"
      variant={variant}
      {...props}
    >
      {Icon ? <Icon className="size-4" /> : children}
      <span className="sr-only">{label || tooltip}</span>
    </Button>
  );

  if (tooltip) {
    return (
      <TooltipProvider>
        <Tooltip>
          <TooltipTrigger asChild>{button}</TooltipTrigger>
          <TooltipContent>
            <p>{tooltip}</p>
          </TooltipContent>
        </Tooltip>
      </TooltipProvider>
    );
  }

  return button;
};

export type ArtifactContentProps = HTMLAttributes<HTMLDivElement>;

export const ArtifactContent = ({
  className,
  ...props
}: ArtifactContentProps) => (
  <div className={cn("flex-1 overflow-auto p-4", className)} {...props} />
);


================================================
FILE: components/ai-elements/canvas.tsx
================================================
import { Background, ReactFlow, type ReactFlowProps } from "@xyflow/react";
import type { ReactNode } from "react";
import "@xyflow/react/dist/style.css";

type CanvasProps = ReactFlowProps & {
  children?: ReactNode;
};

export const Canvas = ({ children, ...props }: CanvasProps) => (
  <ReactFlow
    deleteKeyCode={["Backspace", "Delete"]}
    fitView
    panOnDrag={false}
    panOnScroll
    selectionOnDrag={true}
    zoomOnDoubleClick={false}
    {...props}
  >
    <Background bgColor="var(--sidebar)" />
    {children}
  </ReactFlow>
);


================================================
FILE: components/ai-elements/chain-of-thought.tsx
================================================
"use client";

import { useControllableState } from "@radix-ui/react-use-controllable-state";
import {
  BrainIcon,
  ChevronDownIcon,
  DotIcon,
  type LucideIcon,
} from "lucide-react";
import type { ComponentProps, ReactNode } from "react";
import { createContext, memo, useContext, useMemo } from "react";
import { Badge } from "@/components/ui/badge";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";

type ChainOfThoughtContextValue = {
  isOpen: boolean;
  setIsOpen: (open: boolean) => void;
};

const ChainOfThoughtContext = createContext<ChainOfThoughtContextValue | null>(
  null
);

const useChainOfThought = () => {
  const context = useContext(ChainOfThoughtContext);
  if (!context) {
    throw new Error(
      "ChainOfThought components must be used within ChainOfThought"
    );
  }
  return context;
};

export type ChainOfThoughtProps = ComponentProps<"div"> & {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean) => void;
};

export const ChainOfThought = memo(
  ({
    className,
    open,
    defaultOpen = false,
    onOpenChange,
    children,
    ...props
  }: ChainOfThoughtProps) => {
    const [isOpen, setIsOpen] = useControllableState({
      prop: open,
      defaultProp: defaultOpen,
      onChange: onOpenChange,
    });

    const chainOfThoughtContext = useMemo(
      () => ({ isOpen, setIsOpen }),
      [isOpen, setIsOpen]
    );

    return (
      <ChainOfThoughtContext.Provider value={chainOfThoughtContext}>
        <div
          className={cn("not-prose max-w-prose space-y-4", className)}
          {...props}
        >
          {children}
        </div>
      </ChainOfThoughtContext.Provider>
    );
  }
);

export type ChainOfThoughtHeaderProps = ComponentProps<
  typeof CollapsibleTrigger
>;

export const ChainOfThoughtHeader = memo(
  ({ className, children, ...props }: ChainOfThoughtHeaderProps) => {
    const { isOpen, setIsOpen } = useChainOfThought();

    return (
      <Collapsible onOpenChange={setIsOpen} open={isOpen}>
        <CollapsibleTrigger
          className={cn(
            "flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
            className
          )}
          {...props}
        >
          <BrainIcon className="size-4" />
          <span className="flex-1 text-left">
            {children ?? "Chain of Thought"}
          </span>
          <ChevronDownIcon
            className={cn(
              "size-4 transition-transform",
              isOpen ? "rotate-180" : "rotate-0"
            )}
          />
        </CollapsibleTrigger>
      </Collapsible>
    );
  }
);

export type ChainOfThoughtStepProps = ComponentProps<"div"> & {
  icon?: LucideIcon;
  label: ReactNode;
  description?: ReactNode;
  status?: "complete" | "active" | "pending";
};

export const ChainOfThoughtStep = memo(
  ({
    className,
    icon: Icon = DotIcon,
    label,
    description,
    status = "complete",
    children,
    ...props
  }: ChainOfThoughtStepProps) => {
    const statusStyles = {
      complete: "text-muted-foreground",
      active: "text-foreground",
      pending: "text-muted-foreground/50",
    };

    return (
      <div
        className={cn(
          "flex gap-2 text-sm",
          statusStyles[status],
          "fade-in-0 slide-in-from-top-2 animate-in",
          className
        )}
        {...props}
      >
        <div className="relative mt-0.5">
          <Icon className="size-4" />
          <div className="absolute top-7 bottom-0 left-1/2 -mx-px w-px bg-border" />
        </div>
        <div className="flex-1 space-y-2 overflow-hidden">
          <div>{label}</div>
          {description && (
            <div className="text-muted-foreground text-xs">{description}</div>
          )}
          {children}
        </div>
      </div>
    );
  }
);

export type ChainOfThoughtSearchResultsProps = ComponentProps<"div">;

export const ChainOfThoughtSearchResults = memo(
  ({ className, ...props }: ChainOfThoughtSearchResultsProps) => (
    <div
      className={cn("flex flex-wrap items-center gap-2", className)}
      {...props}
    />
  )
);

export type ChainOfThoughtSearchResultProps = ComponentProps<typeof Badge>;

export const ChainOfThoughtSearchResult = memo(
  ({ className, children, ...props }: ChainOfThoughtSearchResultProps) => (
    <Badge
      className={cn("gap-1 px-2 py-0.5 font-normal text-xs", className)}
      variant="secondary"
      {...props}
    >
      {children}
    </Badge>
  )
);

export type ChainOfThoughtContentProps = ComponentProps<
  typeof CollapsibleContent
>;

export const ChainOfThoughtContent = memo(
  ({ className, children, ...props }: ChainOfThoughtContentProps) => {
    const { isOpen } = useChainOfThought();

    return (
      <Collapsible open={isOpen}>
        <CollapsibleContent
          className={cn(
            "mt-2 space-y-3",
            "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
            className
          )}
          {...props}
        >
          {children}
        </CollapsibleContent>
      </Collapsible>
    );
  }
);

export type ChainOfThoughtImageProps = ComponentProps<"div"> & {
  caption?: string;
};

export const ChainOfThoughtImage = memo(
  ({ className, children, caption, ...props }: ChainOfThoughtImageProps) => (
    <div className={cn("mt-2 space-y-2", className)} {...props}>
      <div className="relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-lg bg-muted p-3">
        {children}
      </div>
      {caption && <p className="text-muted-foreground text-xs">{caption}</p>}
    </div>
  )
);

ChainOfThought.displayName = "ChainOfThought";
ChainOfThoughtHeader.displayName = "ChainOfThoughtHeader";
ChainOfThoughtStep.displayName = "ChainOfThoughtStep";
ChainOfThoughtSearchResults.displayName = "ChainOfThoughtSearchResults";
ChainOfThoughtSearchResult.displayName = "ChainOfThoughtSearchResult";
ChainOfThoughtContent.displayName = "ChainOfThoughtContent";
ChainOfThoughtImage.displayName = "ChainOfThoughtImage";


================================================
FILE: components/ai-elements/checkpoint.tsx
================================================
"use client";

import { BookmarkIcon, type LucideProps } from "lucide-react";
import type { ComponentProps, HTMLAttributes } from "react";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";

export type CheckpointProps = HTMLAttributes<HTMLDivElement>;

export const Checkpoint = ({
  className,
  children,
  ...props
}: CheckpointProps) => (
  <div
    className={cn(
      "flex items-center gap-0.5 overflow-hidden text-muted-foreground",
      className
    )}
    {...props}
  >
    {children}
    <Separator />
  </div>
);

export type CheckpointIconProps = LucideProps;

export const CheckpointIcon = ({
  className,
  children,
  ...props
}: CheckpointIconProps) =>
  children ?? (
    <BookmarkIcon className={cn("size-4 shrink-0", className)} {...props} />
  );

export type CheckpointTriggerProps = ComponentProps<typeof Button> & {
  tooltip?: string;
};

export const CheckpointTrigger = ({
  children,
  className,
  variant = "ghost",
  size = "sm",
  tooltip,
  ...props
}: CheckpointTriggerProps) =>
  tooltip ? (
    <Tooltip>
      <TooltipTrigger asChild>
        <Button size={size} type="button" variant={variant} {...props}>
          {children}
        </Button>
      </TooltipTrigger>
      <TooltipContent align="start" side="bottom">
        {tooltip}
      </TooltipContent>
    </Tooltip>
  ) : (
    <Button size={size} type="button" variant={variant} {...props}>
      {children}
    </Button>
  );


================================================
FILE: components/ai-elements/confirmation.tsx
================================================
"use client";

import type { ToolUIPart } from "ai";
import {
  type ComponentProps,
  createContext,
  type ReactNode,
  useContext,
} from "react";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

type ToolUIPartApproval =
  | {
      id: string;
      approved?: never;
      reason?: never;
    }
  | {
      id: string;
      approved: boolean;
      reason?: string;
    }
  | {
      id: string;
      approved: true;
      reason?: string;
    }
  | {
      id: string;
      approved: true;
      reason?: string;
    }
  | {
      id: string;
      approved: false;
      reason?: string;
    }
  | undefined;

type ConfirmationContextValue = {
  approval: ToolUIPartApproval;
  state: ToolUIPart["state"];
};

const ConfirmationContext = createContext<ConfirmationContextValue | null>(
  null
);

const useConfirmation = () => {
  const context = useContext(ConfirmationContext);

  if (!context) {
    throw new Error("Confirmation components must be used within Confirmation");
  }

  return context;
};

export type ConfirmationProps = ComponentProps<typeof Alert> & {
  approval?: ToolUIPartApproval;
  state: ToolUIPart["state"];
};

export const Confirmation = ({
  className,
  approval,
  state,
  ...props
}: ConfirmationProps) => {
  if (!approval || state === "input-streaming" || state === "input-available") {
    return null;
  }

  return (
    <ConfirmationContext.Provider value={{ approval, state }}>
      <Alert className={cn("flex flex-col gap-2", className)} {...props} />
    </ConfirmationContext.Provider>
  );
};

export type ConfirmationTitleProps = ComponentProps<typeof AlertDescription>;

export const ConfirmationTitle = ({
  className,
  ...props
}: ConfirmationTitleProps) => (
  <AlertDescription className={cn("inline", className)} {...props} />
);

export type ConfirmationRequestProps = {
  children?: ReactNode;
};

export const ConfirmationRequest = ({ children }: ConfirmationRequestProps) => {
  const { state } = useConfirmation();

  // Only show when approval is requested
  if (state !== "approval-requested") {
    return null;
  }

  return children;
};

export type ConfirmationAcceptedProps = {
  children?: ReactNode;
};

export const ConfirmationAccepted = ({
  children,
}: ConfirmationAcceptedProps) => {
  const { approval, state } = useConfirmation();

  // Only show when approved and in response states
  if (
    !approval?.approved ||
    (state !== "approval-responded" &&
      state !== "output-denied" &&
      state !== "output-available")
  ) {
    return null;
  }

  return children;
};

export type ConfirmationRejectedProps = {
  children?: ReactNode;
};

export const ConfirmationRejected = ({
  children,
}: ConfirmationRejectedProps) => {
  const { approval, state } = useConfirmation();

  // Only show when rejected and in response states
  if (
    approval?.approved !== false ||
    (state !== "approval-responded" &&
      state !== "output-denied" &&
      state !== "output-available")
  ) {
    return null;
  }

  return children;
};

export type ConfirmationActionsProps = ComponentProps<"div">;

export const ConfirmationActions = ({
  className,
  ...props
}: ConfirmationActionsProps) => {
  const { state } = useConfirmation();

  // Only show when approval is requested
  if (state !== "approval-requested") {
    return null;
  }

  return (
    <div
      className={cn("flex items-center justify-end gap-2 self-end", className)}
      {...props}
    />
  );
};

export type ConfirmationActionProps = ComponentProps<typeof Button>;

export const ConfirmationAction = (props: ConfirmationActionProps) => (
  <Button className="h-8 px-3 text-sm" type="button" {...props} />
);


================================================
FILE: components/ai-elements/connection.tsx
================================================
import type { ConnectionLineComponent } from "@xyflow/react";

const HALF = 0.5;

export const Connection: ConnectionLineComponent = ({
  fromX,
  fromY,
  toX,
  toY,
}) => (
  <g>
    <path
      className="animated"
      d={`M${fromX},${fromY} C ${fromX + (toX - fromX) * HALF},${fromY} ${fromX + (toX - fromX) * HALF},${toY} ${toX},${toY}`}
      fill="none"
      stroke="var(--color-ring)"
      strokeWidth={1}
    />
    <circle
      cx={toX}
      cy={toY}
      fill="#fff"
      r={3}
      stroke="var(--color-ring)"
      strokeWidth={1}
    />
  </g>
);


================================================
FILE: components/ai-elements/controls.tsx
================================================
"use client";

import { Controls as ControlsPrimitive } from "@xyflow/react";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";

export type ControlsProps = ComponentProps<typeof ControlsPrimitive>;

export const Controls = ({ className, ...props }: ControlsProps) => (
  <ControlsPrimitive
    className={cn(
      "gap-px overflow-hidden rounded-md border bg-card p-1 shadow-none!",
      "[&>button]:rounded-md [&>button]:border-none! [&>button]:bg-transparent! [&>button]:hover:bg-secondary!",
      className
    )}
    {...props}
  />
);


================================================
FILE: components/ai-elements/conversation.tsx
================================================
"use client";

import { ArrowDownIcon } from "lucide-react";
import type { ComponentProps } from "react";
import { useCallback } from "react";
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

export type ConversationProps = ComponentProps<typeof StickToBottom>;

export const Conversation = ({ className, ...props }: ConversationProps) => (
  <StickToBottom
    className={cn("relative flex-1 overflow-y-hidden", className)}
    initial="smooth"
    resize="smooth"
    role="log"
    {...props}
  />
);

export type ConversationContentProps = ComponentProps<
  typeof StickToBottom.Content
>;

export const ConversationContent = ({
  className,
  ...props
}: ConversationContentProps) => (
  <StickToBottom.Content
    className={cn("flex flex-col gap-8 p-4", className)}
    {...props}
  />
);

export type ConversationEmptyStateProps = ComponentProps<"div"> & {
  title?: string;
  description?: string;
  icon?: React.ReactNode;
};

export const ConversationEmptyState = ({
  className,
  title = "No messages yet",
  description = "Start a conversation to see messages here",
  icon,
  children,
  ...props
}: ConversationEmptyStateProps) => (
  <div
    className={cn(
      "flex size-full flex-col items-center justify-center gap-3 p-8 text-center",
      className
    )}
    {...props}
  >
    {children ?? (
      <>
        {icon && <div className="text-muted-foreground">{icon}</div>}
        <div className="space-y-1">
          <h3 className="font-medium text-sm">{title}</h3>
          {description && (
            <p className="text-muted-foreground text-sm">{description}</p>
          )}
        </div>
      </>
    )}
  </div>
);

export type ConversationScrollButtonProps = ComponentProps<typeof Button>;

export const ConversationScrollButton = ({
  className,
  ...props
}: ConversationScrollButtonProps) => {
  const { isAtBottom, scrollToBottom } = useStickToBottomContext();

  const handleScrollToBottom = useCallback(() => {
    scrollToBottom();
  }, [scrollToBottom]);

  return (
    !isAtBottom && (
      <Button
        className={cn(
          "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full",
          className
        )}
        onClick={handleScrollToBottom}
        size="icon"
        type="button"
        variant="outline"
        {...props}
      >
        <ArrowDownIcon className="size-4" />
      </Button>
    )
  );
};


================================================
FILE: components/ai-elements/edge.tsx
================================================
import {
  BaseEdge,
  type EdgeProps,
  getBezierPath,
  getSimpleBezierPath,
  type InternalNode,
  type Node,
  Position,
  useInternalNode,
} from "@xyflow/react";

const Temporary = ({
  id,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
}: EdgeProps) => {
  const [edgePath] = getSimpleBezierPath({
    sourceX,
    sourceY,
    sourcePosition,
    targetX,
    targetY,
    targetPosition,
  });

  return (
    <BaseEdge
      className="stroke-1 stroke-ring"
      id={id}
      path={edgePath}
      style={{
        strokeDasharray: "5, 5",
      }}
    />
  );
};

const getHandleCoordsByPosition = (
  node: InternalNode<Node>,
  handlePosition: Position
) => {
  // Choose the handle type based on position - Left is for target, Right is for source
  const handleType = handlePosition === Position.Left ? "target" : "source";

  const handle = node.internals.handleBounds?.[handleType]?.find(
    (h) => h.position === handlePosition
  );

  if (!handle) {
    return [0, 0] as const;
  }

  let offsetX = handle.width / 2;
  let offsetY = handle.height / 2;

  // this is a tiny detail to make the markerEnd of an edge visible.
  // The handle position that gets calculated has the origin top-left, so depending which side we are using, we add a little offset
  // when the handlePosition is Position.Right for example, we need to add an offset as big as the handle itself in order to get the correct position
  switch (handlePosition) {
    case Position.Left:
      offsetX = 0;
      break;
    case Position.Right:
      offsetX = handle.width;
      break;
    case Position.Top:
      offsetY = 0;
      break;
    case Position.Bottom:
      offsetY = handle.height;
      break;
    default:
      throw new Error(`Invalid handle position: ${handlePosition}`);
  }

  const x = node.internals.positionAbsolute.x + handle.x + offsetX;
  const y = node.internals.positionAbsolute.y + handle.y + offsetY;

  return [x, y] as const;
};

const getEdgeParams = (
  source: InternalNode<Node>,
  target: InternalNode<Node>
) => {
  const sourcePos = Position.Right;
  const [sx, sy] = getHandleCoordsByPosition(source, sourcePos);
  const targetPos = Position.Left;
  const [tx, ty] = getHandleCoordsByPosition(target, targetPos);

  return {
    sx,
    sy,
    tx,
    ty,
    sourcePos,
    targetPos,
  };
};

const Animated = ({ id, source, target, markerEnd, style }: EdgeProps) => {
  const sourceNode = useInternalNode(source);
  const targetNode = useInternalNode(target);

  if (!(sourceNode && targetNode)) {
    return null;
  }

  const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(
    sourceNode,
    targetNode
  );

  const [edgePath] = getBezierPath({
    sourceX: sx,
    sourceY: sy,
    sourcePosition: sourcePos,
    targetX: tx,
    targetY: ty,
    targetPosition: targetPos,
  });

  return (
    <>
      <BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />
      <circle fill="var(--primary)" r="4">
        <animateMotion dur="2s" path={edgePath} repeatCount="indefinite" />
      </circle>
    </>
  );
};

export const Edge = {
  Temporary,
  Animated,
};


================================================
FILE: components/ai-elements/image.tsx
================================================
import type { Experimental_GeneratedImage } from "ai";
import { cn } from "@/lib/utils";

export type ImageProps = Experimental_GeneratedImage & {
  className?: string;
  alt?: string;
};

export const Image = ({
  base64,
  uint8Array,
  mediaType,
  ...props
}: ImageProps) => (
  // biome-ignore lint/performance/noImgElement: base64 data URLs require native img
  <img
    {...props}
    alt={props.alt}
    className={cn(
      "h-auto max-w-full overflow-hidden rounded-md",
      props.className
    )}
    src={`data:${mediaType};base64,${base64}`}
  />
);


================================================
FILE: components/ai-elements/inline-citation.tsx
================================================
"use client";

import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import {
  type ComponentProps,
  createContext,
  useCallback,
  useContext,
  useEffect,
  useState,
} from "react";
import { Badge } from "@/components/ui/badge";
import {
  Carousel,
  type CarouselApi,
  CarouselContent,
  CarouselItem,
} from "@/components/ui/carousel";
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card";
import { cn } from "@/lib/utils";

export type InlineCitationProps = ComponentProps<"span">;

export const InlineCitation = ({
  className,
  ...props
}: InlineCitationProps) => (
  <span
    className={cn("group inline items-center gap-1", className)}
    {...props}
  />
);

export type InlineCitationTextProps = ComponentProps<"span">;

export const InlineCitationText = ({
  className,
  ...props
}: InlineCitationTextProps) => (
  <span
    className={cn("transition-colors group-hover:bg-accent", className)}
    {...props}
  />
);

export type InlineCitationCardProps = ComponentProps<typeof HoverCard>;

export const InlineCitationCard = (props: InlineCitationCardProps) => (
  <HoverCard closeDelay={0} openDelay={0} {...props} />
);

export type InlineCitationCardTriggerProps = ComponentProps<typeof Badge> & {
  sources: string[];
};

export const InlineCitationCardTrigger = ({
  sources,
  className,
  ...props
}: InlineCitationCardTriggerProps) => (
  <HoverCardTrigger asChild>
    <Badge
      className={cn("ml-1 rounded-full", className)}
      variant="secondary"
      {...props}
    >
      {sources[0] ? (
        <>
          {new URL(sources[0]).hostname}{" "}
          {sources.length > 1 && `+${sources.length - 1}`}
        </>
      ) : (
        "unknown"
      )}
    </Badge>
  </HoverCardTrigger>
);

export type InlineCitationCardBodyProps = ComponentProps<"div">;

export const InlineCitationCardBody = ({
  className,
  ...props
}: InlineCitationCardBodyProps) => (
  <HoverCardContent className={cn("relative w-80 p-0", className)} {...props} />
);

const CarouselApiContext = createContext<CarouselApi | undefined>(undefined);

const useCarouselApi = () => {
  const context = useContext(CarouselApiContext);
  return context;
};

export type InlineCitationCarouselProps = ComponentProps<typeof Carousel>;

export const InlineCitationCarousel = ({
  className,
  children,
  ...props
}: InlineCitationCarouselProps) => {
  const [api, setApi] = useState<CarouselApi>();

  return (
    <CarouselApiContext.Provider value={api}>
      <Carousel className={cn("w-full", className)} setApi={setApi} {...props}>
        {children}
      </Carousel>
    </CarouselApiContext.Provider>
  );
};

export type InlineCitationCarouselContentProps = ComponentProps<"div">;

export const InlineCitationCarouselContent = (
  props: InlineCitationCarouselContentProps
) => <CarouselContent {...props} />;

export type InlineCitationCarouselItemProps = ComponentProps<"div">;

export const InlineCitationCarouselItem = ({
  className,
  ...props
}: InlineCitationCarouselItemProps) => (
  <CarouselItem
    className={cn("w-full space-y-2 p-4 pl-8", className)}
    {...props}
  />
);

export type InlineCitationCarouselHeaderProps = ComponentProps<"div">;

export const InlineCitationCarouselHeader = ({
  className,
  ...props
}: InlineCitationCarouselHeaderProps) => (
  <div
    className={cn(
      "flex items-center justify-between gap-2 rounded-t-md bg-secondary p-2",
      className
    )}
    {...props}
  />
);

export type InlineCitationCarouselIndexProps = ComponentProps<"div">;

export const InlineCitationCarouselIndex = ({
  children,
  className,
  ...props
}: InlineCitationCarouselIndexProps) => {
  const api = useCarouselApi();
  const [current, setCurrent] = useState(0);
  const [count, setCount] = useState(0);

  useEffect(() => {
    if (!api) {
      return;
    }

    setCount(api.scrollSnapList().length);
    setCurrent(api.selectedScrollSnap() + 1);

    api.on("select", () => {
      setCurrent(api.selectedScrollSnap() + 1);
    });
  }, [api]);

  return (
    <div
      className={cn(
        "flex flex-1 items-center justify-end px-3 py-1 text-muted-foreground text-xs",
        className
      )}
      {...props}
    >
      {children ?? `${current}/${count}`}
    </div>
  );
};

export type InlineCitationCarouselPrevProps = ComponentProps<"button">;

export const InlineCitationCarouselPrev = ({
  className,
  ...props
}: InlineCitationCarouselPrevProps) => {
  const api = useCarouselApi();

  const handleClick = useCallback(() => {
    if (api) {
      api.scrollPrev();
    }
  }, [api]);

  return (
    <button
      aria-label="Previous"
      className={cn("shrink-0", className)}
      onClick={handleClick}
      type="button"
      {...props}
    >
      <ArrowLeftIcon className="size-4 text-muted-foreground" />
    </button>
  );
};

export type InlineCitationCarouselNextProps = ComponentProps<"button">;

export const InlineCitationCarouselNext = ({
  className,
  ...props
}: InlineCitationCarouselNextProps) => {
  const api = useCarouselApi();

  const handleClick = useCallback(() => {
    if (api) {
      api.scrollNext();
    }
  }, [api]);

  return (
    <button
      aria-label="Next"
      className={cn("shrink-0", className)}
      onClick={handleClick}
      type="button"
      {...props}
    >
      <ArrowRightIcon className="size-4 text-muted-foreground" />
    </button>
  );
};

export type InlineCitationSourceProps = ComponentProps<"div"> & {
  title?: string;
  url?: string;
  description?: string;
};

export const InlineCitationSource = ({
  title,
  url,
  description,
  className,
  children,
  ...props
}: InlineCitationSourceProps) => (
  <div className={cn("space-y-1", className)} {...props}>
    {title && (
      <h4 className="truncate font-medium text-sm leading-tight">{title}</h4>
    )}
    {url && (
      <p className="truncate break-all text-muted-foreground text-xs">{url}</p>
    )}
    {description && (
      <p className="line-clamp-3 text-muted-foreground text-sm leading-relaxed">
        {description}
      </p>
    )}
    {children}
  </div>
);

export type InlineCitationQuoteProps = ComponentProps<"blockquote">;

export const InlineCitationQuote = ({
  children,
  className,
  ...props
}: InlineCitationQuoteProps) => (
  <blockquote
    className={cn(
      "border-muted border-l-2 pl-3 text-muted-foreground text-sm italic",
      className
    )}
    {...props}
  >
    {children}
  </blockquote>
);


================================================
FILE: components/ai-elements/loader.tsx
================================================
import type { HTMLAttributes } from "react";
import { cn } from "@/lib/utils";

type LoaderIconProps = {
  size?: number;
};

const LoaderIcon = ({ size = 16 }: LoaderIconProps) => (
  <svg
    height={size}
    strokeLinejoin="round"
    style={{ color: "currentcolor" }}
    viewBox="0 0 16 16"
    width={size}
  >
    <title>Loader</title>
    <g clipPath="url(#clip0_2393_1490)">
      <path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
      <path
        d="M8 16V12"
        opacity="0.5"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        d="M3.29773 1.52783L5.64887 4.7639"
        opacity="0.9"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        d="M12.7023 1.52783L10.3511 4.7639"
        opacity="0.1"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        d="M12.7023 14.472L10.3511 11.236"
        opacity="0.4"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        d="M3.29773 14.472L5.64887 11.236"
        opacity="0.6"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        d="M15.6085 5.52783L11.8043 6.7639"
        opacity="0.2"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        d="M0.391602 10.472L4.19583 9.23598"
        opacity="0.7"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        d="M15.6085 10.4722L11.8043 9.2361"
        opacity="0.3"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        d="M0.391602 5.52783L4.19583 6.7639"
        opacity="0.8"
        stroke="currentColor"
        strokeWidth="1.5"
      />
    </g>
    <defs>
      <clipPath id="clip0_2393_1490">
        <rect fill="white" height="16" width="16" />
      </clipPath>
    </defs>
  </svg>
);

export type LoaderProps = HTMLAttributes<HTMLDivElement> & {
  size?: number;
};

export const Loader = ({ className, size = 16, ...props }: LoaderProps) => (
  <div
    className={cn(
      "inline-flex animate-spin items-center justify-center",
      className
    )}
    {...props}
  >
    <LoaderIcon size={size} />
  </div>
);


================================================
FILE: components/ai-elements/message.tsx
================================================
"use client";

import type { FileUIPart, UIMessage } from "ai";
import {
  ChevronLeftIcon,
  ChevronRightIcon,
  PaperclipIcon,
  XIcon,
} from "lucide-react";
import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
import { createContext, memo, useContext, useEffect, useState } from "react";
import { Streamdown } from "streamdown";
import { Button } from "@/components/ui/button";
import { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";

export type MessageProps = HTMLAttributes<HTMLDivElement> & {
  from: UIMessage["role"];
};

export const Message = ({ className, from, ...props }: MessageProps) => (
  <div
    className={cn(
      "group flex w-full max-w-[95%] flex-col gap-2",
      from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
      className
    )}
    {...props}
  />
);

export type MessageContentProps = HTMLAttributes<HTMLDivElement>;

export const MessageContent = ({
  children,
  className,
  ...props
}: MessageContentProps) => (
  <div
    className={cn(
      "is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
      "group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
      "group-[.is-assistant]:text-foreground",
      className
    )}
    {...props}
  >
    {children}
  </div>
);

export type MessageActionsProps = ComponentProps<"div">;

export const MessageActions = ({
  className,
  children,
  ...props
}: MessageActionsProps) => (
  <div className={cn("flex items-center gap-1", className)} {...props}>
    {children}
  </div>
);

export type MessageActionProps = ComponentProps<typeof Button> & {
  tooltip?: string;
  label?: string;
};

export const MessageAction = ({
  tooltip,
  children,
  label,
  variant = "ghost",
  size = "icon-sm",
  ...props
}: MessageActionProps) => {
  const button = (
    <Button size={size} type="button" variant={variant} {...props}>
      {children}
      <span className="sr-only">{label || tooltip}</span>
    </Button>
  );

  if (tooltip) {
    return (
      <TooltipProvider>
        <Tooltip>
          <TooltipTrigger asChild>{button}</TooltipTrigger>
          <TooltipContent>
            <p>{tooltip}</p>
          </TooltipContent>
        </Tooltip>
      </TooltipProvider>
    );
  }

  return button;
};

type MessageBranchContextType = {
  currentBranch: number;
  totalBranches: number;
  goToPrevious: () => void;
  goToNext: () => void;
  branches: ReactElement[];
  setBranches: (branches: ReactElement[]) => void;
};

const MessageBranchContext = createContext<MessageBranchContextType | null>(
  null
);

const useMessageBranch = () => {
  const context = useContext(MessageBranchContext);

  if (!context) {
    throw new Error(
      "MessageBranch components must be used within MessageBranch"
    );
  }

  return context;
};

export type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
  defaultBranch?: number;
  onBranchChange?: (branchIndex: number) => void;
};

export const MessageBranch = ({
  defaultBranch = 0,
  onBranchChange,
  className,
  ...props
}: MessageBranchProps) => {
  const [currentBranch, setCurrentBranch] = useState(defaultBranch);
  const [branches, setBranches] = useState<ReactElement[]>([]);

  const handleBranchChange = (newBranch: number) => {
    setCurrentBranch(newBranch);
    onBranchChange?.(newBranch);
  };

  const goToPrevious = () => {
    const newBranch =
      currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
    handleBranchChange(newBranch);
  };

  const goToNext = () => {
    const newBranch =
      currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
    handleBranchChange(newBranch);
  };

  const contextValue: MessageBranchContextType = {
    currentBranch,
    totalBranches: branches.length,
    goToPrevious,
    goToNext,
    branches,
    setBranches,
  };

  return (
    <MessageBranchContext.Provider value={contextValue}>
      <div
        className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
        {...props}
      />
    </MessageBranchContext.Provider>
  );
};

export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;

export const MessageBranchContent = ({
  children,
  ...props
}: MessageBranchContentProps) => {
  const { currentBranch, setBranches, branches } = useMessageBranch();
  const childrenArray = Array.isArray(children) ? children : [children];

  // Use useEffect to update branches when they change
  useEffect(() => {
    if (branches.length !== childrenArray.length) {
      setBranches(childrenArray);
    }
  }, [childrenArray, branches, setBranches]);

  return childrenArray.map((branch, index) => (
    <div
      className={cn(
        "grid gap-2 overflow-hidden [&>div]:pb-0",
        index === currentBranch ? "block" : "hidden"
      )}
      key={branch.key}
      {...props}
    >
      {branch}
    </div>
  ));
};

export type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
  from: UIMessage["role"];
};

export const MessageBranchSelector = ({
  className,
  from,
  ...props
}: MessageBranchSelectorProps) => {
  const { totalBranches } = useMessageBranch();

  // Don't render if there's only one branch
  if (totalBranches <= 1) {
    return null;
  }

  return (
    <ButtonGroup
      className="[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md"
      orientation="horizontal"
      {...props}
    />
  );
};

export type MessageBranchPreviousProps = ComponentProps<typeof Button>;

export const MessageBranchPrevious = ({
  children,
  ...props
}: MessageBranchPreviousProps) => {
  const { goToPrevious, totalBranches } = useMessageBranch();

  return (
    <Button
      aria-label="Previous branch"
      disabled={totalBranches <= 1}
      onClick={goToPrevious}
      size="icon-sm"
      type="button"
      variant="ghost"
      {...props}
    >
      {children ?? <ChevronLeftIcon size={14} />}
    </Button>
  );
};

export type MessageBranchNextProps = ComponentProps<typeof Button>;

export const MessageBranchNext = ({
  children,
  className,
  ...props
}: MessageBranchNextProps) => {
  const { goToNext, totalBranches } = useMessageBranch();

  return (
    <Button
      aria-label="Next branch"
      disabled={totalBranches <= 1}
      onClick={goToNext}
      size="icon-sm"
      type="button"
      variant="ghost"
      {...props}
    >
      {children ?? <ChevronRightIcon size={14} />}
    </Button>
  );
};

export type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;

export const MessageBranchPage = ({
  className,
  ...props
}: MessageBranchPageProps) => {
  const { currentBranch, totalBranches } = useMessageBranch();

  return (
    <ButtonGroupText
      className={cn(
        "border-none bg-transparent text-muted-foreground shadow-none",
        className
      )}
      {...props}
    >
      {currentBranch + 1} of {totalBranches}
    </ButtonGroupText>
  );
};

export type MessageResponseProps = ComponentProps<typeof Streamdown>;

export const MessageResponse = memo(
  ({ className, ...props }: MessageResponseProps) => (
    <Streamdown
      className={cn(
        "size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
        className
      )}
      {...props}
    />
  ),
  (prevProps, nextProps) => prevProps.children === nextProps.children
);

MessageResponse.displayName = "MessageResponse";

export type MessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {
  data: FileUIPart;
  className?: string;
  onRemove?: () => void;
};

export function MessageAttachment({
  data,
  className,
  onRemove,
  ...props
}: MessageAttachmentProps) {
  const filename = data.filename || "";
  const mediaType =
    data.mediaType?.startsWith("image/") && data.url ? "image" : "file";
  const isImage = mediaType === "image";
  const attachmentLabel = filename || (isImage ? "Image" : "Attachment");

  return (
    <div
      className={cn(
        "group relative size-24 overflow-hidden rounded-lg",
        className
      )}
      {...props}
    >
      {isImage ? (
        <>
          {/* biome-ignore lint/performance/noImgElement: dynamic user-uploaded images */}
          <img
            alt={filename || "attachment"}
            className="size-full object-cover"
            height={100}
            src={data.url}
            width={100}
          />
          {onRemove && (
            <Button
              aria-label="Remove attachment"
              className="absolute top-2 right-2 size-6 rounded-full bg-background/80 p-0 opacity-0 backdrop-blur-sm transition-opacity hover:bg-background group-hover:opacity-100 [&>svg]:size-3"
              onClick={(e) => {
                e.stopPropagation();
                onRemove();
              }}
              type="button"
              variant="ghost"
            >
              <XIcon />
              <span className="sr-only">Remove</span>
            </Button>
          )}
        </>
      ) : (
        <>
          <Tooltip>
            <TooltipTrigger asChild>
              <div className="flex size-full shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
                <PaperclipIcon className="size-4" />
              </div>
            </TooltipTrigger>
            <TooltipContent>
              <p>{attachmentLabel}</p>
            </TooltipContent>
          </Tooltip>
          {onRemove && (
            <Button
              aria-label="Remove attachment"
              className="size-6 shrink-0 rounded-full p-0 opacity-0 transition-opacity hover:bg-accent group-hover:opacity-100 [&>svg]:size-3"
              onClick={(e) => {
                e.stopPropagation();
                onRemove();
              }}
              type="button"
              variant="ghost"
            >
              <XIcon />
              <span className="sr-only">Remove</span>
            </Button>
          )}
        </>
      )}
    </div>
  );
}

export type MessageAttachmentsProps = ComponentProps<"div">;

export function MessageAttachments({
  children,
  className,
  ...props
}: MessageAttachmentsProps) {
  if (!children) {
    return null;
  }

  return (
    <div
      className={cn(
        "ml-auto flex w-fit flex-wrap items-start gap-2",
        className
      )}
      {...props}
    >
      {children}
    </div>
  );
}

export type MessageToolbarProps = ComponentProps<"div">;

export const MessageToolbar = ({
  className,
  children,
  ...props
}: MessageToolbarProps) => (
  <div
    className={cn(
      "mt-4 flex w-full items-center justify-between gap-4",
      className
    )}
    {...props}
  >
    {children}
  </div>
);


================================================
FILE: components/ai-elements/model-selector.tsx
================================================
import Image from "next/image";
import type { ComponentProps, ReactNode } from "react";
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "@/components/ui/command";
import {
  Dialog,
  DialogContent,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import { cn } from "@/lib/utils";

export type ModelSelectorProps = ComponentProps<typeof Dialog>;

export const ModelSelector = (props: ModelSelectorProps) => (
  <Dialog {...props} />
);

export type ModelSelectorTriggerProps = ComponentProps<typeof DialogTrigger>;

export const ModelSelectorTrigger = (props: ModelSelectorTriggerProps) => (
  <DialogTrigger {...props} />
);

export type ModelSelectorContentProps = ComponentProps<typeof DialogContent> & {
  title?: ReactNode;
};

export const ModelSelectorContent = ({
  className,
  children,
  title = "Model Selector",
  ...props
}: ModelSelectorContentProps) => (
  <DialogContent className={cn("p-0", className)} {...props}>
    <DialogTitle className="sr-only">{title}</DialogTitle>
    <Command className="**:data-[slot=command-input-wrapper]:h-auto">
      {children}
    </Command>
  </DialogContent>
);

export type ModelSelectorDialogProps = ComponentProps<typeof CommandDialog>;

export const ModelSelectorDialog = (props: ModelSelectorDialogProps) => (
  <CommandDialog {...props} />
);

export type ModelSelectorInputProps = ComponentProps<typeof CommandInput>;

export const ModelSelectorInput = ({
  className,
  ...props
}: ModelSelectorInputProps) => (
  <CommandInput className={cn("h-auto py-3.5", className)} {...props} />
);

export type ModelSelectorListProps = ComponentProps<typeof CommandList>;

export const ModelSelectorList = (props: ModelSelectorListProps) => (
  <CommandList {...props} />
);

export type ModelSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;

export const ModelSelectorEmpty = (props: ModelSelectorEmptyProps) => (
  <CommandEmpty {...props} />
);

export type ModelSelectorGroupProps = ComponentProps<typeof CommandGroup>;

export const ModelSelectorGroup = (props: ModelSelectorGroupProps) => (
  <CommandGroup {...props} />
);

export type ModelSelectorItemProps = ComponentProps<typeof CommandItem>;

export const ModelSelectorItem = (props: ModelSelectorItemProps) => (
  <CommandItem {...props} />
);

export type ModelSelectorShortcutProps = ComponentProps<typeof CommandShortcut>;

export const ModelSelectorShortcut = (props: ModelSelectorShortcutProps) => (
  <CommandShortcut {...props} />
);

export type ModelSelectorSeparatorProps = ComponentProps<
  typeof CommandSeparator
>;

export const ModelSelectorSeparator = (props: ModelSelectorSeparatorProps) => (
  <CommandSeparator {...props} />
);

export type ModelSelectorLogoProps = {
  className?: string;
  provider:
    | "moonshotai-cn"
    | "lucidquery"
    | "moonshotai"
    | "zai-coding-plan"
    | "alibaba"
    | "xai"
    | "vultr"
    | "nvidia"
    | "upstage"
    | "groq"
    | "github-copilot"
    | "mistral"
    | "vercel"
    | "nebius"
    | "deepseek"
    | "alibaba-cn"
    | "google-vertex-anthropic"
    | "venice"
    | "chutes"
    | "cortecs"
    | "github-models"
    | "togetherai"
    | "azure"
    | "baseten"
    | "huggingface"
    | "opencode"
    | "fastrouter"
    | "google"
    | "google-vertex"
    | "cloudflare-workers-ai"
    | "inception"
    | "wandb"
    | "openai"
    | "zhipuai-coding-plan"
    | "perplexity"
    | "openrouter"
    | "zenmux"
    | "v0"
    | "iflowcn"
    | "synthetic"
    | "deepinfra"
    | "zhipuai"
    | "submodel"
    | "zai"
    | "inference"
    | "requesty"
    | "morph"
    | "lmstudio"
    | "anthropic"
    | "aihubmix"
    | "fireworks-ai"
    | "modelscope"
    | "llama"
    | "scaleway"
    | "amazon-bedrock"
    | "cerebras"
    | (string & {});
};

export const ModelSelectorLogo = ({
  provider,
  className,
}: ModelSelectorLogoProps) => (
  <Image
    alt={`${provider} logo`}
    className={cn("size-3 dark:invert", className)}
    height={12}
    src={`https://models.dev/logos/${provider}.svg`}
    unoptimized
    width={12}
  />
);

export type ModelSelectorLogoGroupProps = ComponentProps<"div">;

export const ModelSelectorLogoGroup = ({
  className,
  ...props
}: ModelSelectorLogoGroupProps) => (
  <div
    className={cn(
      "flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:bg-background [&>img]:p-px [&>img]:ring-1 dark:[&>img]:bg-foreground",
      className
    )}
    {...props}
  />
);

export type ModelSelectorNameProps = ComponentProps<"span">;

export const ModelSelectorName = ({
  className,
  ...props
}: ModelSelectorNameProps) => (
  <span className={cn("flex-1 truncate text-left", className)} {...props} />
);


================================================
FILE: components/ai-elements/node.tsx
================================================
import { Handle, Position } from "@xyflow/react";
import type { ComponentProps } from "react";
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { cn } from "@/lib/utils";

export type NodeProps = ComponentProps<typeof Card> & {
  handles: {
    target: boolean;
    source: boolean;
  };
};

export const Node = ({ handles, className, ...props }: NodeProps) => (
  <Card
    className={cn(
      "node-container relative size-full h-auto w-sm gap-0 rounded-md p-0",
      className
    )}
    {...props}
  >
    {handles.target && <Handle position={Position.Left} type="target" />}
    {handles.source && <Handle position={Position.Right} type="source" />}
    {props.children}
  </Card>
);

export type NodeHeaderProps = ComponentProps<typeof CardHeader>;

export const NodeHeader = ({ className, ...props }: NodeHeaderProps) => (
  <CardHeader
    className={cn("gap-0.5 rounded-t-md border-b bg-secondary p-3!", className)}
    {...props}
  />
);

export type NodeTitleProps = ComponentProps<typeof CardTitle>;

export const NodeTitle = (props: NodeTitleProps) => <CardTitle {...props} />;

export type NodeDescriptionProps = ComponentProps<typeof CardDescription>;

export const NodeDescription = (props: NodeDescriptionProps) => (
  <CardDescription {...props} />
);

export type NodeActionProps = ComponentProps<typeof CardAction>;

export const NodeAction = (props: NodeActionProps) => <CardAction {...props} />;

export type NodeContentProps = ComponentProps<typeof CardContent>;

export const NodeContent = ({ className, ...props }: NodeContentProps) => (
  <CardContent className={cn("p-3", className)} {...props} />
);

export type NodeFooterProps = ComponentProps<typeof CardFooter>;

export const NodeFooter = ({ className, ...props }: NodeFooterProps) => (
  <CardFooter
    className={cn("rounded-b-md border-t bg-secondary p-3!", className)}
    {...props}
  />
);


================================================
FILE: components/ai-elements/open-in-chat.tsx
================================================
"use client";

import {
  ChevronDownIcon,
  ExternalLinkIcon,
  MessageCircleIcon,
} from "lucide-react";
import { type ComponentProps, createContext, useContext } from "react";
import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";

const providers = {
  github: {
    title: "Open in GitHub",
    createUrl: (url: string) => url,
    icon: (
      <svg fill="currentColor" role="img" viewBox="0 0 24 24">
        <title>GitHub</title>
        <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
      </svg>
    ),
  },
  scira: {
    title: "Open in Scira",
    createUrl: (q: string) =>
      `https://scira.ai/?${new URLSearchParams({
        q,
      })}`,
    icon: (
      <svg
        fill="none"
        height="934"
        viewBox="0 0 910 934"
        width="910"
        xmlns="http://www.w3.org/2000/svg"
      >
        <title>Scira AI</title>
        <path
          d="M647.664 197.775C569.13 189.049 525.5 145.419 516.774 66.8849C508.048 145.419 464.418 189.049 385.884 197.775C464.418 206.501 508.048 250.131 516.774 328.665C525.5 250.131 569.13 206.501 647.664 197.775Z"
          fill="currentColor"
          stroke="currentColor"
          strokeLinejoin="round"
          strokeWidth="8"
        />
        <path
          d="M516.774 304.217C510.299 275.491 498.208 252.087 480.335 234.214C462.462 216.341 439.058 204.251 410.333 197.775C439.059 191.3 462.462 179.209 480.335 161.336C498.208 143.463 510.299 120.06 516.774 91.334C523.25 120.059 535.34 143.463 553.213 161.336C571.086 179.209 594.49 191.3 623.216 197.775C594.49 204.251 571.086 216.341 553.213 234.214C535.34 252.087 523.25 275.491 516.774 304.217Z"
          fill="currentColor"
          stroke="currentColor"
          strokeLinejoin="round"
          strokeWidth="8"
        />
        <path
          d="M857.5 508.116C763.259 497.644 710.903 445.288 700.432 351.047C689.961 445.288 637.605 497.644 543.364 508.116C637.605 518.587 689.961 570.943 700.432 665.184C710.903 570.943 763.259 518.587 857.5 508.116Z"
          stroke="currentColor"
          strokeLinejoin="round"
          strokeWidth="20"
        />
        <path
          d="M700.432 615.957C691.848 589.05 678.575 566.357 660.383 548.165C642.191 529.973 619.499 516.7 592.593 508.116C619.499 499.533 642.191 486.258 660.383 468.066C678.575 449.874 691.848 427.181 700.432 400.274C709.015 427.181 722.289 449.874 740.481 468.066C758.673 486.258 781.365 499.533 808.271 508.116C781.365 516.7 758.673 529.973 740.481 548.165C722.289 566.357 709.015 589.05 700.432 615.957Z"
          stroke="currentColor"
          strokeLinejoin="round"
          strokeWidth="20"
        />
        <path
          d="M889.949 121.237C831.049 114.692 798.326 81.9698 791.782 23.0692C785.237 81.9698 752.515 114.692 693.614 121.237C752.515 127.781 785.237 160.504 791.782 219.404C798.326 160.504 831.049 127.781 889.949 121.237Z"
          fill="currentColor"
          stroke="currentColor"
          strokeLinejoin="round"
          strokeWidth="8"
        />
        <path
          d="M791.782 196.795C786.697 176.937 777.869 160.567 765.16 147.858C752.452 135.15 736.082 126.322 716.226 121.237C736.082 116.152 752.452 107.324 765.16 94.6152C777.869 81.9065 786.697 65.5368 791.782 45.6797C796.867 65.5367 805.695 81.9066 818.403 94.6152C831.112 107.324 847.481 116.152 867.338 121.237C847.481 126.322 831.112 135.15 818.403 147.858C805.694 160.567 796.867 176.937 791.782 196.795Z"
          fill="currentColor"
          stroke="currentColor"
          strokeLinejoin="round"
          strokeWidth="8"
        />
        <path
          d="M760.632 764.337C720.719 814.616 669.835 855.1 611.872 882.692C553.91 910.285 490.404 924.255 426.213 923.533C362.022 922.812 298.846 907.419 241.518 878.531C184.19 849.643 134.228 808.026 95.4548 756.863C56.6815 705.7 30.1238 646.346 17.8129 583.343C5.50207 520.339 7.76433 455.354 24.4266 393.359C41.089 331.364 71.7099 274.001 113.947 225.658C156.184 177.315 208.919 139.273 268.117 114.442"
          stroke="currentColor"
          strokeLinecap="round"
          strokeLinejoin="round"
          strokeWidth="30"
        />
      </svg>
    ),
  },
  chatgpt: {
    title: "Open in ChatGPT",
    createUrl: (prompt: string) =>
      `https://chatgpt.com/?${new URLSearchParams({
        hints: "search",
        prompt,
      })}`,
    icon: (
      <svg
        fill="currentColor"
        role="img"
        viewBox="0 0 24 24"
        xmlns="http://www.w3.org/2000/svg"
      >
        <title>OpenAI</title>
        <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
      </svg>
    ),
  },
  claude: {
    title: "Open in Claude",
    createUrl: (q: string) =>
      `https://claude.ai/new?${new URLSearchParams({
        q,
      })}`,
    icon: (
      <svg
        fill="currentColor"
        role="img"
        viewBox="0 0 12 12"
        xmlns="http://www.w3.org/2000/svg"
      >
        <title>Claude</title>
        <path
          clipRule="evenodd"
          d="M2.3545 7.9775L4.7145 6.654L4.7545 6.539L4.7145 6.475H4.6L4.205 6.451L2.856 6.4145L1.6865 6.366L0.5535 6.305L0.268 6.2445L0 5.892L0.0275 5.716L0.2675 5.5555L0.6105 5.5855L1.3705 5.637L2.5095 5.716L3.3355 5.7645L4.56 5.892H4.7545L4.782 5.8135L4.715 5.7645L4.6635 5.716L3.4845 4.918L2.2085 4.074L1.5405 3.588L1.1785 3.3425L0.9965 3.1115L0.9175 2.6075L1.2455 2.2465L1.686 2.2765L1.7985 2.307L2.245 2.65L3.199 3.388L4.4445 4.3045L4.627 4.4565L4.6995 4.405L4.709 4.3685L4.627 4.2315L3.9495 3.0085L3.2265 1.7635L2.9045 1.2475L2.8195 0.938C2.78711 0.819128 2.76965 0.696687 2.7675 0.5735L3.1415 0.067L3.348 0L3.846 0.067L4.056 0.249L4.366 0.956L4.867 2.0705L5.6445 3.5855L5.8725 4.0345L5.994 4.4505L6.0395 4.578H6.1185V4.505L6.1825 3.652L6.301 2.6045L6.416 1.257L6.456 0.877L6.644 0.422L7.0175 0.176L7.3095 0.316L7.5495 0.6585L7.516 0.8805L7.373 1.806L7.0935 3.2575L6.9115 4.2285H7.0175L7.139 4.1075L7.6315 3.4545L8.4575 2.4225L8.8225 2.0125L9.2475 1.5605L9.521 1.345H10.0375L10.4175 1.9095L10.2475 2.4925L9.7155 3.166L9.275 3.737L8.643 4.587L8.248 5.267L8.2845 5.322L8.3785 5.312L9.8065 5.009L10.578 4.869L11.4985 4.7115L11.915 4.9055L11.9605 5.103L11.7965 5.5065L10.812 5.7495L9.6575 5.9805L7.938 6.387L7.917 6.402L7.9415 6.4325L8.716 6.5055L9.047 6.5235H9.858L11.368 6.636L11.763 6.897L12 7.216L11.9605 7.4585L11.353 7.7685L10.533 7.574L8.6185 7.119L7.9625 6.9545H7.8715V7.0095L8.418 7.5435L9.421 8.4485L10.6755 9.6135L10.739 9.9025L10.578 10.13L10.408 10.1055L9.3055 9.277L8.88 8.9035L7.917 8.0935H7.853V8.1785L8.075 8.503L9.2475 10.2635L9.3085 10.8035L9.2235 10.98L8.9195 11.0865L8.5855 11.0255L7.8985 10.063L7.191 8.9795L6.6195 8.008L6.5495 8.048L6.2125 11.675L6.0545 11.86L5.69 12L5.3865 11.7695L5.2255 11.396L5.3865 10.658L5.581 9.696L5.7385 8.931L5.8815 7.981L5.9665 7.665L5.9605 7.644L5.8905 7.653L5.1735 8.6365L4.0835 10.109L3.2205 11.0315L3.0135 11.1135L2.655 10.9285L2.6885 10.5975L2.889 10.303L4.083 8.785L4.803 7.844L5.268 7.301L5.265 7.222H5.2375L2.066 9.28L1.501 9.353L1.2575 9.125L1.288 8.752L1.4035 8.6305L2.3575 7.9745L2.3545 7.9775Z"
          fillRule="evenodd"
        />
      </svg>
    ),
  },
  t3: {
    title: "Open in T3 Chat",
    createUrl: (q: string) =>
      `https://t3.chat/new?${new URLSearchParams({
        q,
      })}`,
    icon: <MessageCircleIcon />,
  },
  v0: {
    title: "Open in v0",
    createUrl: (q: string) =>
      `https://v0.app?${new URLSearchParams({
        q,
      })}`,
    icon: (
      <svg
        fill="currentColor"
        viewBox="0 0 147 70"
        xmlns="http://www.w3.org/2000/svg"
      >
        <title>v0</title>
        <path d="M56 50.2031V14H70V60.1562C70 65.5928 65.5928 70 60.1562 70C57.5605 70 54.9982 68.9992 53.1562 67.1573L0 14H19.7969L56 50.2031Z" />
        <path d="M147 56H133V23.9531L100.953 56H133V70H96.6875C85.8144 70 77 61.1856 77 50.3125V14H91V46.1562L123.156 14H91V0H127.312C138.186 0 147 8.81439 147 19.6875V56Z" />
      </svg>
    ),
  },
  cursor: {
    title: "Open in Cursor",
    createUrl: (text: string) => {
      const url = new URL("https://cursor.com/link/prompt");
      url.searchParams.set("text", text);
      return url.toString();
    },
    icon: (
      <svg
        version="1.1"
        viewBox="0 0 466.73 532.09"
        xmlns="http://www.w3.org/2000/svg"
      >
        <title>Cursor</title>
        <path
          d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"
          fill="currentColor"
        />
      </svg>
    ),
  },
};

const OpenInContext = createContext<{ query: string } | undefined>(undefined);

const useOpenInContext = () => {
  const context = useContext(OpenInContext);
  if (!context) {
    throw new Error("OpenIn components must be used within an OpenIn provider");
  }
  return context;
};

export type OpenInProps = ComponentProps<typeof DropdownMenu> & {
  query: string;
};

export const OpenIn = ({ query, ...props }: OpenInProps) => (
  <OpenInContext.Provider value={{ query }}>
    <DropdownMenu {...props} />
  </OpenInContext.Provider>
);

export type OpenInContentProps = ComponentProps<typeof DropdownMenuContent>;

export const OpenInContent = ({ className, ...props }: OpenInContentProps) => (
  <DropdownMenuContent
    align="start"
    className={cn("w-[240px]", className)}
    {...props}
  />
);

export type OpenInItemProps = ComponentProps<typeof DropdownMenuItem>;

export const OpenInItem = (props: OpenInItemProps) => (
  <DropdownMenuItem {...props} />
);

export type OpenInLabelProps = ComponentProps<typeof DropdownMenuLabel>;

export const OpenInLabel = (props: OpenInLabelProps) => (
  <DropdownMenuLabel {...props} />
);

export type OpenInSeparatorProps = ComponentProps<typeof DropdownMenuSeparator>;

export const OpenInSeparator = (props: OpenInSeparatorProps) => (
  <DropdownMenuSeparator {...props} />
);

export type OpenInTriggerProps = ComponentProps<typeof DropdownMenuTrigger>;

export const OpenInTrigger = ({ children, ...props }: OpenInTriggerProps) => (
  <DropdownMenuTrigger {...props} asChild>
    {children ?? (
      <Button type="button" variant="outline">
        Open in chat
        <ChevronDownIcon className="size-4" />
      </Button>
    )}
  </DropdownMenuTrigger>
);

export type OpenInChatGPTProps = ComponentProps<typeof DropdownMenuItem>;

export const OpenInChatGPT = (props: OpenInChatGPTProps) => {
  const { query } = useOpenInContext();
  return (
    <DropdownMenuItem asChild {...props}>
      <a
        className="flex items-center gap-2"
        href={providers.chatgpt.createUrl(query)}
        rel="noopener"
        target="_blank"
      >
        <span className="shrink-0">{providers.chatgpt.icon}</span>
        <span className="flex-1">{providers.chatgpt.title}</span>
        <ExternalLinkIcon className="size-4 shrink-0" />
      </a>
    </DropdownMenuItem>
  );
};

export type OpenInClaudeProps = ComponentProps<typeof DropdownMenuItem>;

export const OpenInClaude = (props: OpenInClaudeProps) => {
  const { query } = useOpenInContext();
  return (
    <DropdownMenuItem asChild {...props}>
      <a
        className="flex items-center gap-2"
        href={providers.claude.createUrl(query)}
        rel="noopener"
        target="_blank"
      >
        <span className="shrink-0">{providers.claude.icon}</span>
        <span className="flex-1">{providers.claude.title}</span>
        <ExternalLinkIcon className="size-4 shrink-0" />
      </a>
    </DropdownMenuItem>
  );
};

export type OpenInT3Props = ComponentProps<typeof DropdownMenuItem>;

export const OpenInT3 = (props: OpenInT3Props) => {
  const { query } = useOpenInContext();
  return (
    <DropdownMenuItem asChild {...props}>
      <a
        className="flex items-center gap-2"
        href={providers.t3.createUrl(query)}
        rel="noopener"
        target="_blank"
      >
        <span className="shrink-0">{providers.t3.icon}</span>
        <span className="flex-1">{providers.t3.title}</span>
        <ExternalLinkIcon className="size-4 shrink-0" />
      </a>
    </DropdownMenuItem>
  );
};

export type OpenInSciraProps = ComponentProps<typeof DropdownMenuItem>;

export const OpenInScira = (props: OpenInSciraProps) => {
  const { query } = useOpenInContext();
  return (
    <DropdownMenuItem asChild {...props}>
      <a
        className="flex items-center gap-2"
        href={providers.scira.createUrl(query)}
        rel="noopener"
        target="_blank"
      >
        <span className="shrink-0">{providers.scira.icon}</span>
        <span className="flex-1">{providers.scira.title}</span>
        <ExternalLinkIcon className="size-4 shrink-0" />
      </a>
    </DropdownMenuItem>
  );
};

export type OpenInv0Props = ComponentProps<typeof DropdownMenuItem>;

export const OpenInv0 = (props: OpenInv0Props) => {
  const { query } = useOpenInContext();
  return (
    <DropdownMenuItem asChild {...props}>
      <a
        className="flex items-center gap-2"
        href={providers.v0.createUrl(query)}
        rel="noopener"
        target="_blank"
      >
        <span className="shrink-0">{providers.v0.icon}</span>
        <span className="flex-1">{providers.v0.title}</span>
        <ExternalLinkIcon className="size-4 shrink-0" />
      </a>
    </DropdownMenuItem>
  );
};

export type OpenInCursorProps = ComponentProps<typeof DropdownMenuItem>;

export const OpenInCursor = (props: OpenInCursorProps) => {
  const { query } = useOpenInContext();
  return (
    <DropdownMenuItem asChild {...props}>
      <a
        className="flex items-center gap-2"
        href={providers.cursor.createUrl(query)}
        rel="noopener"
        target="_blank"
      >
        <span className="shrink-0">{providers.cursor.icon}</span>
        <span className="flex-1">{providers.cursor.title}</span>
        <ExternalLinkIcon className="size-4 shrink-0" />
      </a>
    </DropdownMenuItem>
  );
};


================================================
FILE: components/ai-elements/panel.tsx
================================================
import { Panel as PanelPrimitive } from "@xyflow/react";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";

type PanelProps = ComponentProps<typeof PanelPrimitive>;

export const Panel = ({ className, ...props }: PanelProps) => (
  <PanelPrimitive
    className={cn(
      "m-4 overflow-hidden rounded-md border bg-card p-1",
      className
    )}
    {...props}
  />
);


================================================
FILE: components/ai-elements/plan.tsx
================================================
"use client";

import { ChevronsUpDownIcon } from "lucide-react";
import type { ComponentProps } from "react";
import { createContext, useContext } from "react";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import { Shimmer } from "./shimmer";

type PlanContextValue = {
  isStreaming: boolean;
};

const PlanContext = createContext<PlanContextValue | null>(null);

const usePlan = () => {
  const context = useContext(PlanContext);
  if (!context) {
    throw new Error("Plan components must be used within Plan");
  }
  return context;
};

export type PlanProps = ComponentProps<typeof Collapsible> & {
  isStreaming?: boolean;
};

export const Plan = ({
  className,
  isStreaming = false,
  children,
  ...props
}: PlanProps) => (
  <PlanContext.Provider value={{ isStreaming }}>
    <Collapsible asChild data-slot="plan" {...props}>
      <Card className={cn("shadow-none", className)}>{children}</Card>
    </Collapsible>
  </PlanContext.Provider>
);

export type PlanHeaderProps = ComponentProps<typeof CardHeader>;

export const PlanHeader = ({ className, ...props }: PlanHeaderProps) => (
  <CardHeader
    className={cn("flex items-start justify-between", className)}
    data-slot="plan-header"
    {...props}
  />
);

export type PlanTitleProps = Omit<
  ComponentProps<typeof CardTitle>,
  "children"
> & {
  children: string;
};

export const PlanTitle = ({ children, ...props }: PlanTitleProps) => {
  const { isStreaming } = usePlan();

  return (
    <CardTitle data-slot="plan-title" {...props}>
      {isStreaming ? <Shimmer>{children}</Shimmer> : children}
    </CardTitle>
  );
};

export type PlanDescriptionProps = Omit<
  ComponentProps<typeof CardDescription>,
  "children"
> & {
  children: string;
};

export const PlanDescription = ({
  className,
  children,
  ...props
}: PlanDescriptionProps) => {
  const { isStreaming } = usePlan();

  return (
    <CardDescription
      className={cn("text-balance", className)}
      data-slot="plan-description"
      {...props}
    >
      {isStreaming ? <Shimmer>{children}</Shimmer> : children}
    </CardDescription>
  );
};

export type PlanActionProps = ComponentProps<typeof CardAction>;

export const PlanAction = (props: PlanActionProps) => (
  <CardAction data-slot="plan-action" {...props} />
);

export type PlanContentProps = ComponentProps<typeof CardContent>;

export const PlanContent = (props: PlanContentProps) => (
  <CollapsibleContent asChild>
    <CardContent data-slot="plan-content" {...props} />
  </CollapsibleContent>
);

export type PlanFooterProps = ComponentProps<"div">;

export const PlanFooter = (props: PlanFooterProps) => (
  <CardFooter data-slot="plan-footer" {...props} />
);

export type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>;

export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => (
  <CollapsibleTrigger asChild>
    <Button
      className={cn("size-8", className)}
      data-slot="plan-trigger"
      size="icon"
      variant="ghost"
      {...props}
    >
      <ChevronsUpDownIcon className="size-4" />
      <span className="sr-only">Toggle plan</span>
    </Button>
  </CollapsibleTrigger>
);


================================================
FILE: components/ai-elements/prompt-input.tsx
================================================
"use client";

import type { ChatStatus, FileUIPart } from "ai";
import {
  CornerDownLeftIcon,
  ImageIcon,
  Loader2Icon,
  MicIcon,
  PaperclipIcon,
  PlusIcon,
  SquareIcon,
  XIcon,
} from "lucide-react";
import { nanoid } from "nanoid";
import {
  type ChangeEvent,
  type ChangeEventHandler,
  Children,
  type ClipboardEventHandler,
  type ComponentProps,
  createContext,
  type FormEvent,
  type FormEventHandler,
  Fragment,
  type HTMLAttributes,
  type KeyboardEventHandler,
  type PropsWithChildren,
  type ReactNode,
  type RefObject,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import { Button } from "@/components/ui/button";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from "@/components/ui/command";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card";
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupTextarea,
} from "@/components/ui/input-group";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";

// ============================================================================
// Provider Context & Types
// ============================================================================

export type AttachmentsContext = {
  files: (FileUIPart & { id: string })[];
  add: (files: File[] | FileList) => void;
  remove: (id: string) => void;
  clear: () => void;
  openFileDialog: () => void;
  fileInputRef: RefObject<HTMLInputElement | null>;
};

export type TextInputContext = {
  value: string;
  setInput: (v: string) => void;
  clear: () => void;
};

export type PromptInputControllerProps = {
  textInput: TextInputContext;
  attachments: AttachmentsContext;
  /** INTERNAL: Allows PromptInput to register its file textInput + "open" callback */
  __registerFileInput: (
    ref: RefObject<HTMLInputElement | null>,
    open: () => void
  ) => void;
};

const PromptInputController = createContext<PromptInputControllerProps | null>(
  null
);
const ProviderAttachmentsContext = createContext<AttachmentsContext | null>(
  null
);

export const usePromptInputController = () => {
  const ctx = useContext(PromptInputController);
  if (!ctx) {
    throw new Error(
      "Wrap your component inside <PromptInputProvider> to use usePromptInputController()."
    );
  }
  return ctx;
};

// Optional variants (do NOT throw). Useful for dual-mode components.
const useOptionalPromptInputController = () =>
  useContext(PromptInputController);

export const useProviderAttachments = () => {
  const ctx = useContext(ProviderAttachmentsContext);
  if (!ctx) {
    throw new Error(
      "Wrap your component inside <PromptInputProvider> to use useProviderAttachments()."
    );
  }
  return ctx;
};

const useOptionalProviderAttachments = () =>
  useContext(ProviderAttachmentsContext);

export type PromptInputProviderProps = PropsWithChildren<{
  initialInput?: string;
}>;

/**
 * Optional global provider that lifts PromptInput state outside of PromptInput.
 * If you don't use it, PromptInput stays fully self-managed.
 */
export function PromptInputProvider({
  initialInput: initialTextInput = "",
  children,
}: PromptInputProviderProps) {
  // ----- textInput state
  const [textInput, setTextInput] = useState(initialTextInput);
  const clearInput = useCallback(() => setTextInput(""), []);

  // ----- attachments state (global when wrapped)
  const [attachmentFiles, setAttachmentFiles] = useState<
    (FileUIPart & { id: string })[]
  >([]);
  const fileInputRef = useRef<HTMLInputElement | null>(null);
  const openRef = useRef<() => void>(() => undefined);

  const add = useCallback((files: File[] | FileList) => {
    const incoming = Array.from(files);
    if (incoming.length === 0) {
      return;
    }

    setAttachmentFiles((prev) =>
      prev.concat(
        incoming.map((file) => ({
          id: nanoid(),
          type: "file" as const,
          url: URL.createObjectURL(file),
          mediaType: file.type,
          filename: file.name,
        }))
      )
    );
  }, []);

  const remove = useCallback((id: string) => {
    setAttachmentFiles((prev) => {
      const found = prev.find((f) => f.id === id);
      if (found?.url) {
        URL.revokeObjectURL(found.url);
      }
      return prev.filter((f) => f.id !== id);
    });
  }, []);

  const clear = useCallback(() => {
    setAttachmentFiles((prev) => {
      for (const f of prev) {
        if (f.url) {
          URL.revokeObjectURL(f.url);
        }
      }
      return [];
    });
  }, []);

  // Keep a ref to attachments for cleanup on unmount (avoids stale closure)
  const attachmentsRef = useRef(attachmentFiles);
  attachmentsRef.current = attachmentFiles;

  // Cleanup blob URLs on unmount to prevent memory leaks
  useEffect(() => {
    return () => {
      for (const f of attachmentsRef.current) {
        if (f.url) {
          URL.revokeObjectURL(f.url);
        }
      }
    };
  }, []);

  const openFileDialog = useCallback(() => {
    openRef.current?.();
  }, []);

  const attachments = useMemo<AttachmentsContext>(
    () => ({
      files: attachmentFiles,
      add,
      remove,
      clear,
      openFileDialog,
      fileInputRef,
    }),
    [attachmentFiles, add, remove, clear, openFileDialog]
  );

  const __registerFileInput = useCallback(
    (ref: RefObject<HTMLInputElement | null>, open: () => void) => {
      fileInputRef.current = ref.current;
      openRef.current = open;
    },
    []
  );

  const controller = useMemo<PromptInputControllerProps>(
    () => ({
      textInput: {
        value: textInput,
        setInput: setTextInput,
        clear: clearInput,
      },
      attachments,
      __registerFileInput,
    }),
    [textInput, clearInput, attachments, __registerFileInput]
  );

  return (
    <PromptInputController.Provider value={controller}>
      <ProviderAttachmentsContext.Provider value={attachments}>
        {children}
      </ProviderAttachmentsContext.Provider>
    </PromptInputController.Provider>
  );
}

// ============================================================================
// Component Context & Hooks
// ============================================================================

const LocalAttachmentsContext = createContext<AttachmentsContext | null>(null);

export const usePromptInputAttachments = () => {
  // Dual-mode: prefer provider if present, otherwise use local
  const provider = useOptionalProviderAttachments();
  const local = useContext(LocalAttachmentsContext);
  const context = provider ?? local;
  if (!context) {
    throw new Error(
      "usePromptInputAttachments must be used within a PromptInput or PromptInputProvider"
    );
  }
  return context;
};

export type PromptInputAttachmentProps = HTMLAttributes<HTMLDivElement> & {
  data: FileUIPart & { id: string };
  className?: string;
};

export function PromptInputAttachment({
  data,
  className,
  ...props
}: PromptInputAttachmentProps) {
  const attachments = usePromptInputAttachments();

  const filename = data.filename || "";

  const mediaType =
    data.mediaType?.startsWith("image/") && data.url ? "image" : "file";
  const isImage = mediaType === "image";

  const attachmentLabel = filename || (isImage ? "Image" : "Attachment");

  return (
    <PromptInputHoverCard>
      <HoverCardTrigger asChild>
        <div
          className={cn(
            "group relative flex h-8 cursor-pointer select-none items-center gap-1.5 rounded-md border border-border px-1.5 font-medium text-sm transition-all hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
            className
          )}
          key={data.id}
          {...props}
        >
          <div className="relative size-5 shrink-0">
            <div className="flex overflow-hidden absolute inset-0 justify-center items-center rounded transition-opacity size-5 bg-background group-hover:opacity-0">
              {isImage ? (
                /* biome-ignore lint/performance/noImgElement: dynamic user uploads */
                <img
                  alt={filename || "attachment"}
                  className="object-cover size-5"
                  height={20}
                  src={data.url}
                  width={20}
                />
              ) : (
                <div className="flex justify-center items-center size-5 text-muted-foreground">
                  <PaperclipIcon className="size-3" />
                </div>
              )}
            </div>
            <Button
              aria-label="Remove attachment"
              className="absolute inset-0 size-5 cursor-pointer rounded p-0 opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100 [&>svg]:size-2.5"
              onClick={(e) => {
                e.stopPropagation();
                attachments.remove(data.id);
              }}
              type="button"
              variant="ghost"
            >
              <XIcon />
              <span className="sr-only">Remove</span>
            </Button>
          </div>

          <span className="flex-1 truncate">{attachmentLabel}</span>
        </div>
      </HoverCardTrigger>
      <PromptInputHoverCardContent className="p-2 w-auto">
        <div className="space-y-3 w-auto">
          {isImage && (
            <div className="flex overflow-hidden justify-center items-center w-96 max-h-96 rounded-md border">
              {/* biome-ignore lint/performance/noImgElement: dynamic user uploads */}
              <img
                alt={filename || "attachment preview"}
                className="object-contain max-w-full max-h-full"
                height={384}
                src={data.url}
                width={448}
              />
            </div>
          )}
          <div className="flex items-center gap-2.5">
            <div className="min-w-0 flex-1 space-y-1 px-0.5">
              <h4 className="text-sm font-semibold leading-none truncate">
                {filename || (isImage ? "Image" : "Attachment")}
              </h4>
              {data.mediaType && (
                <p className="font-mono text-xs truncate text-muted-foreground">
                  {data.mediaType}
                </p>
              )}
            </div>
          </div>
        </div>
      </PromptInputHoverCardContent>
    </PromptInputHoverCard>
  );
}

export type PromptInputAttachmentsProps = Omit<
  HTMLAttributes<HTMLDivElement>,
  "children"
> & {
  children: (attachment: FileUIPart & { id: string }) => ReactNode;
};

export function PromptInputAttachments({
  children,
  className,
  ...props
}: PromptInputAttachmentsProps) {
  const attachments = usePromptInputAttachments();

  if (!attachments.files.length) {
    return null;
  }

  return (
    <div
      className={cn("flex flex-wrap gap-2 items-center p-3 w-full", className)}
      {...props}
    >
      {attachments.files.map((file) => (
        <Fragment key={file.id}>{children(file)}</Fragment>
      ))}
    </div>
  );
}

export type PromptInputActionAddAttachmentsProps = ComponentProps<
  typeof DropdownMenuItem
> & {
  label?: string;
};

export const PromptInputActionAddAttachments = ({
  label = "Add photos or files",
  ...props
}: PromptInputActionAddAttachmentsProps) => {
  const attachments = usePromptInputAttachments();

  return (
    <DropdownMenuItem
      {...props}
      onSelect={(e) => {
        e.preventDefault();
        attachments.openFileDialog();
      }}
    >
      <ImageIcon className="mr-2 size-4" /> {label}
    </DropdownMenuItem>
  );
};

export type PromptInputMessage = {
  text: string;
  files: FileUIPart[];
};

export type PromptInputProps = Omit<
  HTMLAttributes<HTMLFormElement>,
  "onSubmit" | "onError"
> & {
  accept?: string; // e.g., "image/*" or leave undefined for any
  multiple?: boolean;
  // When true, accepts drops anywhere on document. Default false (opt-in).
  globalDrop?: boolean;
  // Render a hidden input with given name and keep it in sync for native form posts. Default false.
  syncHiddenInput?: boolean;
  // Minimal constraints
  maxFiles?: number;
  maxFileSize?: number; // bytes
  onError?: (err: {
    code: "max_files" | "max_file_size" | "accept";
    message: string;
  }) => void;
  onSubmit: (
    message: PromptInputMessage,
    event: FormEvent<HTMLFormElement>
  ) => void | Promise<void>;
};

export const PromptInput = ({
  className,
  accept,
  multiple,
  globalDrop,
  syncHiddenInput,
  maxFiles,
  maxFileSize,
  onError,
  onSubmit,
  children,
  ...props
}: PromptInputProps) => {
  // Try to use a provider controller if present
  const controller = useOptionalPromptInputController();
  const usingProvider = !!controller;

  // Refs
  const inputRef = useRef<HTMLInputElement | null>(null);
  const formRef = useRef<HTMLFormElement | null>(null);

  // ----- Local attachments (only used when no provider)
  const [items, setItems] = useState<(FileUIPart & { id: string })[]>([]);
  const files = usingProvider ? controller.attachments.files : items;

  // Keep a ref to files for cleanup on unmount (avoids stale closure)
  const filesRef = useRef(files);
  filesRef.current = files;

  const openFileDialogLocal = useCallback(() => {
    inputRef.current?.click();
  }, []);

  const matchesAccept = useCallback(
    (f: File) => {
      if (!accept || accept.trim() === "") {
        return true;
      }

      const patterns = accept
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean);

      return patterns.some((pattern) => {
        if (pattern.endsWith("/*")) {
          const prefix = pattern.slice(0, -1); // e.g: image/* -> image/
          return f.type.startsWith(prefix);
        }
        return f.type === pattern;
      });
    },
    [accept]
  );

  const addLocal = useCallback(
    (fileList: File[] | FileList) => {
      const incoming = Array.from(fileList);
      const accepted = incoming.filter((f) => matchesAccept(f));
      if (incoming.length && accepted.length === 0) {
        onError?.({
          code: "accept",
          message: "No files match the accepted types.",
        });
        return;
      }
      const withinSize = (f: File) =>
        maxFileSize ? f.size <= maxFileSize : true;
      const sized = accepted.filter(withinSize);
      if (accepted.length > 0 && sized.length === 0) {
        onError?.({
          code: "max_file_size",
          message: "All files exceed the maximum size.",
        });
        return;
      }

      setItems((prev) => {
        const capacity =
          typeof maxFiles === "number"
            ? Math.max(0, maxFiles - prev.length)
            : undefined;
        const capped =
          typeof capacity === "number" ? sized.slice(0, capacity) : sized;
        if (typeof capacity === "number" && sized.length > capacity) {
          onError?.({
            code: "max_files",
            message: "Too many files. Some were not added.",
          });
        }
        const next: (FileUIPart & { id: string })[] = [];
        for (const file of capped) {
          next.push({
            id: nanoid(),
            type: "file",
            url: URL.createObjectURL(file),
            mediaType: file.type,
            filename: file.name,
          });
        }
        return prev.concat(next);
      });
    },
    [matchesAccept, maxFiles, maxFileSize, onError]
  );

  const removeLocal = useCallback(
    (id: string) =>
      setItems((prev) => {
        const found = prev.find((file) => file.id === id);
        if (found?.url) {
          URL.revokeObjectURL(found.url);
        }
        return prev.filter((file) => file.id !== id);
      }),
    []
  );

  const clearLocal = useCallback(
    () =>
      setItems((prev) => {
        for (const file of prev) {
          if (file.url) {
            URL.revokeObjectURL(file.url);
          }
        }
        return [];
      }),
    []
  );

  const add = usingProvider ? controller.attachments.add : addLocal;
  const remove = usingProvider ? controller.attachments.remove : removeLocal;
  const clear = usingProvider ? controller.attachments.clear : clearLocal;
  const openFileDialog = usingProvider
    ? controller.attachments.openFileDialog
    : openFileDialogLocal;

  // Let provider know about our hidden file input so external menus can call openFileDialog()
  useEffect(() => {
    if (!usingProvider) {
      return;
    }
    controller.__registerFileInput(inputRef, () => inputRef.current?.click());
  }, [usingProvider, controller]);

  // Note: File input cannot be programmatically set for security reasons
  // The syncHiddenInput prop is no longer functional
  useEffect(() => {
    if (syncHiddenInput && inputRef.current && files.length === 0) {
      inputRef.current.value = "";
    }
  }, [files, syncHiddenInput]);

  // Attach drop handlers on nearest form and document (opt-in)
  useEffect(() => {
    const form = formRef.current;
    if (!form) {
      return;
    }
    if (globalDrop) {
      return;
    }

    const onDragOver = (e: DragEvent) => {
      if (e.dataTransfer?.types?.includes("Files")) {
        e.preventDefault();
      }
    };
    const onDrop = (e: DragEvent) => {
      if (e.dataTransfer?.types?.includes("Files")) {
        e.preventDefault();
      }
      if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
        add(e.dataTransfer.files);
      }
    };
    form.addEventListener("dragover", onDragOver);
    form.addEventListener("drop", onDrop);
    return () => {
      form.removeEventListener("dragover", onDragOver);
      form.removeEventListener("drop", onDrop);
    };
  }, [add, globalDrop]);

  useEffect(() => {
    if (!globalDrop) {
      return;
    }

    const onDragOver = (e: DragEvent) => {
      if (e.dataTransfer?.types?.includes("Files")) {
        e.preventDefault();
      }
    };
    const onDrop = (e: DragEvent) => {
      if (e.dataTransfer?.types?.includes("Files")) {
        e.preventDefault();
      }
      if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
        add(e.dataTransfer.files);
      }
    };
    document.addEventListener("dragover", onDragOver);
    document.addEventListener("drop", onDrop);
    return () => {
      document.removeEventListener("dragover", onDragOver);
      document.removeEventListener("drop", onDrop);
    };
  }, [add, globalDrop]);

  useEffect(
    () => () => {
      if (!usingProvider) {
        for (const f of filesRef.current) {
          if (f.url) {
            URL.revokeObjectURL(f.url);
          }
        }
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps -- cleanup only on unmount; filesRef always current
    [usingProvider]
  );

  const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {
    if (event.currentTarget.files) {
      add(event.currentTarget.files);
    }
    // Reset input value to allow selecting files that were previously removed
    event.currentTarget.value = "";
  };

  const convertBlobUrlToDataUrl = async (
    url: string
  ): Promise<string | null> => {
    try {
      const response = await fetch(url);
      const blob = await response.blob();
      return new Promise((resolve) => {
        const reader = new FileReader();
        reader.onloadend = () => resolve(reader.result as string);
        reader.onerror = () => resolve(null);
        reader.readAsDataURL(blob);
      });
    } catch {
      return null;
    }
  };

  const ctx = useMemo<AttachmentsContext>(
    () => ({
      files: files.map((item) => ({ ...item, id: item.id })),
      add,
      remove,
      clear,
      openFileDialog,
      fileInputRef: inputRef,
    }),
    [files, add, remove, clear, openFileDialog]
  );

  const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
    event.preventDefault();

    const form = event.currentTarget;
    const text = usingProvider
      ? controller.textInput.value
      : (() => {
          const formData = new FormData(form);
          return (formData.get("message") as string) || "";
        })();

    // Reset form immediately after capturing text to avoid race condition
    // where user input during async blob conversion would be lost
    if (!usingProvider) {
      form.reset();
    }

    // Convert blob URLs to data URLs asynchronously
    Promise.all(
      files.map(async ({ id, ...item }) => {
        if (item.url?.startsWith("blob:")) {
          const dataUrl = await convertBlobUrlToDataUrl(item.url);
          // If conversion failed, keep the original blob URL
          return {
            ...item,
            url: dataUrl ?? item.url,
          };
        }
        return item;
      })
    )
      .then((convertedFiles: FileUIPart[]) => {
        try {
          const result = onSubmit({ text, files: convertedFiles }, event);

          // Handle both sync and async onSubmit
          if (result instanceof Promise) {
            result
              .then(() => {
                clear();
                if (usingProvider) {
                  controller.textInput.clear();
                }
              })
              .catch(() => {
                // Don't clear on error - user may want to retry
              });
          } else {
            // Sync function completed without throwing, clear attachments
            clear();
            if (usingProvider) {
              controller.textInput.clear();
            }
          }
        } catch {
          // Don't clear on error - user may want to retry
        }
      })
      .catch(() => {
        // Don't clear on error - user may want to retry
      });
  };

  // Render with or without local provider
  const inner = (
    <>
      <input
        accept={accept}
        aria-label="Upload files"
        className="hidden"
        multiple={multiple}
        onChange={handleChange}
        ref={inputRef}
        title="Upload files"
        type="file"
      />
      <form
        className={cn("w-full", className)}
        onSubmit={handleSubmit}
        ref={formRef}
        {...props}
      >
        <InputGroup className="overflow-hidden">{children}</InputGroup>
      </form>
    </>
  );

  return usingProvider ? (
    inner
  ) : (
    <LocalAttachmentsContext.Provider value={ctx}>
      {inner}
    </LocalAttachmentsContext.Provider>
  );
};

export type PromptInputBodyProps = HTMLAttributes<HTMLDivElement>;

export const PromptInputBody = ({
  className,
  ...props
}: PromptInputBodyProps) => (
  <div className={cn("contents", className)} {...props} />
);

export type PromptInputTextareaProps = ComponentProps<
  typeof InputGroupTextarea
>;

export const PromptInputTextarea = ({
  onChange,
  className,
  placeholder = "What would you like to know?",
  ...props
}: PromptInputTextareaProps) => {
  const controller = useOptionalPromptInputController();
  const attachments = usePromptInputAttachments();
  const [isComposing, setIsComposing] = 
Download .txt
gitextract_y5pipq23/

├── .changeset/
│   └── config.json
├── .cursor/
│   └── rules/
│       └── ultracite.mdc
├── .github/
│   ├── CONTRIBUTING.md
│   └── workflows/
│       ├── lint.yml
│       ├── playwright.yml
│       └── release.yml
├── .gitignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── LICENSE
├── README.md
├── app/
│   ├── (auth)/
│   │   ├── actions.ts
│   │   ├── api/
│   │   │   └── auth/
│   │   │       ├── [...nextauth]/
│   │   │       │   └── route.ts
│   │   │       └── guest/
│   │   │           └── route.ts
│   │   ├── auth.config.ts
│   │   ├── auth.ts
│   │   ├── login/
│   │   │   └── page.tsx
│   │   └── register/
│   │       └── page.tsx
│   ├── (chat)/
│   │   ├── actions.ts
│   │   ├── api/
│   │   │   ├── chat/
│   │   │   │   ├── [id]/
│   │   │   │   │   └── stream/
│   │   │   │   │       └── route.ts
│   │   │   │   ├── route.ts
│   │   │   │   └── schema.ts
│   │   │   ├── document/
│   │   │   │   └── route.ts
│   │   │   ├── files/
│   │   │   │   └── upload/
│   │   │   │       └── route.ts
│   │   │   ├── history/
│   │   │   │   └── route.ts
│   │   │   ├── suggestions/
│   │   │   │   └── route.ts
│   │   │   └── vote/
│   │   │       └── route.ts
│   │   ├── chat/
│   │   │   └── [id]/
│   │   │       └── page.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── globals.css
│   └── layout.tsx
├── artifacts/
│   ├── actions.ts
│   ├── code/
│   │   ├── client.tsx
│   │   └── server.ts
│   ├── image/
│   │   └── client.tsx
│   ├── sheet/
│   │   ├── client.tsx
│   │   └── server.ts
│   └── text/
│       ├── client.tsx
│       └── server.ts
├── biome.jsonc
├── components/
│   ├── ai-elements/
│   │   ├── artifact.tsx
│   │   ├── canvas.tsx
│   │   ├── chain-of-thought.tsx
│   │   ├── checkpoint.tsx
│   │   ├── confirmation.tsx
│   │   ├── connection.tsx
│   │   ├── controls.tsx
│   │   ├── conversation.tsx
│   │   ├── edge.tsx
│   │   ├── image.tsx
│   │   ├── inline-citation.tsx
│   │   ├── loader.tsx
│   │   ├── message.tsx
│   │   ├── model-selector.tsx
│   │   ├── node.tsx
│   │   ├── open-in-chat.tsx
│   │   ├── panel.tsx
│   │   ├── plan.tsx
│   │   ├── prompt-input.tsx
│   │   ├── queue.tsx
│   │   ├── reasoning.tsx
│   │   ├── shimmer.tsx
│   │   ├── sources.tsx
│   │   ├── suggestion.tsx
│   │   ├── task.tsx
│   │   ├── tool.tsx
│   │   ├── toolbar.tsx
│   │   └── web-preview.tsx
│   ├── app-sidebar.tsx
│   ├── artifact-actions.tsx
│   ├── artifact-close-button.tsx
│   ├── artifact-messages.tsx
│   ├── artifact.tsx
│   ├── auth-form.tsx
│   ├── chat-header.tsx
│   ├── chat.tsx
│   ├── code-editor.tsx
│   ├── console.tsx
│   ├── create-artifact.tsx
│   ├── data-stream-handler.tsx
│   ├── data-stream-provider.tsx
│   ├── diffview.tsx
│   ├── document-preview.tsx
│   ├── document-skeleton.tsx
│   ├── document.tsx
│   ├── elements/
│   │   ├── actions.tsx
│   │   ├── branch.tsx
│   │   ├── conversation.tsx
│   │   ├── image.tsx
│   │   ├── inline-citation.tsx
│   │   ├── loader.tsx
│   │   ├── message.tsx
│   │   ├── prompt-input.tsx
│   │   ├── reasoning.tsx
│   │   ├── response.tsx
│   │   ├── source.tsx
│   │   ├── suggestion.tsx
│   │   ├── task.tsx
│   │   ├── tool.tsx
│   │   └── web-preview.tsx
│   ├── greeting.tsx
│   ├── icons.tsx
│   ├── image-editor.tsx
│   ├── message-actions.tsx
│   ├── message-editor.tsx
│   ├── message-reasoning.tsx
│   ├── message.tsx
│   ├── messages.tsx
│   ├── multimodal-input.tsx
│   ├── preview-attachment.tsx
│   ├── sheet-editor.tsx
│   ├── sidebar-history-item.tsx
│   ├── sidebar-history.tsx
│   ├── sidebar-toggle.tsx
│   ├── sidebar-user-nav.tsx
│   ├── sign-out-form.tsx
│   ├── submit-button.tsx
│   ├── suggested-actions.tsx
│   ├── suggestion.tsx
│   ├── text-editor.tsx
│   ├── theme-provider.tsx
│   ├── toast.tsx
│   ├── toolbar.tsx
│   ├── ui/
│   │   ├── alert-dialog.tsx
│   │   ├── alert.tsx
│   │   ├── avatar.tsx
│   │   ├── badge.tsx
│   │   ├── button-group.tsx
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   ├── carousel.tsx
│   │   ├── collapsible.tsx
│   │   ├── command.tsx
│   │   ├── dialog.tsx
│   │   ├── dropdown-menu.tsx
│   │   ├── hover-card.tsx
│   │   ├── input-group.tsx
│   │   ├── input.tsx
│   │   ├── label.tsx
│   │   ├── progress.tsx
│   │   ├── scroll-area.tsx
│   │   ├── select.tsx
│   │   ├── separator.tsx
│   │   ├── sheet.tsx
│   │   ├── sidebar.tsx
│   │   ├── skeleton.tsx
│   │   ├── textarea.tsx
│   │   └── tooltip.tsx
│   ├── version-footer.tsx
│   ├── visibility-selector.tsx
│   └── weather.tsx
├── components.json
├── drizzle.config.ts
├── hooks/
│   ├── use-artifact.ts
│   ├── use-auto-resume.ts
│   ├── use-chat-visibility.ts
│   ├── use-messages.tsx
│   ├── use-mobile.ts
│   └── use-scroll-to-bottom.tsx
├── instrumentation-client.ts
├── instrumentation.ts
├── lib/
│   ├── ai/
│   │   ├── entitlements.ts
│   │   ├── models.mock.ts
│   │   ├── models.test.ts
│   │   ├── models.ts
│   │   ├── prompts.ts
│   │   ├── providers.ts
│   │   └── tools/
│   │       ├── create-document.ts
│   │       ├── get-weather.ts
│   │       ├── request-suggestions.ts
│   │       └── update-document.ts
│   ├── artifacts/
│   │   └── server.ts
│   ├── constants.ts
│   ├── db/
│   │   ├── helpers/
│   │   │   └── 01-core-to-parts.ts
│   │   ├── migrate.ts
│   │   ├── migrations/
│   │   │   ├── 0000_keen_devos.sql
│   │   │   ├── 0001_sparkling_blue_marvel.sql
│   │   │   ├── 0002_wandering_riptide.sql
│   │   │   ├── 0003_cloudy_glorian.sql
│   │   │   ├── 0004_odd_slayback.sql
│   │   │   ├── 0005_wooden_whistler.sql
│   │   │   ├── 0006_marvelous_frog_thor.sql
│   │   │   ├── 0007_flowery_ben_parker.sql
│   │   │   ├── 0008_flat_forgotten_one.sql
│   │   │   └── meta/
│   │   │       ├── 0000_snapshot.json
│   │   │       ├── 0001_snapshot.json
│   │   │       ├── 0002_snapshot.json
│   │   │       ├── 0003_snapshot.json
│   │   │       ├── 0004_snapshot.json
│   │   │       ├── 0005_snapshot.json
│   │   │       ├── 0006_snapshot.json
│   │   │       ├── 0007_snapshot.json
│   │   │       ├── 0008_snapshot.json
│   │   │       └── _journal.json
│   │   ├── queries.ts
│   │   ├── schema.ts
│   │   └── utils.ts
│   ├── editor/
│   │   ├── config.ts
│   │   ├── diff.js
│   │   ├── functions.tsx
│   │   ├── react-renderer.tsx
│   │   └── suggestions.tsx
│   ├── errors.ts
│   ├── ratelimit.ts
│   ├── types.ts
│   └── utils.ts
├── next-env.d.ts
├── next.config.ts
├── package.json
├── playwright.config.ts
├── postcss.config.mjs
├── proxy.ts
├── tests/
│   ├── e2e/
│   │   ├── api.test.ts
│   │   ├── auth.test.ts
│   │   ├── chat.test.ts
│   │   └── model-selector.test.ts
│   ├── fixtures.ts
│   ├── helpers.ts
│   ├── pages/
│   │   └── chat.ts
│   └── prompts/
│       └── utils.ts
├── tsconfig.json
├── tsconfig.tsbuildinfo
├── vercel-template.json
└── vercel.json
Download .txt
SYMBOL INDEX (597 symbols across 149 files)

FILE: app/(auth)/actions.ts
  type LoginActionState (line 14) | type LoginActionState = {
  type RegisterActionState (line 44) | type RegisterActionState = {

FILE: app/(auth)/api/auth/guest/route.ts
  function GET (line 6) | async function GET(request: Request) {

FILE: app/(auth)/auth.ts
  type UserType (line 9) | type UserType = "guest" | "regular";
  type Session (line 12) | interface Session extends DefaultSession {
  type User (line 19) | interface User {
  type JWT (line 27) | interface JWT extends DefaultJWT {
  method authorize (line 43) | async authorize({ email, password }: any) {
  method authorize (line 70) | async authorize() {
  method jwt (line 77) | jwt({ token, user }) {
  method session (line 85) | session({ session, token }) {

FILE: app/(auth)/login/page.tsx
  function Page (line 13) | function Page() {

FILE: app/(auth)/register/page.tsx
  function Page (line 12) | function Page() {

FILE: app/(chat)/actions.ts
  function saveChatModelAsCookie (line 15) | async function saveChatModelAsCookie(model: string) {
  function generateTitleFromUserMessage (line 20) | async function generateTitleFromUserMessage({
  function deleteTrailingMessages (line 36) | async function deleteTrailingMessages({ id }: { id: string }) {
  function updateChatVisibility (line 45) | async function updateChatVisibility({

FILE: app/(chat)/api/chat/[id]/stream/route.ts
  function GET (line 1) | function GET() {

FILE: app/(chat)/api/chat/route.ts
  function getStreamContext (line 44) | function getStreamContext() {
  function POST (line 54) | async function POST(request: Request) {
  function DELETE (line 291) | async function DELETE(request: Request) {

FILE: app/(chat)/api/chat/schema.ts
  type PostRequestBody (line 39) | type PostRequestBody = z.infer<typeof postRequestBodySchema>;

FILE: app/(chat)/api/document/route.ts
  function GET (line 10) | async function GET(request: Request) {
  function POST (line 42) | async function POST(request: Request) {
  function DELETE (line 87) | async function DELETE(request: Request) {

FILE: app/(chat)/api/files/upload/route.ts
  function POST (line 20) | async function POST(request: Request) {

FILE: app/(chat)/api/history/route.ts
  function GET (line 6) | async function GET(request: NextRequest) {
  function DELETE (line 36) | async function DELETE() {

FILE: app/(chat)/api/suggestions/route.ts
  function GET (line 5) | async function GET(request: Request) {

FILE: app/(chat)/api/vote/route.ts
  function GET (line 5) | async function GET(request: Request) {
  function PATCH (line 37) | async function PATCH(request: Request) {

FILE: app/(chat)/chat/[id]/page.tsx
  function Page (line 12) | function Page(props: { params: Promise<{ id: string }> }) {
  function ChatPage (line 20) | async function ChatPage({ params }: { params: Promise<{ id: string }> }) {

FILE: app/(chat)/layout.tsx
  function Layout (line 9) | function Layout({ children }: { children: React.ReactNode }) {
  function SidebarWrapper (line 25) | async function SidebarWrapper({ children }: { children: React.ReactNode ...

FILE: app/(chat)/page.tsx
  function Page (line 8) | function Page() {
  function NewChatPage (line 16) | async function NewChatPage() {

FILE: app/layout.tsx
  constant LIGHT_THEME_COLOR (line 31) | const LIGHT_THEME_COLOR = "hsl(0 0% 100%)";
  constant DARK_THEME_COLOR (line 32) | const DARK_THEME_COLOR = "hsl(240deg 10% 3.92%)";
  constant THEME_COLOR_SCRIPT (line 33) | const THEME_COLOR_SCRIPT = `\
  function RootLayout (line 51) | function RootLayout({

FILE: artifacts/actions.ts
  function getSuggestions (line 5) | async function getSuggestions({ documentId }: { documentId: string }) {

FILE: artifacts/code/client.tsx
  constant OUTPUT_HANDLERS (line 19) | const OUTPUT_HANDLERS = {
  function detectRequiredHandlers (line 55) | function detectRequiredHandlers(code: string): string[] {
  type Metadata (line 65) | type Metadata = {

FILE: artifacts/sheet/client.tsx
  type Metadata (line 13) | type Metadata = any;

FILE: artifacts/text/client.tsx
  type TextArtifactMetadata (line 17) | type TextArtifactMetadata = {

FILE: components/ai-elements/artifact.tsx
  type ArtifactProps (line 14) | type ArtifactProps = HTMLAttributes<HTMLDivElement>;
  type ArtifactHeaderProps (line 26) | type ArtifactHeaderProps = HTMLAttributes<HTMLDivElement>;
  type ArtifactCloseProps (line 41) | type ArtifactCloseProps = ComponentProps<typeof Button>;
  type ArtifactTitleProps (line 65) | type ArtifactTitleProps = HTMLAttributes<HTMLParagraphElement>;
  type ArtifactDescriptionProps (line 74) | type ArtifactDescriptionProps = HTMLAttributes<HTMLParagraphElement>;
  type ArtifactActionsProps (line 83) | type ArtifactActionsProps = HTMLAttributes<HTMLDivElement>;
  type ArtifactActionProps (line 92) | type ArtifactActionProps = ComponentProps<typeof Button> & {
  type ArtifactContentProps (line 140) | type ArtifactContentProps = HTMLAttributes<HTMLDivElement>;

FILE: components/ai-elements/canvas.tsx
  type CanvasProps (line 5) | type CanvasProps = ReactFlowProps & {

FILE: components/ai-elements/chain-of-thought.tsx
  type ChainOfThoughtContextValue (line 20) | type ChainOfThoughtContextValue = {
  type ChainOfThoughtProps (line 39) | type ChainOfThoughtProps = ComponentProps<"div"> & {
  type ChainOfThoughtHeaderProps (line 78) | type ChainOfThoughtHeaderProps = ComponentProps<
  type ChainOfThoughtStepProps (line 111) | type ChainOfThoughtStepProps = ComponentProps<"div"> & {
  type ChainOfThoughtSearchResultsProps (line 160) | type ChainOfThoughtSearchResultsProps = ComponentProps<"div">;
  type ChainOfThoughtSearchResultProps (line 171) | type ChainOfThoughtSearchResultProps = ComponentProps<typeof Badge>;
  type ChainOfThoughtContentProps (line 185) | type ChainOfThoughtContentProps = ComponentProps<
  type ChainOfThoughtImageProps (line 210) | type ChainOfThoughtImageProps = ComponentProps<"div"> & {

FILE: components/ai-elements/checkpoint.tsx
  type CheckpointProps (line 14) | type CheckpointProps = HTMLAttributes<HTMLDivElement>;
  type CheckpointIconProps (line 33) | type CheckpointIconProps = LucideProps;
  type CheckpointTriggerProps (line 44) | type CheckpointTriggerProps = ComponentProps<typeof Button> & {

FILE: components/ai-elements/confirmation.tsx
  type ToolUIPartApproval (line 14) | type ToolUIPartApproval =
  type ConfirmationContextValue (line 42) | type ConfirmationContextValue = {
  type ConfirmationProps (line 61) | type ConfirmationProps = ComponentProps<typeof Alert> & {
  type ConfirmationTitleProps (line 83) | type ConfirmationTitleProps = ComponentProps<typeof AlertDescription>;
  type ConfirmationRequestProps (line 92) | type ConfirmationRequestProps = {
  type ConfirmationAcceptedProps (line 107) | type ConfirmationAcceptedProps = {
  type ConfirmationRejectedProps (line 129) | type ConfirmationRejectedProps = {
  type ConfirmationActionsProps (line 151) | type ConfirmationActionsProps = ComponentProps<"div">;
  type ConfirmationActionProps (line 172) | type ConfirmationActionProps = ComponentProps<typeof Button>;

FILE: components/ai-elements/connection.tsx
  constant HALF (line 3) | const HALF = 0.5;

FILE: components/ai-elements/controls.tsx
  type ControlsProps (line 7) | type ControlsProps = ComponentProps<typeof ControlsPrimitive>;

FILE: components/ai-elements/conversation.tsx
  type ConversationProps (line 10) | type ConversationProps = ComponentProps<typeof StickToBottom>;
  type ConversationContentProps (line 22) | type ConversationContentProps = ComponentProps<
  type ConversationEmptyStateProps (line 36) | type ConversationEmptyStateProps = ComponentProps<"div"> & {
  type ConversationScrollButtonProps (line 71) | type ConversationScrollButtonProps = ComponentProps<typeof Button>;

FILE: components/ai-elements/image.tsx
  type ImageProps (line 4) | type ImageProps = Experimental_GeneratedImage & {

FILE: components/ai-elements/inline-citation.tsx
  type InlineCitationProps (line 26) | type InlineCitationProps = ComponentProps<"span">;
  type InlineCitationTextProps (line 38) | type InlineCitationTextProps = ComponentProps<"span">;
  type InlineCitationCardProps (line 50) | type InlineCitationCardProps = ComponentProps<typeof HoverCard>;
  type InlineCitationCardTriggerProps (line 56) | type InlineCitationCardTriggerProps = ComponentProps<typeof Badge> & {
  type InlineCitationCardBodyProps (line 83) | type InlineCitationCardBodyProps = ComponentProps<"div">;
  type InlineCitationCarouselProps (line 99) | type InlineCitationCarouselProps = ComponentProps<typeof Carousel>;
  type InlineCitationCarouselContentProps (line 117) | type InlineCitationCarouselContentProps = ComponentProps<"div">;
  type InlineCitationCarouselItemProps (line 123) | type InlineCitationCarouselItemProps = ComponentProps<"div">;
  type InlineCitationCarouselHeaderProps (line 135) | type InlineCitationCarouselHeaderProps = ComponentProps<"div">;
  type InlineCitationCarouselIndexProps (line 150) | type InlineCitationCarouselIndexProps = ComponentProps<"div">;
  type InlineCitationCarouselPrevProps (line 187) | type InlineCitationCarouselPrevProps = ComponentProps<"button">;
  type InlineCitationCarouselNextProps (line 214) | type InlineCitationCarouselNextProps = ComponentProps<"button">;
  type InlineCitationSourceProps (line 241) | type InlineCitationSourceProps = ComponentProps<"div"> & {
  type InlineCitationQuoteProps (line 271) | type InlineCitationQuoteProps = ComponentProps<"blockquote">;

FILE: components/ai-elements/loader.tsx
  type LoaderIconProps (line 4) | type LoaderIconProps = {
  type LoaderProps (line 82) | type LoaderProps = HTMLAttributes<HTMLDivElement> & {

FILE: components/ai-elements/message.tsx
  type MessageProps (line 23) | type MessageProps = HTMLAttributes<HTMLDivElement> & {
  type MessageContentProps (line 38) | type MessageContentProps = HTMLAttributes<HTMLDivElement>;
  type MessageActionsProps (line 58) | type MessageActionsProps = ComponentProps<"div">;
  type MessageActionProps (line 70) | type MessageActionProps = ComponentProps<typeof Button> & {
  type MessageBranchContextType (line 106) | type MessageBranchContextType = {
  type MessageBranchProps (line 131) | type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
  type MessageBranchContentProps (line 181) | type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
  type MessageBranchSelectorProps (line 211) | type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
  type MessageBranchPreviousProps (line 236) | type MessageBranchPreviousProps = ComponentProps<typeof Button>;
  type MessageBranchNextProps (line 259) | type MessageBranchNextProps = ComponentProps<typeof Button>;
  type MessageBranchPageProps (line 283) | type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;
  type MessageResponseProps (line 304) | type MessageResponseProps = ComponentProps<typeof Streamdown>;
  type MessageAttachmentProps (line 321) | type MessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {
  function MessageAttachment (line 327) | function MessageAttachment({
  type MessageAttachmentsProps (line 406) | type MessageAttachmentsProps = ComponentProps<"div">;
  function MessageAttachments (line 408) | function MessageAttachments({
  type MessageToolbarProps (line 430) | type MessageToolbarProps = ComponentProps<"div">;

FILE: components/ai-elements/model-selector.tsx
  type ModelSelectorProps (line 22) | type ModelSelectorProps = ComponentProps<typeof Dialog>;
  type ModelSelectorTriggerProps (line 28) | type ModelSelectorTriggerProps = ComponentProps<typeof DialogTrigger>;
  type ModelSelectorContentProps (line 34) | type ModelSelectorContentProps = ComponentProps<typeof DialogContent> & {
  type ModelSelectorDialogProps (line 52) | type ModelSelectorDialogProps = ComponentProps<typeof CommandDialog>;
  type ModelSelectorInputProps (line 58) | type ModelSelectorInputProps = ComponentProps<typeof CommandInput>;
  type ModelSelectorListProps (line 67) | type ModelSelectorListProps = ComponentProps<typeof CommandList>;
  type ModelSelectorEmptyProps (line 73) | type ModelSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;
  type ModelSelectorGroupProps (line 79) | type ModelSelectorGroupProps = ComponentProps<typeof CommandGroup>;
  type ModelSelectorItemProps (line 85) | type ModelSelectorItemProps = ComponentProps<typeof CommandItem>;
  type ModelSelectorShortcutProps (line 91) | type ModelSelectorShortcutProps = ComponentProps<typeof CommandShortcut>;
  type ModelSelectorSeparatorProps (line 97) | type ModelSelectorSeparatorProps = ComponentProps<
  type ModelSelectorLogoProps (line 105) | type ModelSelectorLogoProps = {
  type ModelSelectorLogoGroupProps (line 181) | type ModelSelectorLogoGroupProps = ComponentProps<"div">;
  type ModelSelectorNameProps (line 196) | type ModelSelectorNameProps = ComponentProps<"span">;

FILE: components/ai-elements/node.tsx
  type NodeProps (line 14) | type NodeProps = ComponentProps<typeof Card> & {
  type NodeHeaderProps (line 35) | type NodeHeaderProps = ComponentProps<typeof CardHeader>;
  type NodeTitleProps (line 44) | type NodeTitleProps = ComponentProps<typeof CardTitle>;
  type NodeDescriptionProps (line 48) | type NodeDescriptionProps = ComponentProps<typeof CardDescription>;
  type NodeActionProps (line 54) | type NodeActionProps = ComponentProps<typeof CardAction>;
  type NodeContentProps (line 58) | type NodeContentProps = ComponentProps<typeof CardContent>;
  type NodeFooterProps (line 64) | type NodeFooterProps = ComponentProps<typeof CardFooter>;

FILE: components/ai-elements/open-in-chat.tsx
  type OpenInProps (line 196) | type OpenInProps = ComponentProps<typeof DropdownMenu> & {
  type OpenInContentProps (line 206) | type OpenInContentProps = ComponentProps<typeof DropdownMenuContent>;
  type OpenInItemProps (line 216) | type OpenInItemProps = ComponentProps<typeof DropdownMenuItem>;
  type OpenInLabelProps (line 222) | type OpenInLabelProps = ComponentProps<typeof DropdownMenuLabel>;
  type OpenInSeparatorProps (line 228) | type OpenInSeparatorProps = ComponentProps<typeof DropdownMenuSeparator>;
  type OpenInTriggerProps (line 234) | type OpenInTriggerProps = ComponentProps<typeof DropdownMenuTrigger>;
  type OpenInChatGPTProps (line 247) | type OpenInChatGPTProps = ComponentProps<typeof DropdownMenuItem>;
  type OpenInClaudeProps (line 267) | type OpenInClaudeProps = ComponentProps<typeof DropdownMenuItem>;
  type OpenInT3Props (line 287) | type OpenInT3Props = ComponentProps<typeof DropdownMenuItem>;
  type OpenInSciraProps (line 307) | type OpenInSciraProps = ComponentProps<typeof DropdownMenuItem>;
  type OpenInv0Props (line 327) | type OpenInv0Props = ComponentProps<typeof DropdownMenuItem>;
  type OpenInCursorProps (line 347) | type OpenInCursorProps = ComponentProps<typeof DropdownMenuItem>;

FILE: components/ai-elements/panel.tsx
  type PanelProps (line 5) | type PanelProps = ComponentProps<typeof PanelPrimitive>;

FILE: components/ai-elements/plan.tsx
  type PlanContextValue (line 24) | type PlanContextValue = {
  type PlanProps (line 38) | type PlanProps = ComponentProps<typeof Collapsible> & {
  type PlanHeaderProps (line 55) | type PlanHeaderProps = ComponentProps<typeof CardHeader>;
  type PlanTitleProps (line 65) | type PlanTitleProps = Omit<
  type PlanDescriptionProps (line 82) | type PlanDescriptionProps = Omit<
  type PlanActionProps (line 107) | type PlanActionProps = ComponentProps<typeof CardAction>;
  type PlanContentProps (line 113) | type PlanContentProps = ComponentProps<typeof CardContent>;
  type PlanFooterProps (line 121) | type PlanFooterProps = ComponentProps<"div">;
  type PlanTriggerProps (line 127) | type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>;

FILE: components/ai-elements/prompt-input.tsx
  type AttachmentsContext (line 77) | type AttachmentsContext = {
  type TextInputContext (line 86) | type TextInputContext = {
  type PromptInputControllerProps (line 92) | type PromptInputControllerProps = {
  type PromptInputProviderProps (line 136) | type PromptInputProviderProps = PropsWithChildren<{
  function PromptInputProvider (line 144) | function PromptInputProvider({
  type PromptInputAttachmentProps (line 279) | type PromptInputAttachmentProps = HTMLAttributes<HTMLDivElement> & {
  function PromptInputAttachment (line 284) | function PromptInputAttachment({
  type PromptInputAttachmentsProps (line 377) | type PromptInputAttachmentsProps = Omit<
  function PromptInputAttachments (line 384) | function PromptInputAttachments({
  type PromptInputActionAddAttachmentsProps (line 407) | type PromptInputActionAddAttachmentsProps = ComponentProps<
  type PromptInputMessage (line 432) | type PromptInputMessage = {
  type PromptInputProps (line 437) | type PromptInputProps = Omit<
  type PromptInputBodyProps (line 818) | type PromptInputBodyProps = HTMLAttributes<HTMLDivElement>;
  type PromptInputTextareaProps (line 827) | type PromptInputTextareaProps = ComponentProps<
  type PromptInputHeaderProps (line 928) | type PromptInputHeaderProps = Omit<
  type PromptInputFooterProps (line 944) | type PromptInputFooterProps = Omit<
  type PromptInputToolsProps (line 960) | type PromptInputToolsProps = HTMLAttributes<HTMLDivElement>;
  type PromptInputButtonProps (line 969) | type PromptInputButtonProps = ComponentProps<typeof InputGroupButton>;
  type PromptInputActionMenuProps (line 991) | type PromptInputActionMenuProps = ComponentProps<typeof DropdownMenu>;
  type PromptInputActionMenuTriggerProps (line 996) | type PromptInputActionMenuTriggerProps = PromptInputButtonProps;
  type PromptInputActionMenuContentProps (line 1010) | type PromptInputActionMenuContentProps = ComponentProps<
  type PromptInputActionMenuItemProps (line 1020) | type PromptInputActionMenuItemProps = ComponentProps<
  type PromptInputSubmitProps (line 1033) | type PromptInputSubmitProps = ComponentProps<typeof InputGroupButton> & {
  type SpeechRecognition (line 1069) | interface SpeechRecognition extends EventTarget {
  type SpeechRecognitionEvent (line 1085) | interface SpeechRecognitionEvent extends Event {
  type SpeechRecognitionResultList (line 1090) | type SpeechRecognitionResultList = {
  type SpeechRecognitionResult (line 1096) | type SpeechRecognitionResult = {
  type SpeechRecognitionAlternative (line 1103) | type SpeechRecognitionAlternative = {
  type SpeechRecognitionErrorEvent (line 1108) | interface SpeechRecognitionErrorEvent extends Event {
  type Window (line 1113) | interface Window {
  type PromptInputSpeechButtonProps (line 1123) | type PromptInputSpeechButtonProps = ComponentProps<
  type PromptInputSelectProps (line 1229) | type PromptInputSelectProps = ComponentProps<typeof Select>;
  type PromptInputSelectTriggerProps (line 1235) | type PromptInputSelectTriggerProps = ComponentProps<
  type PromptInputSelectContentProps (line 1253) | type PromptInputSelectContentProps = ComponentProps<
  type PromptInputSelectItemProps (line 1264) | type PromptInputSelectItemProps = ComponentProps<typeof SelectItem>;
  type PromptInputSelectValueProps (line 1273) | type PromptInputSelectValueProps = ComponentProps<typeof SelectValue>;
  type PromptInputHoverCardProps (line 1282) | type PromptInputHoverCardProps = ComponentProps<typeof HoverCard>;
  type PromptInputHoverCardTriggerProps (line 1292) | type PromptInputHoverCardTriggerProps = ComponentProps<
  type PromptInputHoverCardContentProps (line 1300) | type PromptInputHoverCardContentProps = ComponentProps<
  type PromptInputTabsListProps (line 1311) | type PromptInputTabsListProps = HTMLAttributes<HTMLDivElement>;
  type PromptInputTabProps (line 1318) | type PromptInputTabProps = HTMLAttributes<HTMLDivElement>;
  type PromptInputTabLabelProps (line 1325) | type PromptInputTabLabelProps = HTMLAttributes<HTMLHeadingElement>;
  type PromptInputTabBodyProps (line 1340) | type PromptInputTabBodyProps = HTMLAttributes<HTMLDivElement>;
  type PromptInputTabItemProps (line 1349) | type PromptInputTabItemProps = HTMLAttributes<HTMLDivElement>;
  type PromptInputCommandProps (line 1364) | type PromptInputCommandProps = ComponentProps<typeof Command>;
  type PromptInputCommandInputProps (line 1371) | type PromptInputCommandInputProps = ComponentProps<typeof CommandInput>;
  type PromptInputCommandListProps (line 1380) | type PromptInputCommandListProps = ComponentProps<typeof CommandList>;
  type PromptInputCommandEmptyProps (line 1389) | type PromptInputCommandEmptyProps = ComponentProps<typeof CommandEmpty>;
  type PromptInputCommandGroupProps (line 1398) | type PromptInputCommandGroupProps = ComponentProps<typeof CommandGroup>;
  type PromptInputCommandItemProps (line 1407) | type PromptInputCommandItemProps = ComponentProps<typeof CommandItem>;
  type PromptInputCommandSeparatorProps (line 1416) | type PromptInputCommandSeparatorProps = ComponentProps<

FILE: components/ai-elements/queue.tsx
  type QueueMessagePart (line 14) | type QueueMessagePart = {
  type QueueMessage (line 22) | type QueueMessage = {
  type QueueTodo (line 27) | type QueueTodo = {
  type QueueItemProps (line 34) | type QueueItemProps = ComponentProps<"li">;
  type QueueItemIndicatorProps (line 46) | type QueueItemIndicatorProps = ComponentProps<"span"> & {
  type QueueItemContentProps (line 67) | type QueueItemContentProps = ComponentProps<"span"> & {
  type QueueItemDescriptionProps (line 88) | type QueueItemDescriptionProps = ComponentProps<"div"> & {
  type QueueItemActionsProps (line 109) | type QueueItemActionsProps = ComponentProps<"div">;
  type QueueItemActionProps (line 118) | type QueueItemActionProps = Omit<
  type QueueItemAttachmentProps (line 139) | type QueueItemAttachmentProps = ComponentProps<"div">;
  type QueueItemImageProps (line 148) | type QueueItemImageProps = ComponentProps<"img">;
  type QueueItemFileProps (line 164) | type QueueItemFileProps = ComponentProps<"span">;
  type QueueListProps (line 183) | type QueueListProps = ComponentProps<typeof ScrollArea>;
  type QueueSectionProps (line 198) | type QueueSectionProps = ComponentProps<typeof Collapsible>;
  type QueueSectionTriggerProps (line 209) | type QueueSectionTriggerProps = ComponentProps<"button">;
  type QueueSectionLabelProps (line 231) | type QueueSectionLabelProps = ComponentProps<"span"> & {
  type QueueSectionContentProps (line 254) | type QueueSectionContentProps = ComponentProps<
  type QueueProps (line 265) | type QueueProps = ComponentProps<"div">;

FILE: components/ai-elements/reasoning.tsx
  type ReasoningContextValue (line 16) | type ReasoningContextValue = {
  type ReasoningProps (line 33) | type ReasoningProps = ComponentProps<typeof Collapsible> & {
  constant AUTO_CLOSE_DELAY (line 41) | const AUTO_CLOSE_DELAY = 300;
  constant MS_IN_S (line 42) | const MS_IN_S = 1000;
  type ReasoningTriggerProps (line 114) | type ReasoningTriggerProps = ComponentProps<
  type ReasoningContentProps (line 164) | type ReasoningContentProps = ComponentProps<

FILE: components/ai-elements/shimmer.tsx
  type TextShimmerProps (line 13) | type TextShimmerProps = {

FILE: components/ai-elements/sources.tsx
  type SourcesProps (line 12) | type SourcesProps = ComponentProps<"div">;
  type SourcesTriggerProps (line 21) | type SourcesTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
  type SourcesContentProps (line 44) | type SourcesContentProps = ComponentProps<typeof CollapsibleContent>;
  type SourceProps (line 60) | type SourceProps = ComponentProps<"a">;

FILE: components/ai-elements/suggestion.tsx
  type SuggestionsProps (line 8) | type SuggestionsProps = ComponentProps<typeof ScrollArea>;
  type SuggestionProps (line 23) | type SuggestionProps = Omit<ComponentProps<typeof Button>, "onClick"> & {

FILE: components/ai-elements/task.tsx
  type TaskItemFileProps (line 12) | type TaskItemFileProps = ComponentProps<"div">;
  type TaskItemProps (line 30) | type TaskItemProps = ComponentProps<"div">;
  type TaskProps (line 38) | type TaskProps = ComponentProps<typeof Collapsible>;
  type TaskTriggerProps (line 48) | type TaskTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
  type TaskContentProps (line 69) | type TaskContentProps = ComponentProps<typeof CollapsibleContent>;

FILE: components/ai-elements/tool.tsx
  type ToolProps (line 22) | type ToolProps = ComponentProps<typeof Collapsible>;
  type ToolHeaderProps (line 31) | type ToolHeaderProps = {
  type ToolContentProps (line 92) | type ToolContentProps = ComponentProps<typeof CollapsibleContent>;
  type ToolInputProps (line 104) | type ToolInputProps = ComponentProps<"div"> & {
  type ToolOutputProps (line 119) | type ToolOutputProps = ComponentProps<"div"> & {

FILE: components/ai-elements/toolbar.tsx
  type ToolbarProps (line 5) | type ToolbarProps = ComponentProps<typeof NodeToolbar>;

FILE: components/ai-elements/web-preview.tsx
  type WebPreviewContextValue (line 21) | type WebPreviewContextValue = {
  type WebPreviewProps (line 38) | type WebPreviewProps = ComponentProps<"div"> & {
  type WebPreviewNavigationProps (line 80) | type WebPreviewNavigationProps = ComponentProps<"div">;
  type WebPreviewNavigationButtonProps (line 95) | type WebPreviewNavigationButtonProps = ComponentProps<typeof Button> & {
  type WebPreviewUrlProps (line 127) | type WebPreviewUrlProps = ComponentProps<typeof Input>;
  type WebPreviewBodyProps (line 168) | type WebPreviewBodyProps = ComponentProps<"iframe"> & {
  type WebPreviewConsoleProps (line 194) | type WebPreviewConsoleProps = ComponentProps<"div"> & {

FILE: components/app-sidebar.tsx
  function AppSidebar (line 37) | function AppSidebar({ user }: { user: User | undefined }) {

FILE: components/artifact-actions.tsx
  type ArtifactActionsProps (line 9) | type ArtifactActionsProps = {
  function PureArtifactActions (line 19) | function PureArtifactActions({

FILE: components/artifact-close-button.tsx
  function PureArtifactCloseButton (line 6) | function PureArtifactCloseButton() {

FILE: components/artifact-messages.tsx
  type ArtifactMessagesProps (line 11) | type ArtifactMessagesProps = {
  function PureArtifactMessages (line 23) | function PureArtifactMessages({
  function areEqual (line 88) | function areEqual(

FILE: components/artifact.tsx
  type ArtifactKind (line 38) | type ArtifactKind = (typeof artifactDefinitions)[number]["kind"];
  type UIArtifact (line 40) | type UIArtifact = {
  function PureArtifact (line 55) | function PureArtifact({

FILE: components/auth-form.tsx
  function AuthForm (line 6) | function AuthForm({

FILE: components/chat-header.tsx
  function PureChatHeader (line 13) | function PureChatHeader({

FILE: components/chat.tsx
  function Chat (line 35) | function Chat({

FILE: components/code-editor.tsx
  type EditorProps (line 11) | type EditorProps = {
  function PureCodeEditor (line 20) | function PureCodeEditor({ content, onSaveContent, status }: EditorProps) {
  function areEqual (line 101) | function areEqual(prevProps: EditorProps, nextProps: EditorProps) {

FILE: components/console.tsx
  type ConsoleOutputContent (line 15) | type ConsoleOutputContent = {
  type ConsoleOutput (line 20) | type ConsoleOutput = {
  type ConsoleProps (line 26) | type ConsoleProps = {
  function Console (line 31) | function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {

FILE: components/create-artifact.tsx
  type ArtifactActionContext (line 8) | type ArtifactActionContext<M = any> = {
  type ArtifactAction (line 18) | type ArtifactAction<M = any> = {
  type ArtifactToolbarContext (line 26) | type ArtifactToolbarContext = {
  type ArtifactToolbarItem (line 30) | type ArtifactToolbarItem = {
  type ArtifactContent (line 36) | type ArtifactContent<M = any> = {
  type InitializeParameters (line 52) | type InitializeParameters<M = any> = {
  type ArtifactConfig (line 57) | type ArtifactConfig<T extends string, M = any> = {
  class Artifact (line 71) | class Artifact<T extends string, M = any> {
    method constructor (line 84) | constructor(config: ArtifactConfig<T, M>) {

FILE: components/data-stream-handler.tsx
  function DataStreamHandler (line 11) | function DataStreamHandler() {

FILE: components/data-stream-provider.tsx
  type DataStreamContextValue (line 8) | type DataStreamContextValue = {
  function DataStreamProvider (line 17) | function DataStreamProvider({
  function useDataStream (line 35) | function useDataStream() {

FILE: components/diffview.tsx
  method toDOM (line 24) | toDOM(mark) {
  function computeDiff (line 45) | function computeDiff(oldDoc: ProsemirrorNode, newDoc: ProsemirrorNode) {
  type DiffEditorProps (line 49) | type DiffEditorProps = {

FILE: components/document-preview.tsx
  type DocumentPreviewProps (line 25) | type DocumentPreviewProps = {
  function DocumentPreview (line 31) | function DocumentPreview({

FILE: components/document.tsx
  type DocumentToolResultProps (line 25) | type DocumentToolResultProps = {
  function PureDocumentToolResult (line 31) | function PureDocumentToolResult({
  type DocumentToolCallProps (line 88) | type DocumentToolCallProps = {
  function PureDocumentToolCall (line 97) | function PureDocumentToolCall({

FILE: components/elements/actions.tsx
  type ActionsProps (line 13) | type ActionsProps = ComponentProps<"div">;
  type ActionProps (line 21) | type ActionProps = ComponentProps<typeof Button> & {

FILE: components/elements/branch.tsx
  type BranchContextType (line 10) | type BranchContextType = {
  type BranchProps (line 31) | type BranchProps = HTMLAttributes<HTMLDivElement> & {
  type BranchMessagesProps (line 81) | type BranchMessagesProps = HTMLAttributes<HTMLDivElement>;
  type BranchSelectorProps (line 111) | type BranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
  type BranchPreviousProps (line 139) | type BranchPreviousProps = ComponentProps<typeof Button>;
  type BranchNextProps (line 169) | type BranchNextProps = ComponentProps<typeof Button>;
  type BranchPageProps (line 199) | type BranchPageProps = HTMLAttributes<HTMLSpanElement>;

FILE: components/elements/conversation.tsx
  type ConversationProps (line 10) | type ConversationProps = ComponentProps<typeof StickToBottom>;
  type ConversationContentProps (line 25) | type ConversationContentProps = ComponentProps<
  type ConversationScrollButtonProps (line 36) | type ConversationScrollButtonProps = ComponentProps<typeof Button>;

FILE: components/elements/image.tsx
  type ImageProps (line 4) | type ImageProps = Experimental_GeneratedImage & {

FILE: components/elements/inline-citation.tsx
  type InlineCitationProps (line 26) | type InlineCitationProps = ComponentProps<"span">;
  type InlineCitationTextProps (line 38) | type InlineCitationTextProps = ComponentProps<"span">;
  type InlineCitationCardProps (line 50) | type InlineCitationCardProps = ComponentProps<typeof HoverCard>;
  type InlineCitationCardTriggerProps (line 56) | type InlineCitationCardTriggerProps = ComponentProps<typeof Badge> & {
  type InlineCitationCardBodyProps (line 83) | type InlineCitationCardBodyProps = ComponentProps<"div">;
  type InlineCitationCarouselProps (line 99) | type InlineCitationCarouselProps = ComponentProps<typeof Carousel>;
  type InlineCitationCarouselContentProps (line 117) | type InlineCitationCarouselContentProps = ComponentProps<"div">;
  type InlineCitationCarouselItemProps (line 123) | type InlineCitationCarouselItemProps = ComponentProps<"div">;
  type InlineCitationCarouselHeaderProps (line 135) | type InlineCitationCarouselHeaderProps = ComponentProps<"div">;
  type InlineCitationCarouselIndexProps (line 150) | type InlineCitationCarouselIndexProps = ComponentProps<"div">;
  type InlineCitationCarouselPrevProps (line 187) | type InlineCitationCarouselPrevProps = ComponentProps<"button">;
  type InlineCitationCarouselNextProps (line 214) | type InlineCitationCarouselNextProps = ComponentProps<"button">;
  type InlineCitationSourceProps (line 241) | type InlineCitationSourceProps = ComponentProps<"div"> & {
  type InlineCitationQuoteProps (line 271) | type InlineCitationQuoteProps = ComponentProps<"blockquote">;

FILE: components/elements/loader.tsx
  type LoaderIconProps (line 4) | type LoaderIconProps = {
  type LoaderProps (line 82) | type LoaderProps = HTMLAttributes<HTMLDivElement> & {

FILE: components/elements/message.tsx
  type MessageProps (line 6) | type MessageProps = HTMLAttributes<HTMLDivElement> & {
  type MessageContentProps (line 22) | type MessageContentProps = HTMLAttributes<HTMLDivElement>;
  type MessageAvatarProps (line 43) | type MessageAvatarProps = ComponentProps<typeof Avatar> & {

FILE: components/elements/prompt-input.tsx
  type PromptInputProps (line 22) | type PromptInputProps = HTMLAttributes<HTMLFormElement>;
  type PromptInputTextareaProps (line 34) | type PromptInputTextareaProps = ComponentProps<typeof Textarea> & {
  type PromptInputToolbarProps (line 99) | type PromptInputToolbarProps = HTMLAttributes<HTMLDivElement>;
  type PromptInputToolsProps (line 111) | type PromptInputToolsProps = HTMLAttributes<HTMLDivElement>;
  type PromptInputButtonProps (line 127) | type PromptInputButtonProps = ComponentProps<typeof Button>;
  type PromptInputSubmitProps (line 154) | type PromptInputSubmitProps = ComponentProps<typeof Button> & {
  type PromptInputModelSelectProps (line 189) | type PromptInputModelSelectProps = ComponentProps<typeof Select>;
  type PromptInputModelSelectTriggerProps (line 195) | type PromptInputModelSelectTriggerProps = ComponentProps<
  type PromptInputModelSelectContentProps (line 214) | type PromptInputModelSelectContentProps = ComponentProps<
  type PromptInputModelSelectItemProps (line 225) | type PromptInputModelSelectItemProps = ComponentProps<typeof SelectItem>;
  type PromptInputModelSelectValueProps (line 234) | type PromptInputModelSelectValueProps = ComponentProps<

FILE: components/elements/reasoning.tsx
  type ReasoningContextValue (line 15) | type ReasoningContextValue = {
  type ReasoningProps (line 32) | type ReasoningProps = ComponentProps<typeof Collapsible> & {
  constant AUTO_CLOSE_DELAY (line 40) | const AUTO_CLOSE_DELAY = 500;
  constant MS_IN_S (line 41) | const MS_IN_S = 1000;
  type ReasoningTriggerProps (line 113) | type ReasoningTriggerProps = ComponentProps<typeof CollapsibleTrigger>;
  type ReasoningContentProps (line 148) | type ReasoningContentProps = ComponentProps<

FILE: components/elements/response.tsx
  type ResponseProps (line 7) | type ResponseProps = ComponentProps<typeof Streamdown>;
  function Response (line 9) | function Response({ className, children, ...props }: ResponseProps) {

FILE: components/elements/source.tsx
  type SourcesProps (line 12) | type SourcesProps = ComponentProps<"div">;
  type SourcesTriggerProps (line 21) | type SourcesTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
  type SourcesContentProps (line 41) | type SourcesContentProps = ComponentProps<typeof CollapsibleContent>;
  type SourceProps (line 57) | type SourceProps = ComponentProps<"a">;

FILE: components/elements/suggestion.tsx
  type SuggestionsProps (line 8) | type SuggestionsProps = ComponentProps<typeof ScrollArea>;
  type SuggestionProps (line 23) | type SuggestionProps = Omit<ComponentProps<typeof Button>, "onClick"> & {

FILE: components/elements/task.tsx
  type TaskItemFileProps (line 12) | type TaskItemFileProps = ComponentProps<"div">;
  type TaskItemProps (line 30) | type TaskItemProps = ComponentProps<"div">;
  type TaskProps (line 38) | type TaskProps = ComponentProps<typeof Collapsible>;
  type TaskTriggerProps (line 55) | type TaskTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
  type TaskContentProps (line 76) | type TaskContentProps = ComponentProps<typeof CollapsibleContent>;

FILE: components/elements/tool.tsx
  type ToolProps (line 21) | type ToolProps = ComponentProps<typeof Collapsible>;
  type ToolHeaderProps (line 30) | type ToolHeaderProps = {
  type ToolContentProps (line 92) | type ToolContentProps = ComponentProps<typeof CollapsibleContent>;
  type ToolInputProps (line 104) | type ToolInputProps = ComponentProps<"div"> & {
  type ToolOutputProps (line 119) | type ToolOutputProps = ComponentProps<"div"> & {

FILE: components/elements/web-preview.tsx
  type WebPreviewContextValue (line 21) | type WebPreviewContextValue = {
  type WebPreviewProps (line 38) | type WebPreviewProps = ComponentProps<"div"> & {
  type WebPreviewNavigationProps (line 80) | type WebPreviewNavigationProps = ComponentProps<"div">;
  type WebPreviewNavigationButtonProps (line 95) | type WebPreviewNavigationButtonProps = ComponentProps<typeof Button> & {
  type WebPreviewUrlProps (line 127) | type WebPreviewUrlProps = ComponentProps<typeof Input>;
  type WebPreviewBodyProps (line 157) | type WebPreviewBodyProps = ComponentProps<"iframe"> & {
  type WebPreviewConsoleProps (line 183) | type WebPreviewConsoleProps = ComponentProps<"div"> & {

FILE: components/image-editor.tsx
  type ImageEditorProps (line 4) | type ImageEditorProps = {
  function ImageEditor (line 13) | function ImageEditor({

FILE: components/message-actions.tsx
  function PureMessageActions (line 11) | function PureMessageActions({

FILE: components/message-editor.tsx
  type MessageEditorProps (line 18) | type MessageEditorProps = {
  function MessageEditor (line 25) | function MessageEditor({

FILE: components/message-reasoning.tsx
  type MessageReasoningProps (line 10) | type MessageReasoningProps = {
  function MessageReasoning (line 15) | function MessageReasoning({

FILE: components/messages.tsx
  type MessagesProps (line 10) | type MessagesProps = {
  function PureMessages (line 23) | function PureMessages({

FILE: components/multimodal-input.tsx
  function setCookie (line 50) | function setCookie(name: string, value: string) {
  function PureMultimodalInput (line 56) | function PureMultimodalInput({
  function PureAttachmentsButton (line 434) | function PureAttachmentsButton({
  function PureModelSelectorCompact (line 464) | function PureModelSelectorCompact({
  function PureStopButton (line 536) | function PureStopButton({

FILE: components/sheet-editor.tsx
  type SheetEditorProps (line 11) | type SheetEditorProps = {
  constant MIN_ROWS (line 19) | const MIN_ROWS = 50;
  constant MIN_COLS (line 20) | const MIN_COLS = 26;
  function areEqual (line 130) | function areEqual(prevProps: SheetEditorProps, nextProps: SheetEditorPro...

FILE: components/sidebar-history.tsx
  type GroupedChats (line 31) | type GroupedChats = {
  type ChatHistory (line 39) | type ChatHistory = {
  constant PAGE_SIZE (line 44) | const PAGE_SIZE = 20;
  function getChatHistoryPaginationKey (line 79) | function getChatHistoryPaginationKey(
  function SidebarHistory (line 100) | function SidebarHistory({ user }: { user: User | undefined }) {

FILE: components/sidebar-toggle.tsx
  function SidebarToggle (line 13) | function SidebarToggle({

FILE: components/sidebar-user-nav.tsx
  function SidebarUserNav (line 25) | function SidebarUserNav({ user }: { user: User }) {

FILE: components/submit-button.tsx
  function SubmitButton (line 9) | function SubmitButton({

FILE: components/suggested-actions.tsx
  type SuggestedActionsProps (line 10) | type SuggestedActionsProps = {
  function PureSuggestedActions (line 16) | function PureSuggestedActions({ chatId, sendMessage }: SuggestedActionsP...

FILE: components/text-editor.tsx
  type EditorProps (line 26) | type EditorProps = {
  function PureEditor (line 35) | function PureEditor({
  function areEqual (line 153) | function areEqual(prevProps: EditorProps, nextProps: EditorProps) {

FILE: components/theme-provider.tsx
  function ThemeProvider (line 6) | function ThemeProvider({ children, ...props }: ThemeProviderProps) {

FILE: components/toast.tsx
  function toast (line 13) | function toast(props: Omit<ToastProps, "id">) {
  function Toast (line 19) | function Toast(props: ToastProps) {
  type ToastProps (line 71) | type ToastProps = {

FILE: components/toolbar.tsx
  type ToolProps (line 32) | type ToolProps = {

FILE: components/ui/badge.tsx
  type BadgeProps (line 26) | interface BadgeProps
  function Badge (line 30) | function Badge({ className, variant, ...props }: BadgeProps) {

FILE: components/ui/button-group.tsx
  function ButtonGroup (line 24) | function ButtonGroup({
  function ButtonGroupText (line 40) | function ButtonGroupText({
  function ButtonGroupSeparator (line 60) | function ButtonGroupSeparator({

FILE: components/ui/button.tsx
  type ButtonProps (line 37) | interface ButtonProps

FILE: components/ui/carousel.tsx
  type CarouselApi (line 12) | type CarouselApi = UseEmblaCarouselType[1]
  type UseCarouselParameters (line 13) | type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
  type CarouselOptions (line 14) | type CarouselOptions = UseCarouselParameters[0]
  type CarouselPlugin (line 15) | type CarouselPlugin = UseCarouselParameters[1]
  type CarouselProps (line 17) | type CarouselProps = {
  type CarouselContextProps (line 24) | type CarouselContextProps = {
  function useCarousel (line 35) | function useCarousel() {

FILE: components/ui/input-group.tsx
  function InputGroup (line 11) | function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
  function InputGroupAddon (line 60) | function InputGroupAddon({
  function InputGroupButton (line 100) | function InputGroupButton({
  function InputGroupText (line 119) | function InputGroupText({ className, ...props }: React.ComponentProps<"s...
  function InputGroupInput (line 131) | function InputGroupInput({
  function InputGroupTextarea (line 147) | function InputGroupTextarea({

FILE: components/ui/sheet.tsx
  type SheetContentProps (line 52) | interface SheetContentProps

FILE: components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_NAME (line 27) | const SIDEBAR_COOKIE_NAME = "sidebar_state";
  constant SIDEBAR_COOKIE_MAX_AGE (line 28) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
  constant SIDEBAR_WIDTH (line 29) | const SIDEBAR_WIDTH = "16rem";
  constant SIDEBAR_WIDTH_MOBILE (line 30) | const SIDEBAR_WIDTH_MOBILE = "18rem";
  constant SIDEBAR_WIDTH_ICON (line 31) | const SIDEBAR_WIDTH_ICON = "3rem";
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 32) | const SIDEBAR_KEYBOARD_SHORTCUT = "b";
  type SidebarContextProps (line 34) | type SidebarContextProps = {
  function useSidebar (line 46) | function useSidebar() {

FILE: components/ui/skeleton.tsx
  function Skeleton (line 3) | function Skeleton({

FILE: components/version-footer.tsx
  type VersionFooterProps (line 14) | type VersionFooterProps = {

FILE: components/visibility-selector.tsx
  type VisibilityType (line 20) | type VisibilityType = "private" | "public";
  function VisibilitySelector (line 42) | function VisibilitySelector({

FILE: components/weather.tsx
  type WeatherAtLocation (line 83) | type WeatherAtLocation = {
  constant SAMPLE (line 122) | const SAMPLE = {
  function n (line 277) | function n(num: number): number {
  function Weather (line 281) | function Weather({

FILE: hooks/use-artifact.ts
  type Selector (line 22) | type Selector<T> = (state: UIArtifact) => T;
  function useArtifactSelector (line 24) | function useArtifactSelector<Selected>(selector: Selector<Selected>) {
  function useArtifact (line 39) | function useArtifact() {

FILE: hooks/use-auto-resume.ts
  type UseAutoResumeParams (line 8) | type UseAutoResumeParams = {
  function useAutoResume (line 15) | function useAutoResume({

FILE: hooks/use-chat-visibility.ts
  function useChatVisibility (line 13) | function useChatVisibility({

FILE: hooks/use-messages.tsx
  function useMessages (line 6) | function useMessages({

FILE: hooks/use-mobile.ts
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768
  function useIsMobile (line 5) | function useIsMobile() {

FILE: hooks/use-scroll-to-bottom.tsx
  function useScrollToBottom (line 3) | function useScrollToBottom() {

FILE: instrumentation.ts
  function register (line 3) | function register() {

FILE: lib/ai/entitlements.ts
  type Entitlements (line 3) | type Entitlements = {

FILE: lib/ai/models.mock.ts
  function getResponseForPrompt (line 14) | function getResponseForPrompt(prompt: unknown): string {
  method start (line 50) | async start(controller) {
  method start (line 94) | async start(controller) {
  method start (line 142) | start(controller) {

FILE: lib/ai/models.ts
  constant DEFAULT_CHAT_MODEL (line 2) | const DEFAULT_CHAT_MODEL = "openai/gpt-4.1-mini";
  type ChatModel (line 4) | type ChatModel = {

FILE: lib/ai/prompts.ts
  type RequestHints (line 44) | type RequestHints = {

FILE: lib/ai/providers.ts
  constant THINKING_SUFFIX_REGEX (line 9) | const THINKING_SUFFIX_REGEX = /-thinking$/;
  function getLanguageModel (line 30) | function getLanguageModel(modelId: string) {
  function getTitleModel (line 51) | function getTitleModel() {
  function getArtifactModel (line 58) | function getArtifactModel() {

FILE: lib/ai/tools/create-document.ts
  type CreateDocumentProps (line 11) | type CreateDocumentProps = {

FILE: lib/ai/tools/get-weather.ts
  function geocodeCity (line 4) | async function geocodeCity(

FILE: lib/ai/tools/request-suggestions.ts
  type RequestSuggestionsProps (line 10) | type RequestSuggestionsProps = {

FILE: lib/ai/tools/update-document.ts
  type UpdateDocumentProps (line 8) | type UpdateDocumentProps = {

FILE: lib/artifacts/server.ts
  type SaveDocumentProps (line 11) | type SaveDocumentProps = {
  type CreateDocumentCallbackProps (line 19) | type CreateDocumentCallbackProps = {
  type UpdateDocumentCallbackProps (line 26) | type UpdateDocumentCallbackProps = {
  type DocumentHandler (line 33) | type DocumentHandler<T = ArtifactKind> = {
  function createDocumentHandler (line 39) | function createDocumentHandler<T extends ArtifactKind>(config: {

FILE: lib/constants.ts
  constant DUMMY_PASSWORD (line 13) | const DUMMY_PASSWORD = generateDummyPassword();

FILE: lib/db/migrations/0000_keen_devos.sql
  type "Chat" (line 1) | CREATE TABLE IF NOT EXISTS "Chat" (
  type "User" (line 8) | CREATE TABLE IF NOT EXISTS "User" (

FILE: lib/db/migrations/0001_sparkling_blue_marvel.sql
  type "Suggestion" (line 1) | CREATE TABLE IF NOT EXISTS "Suggestion" (
  type "Document" (line 14) | CREATE TABLE IF NOT EXISTS "Document" (

FILE: lib/db/migrations/0002_wandering_riptide.sql
  type "Message" (line 1) | CREATE TABLE IF NOT EXISTS "Message" (
  type "Vote" (line 9) | CREATE TABLE IF NOT EXISTS "Vote" (

FILE: lib/db/migrations/0005_wooden_whistler.sql
  type "Message_v2" (line 1) | CREATE TABLE IF NOT EXISTS "Message_v2" (
  type "Vote_v2" (line 10) | CREATE TABLE IF NOT EXISTS "Vote_v2" (

FILE: lib/db/migrations/0006_marvelous_frog_thor.sql
  type "Stream" (line 1) | CREATE TABLE IF NOT EXISTS "Stream" (

FILE: lib/db/queries.ts
  function getUser (line 44) | async function getUser(email: string): Promise<User[]> {
  function createUser (line 55) | async function createUser(email: string, password: string) {
  function createGuestUser (line 65) | async function createGuestUser() {
  function saveChat (line 82) | async function saveChat({
  function deleteChatById (line 106) | async function deleteChatById({ id }: { id: string }) {
  function deleteAllChatsByUserId (line 125) | async function deleteAllChatsByUserId({ userId }: { userId: string }) {
  function getChatsByUserId (line 156) | async function getChatsByUserId({
  function getChatById (line 232) | async function getChatById({ id }: { id: string }) {
  function saveMessages (line 245) | async function saveMessages({ messages }: { messages: DBMessage[] }) {
  function updateMessage (line 253) | async function updateMessage({
  function getMessagesByChatId (line 267) | async function getMessagesByChatId({ id }: { id: string }) {
  function voteMessage (line 282) | async function voteMessage({
  function getVotesByChatId (line 313) | async function getVotesByChatId({ id }: { id: string }) {
  function saveDocument (line 324) | async function saveDocument({
  function getDocumentsById (line 354) | async function getDocumentsById({ id }: { id: string }) {
  function getDocumentById (line 371) | async function getDocumentById({ id }: { id: string }) {
  function deleteDocumentsByIdAfterTimestamp (line 388) | async function deleteDocumentsByIdAfterTimestamp({
  function saveSuggestions (line 417) | async function saveSuggestions({
  function getSuggestionsByDocumentId (line 432) | async function getSuggestionsByDocumentId({
  function getMessageById (line 450) | async function getMessageById({ id }: { id: string }) {
  function deleteMessagesByChatIdAfterTimestamp (line 461) | async function deleteMessagesByChatIdAfterTimestamp({
  function updateChatVisibilityById (line 501) | async function updateChatVisibilityById({
  function updateChatTitleById (line 518) | async function updateChatTitleById({
  function getMessageCountByUserId (line 533) | async function getMessageCountByUserId({
  function createStreamId (line 567) | async function createStreamId({
  function getStreamIdsByChatId (line 586) | async function getStreamIdsByChatId({ chatId }: { chatId: string }) {

FILE: lib/db/schema.ts
  type User (line 20) | type User = InferSelectModel<typeof user>;
  type Chat (line 34) | type Chat = InferSelectModel<typeof chat>;
  type MessageDeprecated (line 48) | type MessageDeprecated = InferSelectModel<typeof messageDeprecated>;
  type DBMessage (line 61) | type DBMessage = InferSelectModel<typeof message>;
  type VoteDeprecated (line 83) | type VoteDeprecated = InferSelectModel<typeof voteDeprecated>;
  type Vote (line 103) | type Vote = InferSelectModel<typeof vote>;
  type Document (line 126) | type Document = InferSelectModel<typeof document>;
  type Suggestion (line 152) | type Suggestion = InferSelectModel<typeof suggestion>;
  type Stream (line 170) | type Stream = InferSelectModel<typeof stream>;

FILE: lib/db/utils.ts
  function generateHashedPassword (line 4) | function generateHashedPassword(password: string) {
  function generateDummyPassword (line 11) | function generateDummyPassword() {

FILE: lib/editor/config.ts
  function headingRule (line 16) | function headingRule(level: number) {

FILE: lib/editor/diff.js
  function mapDocumentNode (line 446) | function mapDocumentNode(node, mapper) {

FILE: lib/editor/react-renderer.tsx
  class ReactRenderer (line 4) | class ReactRenderer {
    method render (line 5) | static render(component: React.ReactElement, dom: HTMLElement) {

FILE: lib/editor/suggestions.tsx
  type UISuggestion (line 13) | interface UISuggestion extends Suggestion {
  type Position (line 18) | type Position = {
  function findPositionsInDoc (line 23) | function findPositionsInDoc(doc: Node, searchText: string): Position | n...
  function projectWithPositions (line 46) | function projectWithPositions(
  function createSuggestionWidget (line 69) | function createSuggestionWidget(
  method init (line 138) | init() {
  method apply (line 141) | apply(tr, state) {
  method decorations (line 154) | decorations(state) {

FILE: lib/errors.ts
  type ErrorType (line 1) | type ErrorType =
  type Surface (line 9) | type Surface =
  type ErrorCode (line 21) | type ErrorCode = `${ErrorType}:${Surface}`;
  type ErrorVisibility (line 23) | type ErrorVisibility = "response" | "log" | "none";
  class ChatbotError (line 38) | class ChatbotError extends Error {
    method constructor (line 43) | constructor(errorCode: ErrorCode, cause?: string) {
    method toResponse (line 55) | toResponse() {
  function getMessageByErrorCode (line 78) | function getMessageByErrorCode(errorCode: ErrorCode): string {
  function getStatusCodeByType (line 120) | function getStatusCodeByType(type: ErrorType) {

FILE: lib/ratelimit.ts
  constant MAX_MESSAGES (line 6) | const MAX_MESSAGES = 10;
  constant TTL_SECONDS (line 7) | const TTL_SECONDS = 60 * 60;
  function getClient (line 11) | function getClient() {
  function checkIpRateLimit (line 22) | async function checkIpRateLimit(ip: string | undefined) {

FILE: lib/types.ts
  type DataPart (line 10) | type DataPart = { type: "append-message"; message: string };
  type MessageMetadata (line 16) | type MessageMetadata = z.infer<typeof messageMetadataSchema>;
  type weatherTool (line 18) | type weatherTool = InferUITool<typeof getWeather>;
  type createDocumentTool (line 19) | type createDocumentTool = InferUITool<ReturnType<typeof createDocument>>;
  type updateDocumentTool (line 20) | type updateDocumentTool = InferUITool<ReturnType<typeof updateDocument>>;
  type requestSuggestionsTool (line 21) | type requestSuggestionsTool = InferUITool<
  type ChatTools (line 25) | type ChatTools = {
  type CustomUIDataTypes (line 32) | type CustomUIDataTypes = {
  type ChatMessage (line 47) | type ChatMessage = UIMessage<
  type Attachment (line 53) | type Attachment = {

FILE: lib/utils.ts
  function cn (line 14) | function cn(...inputs: ClassValue[]) {
  function fetchWithErrorHandlers (line 29) | async function fetchWithErrorHandlers(
  function getLocalStorage (line 51) | function getLocalStorage(key: string) {
  function generateUUID (line 58) | function generateUUID(): string {
  type ResponseMessageWithoutId (line 66) | type ResponseMessageWithoutId = ToolModelMessage | AssistantModelMessage;
  type ResponseMessage (line 67) | type ResponseMessage = ResponseMessageWithoutId & { id: string };
  function getMostRecentUserMessage (line 69) | function getMostRecentUserMessage(messages: UIMessage[]) {
  function getDocumentTimestampByIndex (line 74) | function getDocumentTimestampByIndex(
  function getTrailingMessageId (line 84) | function getTrailingMessageId({
  function sanitizeText (line 96) | function sanitizeText(text: string) {
  function convertToUIMessages (line 100) | function convertToUIMessages(messages: DBMessage[]): ChatMessage[] {
  function getTextFromMessage (line 111) | function getTextFromMessage(message: ChatMessage | UIMessage): string {

FILE: playwright.config.ts
  constant PORT (line 14) | const PORT = process.env.PORT || 3000;

FILE: proxy.ts
  function proxy (line 5) | async function proxy(request: NextRequest) {

FILE: tests/e2e/api.test.ts
  constant CHAT_URL_REGEX (line 3) | const CHAT_URL_REGEX = /\/chat\/[\w-]+/;
  constant ERROR_TEXT_REGEX (line 4) | const ERROR_TEXT_REGEX = /error|failed|trouble/i;

FILE: tests/e2e/model-selector.test.ts
  constant MODEL_BUTTON_REGEX (line 3) | const MODEL_BUTTON_REGEX = /Gemini|Claude|GPT|Grok/i;

FILE: tests/fixtures.ts
  type Fixtures (line 4) | type Fixtures = {

FILE: tests/helpers.ts
  function generateRandomTestUser (line 4) | function generateRandomTestUser() {
  function generateTestMessage (line 14) | function generateTestMessage() {

FILE: tests/pages/chat.ts
  constant MODEL_BUTTON_REGEX (line 3) | const MODEL_BUTTON_REGEX = /Gemini|Claude|GPT|Grok/i;
  class ChatPage (line 5) | class ChatPage {
    method constructor (line 8) | constructor(page: Page) {
    method goto (line 12) | async goto() {
    method createNewChat (line 16) | async createNewChat() {
    method getInput (line 21) | getInput() {
    method typeMessage (line 25) | async typeMessage(message: string) {
    method sendMessage (line 30) | async sendMessage() {
    method sendUserMessage (line 34) | async sendUserMessage(message: string) {
    method getSendButton (line 39) | getSendButton() {
    method getStopButton (line 43) | getStopButton() {
    method clickSuggestedAction (line 47) | async clickSuggestedAction(index = 0) {
    method openModelSelector (line 54) | async openModelSelector() {
    method selectModel (line 62) | async selectModel(modelName: string) {
    method searchModels (line 67) | async searchModels(query: string) {

FILE: tests/prompts/utils.ts
  function getResponseChunksByPrompt (line 8) | function getResponseChunksByPrompt(
Condensed preview — 225 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,392K chars).
[
  {
    "path": ".changeset/config.json",
    "chars": 323,
    "preview": "{\n  \"$schema\": \"https://unpkg.com/@changesets/config@3.1.1/schema.json\",\n  \"changelog\": [\n    \"@changesets/changelog-git"
  },
  {
    "path": ".cursor/rules/ultracite.mdc",
    "chars": 16820,
    "preview": "---\ndescription: Ultracite Rules - AI-Ready Formatter and Linter\nglobs: \"**/*.{ts,tsx,js,jsx}\"\nalwaysApply: true\n---\n\n# "
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 1554,
    "preview": "# Contributing\n\nThanks for your interest in contributing to the chatbot template! Here's how to get started.\n\n## Develop"
  },
  {
    "path": ".github/workflows/lint.yml",
    "chars": 554,
    "preview": "name: Lint\non:\n  push:\n\njobs:\n  build:\n    runs-on: ubuntu-22.04\n    strategy:\n      matrix:\n        node-version: [20]\n"
  },
  {
    "path": ".github/workflows/playwright.yml",
    "chars": 1938,
    "preview": "name: Playwright Tests\non:\n  push:\n    branches: [main, master]\n  pull_request:\n    branches: [main, master]\n\njobs:\n  te"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1594,
    "preview": "name: Release\n\non:\n  push:\n    branches:\n      - main\n\nconcurrency: ${{ github.workflow }}-${{ github.ref }}\n\npermission"
  },
  {
    "path": ".gitignore",
    "chars": 485,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n.pnp\n"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 43,
    "preview": "{\n  \"recommendations\": [\"biomejs.biome\"]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 1306,
    "preview": "{\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"[javascript]\": {\n    \"editor.defaultFormatter\": \"biomejs.bio"
  },
  {
    "path": "LICENSE",
    "chars": 552,
    "preview": "Copyright 2024 Vercel, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file e"
  },
  {
    "path": "README.md",
    "chars": 3527,
    "preview": "<a href=\"https://chat.vercel.ai/\">\n  <img alt=\"Chatbot\" src=\"app/(chat)/opengraph-image.png\">\n  <h1 align=\"center\">Chatb"
  },
  {
    "path": "app/(auth)/actions.ts",
    "chars": 1860,
    "preview": "\"use server\";\n\nimport { z } from \"zod\";\n\nimport { createUser, getUser } from \"@/lib/db/queries\";\n\nimport { signIn } from"
  },
  {
    "path": "app/(auth)/api/auth/[...nextauth]/route.ts",
    "chars": 47,
    "preview": "export { GET, POST } from \"@/app/(auth)/auth\";\n"
  },
  {
    "path": "app/(auth)/api/auth/guest/route.ts",
    "chars": 643,
    "preview": "import { NextResponse } from \"next/server\";\nimport { getToken } from \"next-auth/jwt\";\nimport { signIn } from \"@/app/(aut"
  },
  {
    "path": "app/(auth)/auth.config.ts",
    "chars": 356,
    "preview": "import type { NextAuthConfig } from \"next-auth\";\n\nexport const authConfig = {\n  pages: {\n    signIn: \"/login\",\n    newUs"
  },
  {
    "path": "app/(auth)/auth.ts",
    "chars": 2047,
    "preview": "import { compare } from \"bcrypt-ts\";\nimport NextAuth, { type DefaultSession } from \"next-auth\";\nimport type { DefaultJWT"
  },
  {
    "path": "app/(auth)/login/page.tsx",
    "chars": 2528,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport { useSession } from \"ne"
  },
  {
    "path": "app/(auth)/register/page.tsx",
    "chars": 2729,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport { useSession } from \"ne"
  },
  {
    "path": "app/(chat)/actions.ts",
    "chars": 1343,
    "preview": "\"use server\";\n\nimport { generateText, type UIMessage } from \"ai\";\nimport { cookies } from \"next/headers\";\nimport type { "
  },
  {
    "path": "app/(chat)/api/chat/[id]/stream/route.ts",
    "chars": 72,
    "preview": "export function GET() {\n  return new Response(null, { status: 204 });\n}\n"
  },
  {
    "path": "app/(chat)/api/chat/route.ts",
    "chars": 9616,
    "preview": "import { geolocation, ipAddress } from \"@vercel/functions\";\nimport {\n  convertToModelMessages,\n  createUIMessageStream,\n"
  },
  {
    "path": "app/(chat)/api/chat/schema.ts",
    "chars": 1064,
    "preview": "import { z } from \"zod\";\n\nconst textPartSchema = z.object({\n  type: z.enum([\"text\"]),\n  text: z.string().min(1).max(2000"
  },
  {
    "path": "app/(chat)/api/document/route.ts",
    "chars": 2871,
    "preview": "import { auth } from \"@/app/(auth)/auth\";\nimport type { ArtifactKind } from \"@/components/artifact\";\nimport {\n  deleteDo"
  },
  {
    "path": "app/(chat)/api/files/upload/route.ts",
    "chars": 1950,
    "preview": "import { put } from \"@vercel/blob\";\nimport { NextResponse } from \"next/server\";\nimport { z } from \"zod\";\n\nimport { auth "
  },
  {
    "path": "app/(chat)/api/history/route.ts",
    "chars": 1252,
    "preview": "import type { NextRequest } from \"next/server\";\nimport { auth } from \"@/app/(auth)/auth\";\nimport { deleteAllChatsByUserI"
  },
  {
    "path": "app/(chat)/api/suggestions/route.ts",
    "chars": 931,
    "preview": "import { auth } from \"@/app/(auth)/auth\";\nimport { getSuggestionsByDocumentId } from \"@/lib/db/queries\";\nimport { Chatbo"
  },
  {
    "path": "app/(chat)/api/vote/route.ts",
    "chars": 1767,
    "preview": "import { auth } from \"@/app/(auth)/auth\";\nimport { getChatById, getVotesByChatId, voteMessage } from \"@/lib/db/queries\";"
  },
  {
    "path": "app/(chat)/chat/[id]/page.tsx",
    "chars": 2042,
    "preview": "import { cookies } from \"next/headers\";\nimport { notFound, redirect } from \"next/navigation\";\nimport { Suspense } from \""
  },
  {
    "path": "app/(chat)/layout.tsx",
    "chars": 1184,
    "preview": "import { cookies } from \"next/headers\";\nimport Script from \"next/script\";\nimport { Suspense } from \"react\";\nimport { App"
  },
  {
    "path": "app/(chat)/page.tsx",
    "chars": 1223,
    "preview": "import { cookies } from \"next/headers\";\nimport { Suspense } from \"react\";\nimport { Chat } from \"@/components/chat\";\nimpo"
  },
  {
    "path": "app/globals.css",
    "chars": 7981,
    "preview": "@import \"tailwindcss\";\n@import \"katex/dist/katex.min.css\";\n\n/* include utility classes in streamdown */\n@source \"../node"
  },
  {
    "path": "app/layout.tsx",
    "chars": 2587,
    "preview": "import type { Metadata } from \"next\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\nimport { Toaster } from \"son"
  },
  {
    "path": "artifacts/actions.ts",
    "chars": 260,
    "preview": "\"use server\";\n\nimport { getSuggestionsByDocumentId } from \"@/lib/db/queries\";\n\nexport async function getSuggestions({ do"
  },
  {
    "path": "artifacts/code/client.tsx",
    "chars": 7379,
    "preview": "import { toast } from \"sonner\";\nimport { CodeEditor } from \"@/components/code-editor\";\nimport {\n  Console,\n  type Consol"
  },
  {
    "path": "artifacts/code/server.ts",
    "chars": 1782,
    "preview": "import { streamObject } from \"ai\";\nimport { z } from \"zod\";\nimport { codePrompt, updateDocumentPrompt } from \"@/lib/ai/p"
  },
  {
    "path": "artifacts/image/client.tsx",
    "chars": 2078,
    "preview": "import { toast } from \"sonner\";\nimport { Artifact } from \"@/components/create-artifact\";\nimport { CopyIcon, RedoIcon, Un"
  },
  {
    "path": "artifacts/sheet/client.tsx",
    "chars": 2936,
    "preview": "import { parse, unparse } from \"papaparse\";\nimport { toast } from \"sonner\";\nimport { Artifact } from \"@/components/creat"
  },
  {
    "path": "artifacts/sheet/server.ts",
    "chars": 1901,
    "preview": "import { streamObject } from \"ai\";\nimport { z } from \"zod\";\nimport { sheetPrompt, updateDocumentPrompt } from \"@/lib/ai/"
  },
  {
    "path": "artifacts/text/client.tsx",
    "chars": 4705,
    "preview": "import { toast } from \"sonner\";\nimport { Artifact } from \"@/components/create-artifact\";\nimport { DiffView } from \"@/com"
  },
  {
    "path": "artifacts/text/server.ts",
    "chars": 1861,
    "preview": "import { smoothStream, streamText } from \"ai\";\nimport { updateDocumentPrompt } from \"@/lib/ai/prompts\";\nimport { getArti"
  },
  {
    "path": "biome.jsonc",
    "chars": 1321,
    "preview": "{\n  \"$schema\": \"./node_modules/@biomejs/biome/configuration_schema.json\",\n  \"extends\": [\n    \"ultracite/biome/core\",\n   "
  },
  {
    "path": "components/ai-elements/artifact.tsx",
    "chars": 3331,
    "preview": "\"use client\";\n\nimport { type LucideIcon, XIcon } from \"lucide-react\";\nimport type { ComponentProps, HTMLAttributes } fro"
  },
  {
    "path": "components/ai-elements/canvas.tsx",
    "chars": 547,
    "preview": "import { Background, ReactFlow, type ReactFlowProps } from \"@xyflow/react\";\nimport type { ReactNode } from \"react\";\nimpo"
  },
  {
    "path": "components/ai-elements/chain-of-thought.tsx",
    "chars": 6309,
    "preview": "\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport {\n  BrainIcon,\n  Ch"
  },
  {
    "path": "components/ai-elements/checkpoint.tsx",
    "chars": 1609,
    "preview": "\"use client\";\n\nimport { BookmarkIcon, type LucideProps } from \"lucide-react\";\nimport type { ComponentProps, HTMLAttribut"
  },
  {
    "path": "components/ai-elements/confirmation.tsx",
    "chars": 3776,
    "preview": "\"use client\";\n\nimport type { ToolUIPart } from \"ai\";\nimport {\n  type ComponentProps,\n  createContext,\n  type ReactNode,\n"
  },
  {
    "path": "components/ai-elements/connection.tsx",
    "chars": 570,
    "preview": "import type { ConnectionLineComponent } from \"@xyflow/react\";\n\nconst HALF = 0.5;\n\nexport const Connection: ConnectionLin"
  },
  {
    "path": "components/ai-elements/controls.tsx",
    "chars": 573,
    "preview": "\"use client\";\n\nimport { Controls as ControlsPrimitive } from \"@xyflow/react\";\nimport type { ComponentProps } from \"react"
  },
  {
    "path": "components/ai-elements/conversation.tsx",
    "chars": 2499,
    "preview": "\"use client\";\n\nimport { ArrowDownIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\nimport { useCa"
  },
  {
    "path": "components/ai-elements/edge.tsx",
    "chars": 3167,
    "preview": "import {\n  BaseEdge,\n  type EdgeProps,\n  getBezierPath,\n  getSimpleBezierPath,\n  type InternalNode,\n  type Node,\n  Posit"
  },
  {
    "path": "components/ai-elements/image.tsx",
    "chars": 565,
    "preview": "import type { Experimental_GeneratedImage } from \"ai\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ImageProps = Exper"
  },
  {
    "path": "components/ai-elements/inline-citation.tsx",
    "chars": 6503,
    "preview": "\"use client\";\n\nimport { ArrowLeftIcon, ArrowRightIcon } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createCon"
  },
  {
    "path": "components/ai-elements/loader.tsx",
    "chars": 2201,
    "preview": "import type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype LoaderIconProps = {\n  size?: number"
  },
  {
    "path": "components/ai-elements/message.tsx",
    "chars": 10882,
    "preview": "\"use client\";\n\nimport type { FileUIPart, UIMessage } from \"ai\";\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  Paperc"
  },
  {
    "path": "components/ai-elements/model-selector.tsx",
    "chars": 4807,
    "preview": "import Image from \"next/image\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport {\n  Command,\n  CommandDia"
  },
  {
    "path": "components/ai-elements/node.tsx",
    "chars": 1976,
    "preview": "import { Handle, Position } from \"@xyflow/react\";\nimport type { ComponentProps } from \"react\";\nimport {\n  Card,\n  CardAc"
  },
  {
    "path": "components/ai-elements/open-in-chat.tsx",
    "chars": 16493,
    "preview": "\"use client\";\n\nimport {\n  ChevronDownIcon,\n  ExternalLinkIcon,\n  MessageCircleIcon,\n} from \"lucide-react\";\nimport { type"
  },
  {
    "path": "components/ai-elements/panel.tsx",
    "chars": 401,
    "preview": "import { Panel as PanelPrimitive } from \"@xyflow/react\";\nimport type { ComponentProps } from \"react\";\nimport { cn } from"
  },
  {
    "path": "components/ai-elements/plan.tsx",
    "chars": 3436,
    "preview": "\"use client\";\n\nimport { ChevronsUpDownIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\nimport { "
  },
  {
    "path": "components/ai-elements/prompt-input.tsx",
    "chars": 38013,
    "preview": "\"use client\";\n\nimport type { ChatStatus, FileUIPart } from \"ai\";\nimport {\n  CornerDownLeftIcon,\n  ImageIcon,\n  Loader2Ic"
  },
  {
    "path": "components/ai-elements/queue.tsx",
    "chars": 6327,
    "preview": "\"use client\";\n\nimport { ChevronDownIcon, PaperclipIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react"
  },
  {
    "path": "components/ai-elements/reasoning.tsx",
    "chars": 5473,
    "preview": "\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { BrainIcon, Chevro"
  },
  {
    "path": "components/ai-elements/shimmer.tsx",
    "chars": 1548,
    "preview": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport {\n  type CSSProperties,\n  type ElementType,\n  type JSX,\n  m"
  },
  {
    "path": "components/ai-elements/sources.tsx",
    "chars": 1858,
    "preview": "\"use client\";\n\nimport { BookIcon, ChevronDownIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\nim"
  },
  {
    "path": "components/ai-elements/suggestion.tsx",
    "chars": 1264,
    "preview": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Sc"
  },
  {
    "path": "components/ai-elements/task.tsx",
    "chars": 2345,
    "preview": "\"use client\";\n\nimport { ChevronDownIcon, SearchIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\n"
  },
  {
    "path": "components/ai-elements/tool.tsx",
    "chars": 4872,
    "preview": "\"use client\";\n\nimport type { ToolUIPart } from \"ai\";\nimport {\n  CheckCircleIcon,\n  ChevronDownIcon,\n  CircleIcon,\n  Cloc"
  },
  {
    "path": "components/ai-elements/toolbar.tsx",
    "chars": 442,
    "preview": "import { NodeToolbar, Position } from \"@xyflow/react\";\nimport type { ComponentProps } from \"react\";\nimport { cn } from \""
  },
  {
    "path": "components/ai-elements/web-preview.tsx",
    "chars": 6702,
    "preview": "\"use client\";\n\nimport { ChevronDownIcon } from \"lucide-react\";\nimport type { ComponentProps, ReactNode } from \"react\";\ni"
  },
  {
    "path": "components/app-sidebar.tsx",
    "chars": 4784,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport type { User } from \"nex"
  },
  {
    "path": "components/artifact-actions.tsx",
    "chars": 3038,
    "preview": "import { type Dispatch, memo, type SetStateAction, useState } from \"react\";\nimport { toast } from \"sonner\";\nimport { cn "
  },
  {
    "path": "components/artifact-close-button.tsx",
    "chars": 826,
    "preview": "import { memo } from \"react\";\nimport { initialArtifactData, useArtifact } from \"@/hooks/use-artifact\";\nimport { CrossIco"
  },
  {
    "path": "components/artifact-messages.tsx",
    "chars": 3129,
    "preview": "import type { UseChatHelpers } from \"@ai-sdk/react\";\nimport equal from \"fast-deep-equal\";\nimport { AnimatePresence, moti"
  },
  {
    "path": "components/artifact.tsx",
    "chars": 16609,
    "preview": "import type { UseChatHelpers } from \"@ai-sdk/react\";\nimport { formatDistance } from \"date-fns\";\nimport equal from \"fast-"
  },
  {
    "path": "components/auth-form.tsx",
    "chars": 1352,
    "preview": "import Form from \"next/form\";\n\nimport { Input } from \"./ui/input\";\nimport { Label } from \"./ui/label\";\n\nexport function "
  },
  {
    "path": "components/chat-header.tsx",
    "chars": 2182,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport { memo } from \"react\";\n"
  },
  {
    "path": "components/chat.tsx",
    "chars": 8627,
    "preview": "\"use client\";\n\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { useRouter, u"
  },
  {
    "path": "components/code-editor.tsx",
    "chars": 3388,
    "preview": "\"use client\";\n\nimport { python } from \"@codemirror/lang-python\";\nimport { EditorState, Transaction } from \"@codemirror/s"
  },
  {
    "path": "components/console.tsx",
    "chars": 6362,
    "preview": "import {\n  type Dispatch,\n  type SetStateAction,\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimpor"
  },
  {
    "path": "components/create-artifact.tsx",
    "chars": 2959,
    "preview": "import type { UseChatHelpers } from \"@ai-sdk/react\";\nimport type { DataUIPart } from \"ai\";\nimport type { ComponentType, "
  },
  {
    "path": "components/data-stream-handler.tsx",
    "chars": 2417,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { useSWRConfig } from \"swr\";\nimport { unstable_serialize } from"
  },
  {
    "path": "components/data-stream-provider.tsx",
    "chars": 1050,
    "preview": "\"use client\";\n\nimport type { DataUIPart } from \"ai\";\nimport type React from \"react\";\nimport { createContext, useContext,"
  },
  {
    "path": "components/diffview.tsx",
    "chars": 2855,
    "preview": "import OrderedMap from \"orderedmap\";\nimport {\n  DOMParser,\n  type MarkSpec,\n  type Node as ProsemirrorNode,\n  Schema,\n} "
  },
  {
    "path": "components/document-preview.tsx",
    "chars": 8283,
    "preview": "\"use client\";\n\nimport equal from \"fast-deep-equal\";\nimport {\n  type MouseEvent,\n  memo,\n  useCallback,\n  useEffect,\n  us"
  },
  {
    "path": "components/document-skeleton.tsx",
    "chars": 1754,
    "preview": "\"use client\";\n\nimport type { ArtifactKind } from \"./artifact\";\n\nexport const DocumentSkeleton = ({\n  artifactKind,\n}: {\n"
  },
  {
    "path": "components/document.tsx",
    "chars": 4414,
    "preview": "import { memo } from \"react\";\nimport { toast } from \"sonner\";\nimport { useArtifact } from \"@/hooks/use-artifact\";\nimport"
  },
  {
    "path": "components/elements/actions.tsx",
    "chars": 1350,
    "preview": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  "
  },
  {
    "path": "components/elements/branch.tsx",
    "chars": 5256,
    "preview": "\"use client\";\n\nimport type { UIMessage } from \"ai\";\nimport { ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\";\nim"
  },
  {
    "path": "components/elements/conversation.tsx",
    "chars": 1670,
    "preview": "\"use client\";\n\nimport { ArrowDownIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\nimport { useCa"
  },
  {
    "path": "components/elements/image.tsx",
    "chars": 565,
    "preview": "import type { Experimental_GeneratedImage } from \"ai\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ImageProps = Exper"
  },
  {
    "path": "components/elements/inline-citation.tsx",
    "chars": 6507,
    "preview": "\"use client\";\n\nimport { ArrowLeftIcon, ArrowRightIcon } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createCon"
  },
  {
    "path": "components/elements/loader.tsx",
    "chars": 2201,
    "preview": "import type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype LoaderIconProps = {\n  size?: number"
  },
  {
    "path": "components/elements/message.tsx",
    "chars": 1567,
    "preview": "import type { UIMessage } from \"ai\";\nimport type { ComponentProps, HTMLAttributes } from \"react\";\nimport { Avatar, Avata"
  },
  {
    "path": "components/elements/prompt-input.tsx",
    "chars": 5743,
    "preview": "\"use client\";\n\nimport type { ChatStatus } from \"ai\";\nimport { Loader2Icon, SendIcon, SquareIcon, XIcon } from \"lucide-re"
  },
  {
    "path": "components/elements/reasoning.tsx",
    "chars": 5107,
    "preview": "\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { BrainIcon, Chevro"
  },
  {
    "path": "components/elements/response.tsx",
    "chars": 569,
    "preview": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { Streamdown } from \"streamdown\";\nimport { cn } from "
  },
  {
    "path": "components/elements/source.tsx",
    "chars": 1830,
    "preview": "\"use client\";\n\nimport { BookIcon, ChevronDownIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\nim"
  },
  {
    "path": "components/elements/suggestion.tsx",
    "chars": 1264,
    "preview": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Sc"
  },
  {
    "path": "components/elements/task.tsx",
    "chars": 2518,
    "preview": "\"use client\";\n\nimport { ChevronDownIcon, SearchIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\n"
  },
  {
    "path": "components/elements/tool.tsx",
    "chars": 4408,
    "preview": "\"use client\";\n\nimport type { ToolUIPart } from \"ai\";\nimport {\n  CheckCircleIcon,\n  ChevronDownIcon,\n  CircleIcon,\n  Cloc"
  },
  {
    "path": "components/elements/web-preview.tsx",
    "chars": 6351,
    "preview": "\"use client\";\n\nimport { ChevronDownIcon } from \"lucide-react\";\nimport type { ComponentProps, ReactNode } from \"react\";\ni"
  },
  {
    "path": "components/greeting.tsx",
    "chars": 800,
    "preview": "import { motion } from \"framer-motion\";\n\nexport const Greeting = () => {\n  return (\n    <div\n      className=\"mx-auto mt"
  },
  {
    "path": "components/icons.tsx",
    "chars": 56049,
    "preview": "export const BotIcon = () => {\n  return (\n    <svg\n      height=\"16\"\n      strokeLinejoin=\"round\"\n      style={{ color: "
  },
  {
    "path": "components/image-editor.tsx",
    "chars": 1087,
    "preview": "import cn from \"classnames\";\nimport { LoaderIcon } from \"./icons\";\n\ntype ImageEditorProps = {\n  title: string;\n  content"
  },
  {
    "path": "components/message-actions.tsx",
    "chars": 5013,
    "preview": "import equal from \"fast-deep-equal\";\nimport { memo } from \"react\";\nimport { toast } from \"sonner\";\nimport { useSWRConfig"
  },
  {
    "path": "components/message-editor.tsx",
    "chars": 2985,
    "preview": "\"use client\";\n\nimport type { UseChatHelpers } from \"@ai-sdk/react\";\nimport {\n  type Dispatch,\n  type SetStateAction,\n  u"
  },
  {
    "path": "components/message-reasoning.tsx",
    "chars": 736,
    "preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport {\n  Reasoning,\n  ReasoningContent,\n  ReasoningTrigger"
  },
  {
    "path": "components/message.tsx",
    "chars": 14089,
    "preview": "\"use client\";\nimport type { UseChatHelpers } from \"@ai-sdk/react\";\nimport { useState } from \"react\";\nimport type { Vote "
  },
  {
    "path": "components/messages.tsx",
    "chars": 3264,
    "preview": "import type { UseChatHelpers } from \"@ai-sdk/react\";\nimport { ArrowDownIcon } from \"lucide-react\";\nimport { useMessages "
  },
  {
    "path": "components/multimodal-input.tsx",
    "chars": 16314,
    "preview": "\"use client\";\n\nimport type { UseChatHelpers } from \"@ai-sdk/react\";\nimport type { UIMessage } from \"ai\";\nimport equal fr"
  },
  {
    "path": "components/preview-attachment.tsx",
    "chars": 1716,
    "preview": "import Image from \"next/image\";\nimport type { Attachment } from \"@/lib/types\";\nimport { Loader } from \"./elements/loader"
  },
  {
    "path": "components/sheet-editor.tsx",
    "chars": 3846,
    "preview": "\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { parse, unparse } from \"papaparse\";\nimport { memo, useEff"
  },
  {
    "path": "components/sidebar-history-item.tsx",
    "chars": 3644,
    "preview": "import Link from \"next/link\";\nimport { memo } from \"react\";\nimport { useChatVisibility } from \"@/hooks/use-chat-visibili"
  },
  {
    "path": "components/sidebar-history.tsx",
    "chars": 11806,
    "preview": "\"use client\";\n\nimport { isToday, isYesterday, subMonths, subWeeks } from \"date-fns\";\nimport { motion } from \"framer-moti"
  },
  {
    "path": "components/sidebar-toggle.tsx",
    "chars": 930,
    "preview": "import type { ComponentProps } from \"react\";\n\nimport { type SidebarTrigger, useSidebar } from \"@/components/ui/sidebar\";"
  },
  {
    "path": "components/sidebar-user-nav.tsx",
    "chars": 4005,
    "preview": "\"use client\";\n\nimport { ChevronUp } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { useRouter } from \"next"
  },
  {
    "path": "components/sign-out-form.tsx",
    "chars": 446,
    "preview": "import Form from \"next/form\";\n\nimport { signOut } from \"@/app/(auth)/auth\";\n\nexport const SignOutForm = () => {\n  return"
  },
  {
    "path": "components/submit-button.tsx",
    "chars": 814,
    "preview": "\"use client\";\n\nimport { useFormStatus } from \"react-dom\";\n\nimport { LoaderIcon } from \"@/components/icons\";\n\nimport { Bu"
  },
  {
    "path": "components/suggested-actions.tsx",
    "chars": 1979,
    "preview": "\"use client\";\n\nimport type { UseChatHelpers } from \"@ai-sdk/react\";\nimport { motion } from \"framer-motion\";\nimport { mem"
  },
  {
    "path": "components/suggestion.tsx",
    "chars": 2477,
    "preview": "\"use client\";\n\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { useState } from \"react\";\nimport { useWi"
  },
  {
    "path": "components/text-editor.tsx",
    "chars": 4454,
    "preview": "\"use client\";\n\nimport { exampleSetup } from \"prosemirror-example-setup\";\nimport { inputRules } from \"prosemirror-inputru"
  },
  {
    "path": "components/theme-provider.tsx",
    "chars": 300,
    "preview": "\"use client\";\n\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\nimport type { ThemeProviderProps } fro"
  },
  {
    "path": "components/toast.tsx",
    "chars": 1992,
    "preview": "\"use client\";\n\nimport { type ReactNode, useEffect, useRef, useState } from \"react\";\nimport { toast as sonnerToast } from"
  },
  {
    "path": "components/toolbar.tsx",
    "chars": 13088,
    "preview": "\"use client\";\nimport type { UseChatHelpers } from \"@ai-sdk/react\";\nimport cx from \"classnames\";\nimport {\n  AnimatePresen"
  },
  {
    "path": "components/ui/alert-dialog.tsx",
    "chars": 4283,
    "preview": "\"use client\";\n\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { "
  },
  {
    "path": "components/ui/alert.tsx",
    "chars": 1584,
    "preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
  },
  {
    "path": "components/ui/avatar.tsx",
    "chars": 1414,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Avatar as AvatarPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@"
  },
  {
    "path": "components/ui/badge.tsx",
    "chars": 1128,
    "preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
  },
  {
    "path": "components/ui/button-group.tsx",
    "chars": 2209,
    "preview": "import { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { "
  },
  {
    "path": "components/ui/button.tsx",
    "chars": 1961,
    "preview": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimp"
  },
  {
    "path": "components/ui/card.tsx",
    "chars": 2174,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Card = React.forwardRef<\n  HTMLDivElement,\n  R"
  },
  {
    "path": "components/ui/carousel.tsx",
    "chars": 6224,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport useEmblaCarousel, {\n  type UseEmblaCarouselType,\n} from \"embla-carou"
  },
  {
    "path": "components/ui/collapsible.tsx",
    "chars": 324,
    "preview": "\"use client\"\n\nimport { Collapsible as CollapsiblePrimitive } from \"radix-ui\"\n\nconst Collapsible = CollapsiblePrimitive.R"
  },
  {
    "path": "components/ui/command.tsx",
    "chars": 4899,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DialogProps } from \"@radix-ui/react-dialog\"\nimport { Command "
  },
  {
    "path": "components/ui/dialog.tsx",
    "chars": 3680,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from"
  },
  {
    "path": "components/ui/dropdown-menu.tsx",
    "chars": 7617,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\"\nimport { C"
  },
  {
    "path": "components/ui/hover-card.tsx",
    "chars": 1245,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { HoverCard as HoverCardPrimitive } from \"radix-ui\"\n\nimport { cn } f"
  },
  {
    "path": "components/ui/input-group.tsx",
    "chars": 4985,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport {"
  },
  {
    "path": "components/ui/input.tsx",
    "chars": 791,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Input = React.forwardRef<HTMLInputElement, React"
  },
  {
    "path": "components/ui/label.tsx",
    "chars": 719,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Label as LabelPrimitive } from \"radix-ui\"\nimport { cva, type Varia"
  },
  {
    "path": "components/ui/progress.tsx",
    "chars": 786,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Progress as ProgressPrimitive } from \"radix-ui\"\n\nimport { cn } fro"
  },
  {
    "path": "components/ui/scroll-area.tsx",
    "chars": 1650,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { ScrollArea as ScrollAreaPrimitive } from \"radix-ui\"\n\nimport { cn }"
  },
  {
    "path": "components/ui/select.tsx",
    "chars": 5737,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Select as SelectPrimitive } from \"radix-ui\"\nimport { Check, Chevro"
  },
  {
    "path": "components/ui/separator.tsx",
    "chars": 765,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Separator as SeparatorPrimitive } from \"radix-ui\"\n\nimport { cn } f"
  },
  {
    "path": "components/ui/sheet.tsx",
    "chars": 4276,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Dialog as SheetPrimitive } from \"radix-ui\"\nimport { cva, type Vari"
  },
  {
    "path": "components/ui/sidebar.tsx",
    "chars": 23843,
    "preview": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { PanelLeft } from \"lucide-reac"
  },
  {
    "path": "components/ui/skeleton.tsx",
    "chars": 261,
    "preview": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) {"
  },
  {
    "path": "components/ui/textarea.tsx",
    "chars": 689,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Textarea = React.forwardRef<\n  HTMLTextAreaEleme"
  },
  {
    "path": "components/ui/tooltip.tsx",
    "chars": 1204,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Tooltip as TooltipPrimitive } from \"radix-ui\"\n\nimport { cn } from "
  },
  {
    "path": "components/version-footer.tsx",
    "chars": 3080,
    "preview": "\"use client\";\n\nimport { isAfter } from \"date-fns\";\nimport { motion } from \"framer-motion\";\nimport { useState } from \"rea"
  },
  {
    "path": "components/visibility-selector.tsx",
    "chars": 3100,
    "preview": "\"use client\";\n\nimport { type ReactNode, useMemo, useState } from \"react\";\nimport { Button } from \"@/components/ui/button"
  },
  {
    "path": "components/weather.tsx",
    "chars": 11786,
    "preview": "\"use client\";\n\nimport cx from \"classnames\";\nimport { format, isWithinInterval } from \"date-fns\";\nimport { useEffect, use"
  },
  {
    "path": "components.json",
    "chars": 396,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n"
  },
  {
    "path": "drizzle.config.ts",
    "chars": 351,
    "preview": "import { config } from \"dotenv\";\nimport { defineConfig } from \"drizzle-kit\";\n\nconfig({\n  path: \".env.local\",\n});\n\nexport"
  },
  {
    "path": "hooks/use-artifact.ts",
    "chars": 2086,
    "preview": "\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport useSWR from \"swr\";\nimport type { UIArtifact } from \""
  },
  {
    "path": "hooks/use-auto-resume.ts",
    "chars": 1311,
    "preview": "\"use client\";\n\nimport type { UseChatHelpers } from \"@ai-sdk/react\";\nimport { useEffect } from \"react\";\nimport { useDataS"
  },
  {
    "path": "hooks/use-chat-visibility.ts",
    "chars": 1420,
    "preview": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport useSWR, { useSWRConfig } from \"swr\";\nimport { unstable_serialize "
  },
  {
    "path": "hooks/use-messages.tsx",
    "chars": 770,
    "preview": "import type { UseChatHelpers } from \"@ai-sdk/react\";\nimport { useEffect, useState } from \"react\";\nimport type { ChatMess"
  },
  {
    "path": "hooks/use-mobile.ts",
    "chars": 565,
    "preview": "import * as React from \"react\"\n\nconst MOBILE_BREAKPOINT = 768\n\nexport function useIsMobile() {\n  const [isMobile, setIsM"
  },
  {
    "path": "hooks/use-scroll-to-bottom.tsx",
    "chars": 3315,
    "preview": "import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport function useScrollToBottom() {\n  const contain"
  },
  {
    "path": "instrumentation-client.ts",
    "chars": 142,
    "preview": "import { initBotId } from \"botid/client/core\";\n\ninitBotId({\n  protect: [\n    {\n      path: \"/api/chat\",\n      method: \"P"
  },
  {
    "path": "instrumentation.ts",
    "chars": 121,
    "preview": "import { registerOTel } from \"@vercel/otel\";\n\nexport function register() {\n  registerOTel({ serviceName: \"chatbot\" });\n}"
  },
  {
    "path": "lib/ai/entitlements.ts",
    "chars": 432,
    "preview": "import type { UserType } from \"@/app/(auth)/auth\";\n\ntype Entitlements = {\n  maxMessagesPerHour: number;\n};\n\nexport const"
  },
  {
    "path": "lib/ai/models.mock.ts",
    "chars": 5219,
    "preview": "import type { LanguageModel } from \"ai\";\n\nconst mockResponses: Record<string, string> = {\n  default: \"This is a mock res"
  },
  {
    "path": "lib/ai/models.test.ts",
    "chars": 2184,
    "preview": "import { simulateReadableStream } from \"ai\";\nimport { MockLanguageModelV3 } from \"ai/test\";\nimport { getResponseChunksBy"
  },
  {
    "path": "lib/ai/models.ts",
    "chars": 1913,
    "preview": "// Curated list of top models from Vercel AI Gateway\nexport const DEFAULT_CHAT_MODEL = \"openai/gpt-4.1-mini\";\n\nexport ty"
  },
  {
    "path": "lib/ai/prompts.ts",
    "chars": 5112,
    "preview": "import type { Geo } from \"@vercel/functions\";\nimport type { ArtifactKind } from \"@/components/artifact\";\n\nexport const a"
  },
  {
    "path": "lib/ai/providers.ts",
    "chars": 1677,
    "preview": "import { gateway } from \"@ai-sdk/gateway\";\nimport {\n  customProvider,\n  extractReasoningMiddleware,\n  wrapLanguageModel,"
  },
  {
    "path": "lib/ai/tools/create-document.ts",
    "chars": 1945,
    "preview": "import { tool, type UIMessageStreamWriter } from \"ai\";\nimport type { Session } from \"next-auth\";\nimport { z } from \"zod\""
  },
  {
    "path": "lib/ai/tools/get-weather.ts",
    "chars": 2103,
    "preview": "import { tool } from \"ai\";\nimport { z } from \"zod\";\n\nasync function geocodeCity(\n  city: string\n): Promise<{ latitude: n"
  },
  {
    "path": "lib/ai/tools/request-suggestions.ts",
    "chars": 3656,
    "preview": "import { Output, streamText, tool, type UIMessageStreamWriter } from \"ai\";\nimport type { Session } from \"next-auth\";\nimp"
  },
  {
    "path": "lib/ai/tools/update-document.ts",
    "chars": 1764,
    "preview": "import { tool, type UIMessageStreamWriter } from \"ai\";\nimport type { Session } from \"next-auth\";\nimport { z } from \"zod\""
  },
  {
    "path": "lib/artifacts/server.ts",
    "chars": 2799,
    "preview": "import type { UIMessageStreamWriter } from \"ai\";\nimport type { Session } from \"next-auth\";\nimport { codeDocumentHandler "
  },
  {
    "path": "lib/constants.ts",
    "chars": 456,
    "preview": "import { generateDummyPassword } from \"./db/utils\";\n\nexport const isProductionEnvironment = process.env.NODE_ENV === \"pr"
  },
  {
    "path": "lib/db/helpers/01-core-to-parts.ts",
    "chars": 7699,
    "preview": "// This is a helper for an older version of ai, v4.3.13\n\n// import { config } from 'dotenv';\n// import postgres from 'po"
  },
  {
    "path": "lib/db/migrate.ts",
    "chars": 846,
    "preview": "import { config } from \"dotenv\";\nimport { drizzle } from \"drizzle-orm/postgres-js\";\nimport { migrate } from \"drizzle-orm"
  },
  {
    "path": "lib/db/migrations/0000_keen_devos.sql",
    "chars": 606,
    "preview": "CREATE TABLE IF NOT EXISTS \"Chat\" (\n\t\"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,\n\t\"createdAt\" timestamp NO"
  },
  {
    "path": "lib/db/migrations/0001_sparkling_blue_marvel.sql",
    "chars": 1528,
    "preview": "CREATE TABLE IF NOT EXISTS \"Suggestion\" (\n\t\"id\" uuid DEFAULT gen_random_uuid() NOT NULL,\n\t\"documentId\" uuid NOT NULL,\n\t\""
  },
  {
    "path": "lib/db/migrations/0002_wandering_riptide.sql",
    "chars": 1341,
    "preview": "CREATE TABLE IF NOT EXISTS \"Message\" (\n\t\"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,\n\t\"chatId\" uuid NOT NUL"
  },
  {
    "path": "lib/db/migrations/0003_cloudy_glorian.sql",
    "chars": 78,
    "preview": "ALTER TABLE \"Chat\" ADD COLUMN \"visibility\" varchar DEFAULT 'private' NOT NULL;"
  },
  {
    "path": "lib/db/migrations/0004_odd_slayback.sql",
    "chars": 73,
    "preview": "ALTER TABLE \"Document\" ADD COLUMN \"text\" varchar DEFAULT 'text' NOT NULL;"
  },
  {
    "path": "lib/db/migrations/0005_wooden_whistler.sql",
    "chars": 1248,
    "preview": "CREATE TABLE IF NOT EXISTS \"Message_v2\" (\n\t\"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,\n\t\"chatId\" uuid NOT "
  },
  {
    "path": "lib/db/migrations/0006_marvelous_frog_thor.sql",
    "chars": 441,
    "preview": "CREATE TABLE IF NOT EXISTS \"Stream\" (\n\t\"id\" uuid DEFAULT gen_random_uuid() NOT NULL,\n\t\"chatId\" uuid NOT NULL,\n\t\"createdA"
  },
  {
    "path": "lib/db/migrations/0007_flowery_ben_parker.sql",
    "chars": 50,
    "preview": "ALTER TABLE \"Chat\" ADD COLUMN \"lastContext\" jsonb;"
  },
  {
    "path": "lib/db/migrations/0008_flat_forgotten_one.sql",
    "chars": 55,
    "preview": "ALTER TABLE \"Chat\" DROP COLUMN IF EXISTS \"lastContext\";"
  },
  {
    "path": "lib/db/migrations/meta/0000_snapshot.json",
    "chars": 2090,
    "preview": "{\n  \"id\": \"715ec9ec-6715-4d0f-9f6c-9b5c7f09827c\",\n  \"prevId\": \"00000000-0000-0000-0000-000000000000\",\n  \"version\": \"7\",\n"
  },
  {
    "path": "lib/db/migrations/meta/0001_snapshot.json",
    "chars": 6007,
    "preview": "{\n  \"id\": \"f3d3437c-4735-4c91-80af-1014048a904e\",\n  \"prevId\": \"715ec9ec-6715-4d0f-9f6c-9b5c7f09827c\",\n  \"version\": \"7\",\n"
  },
  {
    "path": "lib/db/migrations/meta/0002_snapshot.json",
    "chars": 8666,
    "preview": "{\n  \"id\": \"b5d8e862-936f-4419-a50f-97be3e7fe665\",\n  \"prevId\": \"f3d3437c-4735-4c91-80af-1014048a904e\",\n  \"version\": \"7\",\n"
  },
  {
    "path": "lib/db/migrations/meta/0003_snapshot.json",
    "chars": 8853,
    "preview": "{\n  \"id\": \"011efa9e-42c7-4ff6-830a-02106f6638c9\",\n  \"prevId\": \"b5d8e862-936f-4419-a50f-97be3e7fe665\",\n  \"version\": \"7\",\n"
  },
  {
    "path": "lib/db/migrations/meta/0004_snapshot.json",
    "chars": 9025,
    "preview": "{\n  \"id\": \"30ad8233-1432-428b-93fc-2bb1ba867ff1\",\n  \"prevId\": \"011efa9e-42c7-4ff6-830a-02106f6638c9\",\n  \"version\": \"7\",\n"
  },
  {
    "path": "lib/db/migrations/meta/0005_snapshot.json",
    "chars": 11892,
    "preview": "{\n  \"id\": \"c6c102e6-b64e-4f0c-a7a6-32df758de437\",\n  \"prevId\": \"30ad8233-1432-428b-93fc-2bb1ba867ff1\",\n  \"version\": \"7\",\n"
  },
  {
    "path": "lib/db/migrations/meta/0006_snapshot.json",
    "chars": 12979,
    "preview": "{\n  \"id\": \"443de550-b7e8-4bfb-b229-c12dc6c132f0\",\n  \"prevId\": \"c6c102e6-b64e-4f0c-a7a6-32df758de437\",\n  \"version\": \"7\",\n"
  },
  {
    "path": "lib/db/migrations/meta/0007_snapshot.json",
    "chars": 13133,
    "preview": "{\n  \"id\": \"097660a7-976a-4b3e-8ebb-79312e3ece6f\",\n  \"prevId\": \"443de550-b7e8-4bfb-b229-c12dc6c132f0\",\n  \"version\": \"7\",\n"
  },
  {
    "path": "lib/db/migrations/meta/0008_snapshot.json",
    "chars": 12979,
    "preview": "{\n  \"id\": \"31934f42-f6af-42a3-9320-b2e86fb67e81\",\n  \"prevId\": \"097660a7-976a-4b3e-8ebb-79312e3ece6f\",\n  \"version\": \"7\",\n"
  },
  {
    "path": "lib/db/migrations/meta/_journal.json",
    "chars": 1363,
    "preview": "{\n  \"version\": \"7\",\n  \"dialect\": \"postgresql\",\n  \"entries\": [\n    {\n      \"idx\": 0,\n      \"version\": \"7\",\n      \"when\": "
  },
  {
    "path": "lib/db/queries.ts",
    "chars": 13458,
    "preview": "import \"server-only\";\n\nimport {\n  and,\n  asc,\n  count,\n  desc,\n  eq,\n  gt,\n  gte,\n  inArray,\n  lt,\n  type SQL,\n} from \"d"
  },
  {
    "path": "lib/db/schema.ts",
    "chars": 4692,
    "preview": "import type { InferSelectModel } from \"drizzle-orm\";\nimport {\n  boolean,\n  foreignKey,\n  json,\n  pgTable,\n  primaryKey,\n"
  },
  {
    "path": "lib/db/utils.ts",
    "chars": 398,
    "preview": "import { generateId } from \"ai\";\nimport { genSaltSync, hashSync } from \"bcrypt-ts\";\n\nexport function generateHashedPassw"
  },
  {
    "path": "lib/editor/config.ts",
    "chars": 1458,
    "preview": "import { textblockTypeInputRule } from \"prosemirror-inputrules\";\nimport { Schema } from \"prosemirror-model\";\nimport { sc"
  },
  {
    "path": "lib/editor/diff.js",
    "chars": 13167,
    "preview": "// Modified from https://github.com/hamflx/prosemirror-diff/blob/master/src/diff.js\n\nimport { diff_match_patch } from \"d"
  }
]

// ... and 25 more files (download for full content)

About this extraction

This page contains the full source code of the vercel/chatbot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 225 files (1.3 MB), approximately 433.6k tokens, and a symbol index with 597 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!