Full Code of upstash/rag-chat-component for AI

master 39ea46043b05 cached
54 files
51.4 KB
15.2k tokens
32 symbols
1 requests
Download .txt
Repository: upstash/rag-chat-component
Branch: master
Commit: 39ea46043b05
Files: 54
Total size: 51.4 KB

Directory structure:
gitextract_f7i5d7d5/

├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── bun.lockb
├── components.json
├── examples/
│   └── nextjs/
│       ├── .eslintrc.json
│       ├── .gitignore
│       ├── README.md
│       ├── app/
│       │   ├── components/
│       │   │   ├── chat.tsx
│       │   │   ├── features.tsx
│       │   │   ├── footer.tsx
│       │   │   └── header.tsx
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   └── page.tsx
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.mjs
│       ├── prettier.config.js
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       └── vercel.json
├── index.ts
├── package.json
├── playground/
│   ├── .gitignore
│   ├── README.md
│   ├── eslint.config.mjs
│   ├── next.config.ts
│   ├── package.json
│   ├── postcss.config.mjs
│   ├── src/
│   │   └── app/
│   │       ├── globals.css
│   │       ├── layout.tsx
│   │       └── page.tsx
│   ├── tailwind.config.ts
│   └── tsconfig.json
├── postcss.config.mjs
├── src/
│   ├── client/
│   │   ├── components/
│   │   │   ├── chat-component.tsx
│   │   │   ├── lib/
│   │   │   │   └── utils.ts
│   │   │   ├── styles.css
│   │   │   └── ui/
│   │   │       ├── button.tsx
│   │   │       └── scroll-area.tsx
│   │   └── index.ts
│   └── server/
│       ├── actions/
│       │   ├── chat.ts
│       │   └── history.ts
│       ├── constants.ts
│       ├── index.ts
│       └── lib/
│           ├── history/
│           │   ├── get-client.ts
│           │   ├── in-memory.ts
│           │   └── redis.ts
│           └── types.ts
├── tailwind.config.ts
├── tsconfig.json
└── tsup.config.ts

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

================================================
FILE: .gitignore
================================================
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore

# Logs

logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Caches

.cache

# Diagnostic reports (https://nodejs.org/api/report.html)

report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Runtime data

pids
_.pid
_.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover

lib-cov

# Coverage directory used by tools like istanbul

coverage
*.lcov

# nyc test coverage

.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt

# Bower dependency directory (https://bower.io/)

bower_components

# node-waf configuration

.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)

build/Release

# Dependency directories

node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)

web_modules/

# TypeScript cache

*.tsbuildinfo

# Optional npm cache directory

.npm

# Optional eslint cache

.eslintcache

# Optional stylelint cache

.stylelintcache

# Microbundle cache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history

.node_repl_history

# Output of 'npm pack'

*.tgz

# Yarn Integrity file

.yarn-integrity

# dotenv environment variable files

.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)

.parcel-cache

# Next.js build output

.next
out

# Nuxt.js build / generate output

.nuxt
dist

# Gatsby files

# Comment in the public line in if your project uses Gatsby and not Next.js

# https://nextjs.org/blog/next-9-1#public-directory-support

# public

# vuepress build output

.vuepress/dist

# vuepress v2.x temp and cache directory

.temp

# Docusaurus cache and generated files

.docusaurus

# Serverless directories

.serverless/

# FuseBox cache

.fusebox/

# DynamoDB Local files

.dynamodb/

# TernJS port file

.tern-port

# Stores VSCode versions used for testing VSCode extensions

.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store


================================================
FILE: .prettierrc
================================================
{
  "arrowParens": "always",
  "bracketSameLine": false,
  "bracketSpacing": true,
  "semi": true,
  "singleQuote": false,
  "jsxSingleQuote": false,
  "trailingComma": "all",
  "singleAttributePerLine": false,
  "importOrderSeparation": true,
  "importOrderSortSpecifiers": true,
  "importOrderBuiltinModulesToTop": true,
  "tailwindFunctions": ["clsx"],
  "plugins": ["prettier-plugin-tailwindcss"]
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Upstash

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: README.md
================================================
# RAG Chat Component

A customizable Reach chat component that combines Upstash Vector for similarity search, Together AI for LLM, and Vercel AI SDK for streaming responses. This ready-to-use component provides an out of the box solution for adding RAG-Powered chat interfaces to your Next.js application.

<table>
  <tr>
    <td align="center">
      <img src="https://raw.githubusercontent.com/upstash/rag-chat-component/refs/heads/master/public/images/widget-closed.png" alt="RAG Chat Component - Closed State" width="300"/><br/>
      <em>Closed State</em>
    </td>
    <td align="center">
      <img src="https://raw.githubusercontent.com/upstash/rag-chat-component/refs/heads/master/public/images/widget-open.png" alt="RAG Chat Component - Open State" width="300"/><br/>
      <em>Open State</em>
    </td>
  </tr>
</table>

## Features

⚡ Streaming responses support

💻 Server actions

📱 Responsive design

🔍 Real-time context retrieval

💾 Persistent chat history

🎨 Fully customizable UI components

🎨 Dark/light mode support

## Installation

```bash
# Using npm
npm install @upstash/rag-chat-component

# Using pnpm
pnpm add @upstash/rag-chat-component

# Using yarn
yarn add @upstash/rag-chat-component
```

## Quick Start

### 1. Environment Variables

Create an Upstash Vector database and set up the environment variables as below. If you don't have an account, you can start by going to [Upstash Console](https://console.upstash.com).

Choose an embedding model when creating an index in Upstash Vector.

```
UPSTASH_VECTOR_REST_URL=
UPSTASH_VECTOR_REST_TOKEN=

# Optional for persistent chat history
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

OPENAI_API_KEY=

TOGETHER_API_KEY=

# Optional
TOGETHER_MODEL=
```

### 2. Configure Styles

In your `tailwind.config.ts` file, add the configuration below:

```ts
import type { Config } from "tailwindcss";

export default {
  content: ["./node_modules/@upstash/rag-chat-component/**/*.{js,mjs}"],
} satisfies Config;
```

### 3. Implementation

The RAG Chat Component can be integrated into your application using two straightforward approaches. Choose the method that best fits your project structure:

#### 1. Using a Dedicated Component File (Recommended)

Create a seperate component file with the `use client` directive, then import and use it anywhere in your application.

```jsx
// components/chat.tsx
"use client";

import { ChatComponent } from "@upstash/rag-chat-component";

export const Chat = () => {
  return <ChatComponent />;
};
```

```jsx
// page.tsx
import { Chat } from "./components/chat";

export default function Home() {
  return (
    <>
      <Chat />
      <p>Home</p>
    </>
  );
}
```

#### 2. Direct Integration in Client Components

Alternatively, import and use the **ChatComponent** directly in your client-side pages.

```jsx
// page.tsx
"use client";
import { ChatComponent } from "@upstash/rag-chat-component";

export default function Home() {
  return (
    <>
      <ChatComponent />
      <p>Home</p>
    </>
  );
}
```

### 4. Choosing Chat Model

It's possible to choose one of the [together.ai](https://www.together.ai/) models for the chat.
Default model is `meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo`. You can configure it in the environment variables.

```
TOGETHER_MODEL="deepseek-ai/DeepSeek-V3"
```

### 5. Additional Notes

If you're deploying on Vercel and experiencing timeout issues, you can increase the function execution time limit by adding the following configuration to your `vercel.json`:

```
{
  "functions": {
    "app/**/*": {
      "maxDuration": 30
    }
  }
}
```

This extends the function timeout to 30 seconds, allowing more time for RAG operations to complete on serverless functions.

</details>

## Adding Content

You can add content to your RAG Chat component in several ways:

<details>
<summary>1. Using Upstash Vector SDK</summary>

Upstash has Vector SDKs in JS and Python. You can use those SDK to insert data to your Vector index.

[Vector JS SDK](https://github.com/upstash/vector-js)

[Vector Python SDK](https://github.com/upstash/vector-py)

For other languages you can use [Vector REST API](https://upstash.com/docs/vector/api/get-started).

</details>

<details>
<summary>2. Using Upstash Vector UI</summary>

For testing purpose, you can add your data directly through the Upstash Vector Console:

1. Navigate to [Upstash Console](http://console.upstash.com/vector).
2. Go to details page of the Vector database.
3. Navigate to **Data Browser Tab**.
4. Here, you can upsert data or upload a PDF.

<img src="./public/images/vector-databrowser.png" alt="Vector Databrowser"/><br/>

</details>

<details>
<summary>3. docs2vector tool</summary>

If you are planning to insert your documentation (markdown files) to your Vector index, then you can use [docs2vector](https://github.com/upstash/docs2vector/) tool.

</details>

## Development

You can use the playground for development, by basically running the command in the root.

```bash
bun run playground
```

## Roadmap

- Integration with [QStash](https://upstash.com/docs/qstash/overall/getstarted) for infinite timout for serverless functions

## Contributing

We welcome contributions! Please see our contributing guidelines for more details.

## License

MIT License - see the LICENSE file for details.


================================================
FILE: components.json
================================================
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "src/app/globals.css",
    "baseColor": "slate",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}


================================================
FILE: examples/nextjs/.eslintrc.json
================================================
{
  "extends": ["next/core-web-vitals", "next/typescript"]
}


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

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

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

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts


================================================
FILE: examples/nextjs/README.md
================================================
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.


================================================
FILE: examples/nextjs/app/components/chat.tsx
================================================
"use client";

import { ChatComponent } from "@upstash/rag-chat-component";

export const Chat = () => {
  return <ChatComponent />;
};


================================================
FILE: examples/nextjs/app/components/features.tsx
================================================
export default function Features() {
  return (
    <div className="sc mt-16 grid gap-4 sm:grid-cols-2">
      <FeatureCard
        title="Streaming Responses"
        description="Real-time AI responses with token-by-token streaming for a natural chat experience."
      />
      <FeatureCard
        title="RAG Integration"
        description="Built-in support for Retrieval-Augmented Generation to provide context-aware responses."
      />
      <FeatureCard
        title="Fully Customizable"
        description="Easily customize the appearance and behavior to match your application's design."
      />
      <FeatureCard
        title="Server Actions"
        description="Built for Next.js App Router with Server Actions."
      />
    </div>
  );
}

export function FeatureCard({
  title,
  description,
}: {
  title: string;
  description: string;
}) {
  return (
    <div className="rounded-3xl border border-emerald-700/20 bg-white/60 px-8 py-6 shadow-sm backdrop-blur">
      <h3 className="font-display text-lg font-semibold">{title}</h3>
      <p className="text-balance opacity-80">{description}</p>
    </div>
  );
}


================================================
FILE: examples/nextjs/app/components/footer.tsx
================================================
import { ArrowUpRight } from "lucide-react";

export default function Footer() {
  return (
    <footer className="mt-16 flex flex-wrap items-center justify-center gap-6">
      <a
        href="https://github.com/upstash/rag-chat-component"
        target="_blank"
        rel="noopener noreferrer"
        className="flex items-center gap-1 opacity-80 hover:underline"
      >
        GitHub <ArrowUpRight size={14} />
      </a>
      <a
        href="https://github.com/upstash/rag-chat-component?tab=readme-ov-file#rag-chat-component"
        className="flex items-center gap-1 opacity-80 hover:underline"
        target="_blank"
        rel="noopener noreferrer"
      >
        Documentation <ArrowUpRight size={14} />
      </a>
      <a
        href="https://github.com/upstash/rag-chat-component/tree/master/examples"
        className="flex items-center gap-1 opacity-80 hover:underline"
        target="_blank"
        rel="noopener noreferrer"
      >
        Examples <ArrowUpRight size={14} />
      </a>
    </footer>
  );
}


================================================
FILE: examples/nextjs/app/components/header.tsx
================================================
export default function Header() {
  return (
    <header className="">
      <p className="mb-6 inline-block rounded-lg bg-emerald-100 px-3 py-1 text-sm text-emerald-700">
        Powered by{" "}
        <a
          href="http://upstash.com"
          target="_blank"
          className="font-semibold underline"
        >
          Upstash
        </a>{" "}
        ,{" "}
        <a
          href="http://together.ai"
          target="_blank"
          className="font-semibold underline"
        >
          TogetherAI
        </a>{" "}
        and{" "}
        <a
          href="https://sdk.vercel.ai/"
          target="_blank"
          className="font-semibold underline"
        >
          Vercel AI SDK
        </a>
      </p>

      <h1 className="text-balance font-display text-4xl font-bold tracking-tight sm:text-5xl">
        AI Chat Component for Next.js
      </h1>

      <h3 className="mt-3 px-12 font-display text-xl opacity-80 sm:px-32">
        A modern, customizable chat interface with streaming responses and RAG
        capabilities
      </h3>

      <div className="mt-8 flex flex-col items-center justify-center gap-2 md:flex-row">
        <a
          className="flex h-10 items-center justify-center gap-2 rounded-full bg-emerald-900 px-4 font-medium text-white hover:opacity-60"
          href="https://github.com/upstash/rag-chat-component"
          target="_blank"
          rel="noopener noreferrer"
        >
          <svg
            height="24"
            viewBox="0 0 24 24"
            version="1.1"
            className="fill-current"
          >
            <path d="M12.5.75C6.146.75 1 5.896 1 12.25c0 5.089 3.292 9.387 7.863 10.91.575.101.79-.244.79-.546 0-.273-.014-1.178-.014-2.142-2.889.532-3.636-.704-3.866-1.35-.13-.331-.69-1.352-1.18-1.625-.402-.216-.977-.748-.014-.762.906-.014 1.553.834 1.769 1.179 1.035 1.74 2.688 1.25 3.349.948.1-.747.402-1.25.733-1.538-2.559-.287-5.232-1.279-5.232-5.678 0-1.25.445-2.285 1.178-3.09-.115-.288-.517-1.467.115-3.048 0 0 .963-.302 3.163 1.179.92-.259 1.897-.388 2.875-.388.977 0 1.955.13 2.875.388 2.2-1.495 3.162-1.179 3.162-1.179.633 1.581.23 2.76.115 3.048.733.805 1.179 1.825 1.179 3.09 0 4.413-2.688 5.39-5.247 5.678.417.36.776 1.05.776 2.128 0 1.538-.014 2.774-.014 3.162 0 .302.216.662.79.547C20.709 21.637 24 17.324 24 12.25 24 5.896 18.854.75 12.5.75Z"></path>
          </svg>
          View on GitHub
        </a>
        <a
          className="flex h-10 items-center justify-center rounded-full border-2 border-emerald-900/20 px-4 font-medium hover:border-emerald-900"
          href="https://github.com/upstash/rag-chat-component?tab=readme-ov-file#rag-chat-component"
          target="_blank"
          rel="noopener noreferrer"
        >
          Documentation
        </a>
      </div>
    </header>
  );
}


================================================
FILE: examples/nextjs/app/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;

@keyframes anim-zoom {
  0% {
    width: 64px;
    height: 64px;
    opacity: 1;
  }
  99% {
    width: 2000px;
    height: 2000px;
    opacity: 0;
  }
  100% {
    width: 64px;
    height: 64px;
    opacity: 0;
  }
}

.pulse {
  @apply absolute left-0 right-0 size-16 -translate-x-1/2 -translate-y-1/2 rounded-full border-[60px] border-emerald-500/20 opacity-0;
  animation: anim-zoom 40s ease infinite;
}


================================================
FILE: examples/nextjs/app/layout.tsx
================================================
import type { Metadata } from "next";
import { Inter, Inter_Tight } from "next/font/google";
import "./globals.css";

const defaultFont = Inter({
  variable: "--font-sans",
  subsets: ["latin"],
});

const displayFont = Inter_Tight({
  variable: "--font-display",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "RAG Component",
  description: "Streaming Chat Component with Persistent History",
  icons: {
    icon: [{ url: "upstash.png" }],
  },
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html
      lang="en"
      className={`antialiased ${defaultFont.variable} ${displayFont.variable}`}
    >
      <body className="bg-gradient-to-br from-emerald-50 font-sans text-emerald-900">
        {children}
      </body>
    </html>
  );
}


================================================
FILE: examples/nextjs/app/page.tsx
================================================
import Features from "@/app/components/features";
import Footer from "@/app/components/footer";
import Header from "@/app/components/header";
import { Chat } from "./components/chat";

export default function Home() {
  return (
    <div className="flex min-h-screen flex-col items-center justify-center px-8 py-12 text-center sm:px-12 sm:py-16">
      {/* page */}
      <main className="flex max-w-screen-md flex-col items-center">
        <Header />
        <Features />
        <Footer />
      </main>

      {/* chat */}
      <Chat />
      <div className="pointer-events-none fixed bottom-0 right-0 -z-10 translate-x-1/2 translate-y-1/2">
        <div className="-translate-x-14 -translate-y-14">
          {[0, 5, 10, 15, 20, 25, 30, 35].map((o) => (
            <span
              key={o}
              style={{ animationDelay: `${o}s` }}
              className="pulse"
            />
          ))}
        </div>
      </div>
    </div>
  );
}


================================================
FILE: examples/nextjs/next.config.ts
================================================
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
};

export default nextConfig;


================================================
FILE: examples/nextjs/package.json
================================================
{
  "name": "nextjs",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "format": "pnpm prettier --write ."
  },
  "dependencies": {
    "@upstash/rag-chat-component": "^0.2.2",
    "next": "15.1.11",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "postcss": "^8",
    "prettier": "^3.4.2",
    "prettier-plugin-tailwindcss": "^0.6.9",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}


================================================
FILE: examples/nextjs/postcss.config.mjs
================================================
/** @type {import('postcss-load-config').Config} */
const config = {
  plugins: {
    tailwindcss: {},
  },
};

export default config;


================================================
FILE: examples/nextjs/prettier.config.js
================================================
module.exports = {
  arrowParens: "always",
  bracketSameLine: false,
  bracketSpacing: true,
  semi: true,
  singleQuote: false,
  jsxSingleQuote: false,
  trailingComma: "all",
  singleAttributePerLine: false,
  importOrderSeparation: true,
  importOrderSortSpecifiers: true,
  importOrderBuiltinModulesToTop: true,
  tailwindFunctions: ["clsx"],
  plugins: [
    "@ianvs/prettier-plugin-sort-imports",
    "prettier-plugin-tailwindcss",
  ],
};


================================================
FILE: examples/nextjs/tailwind.config.ts
================================================
import type { Config } from "tailwindcss";

export default {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./node_modules/@upstash/rag-chat-component/**/*.{js,mjs}",
  ],
  theme: {
    extend: {
      fontFamily: {
        sans: ["var(--font-sans)"],
        display: ["var(--font-display)"],
      },
    },
  },
} satisfies Config;


================================================
FILE: examples/nextjs/tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}


================================================
FILE: examples/nextjs/vercel.json
================================================
{
  "functions": {
    "app/**/*": {
      "maxDuration": 30
    }
  }
}


================================================
FILE: index.ts
================================================
console.log("Hello via Bun!");


================================================
FILE: package.json
================================================
{
  "name": "@upstash/rag-chat-component",
  "description": "Streaming Chat Component with Persistent History",
  "version": "0.2.2",
  "module": "index.ts",
  "main": "./dist/client/index.mjs",
  "types": "./dist/client/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/client/index.mjs"
    },
    "./styles.css": "./dist/client/styles.mjs"
  },
  "files": [
    "dist"
  ],
  "peerDependencies": {
    "next": "^14 || ^15",
    "react": "^18 || ^19",
    "typescript": "^5"
  },
  "scripts": {
    "build": "tsup",
    "playground": "cd playground && npm run dev"
  },
  "dependencies": {
    "@ai-sdk/openai": "^1.0.18",
    "@radix-ui/react-scroll-area": "^1.2.2",
    "@radix-ui/react-slot": "^1.1.1",
    "@upstash/redis": "^1.34.3",
    "@upstash/vector": "^1.2.0",
    "ai": "^4.0.33",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lodash.debounce": "^4.0.8",
    "lucide-react": "^0.471.0",
    "react": "^19",
    "react-dom": "^19",
    "react-textarea-autosize": "^8.5.7",
    "tailwind-merge": "^2.6.0",
    "tailwindcss": "^3.4.1",
    "tailwindcss-animate": "^1.0.7",
    "together-ai": "^0.11.1"
  },
  "devDependencies": {
    "@types/bun": "latest",
    "@types/lodash.debounce": "^4.0.9",
    "@types/node": "^22",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "autoprefixer": "^10.4.20",
    "esbuild-fix-imports-plugin": "^1.0.10",
    "postcss": "^8",
    "postcss-prefix-selector": "^2.1.0",
    "prettier": "^3.4.2",
    "prettier-plugin-tailwindcss": "^0.6.9",
    "tsup": "^8.2.0"
  },
  "homepage": "https://github.com/upstash/rag-chat-component#readme",
  "author": "Fahreddin Ozcan",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/upstash/rag-chat-component/issues"
  }
}


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

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

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

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts


================================================
FILE: playground/README.md
================================================
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.


================================================
FILE: playground/eslint.config.mjs
================================================
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname,
});

const eslintConfig = [
  ...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;


================================================
FILE: playground/next.config.ts
================================================
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
};

export default nextConfig;


================================================
FILE: playground/package.json
================================================
{
  "name": "playground",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "next": "15.1.6"
  },
  "exports": {
    ".": {
      "import": "./src/client/index.ts"
    }
  },
  "devDependencies": {
    "typescript": "^5",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "eslint": "^9",
    "eslint-config-next": "15.1.6",
    "@eslint/eslintrc": "^3"
  }
}


================================================
FILE: playground/postcss.config.mjs
================================================
/** @type {import('postcss-load-config').Config} */
const config = {
  plugins: {
    tailwindcss: {},
  },
};

export default config;


================================================
FILE: playground/src/app/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --background: #ffffff;
  --foreground: #171717;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  color: var(--foreground);
  background: var(--background);
  font-family: Arial, Helvetica, sans-serif;
}


================================================
FILE: playground/src/app/layout.tsx
================================================
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

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

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

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}


================================================
FILE: playground/src/app/page.tsx
================================================
import { ChatComponent } from "@/client/components/chat-component";

export default function Home() {
  return (
    <main className="min-h-screen p-8">
      <h1 className="mb-8 text-2xl font-bold">Component Playground</h1>
      <div className="mx-auto max-w-3xl">
        <ChatComponent />
      </div>
    </main>
  );
}


================================================
FILE: playground/tailwind.config.ts
================================================
import type { Config } from "tailwindcss";

export default {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
    '../src/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      colors: {
        background: "var(--background)",
        foreground: "var(--foreground)",
      },
    },
  },
  plugins: [],
} satisfies Config;


================================================
FILE: playground/tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["../src/*"],
      "@playground/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}


================================================
FILE: postcss.config.mjs
================================================
/** @type {import("postcss-load-config").Config} */
const config = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    "postcss-prefix-selector": {
      prefix: ".ups-chat",
    },
  },
};

export default config;


================================================
FILE: src/client/components/chat-component.tsx
================================================
"use client";

import { ScrollArea } from "@radix-ui/react-scroll-area";

import * as React from "react";
import { useEffect, useRef, useState } from "react";
import { serverChat } from "../../server/actions/chat";
import { deleteHistory, getHistory } from "../../server/actions/history";

import type { Message } from "../../server/lib/types";
import { cn } from "./lib/utils";
import { Button } from "./ui/button";
import { ArrowUp, Bot, Loader2, X } from "lucide-react";
import TextareaAutosize from "react-textarea-autosize";
import { readStreamableValue } from "ai/rsc";

type ChatComponentProps = {
  theme?: {
    triggerButtonIcon?: React.ReactNode;
    triggerButtonColor?: string;
  };
};

export const ChatComponent = ({ theme }: ChatComponentProps) => {
  const [conversation, setConversation] = useState<Message[]>([]);
  const [sessionId, setSessionId] = useState<string>("");
  const [input, setInput] = useState<string>("");
  const [isLoading, setIsLoading] = useState(false);
  const [isOpen, setIsOpen] = useState(false);
  const [isStreaming, setIsStreaming] = useState(false);
  const scrollAreaRef = useRef<HTMLDivElement>(null);
  const lastMessageRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLTextAreaElement>(null);

  const toggleChat = () => {
    setIsOpen(!isOpen);
    if (!isOpen) {
      setTimeout(() => inputRef.current?.focus(), 100);
    }
  };

  const scrollToBottom = () => {
    if (lastMessageRef.current) {
      lastMessageRef.current.scrollIntoView({ behavior: "smooth" });
    }
  };

  useEffect(() => {
    let id = localStorage.getItem("chat_session_id");
    if (!id) {
      id = "session_" + Math.random().toString(36).substr(2, 9);
      localStorage.setItem("chat_session_id", id);
    }
    setSessionId(id);

    const fetchHistory = async () => {
      try {
        setIsLoading(true);
        const { messages } = await getHistory(id);
        if (messages.length > 0) {
          setConversation(messages);
        }
      } catch (error) {
        console.error("Error fetching chat history:", error);
      } finally {
        setIsLoading(false);
      }
    };

    fetchHistory();
  }, []);

  useEffect(() => {
    scrollToBottom();
  }, [conversation]);

  useEffect(() => {
    if (isStreaming) {
      const intervalId = setInterval(scrollToBottom, 100);
      return () => clearInterval(intervalId);
    }
  }, [isStreaming]);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim()) return;

    const userMessage: Message = {
      content: input,
      role: "user",
      id: Date.now().toString(),
    };

    const newConversation = [...conversation, userMessage];
    setConversation(newConversation);
    setInput("");
    setIsLoading(true);

    try {
      const { output } = await serverChat({
        messages: newConversation,
        sessionId,
      });
      setIsStreaming(true);
      let aiMessage: Message = {
        content: "",
        role: "assistant",
        id: (Date.now() + 1).toString(),
      };
      setConversation((prev) => [...prev, aiMessage]);

      let messageReceived = false;
      for await (const delta of readStreamableValue(output)) {
        if (delta) {
          messageReceived = true;
        }
        aiMessage.content += delta;
        setConversation((prev) =>
          prev.map((msg) =>
            msg.id === aiMessage.id
              ? { ...msg, content: aiMessage.content }
              : msg,
          ),
        );
      }

      if (!messageReceived || !aiMessage.content.trim()) {
        setConversation((prev) => [
          ...prev.slice(0, -1),
          {
            content: "No response received. Please try again.",
            role: "error",
            id: (Date.now() + 2).toString(),
          },
        ]);
      }
    } catch (error) {
      console.error("Error in AI response:", error);
      setConversation((prev) => [
        ...prev.slice(0, -1),
        {
          content: "An error occurred. Please try again.",
          role: "error",
          id: (Date.now() + 3).toString(),
        },
      ]);
    } finally {
      setIsLoading(false);
      setIsStreaming(false);
    }
  };

  const handleClearHistory = async () => {
    if (!sessionId || isLoading) return;

    setIsLoading(true);
    try {
      const { success } = await deleteHistory(sessionId);
      if (success) {
        setConversation([]);
      }
    } catch (error) {
      console.error("Error clearing chat history:", error);
    } finally {
      setIsLoading(false);
    }
  };

  const renderMessage = (message: Message, index: number) => {
    const isLastMessage = index === conversation.length - 1;
    const showDots = isLastMessage && isStreaming;
    const isUser = message.role === "user";
    const isError = message.role === "error";

    return (
      <div
        key={index}
        ref={isLastMessage ? lastMessageRef : null}
        className={cn("mb-2 flex", isUser ? "justify-end" : "justify-start")}
      >
        {isUser ? (
          // User message
          <div className="rounded-2xl bg-black px-4 py-2 text-white">
            {message.content}
          </div>
        ) : isError ? (
          <div className="flex max-w-[90%] items-start gap-3">
            <Bot size={28} strokeWidth={1.5} className="mt-2 shrink-0" />
            <div className="relative rounded-2xl bg-red-50 px-4 py-2 text-red-600">
              <span className="absolute -left-1 top-4 size-4 rotate-45 bg-inherit" />
              {message.content}
            </div>
          </div>
        ) : (
          // Assistant message
          <div className="flex max-w-[90%] items-start gap-3">
            <Bot size={28} strokeWidth={1.5} className="mt-2 shrink-0" />
            <div className="relative rounded-2xl bg-zinc-100 px-4 py-2">
              <span className="absolute -left-1 top-4 size-4 rotate-45 bg-inherit" />
              {message.content}
              {showDots && (
                <span className="ml-1 inline-block">
                  <span className="dots animate-pulse">...</span>
                </span>
              )}
            </div>
          </div>
        )}
      </div>
    );
  };

  const hasMessages = conversation.length > 0;

  return (
    <div className="ups-chat rcc">
      {/* >>> Trigger Button */}
      <Button
        onClick={toggleChat}
        className={cn(
          "fixed bottom-8 right-8 z-[9999] size-12",
          "flex items-center justify-center p-0",
          "rounded-full text-white shadow-xl",
          "transition-all duration-300 ease-in-out",
          isOpen ? "scale-0 opacity-0" : "scale-100 opacity-100",
        )}
        style={{
          backgroundColor: theme?.triggerButtonColor ?? "#10b981", // default: emerald-500
        }}
      >
        {theme?.triggerButtonIcon ?? <Bot size={28} />}
      </Button>

      {/* >>> Chat Modal */}
      <div
        className={cn(
          "fixed z-50 antialiased",
          // Mobile (default)
          "bottom-0 left-0 right-0 w-full",
          "rounded-t-2xl border-2 border-zinc-500",
          // Desktop
          "sm:!bottom-8 sm:!left-auto sm:!right-8 sm:!h-auto sm:!w-[420px]",
          "sm:rounded-2xl sm:border-2",
          "bg-white text-left text-black shadow-2xl",
          "transition-all duration-300",
          isOpen
            ? "pointer-events-auto translate-y-0 opacity-100"
            : "pointer-events-none translate-y-full opacity-0",
        )}
      >
        {/* Chat Header */}
        <header className="flex items-center rounded-t-2xl border-b border-b-zinc-100 bg-zinc-50 px-6 py-5">
          <h3 className="grow text-lg font-semibold">Chat Assistant</h3>

          <div className="ml-auto flex shrink-0 items-center justify-end">
            {/* clear button */}
            <Button
              variant="ghost"
              size="sm"
              className="text-sm text-zinc-400 hover:bg-zinc-200 hover:text-red-500"
              disabled={!hasMessages}
              onClick={() => {
                handleClearHistory();
              }}
            >
              Clear
            </Button>
            {/* close button */}

            <Button
              variant="ghost"
              size="icon"
              className="opacity-30 hover:bg-zinc-300 hover:opacity-100"
              onClick={toggleChat}
            >
              <X size={20} strokeWidth="1.5" />
            </Button>
          </div>
        </header>

        {/* Chat Body */}
        <ScrollArea
          className="h-[40vh] overflow-auto overscroll-contain p-6 sm:h-[420px]"
          ref={scrollAreaRef}
        >
          {/* empty message */}
          {!hasMessages && !isLoading && (
            <div className="flex h-full items-center justify-center text-center text-sm">
              <span className="rounded-xl bg-zinc-50 p-4 text-zinc-500">
                Chat with the AI assistant
              </span>
            </div>
          )}

          {/* chat bubbles */}
          <div className="flex flex-col gap-4">
            {conversation.map(renderMessage)}
            {isLoading && !isStreaming && (
              <div className="flex items-center justify-center py-2">
                <Loader2 className="h-6 w-6 animate-spin" />
              </div>
            )}
          </div>
        </ScrollArea>

        {/* Chat Form */}
        <form onSubmit={handleSubmit} className="relative p-6">
          <TextareaAutosize
            className={cn(
              "flex w-full rounded-3xl border bg-transparent px-4 py-3",
              "focus:ring-primary focus:outline-none focus:ring-2",
              "disabled:cursor-not-allowed disabled:opacity-50",
              "resize-none",
            )}
            rows={1}
            maxRows={5}
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter" && !e.shiftKey) {
                e.preventDefault();
                handleSubmit(e);
              }
            }}
            placeholder="Ask a question..."
            disabled={isLoading || isStreaming}
            ref={inputRef}
          />

          <Button
            type="submit"
            size="icon"
            className={cn(
              "absolute bottom-8 right-8 z-10 bg-black",
              (isLoading || isStreaming) && "cursor-not-allowed opacity-50",
            )}
            disabled={isLoading || isStreaming}
          >
            {isLoading || isStreaming ? (
              <Loader2 size={20} className="animate-spin" />
            ) : (
              <ArrowUp size={20} className="" />
            )}
          </Button>
        </form>
      </div>
    </div>
  );
};


================================================
FILE: src/client/components/lib/utils.ts
================================================
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

================================================
FILE: src/client/components/styles.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;


================================================
FILE: src/client/components/ui/button.tsx
================================================
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../lib/utils";

const buttonVariants = cva(
  "inline-flex items-center justify-center whitespace-nowrap rounded-md disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-black text-white",
        secondary: "bg-black text-white",
        ghost: "",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-8 rounded-md px-3",
        icon: "size-8 rounded-full p-0",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button";
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    );
  },
);
Button.displayName = "Button";

export { Button, buttonVariants };


================================================
FILE: src/client/components/ui/scroll-area.tsx
================================================
"use client";

import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";

import { cn } from "../lib/utils";

const ScrollArea = React.forwardRef<
  React.ElementRef<typeof ScrollAreaPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
  <ScrollAreaPrimitive.Root
    ref={ref}
    className={cn("relative overflow-hidden", className)}
    {...props}
  >
    <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
      {children}
    </ScrollAreaPrimitive.Viewport>
    <ScrollBar />
    <ScrollAreaPrimitive.Corner />
  </ScrollAreaPrimitive.Root>
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;

const ScrollBar = React.forwardRef<
  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
  <ScrollAreaPrimitive.ScrollAreaScrollbar
    ref={ref}
    orientation={orientation}
    className={cn(
      "flex touch-none select-none transition-colors",
      orientation === "vertical" &&
        "h-full w-2.5 border-l border-l-transparent p-[1px]",
      orientation === "horizontal" &&
        "h-2.5 flex-col border-t border-t-transparent p-[1px]",
      className,
    )}
    {...props}
  >
    <ScrollAreaPrimitive.ScrollAreaThumb className="bg-border relative flex-1 rounded-full" />
  </ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;

export { ScrollArea, ScrollBar };


================================================
FILE: src/client/index.ts
================================================
export { ChatComponent } from "./components/chat-component";


================================================
FILE: src/server/actions/chat.ts
================================================
"use server";

import { Index } from "@upstash/vector"
import { createOpenAI } from "@ai-sdk/openai"
import { streamText } from "ai"
import { createStreamableValue, type StreamableValue } from "ai/rsc";
import { DEFAULT_PROMPT } from "../constants";
import type { Message } from "../lib/types";
import { getHistoryClient } from "../lib/history/get-client";

type StreamMessage = {
  role: 'user' | 'assistant';
  content: string;
}

const vectorIndex = new Index()

const together = createOpenAI({
  apiKey: process.env.TOGETHER_API_KEY ?? "",
  baseURL: "https://api.together.xyz/v1",

})

const searchSimilarDocs = async (data: string, topK: number) => {
  const results = await vectorIndex.query({
    data,
    topK: topK ? topK : 5,
    includeMetadata: true,
    includeData: true,
  });

  return results
}

const history = getHistoryClient()

export const serverChat = async ({
  messages,
  sessionId,
}: {
  messages: Message[];
  sessionId: string;
}): Promise<{ output: StreamableValue<string> }> => {
  const userMessage = messages[messages.length - 1]

  await history.addMessage({
    message: userMessage,
    sessionId
  })

  const serverMessages = messages
    .filter(msg => msg.role !== 'error')
    .map(msg => ({
      role: msg.role,
      content: msg.content
    })) as StreamMessage[];

  const similarDocs = await searchSimilarDocs(userMessage.content, 5)

  const context = similarDocs.map(doc => doc.data).join("\n")

  const chatMessages = messages.map(message => message.content).join("\n")

  const system = DEFAULT_PROMPT({ context, question: userMessage.content, chatMessages })

  const stream = createStreamableValue("");

  (async () => {
    const { textStream } = streamText({
      model: together(process.env.TOGETHER_MODEL ?? "deepseek-ai/DeepSeek-V3"),
      system,
      messages: serverMessages,

      async onFinish({ text }) {
        await history.addMessage({
          message: {
            role: "assistant",
            content: text,
            id: Date.now().toString(),
          },
          sessionId
        })
      },

    })

    for await (const delta of textStream) {
      stream.update(delta);
    }

    stream.done();
  })();

  return { output: stream.value }
};


================================================
FILE: src/server/actions/history.ts
================================================
'use server'

import { getHistoryClient } from "../lib/history/get-client";
import type { Message } from "../lib/types";

const history = getHistoryClient();

export async function getHistory(sessionId: string): Promise<{ messages: Message[] }> {
	try {
		const messages = await history.getMessages({ sessionId });
		return { messages };
	} catch (error) {
		console.error("Failed to fetch chat history:", error);
		return { messages: [] };
	}
}

export async function deleteHistory(sessionId: string): Promise<{ success: boolean }> {
	try {
		await history.deleteMessages({ sessionId });
		return { success: true };
	} catch (error) {
		console.error("Failed to delete chat history:", error);
		return { success: false };
	}
}

================================================
FILE: src/server/constants.ts
================================================
type PromptParameters = {
	chatMessages?: string;
	question: string;
	context: string;
};

type Prompt = ({ question, chatMessages, context }: PromptParameters) => string;

export const DEFAULT_PROMPT: Prompt = ({ context, question, chatMessages }) =>
	`You are a concise AI assistant helping users on a website. Provide brief, clear answers in 1-2 sentences when possible.
  
  Context and chat history are provided to help you answer questions accurately. Only use information from these sources.
  
  ${context ? `Context: ${context}\n` : ''}${chatMessages ? `Previous messages: ${chatMessages}\n` : ''}
  Q: ${question}
  A:`;

export const DEFAULT_CHAT_SESSION_ID = "upstash-rag-chat-session";

export const DEFAULT_HISTORY_LENGTH = 5;

================================================
FILE: src/server/index.ts
================================================
export { serverChat } from "./actions/chat";


================================================
FILE: src/server/lib/history/get-client.ts
================================================
import type { BaseMessageHistory } from "../types";
import { RedisHistory } from "./redis";
import { InMemoryHistory } from "./in-memory";

export const getHistoryClient = (): BaseMessageHistory => {
	const redisUrl = process.env.UPSTASH_REDIS_REST_URL;
	const redisToken = process.env.UPSTASH_REDIS_REST_TOKEN;

	if (redisUrl && redisToken) {
		return new RedisHistory({
			config: {
				url: redisUrl,
				token: redisToken,
			}
		});
	}

	return new InMemoryHistory();
}

================================================
FILE: src/server/lib/history/in-memory.ts
================================================
import type { Message, BaseMessageHistory } from "../types";
import { DEFAULT_CHAT_SESSION_ID, DEFAULT_HISTORY_LENGTH } from "../../constants";

declare global {
	var store: Record<string, { messages: Message[] }>;
}
export class InMemoryHistory implements BaseMessageHistory {
	constructor() {
		if (!global.store) global.store = {};
	}

	async addMessage(params: {
		message: Message;
		sessionId: string;
		sessionTTL?: number;
	}): Promise<void> {
		const { message, sessionId = DEFAULT_CHAT_SESSION_ID } = params;
		if (!global.store[sessionId]) {
			global.store[sessionId] = { messages: [] };
		}

		const oldMessages = global.store[sessionId].messages || [];
		const newMessages = [message, ...oldMessages];
		global.store[sessionId].messages = newMessages;
	}

	async deleteMessages({ sessionId }: { sessionId: string }): Promise<void> {
		if (!global.store[sessionId]) {
			return;
		}
		global.store[sessionId].messages = [];
	}

	async getMessages({
		sessionId = DEFAULT_CHAT_SESSION_ID,
		amount = DEFAULT_HISTORY_LENGTH,
		startIndex = 0,
	}): Promise<Message[]> {
		if (!global.store[sessionId]) {
			global.store[sessionId] = { messages: [] };
		}

		const messages = global.store[sessionId]?.messages ?? [];
		const slicedMessages = messages.slice(startIndex, startIndex + amount);
		return slicedMessages.reverse();
	}
}


================================================
FILE: src/server/lib/history/redis.ts
================================================
import { Redis, type RedisConfigNodejs } from "@upstash/redis";
import { DEFAULT_CHAT_SESSION_ID, DEFAULT_HISTORY_LENGTH } from "../../constants";
import type { Message, BaseMessageHistory } from "../types";

export type RedisHistoryConfig = {
	config?: RedisConfigNodejs;
	client?: Redis;
};

export class RedisHistory implements BaseMessageHistory {
	public client: Redis;

	constructor(config: RedisHistoryConfig) {
		const { config: redisConfig, client } = config;

		if (client) {
			this.client = client;
		} else if (redisConfig) {
			this.client = new Redis(redisConfig);
		} else {
			throw new Error(
				"Redis message stores require either a config object or a pre-configured client."
			);
		}
	}

	async addMessage(params: {
		message: Message;
		sessionId: string;
		sessionTTL?: number;
	}): Promise<void> {
		const { message, sessionId = DEFAULT_CHAT_SESSION_ID, sessionTTL } = params;
		await this.client.lpush(sessionId, JSON.stringify(message));
		if (sessionTTL) {
			await this.client.expire(sessionId, sessionTTL);
		}
	}

	async deleteMessages({ sessionId }: { sessionId: string }): Promise<void> {
		await this.client.del(sessionId);
	}

	async getMessages({
		sessionId = DEFAULT_CHAT_SESSION_ID,
		amount = DEFAULT_HISTORY_LENGTH,
		startIndex = 0,
	}): Promise<Message[]> {
		const endIndex = startIndex + amount - 1;
		const messages = await this.client.lrange<Message>(
			sessionId,
			startIndex,
			endIndex
		);

		return messages.reverse();
	}
}

================================================
FILE: src/server/lib/types.ts
================================================
export type Message = {
	role: "user" | "assistant" | "error"
	content: string;
	id: string
}

export interface BaseMessageHistory {
	addMessage(params: {
		message: Message;
		sessionId: string;
		sessionTTL?: number;
	}): Promise<void>;

	deleteMessages(params: {
		sessionId: string
	}): Promise<void>;

	getMessages(params: {
		sessionId: string;
		amount?: number;
		startIndex?: number;
	}): Promise<Message[]>;
}

================================================
FILE: tailwind.config.ts
================================================
import type { Config } from "tailwindcss";

const config = {
  content: ["./src/**/*.{ts,tsx}"],
  theme: {
    screens: {
      'sm': '640px',
      'md': '768px',
      'lg': '1024px',
      'xl': '1280px',
      '2xl': '1536px',
    },
    extend: {
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
} satisfies Config;

export default config;

================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "lib": ["ESNext", "DOM"],
    "declarationDir": "dist",
    "declaration": true,
    "target": "ESNext",
    "module": "ES2022",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowJs": true,
    "outDir": "./dist",
    "types": ["react/experimental", "node"],
    "jsxImportSource": "react",
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "skipDefaultLibCheck": true,
    "moduleResolution": "Bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,
    "strict": true,
    "skipLibCheck": true,
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false
  },
  "include": ["src/**/*", "./package.json"],
  "exclude": ["node_modules"]
}


================================================
FILE: tsup.config.ts
================================================
import { defineConfig } from "tsup";
import { fixExtensionsPlugin } from "esbuild-fix-imports-plugin";

export default defineConfig([
  {
    entry: ["src/client"],
    outDir: "dist/client",
    external: ["react", "next"],

    // 👇 important: cjs doesn't work well
    format: "esm",
    splitting: false,
    sourcemap: false,
    clean: true,
    dts: true,

    // 👇 important: do not bundle
    bundle: false,
    minify: false,
    treeshake: false,
    injectStyle: true,
    esbuildPlugins: [fixExtensionsPlugin()],
  },
  {
    entry: ["src/server"],
    outDir: "dist/server",
    external: ["react", "next"],

    // 👇 important: cjs doesn't work well
    format: "esm",
    splitting: false,
    sourcemap: false,
    clean: true,
    dts: true,

    // 👇 important: do not bundle
    bundle: false,
    minify: false,
    esbuildPlugins: [fixExtensionsPlugin()],
  },
]);
Download .txt
gitextract_f7i5d7d5/

├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── bun.lockb
├── components.json
├── examples/
│   └── nextjs/
│       ├── .eslintrc.json
│       ├── .gitignore
│       ├── README.md
│       ├── app/
│       │   ├── components/
│       │   │   ├── chat.tsx
│       │   │   ├── features.tsx
│       │   │   ├── footer.tsx
│       │   │   └── header.tsx
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   └── page.tsx
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.mjs
│       ├── prettier.config.js
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       └── vercel.json
├── index.ts
├── package.json
├── playground/
│   ├── .gitignore
│   ├── README.md
│   ├── eslint.config.mjs
│   ├── next.config.ts
│   ├── package.json
│   ├── postcss.config.mjs
│   ├── src/
│   │   └── app/
│   │       ├── globals.css
│   │       ├── layout.tsx
│   │       └── page.tsx
│   ├── tailwind.config.ts
│   └── tsconfig.json
├── postcss.config.mjs
├── src/
│   ├── client/
│   │   ├── components/
│   │   │   ├── chat-component.tsx
│   │   │   ├── lib/
│   │   │   │   └── utils.ts
│   │   │   ├── styles.css
│   │   │   └── ui/
│   │   │       ├── button.tsx
│   │   │       └── scroll-area.tsx
│   │   └── index.ts
│   └── server/
│       ├── actions/
│       │   ├── chat.ts
│       │   └── history.ts
│       ├── constants.ts
│       ├── index.ts
│       └── lib/
│           ├── history/
│           │   ├── get-client.ts
│           │   ├── in-memory.ts
│           │   └── redis.ts
│           └── types.ts
├── tailwind.config.ts
├── tsconfig.json
└── tsup.config.ts
Download .txt
SYMBOL INDEX (32 symbols across 16 files)

FILE: examples/nextjs/app/components/features.tsx
  function Features (line 1) | function Features() {
  function FeatureCard (line 24) | function FeatureCard({

FILE: examples/nextjs/app/components/footer.tsx
  function Footer (line 3) | function Footer() {

FILE: examples/nextjs/app/components/header.tsx
  function Header (line 1) | function Header() {

FILE: examples/nextjs/app/layout.tsx
  function RootLayout (line 23) | function RootLayout({

FILE: examples/nextjs/app/page.tsx
  function Home (line 6) | function Home() {

FILE: playground/src/app/layout.tsx
  function RootLayout (line 20) | function RootLayout({

FILE: playground/src/app/page.tsx
  function Home (line 3) | function Home() {

FILE: src/client/components/chat-component.tsx
  type ChatComponentProps (line 17) | type ChatComponentProps = {

FILE: src/client/components/lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {

FILE: src/client/components/ui/button.tsx
  type ButtonProps (line 28) | interface ButtonProps

FILE: src/server/actions/chat.ts
  type StreamMessage (line 11) | type StreamMessage = {
  method onFinish (line 74) | async onFinish({ text }) {

FILE: src/server/actions/history.ts
  function getHistory (line 8) | async function getHistory(sessionId: string): Promise<{ messages: Messag...
  function deleteHistory (line 18) | async function deleteHistory(sessionId: string): Promise<{ success: bool...

FILE: src/server/constants.ts
  type PromptParameters (line 1) | type PromptParameters = {
  type Prompt (line 7) | type Prompt = ({ question, chatMessages, context }: PromptParameters) =>...
  constant DEFAULT_CHAT_SESSION_ID (line 18) | const DEFAULT_CHAT_SESSION_ID = "upstash-rag-chat-session";
  constant DEFAULT_HISTORY_LENGTH (line 20) | const DEFAULT_HISTORY_LENGTH = 5;

FILE: src/server/lib/history/in-memory.ts
  class InMemoryHistory (line 7) | class InMemoryHistory implements BaseMessageHistory {
    method constructor (line 8) | constructor() {
    method addMessage (line 12) | async addMessage(params: {
    method deleteMessages (line 27) | async deleteMessages({ sessionId }: { sessionId: string }): Promise<vo...
    method getMessages (line 34) | async getMessages({

FILE: src/server/lib/history/redis.ts
  type RedisHistoryConfig (line 5) | type RedisHistoryConfig = {
  class RedisHistory (line 10) | class RedisHistory implements BaseMessageHistory {
    method constructor (line 13) | constructor(config: RedisHistoryConfig) {
    method addMessage (line 27) | async addMessage(params: {
    method deleteMessages (line 39) | async deleteMessages({ sessionId }: { sessionId: string }): Promise<vo...
    method getMessages (line 43) | async getMessages({

FILE: src/server/lib/types.ts
  type Message (line 1) | type Message = {
  type BaseMessageHistory (line 7) | interface BaseMessageHistory {
Condensed preview — 54 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (59K chars).
[
  {
    "path": ".gitignore",
    "chars": 2233,
    "preview": "# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore\n\n# Logs\n\nlogs\n_.log\nnpm-debug.log_\nyar"
  },
  {
    "path": ".prettierrc",
    "chars": 403,
    "preview": "{\n  \"arrowParens\": \"always\",\n  \"bracketSameLine\": false,\n  \"bracketSpacing\": true,\n  \"semi\": true,\n  \"singleQuote\": fals"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2024 Upstash\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "README.md",
    "chars": 5320,
    "preview": "# RAG Chat Component\n\nA customizable Reach chat component that combines Upstash Vector for similarity search, Together A"
  },
  {
    "path": "components.json",
    "chars": 346,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n"
  },
  {
    "path": "examples/nextjs/.eslintrc.json",
    "chars": 61,
    "preview": "{\n  \"extends\": [\"next/core-web-vitals\", \"next/typescript\"]\n}\n"
  },
  {
    "path": "examples/nextjs/.gitignore",
    "chars": 463,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "examples/nextjs/README.md",
    "chars": 1450,
    "preview": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-re"
  },
  {
    "path": "examples/nextjs/app/components/chat.tsx",
    "chars": 136,
    "preview": "\"use client\";\n\nimport { ChatComponent } from \"@upstash/rag-chat-component\";\n\nexport const Chat = () => {\n  return <ChatC"
  },
  {
    "path": "examples/nextjs/app/components/features.tsx",
    "chars": 1136,
    "preview": "export default function Features() {\n  return (\n    <div className=\"sc mt-16 grid gap-4 sm:grid-cols-2\">\n      <FeatureC"
  },
  {
    "path": "examples/nextjs/app/components/footer.tsx",
    "chars": 1041,
    "preview": "import { ArrowUpRight } from \"lucide-react\";\n\nexport default function Footer() {\n  return (\n    <footer className=\"mt-16"
  },
  {
    "path": "examples/nextjs/app/components/header.tsx",
    "chars": 2822,
    "preview": "export default function Header() {\n  return (\n    <header className=\"\">\n      <p className=\"mb-6 inline-block rounded-lg"
  },
  {
    "path": "examples/nextjs/app/globals.css",
    "chars": 467,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@keyframes anim-zoom {\n  0% {\n    width: 64px;\n    height: 6"
  },
  {
    "path": "examples/nextjs/app/layout.tsx",
    "chars": 835,
    "preview": "import type { Metadata } from \"next\";\nimport { Inter, Inter_Tight } from \"next/font/google\";\nimport \"./globals.css\";\n\nco"
  },
  {
    "path": "examples/nextjs/app/page.tsx",
    "chars": 957,
    "preview": "import Features from \"@/app/components/features\";\nimport Footer from \"@/app/components/footer\";\nimport Header from \"@/ap"
  },
  {
    "path": "examples/nextjs/next.config.ts",
    "chars": 133,
    "preview": "import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n  /* config options here */\n};\n\nexport default"
  },
  {
    "path": "examples/nextjs/package.json",
    "chars": 679,
    "preview": "{\n  \"name\": \"nextjs\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next "
  },
  {
    "path": "examples/nextjs/postcss.config.mjs",
    "chars": 135,
    "preview": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n  plugins: {\n    tailwindcss: {},\n  },\n};\n\nexport d"
  },
  {
    "path": "examples/nextjs/prettier.config.js",
    "chars": 448,
    "preview": "module.exports = {\n  arrowParens: \"always\",\n  bracketSameLine: false,\n  bracketSpacing: true,\n  semi: true,\n  singleQuot"
  },
  {
    "path": "examples/nextjs/tailwind.config.ts",
    "chars": 430,
    "preview": "import type { Config } from \"tailwindcss\";\n\nexport default {\n  content: [\n    \"./pages/**/*.{js,ts,jsx,tsx,mdx}\",\n    \"."
  },
  {
    "path": "examples/nextjs/tsconfig.json",
    "chars": 598,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    "
  },
  {
    "path": "examples/nextjs/vercel.json",
    "chars": 73,
    "preview": "{\n  \"functions\": {\n    \"app/**/*\": {\n      \"maxDuration\": 30\n    }\n  }\n}\n"
  },
  {
    "path": "index.ts",
    "chars": 31,
    "preview": "console.log(\"Hello via Bun!\");\n"
  },
  {
    "path": "package.json",
    "chars": 1770,
    "preview": "{\n  \"name\": \"@upstash/rag-chat-component\",\n  \"description\": \"Streaming Chat Component with Persistent History\",\n  \"versi"
  },
  {
    "path": "playground/.gitignore",
    "chars": 480,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "playground/README.md",
    "chars": 1450,
    "preview": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-re"
  },
  {
    "path": "playground/eslint.config.mjs",
    "chars": 393,
    "preview": "import { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { FlatCompat } from \"@eslint/eslintrc\";\n\ncon"
  },
  {
    "path": "playground/next.config.ts",
    "chars": 133,
    "preview": "import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n  /* config options here */\n};\n\nexport default"
  },
  {
    "path": "playground/package.json",
    "chars": 635,
    "preview": "{\n  \"name\": \"playground\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"n"
  },
  {
    "path": "playground/postcss.config.mjs",
    "chars": 135,
    "preview": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n  plugins: {\n    tailwindcss: {},\n  },\n};\n\nexport d"
  },
  {
    "path": "playground/src/app/globals.css",
    "chars": 345,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n:root {\n  --background: #ffffff;\n  --foreground: #171717;\n}\n"
  },
  {
    "path": "playground/src/app/layout.tsx",
    "chars": 689,
    "preview": "import type { Metadata } from \"next\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\nimport \"./globals.css\";\n\ncon"
  },
  {
    "path": "playground/src/app/page.tsx",
    "chars": 325,
    "preview": "import { ChatComponent } from \"@/client/components/chat-component\";\n\nexport default function Home() {\n  return (\n    <ma"
  },
  {
    "path": "playground/tailwind.config.ts",
    "chars": 432,
    "preview": "import type { Config } from \"tailwindcss\";\n\nexport default {\n  content: [\n    \"./src/pages/**/*.{js,ts,jsx,tsx,mdx}\",\n  "
  },
  {
    "path": "playground/tsconfig.json",
    "chars": 639,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    "
  },
  {
    "path": "postcss.config.mjs",
    "chars": 224,
    "preview": "/** @type {import(\"postcss-load-config\").Config} */\nconst config = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer:"
  },
  {
    "path": "src/client/components/chat-component.tsx",
    "chars": 10797,
    "preview": "\"use client\";\n\nimport { ScrollArea } from \"@radix-ui/react-scroll-area\";\n\nimport * as React from \"react\";\nimport { useEf"
  },
  {
    "path": "src/client/components/lib/utils.ts",
    "chars": 168,
    "preview": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: C"
  },
  {
    "path": "src/client/components/styles.css",
    "chars": 59,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
  },
  {
    "path": "src/client/components/ui/button.tsx",
    "chars": 1260,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"cla"
  },
  {
    "path": "src/client/components/ui/scroll-area.tsx",
    "chars": 1667,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\";\n\nimpo"
  },
  {
    "path": "src/client/index.ts",
    "chars": 61,
    "preview": "export { ChatComponent } from \"./components/chat-component\";\n"
  },
  {
    "path": "src/server/actions/chat.ts",
    "chars": 2236,
    "preview": "\"use server\";\n\nimport { Index } from \"@upstash/vector\"\nimport { createOpenAI } from \"@ai-sdk/openai\"\nimport { streamText"
  },
  {
    "path": "src/server/actions/history.ts",
    "chars": 727,
    "preview": "'use server'\n\nimport { getHistoryClient } from \"../lib/history/get-client\";\nimport type { Message } from \"../lib/types\";"
  },
  {
    "path": "src/server/constants.ts",
    "chars": 740,
    "preview": "type PromptParameters = {\n\tchatMessages?: string;\n\tquestion: string;\n\tcontext: string;\n};\n\ntype Prompt = ({ question, ch"
  },
  {
    "path": "src/server/index.ts",
    "chars": 45,
    "preview": "export { serverChat } from \"./actions/chat\";\n"
  },
  {
    "path": "src/server/lib/history/get-client.ts",
    "chars": 474,
    "preview": "import type { BaseMessageHistory } from \"../types\";\nimport { RedisHistory } from \"./redis\";\nimport { InMemoryHistory } f"
  },
  {
    "path": "src/server/lib/history/in-memory.ts",
    "chars": 1340,
    "preview": "import type { Message, BaseMessageHistory } from \"../types\";\nimport { DEFAULT_CHAT_SESSION_ID, DEFAULT_HISTORY_LENGTH } "
  },
  {
    "path": "src/server/lib/history/redis.ts",
    "chars": 1480,
    "preview": "import { Redis, type RedisConfigNodejs } from \"@upstash/redis\";\nimport { DEFAULT_CHAT_SESSION_ID, DEFAULT_HISTORY_LENGTH"
  },
  {
    "path": "src/server/lib/types.ts",
    "chars": 419,
    "preview": "export type Message = {\n\trole: \"user\" | \"assistant\" | \"error\"\n\tcontent: string;\n\tid: string\n}\n\nexport interface BaseMess"
  },
  {
    "path": "tailwind.config.ts",
    "chars": 493,
    "preview": "import type { Config } from \"tailwindcss\";\n\nconst config = {\n  content: [\"./src/**/*.{ts,tsx}\"],\n  theme: {\n    screens:"
  },
  {
    "path": "tsconfig.json",
    "chars": 849,
    "preview": "{\n  \"compilerOptions\": {\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"declarationDir\": \"dist\",\n    \"declaration\": true,\n    \"targe"
  },
  {
    "path": "tsup.config.ts",
    "chars": 887,
    "preview": "import { defineConfig } from \"tsup\";\nimport { fixExtensionsPlugin } from \"esbuild-fix-imports-plugin\";\n\nexport default d"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the upstash/rag-chat-component GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 54 files (51.4 KB), approximately 15.2k tokens, and a symbol index with 32 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!