[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/eslintrc\",\n  \"root\": true,\n  \"extends\": [\n    \"next/core-web-vitals\",\n    \"prettier\",\n    \"plugin:tailwindcss/recommended\"\n  ],\n  \"plugins\": [\"tailwindcss\"],\n  \"rules\": {\n    \"tailwindcss/no-custom-classname\": \"off\"\n  },\n  \"settings\": {\n    \"tailwindcss\": {\n      \"callees\": [\"cn\", \"cva\"],\n      \"config\": \"tailwind.config.js\"\n    }\n  },\n  \"overrides\": [\n    {\n      \"files\": [\"*.ts\", \"*.tsx\"],\n      \"parser\": \"@typescript-eslint/parser\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/funding.yaml",
    "content": "# If you find my open-source work helpful, please consider sponsoring me!\n\ngithub: mckaywrigley\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n.yarn/install-state.gz\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n\n.VSCodeCounter\ntool-schemas\ncustom-prompts\n\nsw.js\nsw.js.map\nworkbox-*.js\nworkbox-*.js.map\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpm run lint:fix && npm run format:write && git add .\n"
  },
  {
    "path": ".nvmrc",
    "content": "v20.11.0\n"
  },
  {
    "path": "README.md",
    "content": "# Chatbot UI\n\nThe open-source AI chat app for everyone.\n\n<img src=\"./public/readme/screenshot.png\" alt=\"Chatbot UI\" width=\"600\">\n\n## Demo\n\nView the latest demo [here](https://x.com/mckaywrigley/status/1738273242283151777?s=20).\n\n## Updates\n\nHey everyone! I've heard your feedback and am working hard on a big update.\n\nThings like simpler deployment, better backend compatibility, and improved mobile layouts are on their way.\n\nBe back soon.\n\n-- Mckay\n\n## Official Hosted Version\n\nUse Chatbot UI without having to host it yourself!\n\nFind the official hosted version of Chatbot UI [here](https://chatbotui.com).\n\n## Sponsor\n\nIf you find Chatbot UI useful, please consider [sponsoring](https://github.com/sponsors/mckaywrigley) me to support my open-source work :)\n\n## Issues\n\nWe restrict \"Issues\" to actual issues related to the codebase.\n\nWe're getting excessive amounts of issues that amount to things like feature requests, cloud provider issues, etc.\n\nIf you are having issues with things like setup, please refer to the \"Help\" section in the \"Discussions\" tab above.\n\nIssues unrelated to the codebase will likely be closed immediately.\n\n## Discussions\n\nWe highly encourage you to participate in the \"Discussions\" tab above!\n\nDiscussions are a great place to ask questions, share ideas, and get help.\n\nOdds are if you have a question, someone else has the same question.\n\n## Legacy Code\n\nChatbot UI was recently updated to its 2.0 version.\n\nThe code for 1.0 can be found on the `legacy` branch.\n\n## Updating\n\nIn your terminal at the root of your local Chatbot UI repository, run:\n\n```bash\nnpm run update\n```\n\nIf you run a hosted instance you'll also need to run:\n\n```bash\nnpm run db-push\n```\n\nto apply the latest migrations to your live database.\n\n## Local Quickstart\n\nFollow these steps to get your own Chatbot UI instance running locally.\n\nYou can watch the full video tutorial [here](https://www.youtube.com/watch?v=9Qq3-7-HNgw).\n\n### 1. Clone the Repo\n\n```bash\ngit clone https://github.com/mckaywrigley/chatbot-ui.git\n```\n\n### 2. Install Dependencies\n\nOpen a terminal in the root directory of your local Chatbot UI repository and run:\n\n```bash\nnpm install\n```\n\n### 3. Install Supabase & Run Locally\n\n#### Why Supabase?\n\nPreviously, we used local browser storage to store data. However, this was not a good solution for a few reasons:\n\n- Security issues\n- Limited storage\n- Limits multi-modal use cases\n\nWe now use Supabase because it's easy to use, it's open-source, it's Postgres, and it has a free tier for hosted instances.\n\nWe will support other providers in the future to give you more options.\n\n#### 1. Install Docker\n\nYou will need to install Docker to run Supabase locally. You can download it [here](https://docs.docker.com/get-docker) for free.\n\n#### 2. Install Supabase CLI\n\n**MacOS/Linux**\n\n```bash\nbrew install supabase/tap/supabase\n```\n\n**Windows**\n\n```bash\nscoop bucket add supabase https://github.com/supabase/scoop-bucket.git\nscoop install supabase\n```\n\n#### 3. Start Supabase\n\nIn your terminal at the root of your local Chatbot UI repository, run:\n\n```bash\nsupabase start\n```\n\n### 4. Fill in Secrets\n\n#### 1. Environment Variables\n\nIn your terminal at the root of your local Chatbot UI repository, run:\n\n```bash\ncp .env.local.example .env.local\n```\n\nGet the required values by running:\n\n```bash\nsupabase status\n```\n\nNote: Use `API URL` from `supabase status` for `NEXT_PUBLIC_SUPABASE_URL`\n\nNow go to your `.env.local` file and fill in the values.\n\nIf the environment variable is set, it will disable the input in the user settings.\n\n#### 2. SQL Setup\n\nIn the 1st migration file `supabase/migrations/20240108234540_setup.sql` you will need to replace 2 values with the values you got above:\n\n- `project_url` (line 53): `http://supabase_kong_chatbotui:8000` (default) can remain unchanged if you don't change your `project_id` in the `config.toml` file\n- `service_role_key` (line 54): You got this value from running `supabase status`\n\nThis prevents issues with storage files not being deleted properly.\n\n### 5. Install Ollama (optional for local models)\n\nFollow the instructions [here](https://github.com/jmorganca/ollama#macos).\n\n### 6. Run app locally\n\nIn your terminal at the root of your local Chatbot UI repository, run:\n\n```bash\nnpm run chat\n```\n\nYour local instance of Chatbot UI should now be running at [http://localhost:3000](http://localhost:3000). Be sure to use a compatible node version (i.e. v18).\n\nYou can view your backend GUI at [http://localhost:54323/project/default/editor](http://localhost:54323/project/default/editor).\n\n## Hosted Quickstart\n\nFollow these steps to get your own Chatbot UI instance running in the cloud.\n\nVideo tutorial coming soon.\n\n### 1. Follow Local Quickstart\n\nRepeat steps 1-4 in \"Local Quickstart\" above.\n\nYou will want separate repositories for your local and hosted instances.\n\nCreate a new repository for your hosted instance of Chatbot UI on GitHub and push your code to it.\n\n### 2. Setup Backend with Supabase\n\n#### 1. Create a new project\n\nGo to [Supabase](https://supabase.com/) and create a new project.\n\n#### 2. Get Project Values\n\nOnce you are in the project dashboard, click on the \"Project Settings\" icon tab on the far bottom left.\n\nHere you will get the values for the following environment variables:\n\n- `Project Ref`: Found in \"General settings\" as \"Reference ID\"\n\n- `Project ID`: Found in the URL of your project dashboard (Ex: https://supabase.com/dashboard/project/<YOUR_PROJECT_ID>/settings/general)\n\nWhile still in \"Settings\" click on the \"API\" text tab on the left.\n\nHere you will get the values for the following environment variables:\n\n- `Project URL`: Found in \"API Settings\" as \"Project URL\"\n\n- `Anon key`: Found in \"Project API keys\" as \"anon public\"\n\n- `Service role key`: Found in \"Project API keys\" as \"service_role\" (Reminder: Treat this like a password!)\n\n#### 3. Configure Auth\n\nNext, click on the \"Authentication\" icon tab on the far left.\n\nIn the text tabs, click on \"Providers\" and make sure \"Email\" is enabled.\n\nWe recommend turning off \"Confirm email\" for your own personal instance.\n\n#### 4. Connect to Hosted DB\n\nOpen up your repository for your hosted instance of Chatbot UI.\n\nIn the 1st migration file `supabase/migrations/20240108234540_setup.sql` you will need to replace 2 values with the values you got above:\n\n- `project_url` (line 53): Use the `Project URL` value from above\n- `service_role_key` (line 54): Use the `Service role key` value from above\n\nNow, open a terminal in the root directory of your local Chatbot UI repository. We will execute a few commands here.\n\nLogin to Supabase by running:\n\n```bash\nsupabase login\n```\n\nNext, link your project by running the following command with the \"Project ID\" you got above:\n\n```bash\nsupabase link --project-ref <project-id>\n```\n\nYour project should now be linked.\n\nFinally, push your database to Supabase by running:\n\n```bash\nsupabase db push\n```\n\nYour hosted database should now be set up!\n\n### 3. Setup Frontend with Vercel\n\nGo to [Vercel](https://vercel.com/) and create a new project.\n\nIn the setup page, import your GitHub repository for your hosted instance of Chatbot UI. Within the project Settings, in the \"Build & Development Settings\" section, switch Framework Preset to \"Next.js\".\n\nIn environment variables, add the following from the values you got above:\n\n- `NEXT_PUBLIC_SUPABASE_URL`\n- `NEXT_PUBLIC_SUPABASE_ANON_KEY`\n- `SUPABASE_SERVICE_ROLE_KEY`\n- `NEXT_PUBLIC_OLLAMA_URL` (only needed when using local Ollama models; default: `http://localhost:11434`)\n\nYou can also add API keys as environment variables.\n\n- `OPENAI_API_KEY`\n- `AZURE_OPENAI_API_KEY`\n- `AZURE_OPENAI_ENDPOINT`\n- `AZURE_GPT_45_VISION_NAME`\n\nFor the full list of environment variables, refer to the '.env.local.example' file. If the environment variables are set for API keys, it will disable the input in the user settings.\n\nClick \"Deploy\" and wait for your frontend to deploy.\n\nOnce deployed, you should be able to use your hosted instance of Chatbot UI via the URL Vercel gives you.\n\n## Contributing\n\nWe are working on a guide for contributing.\n\n## Contact\n\nMessage Mckay on [Twitter/X](https://twitter.com/mckaywrigley)\n"
  },
  {
    "path": "__tests__/lib/openapi-conversion.test.ts",
    "content": "import { openapiToFunctions } from \"@/lib/openapi-conversion\"\n\nconst validSchemaURL = JSON.stringify({\n  openapi: \"3.1.0\",\n  info: {\n    title: \"Get weather data\",\n    description: \"Retrieves current weather data for a location.\",\n    version: \"v1.0.0\"\n  },\n  servers: [\n    {\n      url: \"https://weather.example.com\"\n    }\n  ],\n  paths: {\n    \"/location\": {\n      get: {\n        description: \"Get temperature for a specific location\",\n        operationId: \"GetCurrentWeather\",\n        parameters: [\n          {\n            name: \"location\",\n            in: \"query\",\n            description: \"The city and state to retrieve the weather for\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          }\n        ]\n      }\n    },\n    \"/summary\": {\n      get: {\n        description: \"Get description of weather for a specific location\",\n        operationId: \"GetWeatherSummary\",\n        parameters: [\n          {\n            name: \"location\",\n            in: \"query\",\n            description: \"The city and state to retrieve the summary for\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          }\n        ]\n      }\n    }\n  }\n})\n\ndescribe(\"extractOpenapiData for url\", () => {\n  it(\"should parse a valid OpenAPI url schema\", async () => {\n    const { info, routes, functions } = await openapiToFunctions(\n      JSON.parse(validSchemaURL)\n    )\n\n    expect(info.title).toBe(\"Get weather data\")\n    expect(info.description).toBe(\n      \"Retrieves current weather data for a location.\"\n    )\n    expect(info.server).toBe(\"https://weather.example.com\")\n\n    expect(routes).toHaveLength(2)\n\n    expect(functions).toHaveLength(2)\n    expect(functions[0].function.name).toBe(\"GetCurrentWeather\")\n    expect(functions[1].function.name).toBe(\"GetWeatherSummary\")\n  })\n})\n\nconst validSchemaBody = JSON.stringify({\n  openapi: \"3.1.0\",\n  info: {\n    title: \"Get weather data\",\n    description: \"Retrieves current weather data for a location.\",\n    version: \"v1.0.0\"\n  },\n  servers: [\n    {\n      url: \"https://weather.example.com\"\n    }\n  ],\n  paths: {\n    \"/location\": {\n      post: {\n        description: \"Get temperature for a specific location\",\n        operationId: \"GetCurrentWeather\",\n        requestBody: {\n          required: true,\n          content: {\n            \"application/json\": {\n              schema: {\n                type: \"object\",\n                properties: {\n                  location: {\n                    type: \"string\",\n                    description:\n                      \"The city and state to retrieve the weather for\",\n                    example: \"New York, NY\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n})\n\ndescribe(\"extractOpenapiData for body\", () => {\n  it(\"should parse a valid OpenAPI body schema\", async () => {\n    const { info, routes, functions } = await openapiToFunctions(\n      JSON.parse(validSchemaBody)\n    )\n\n    expect(info.title).toBe(\"Get weather data\")\n    expect(info.description).toBe(\n      \"Retrieves current weather data for a location.\"\n    )\n    expect(info.server).toBe(\"https://weather.example.com\")\n\n    expect(routes).toHaveLength(1)\n    expect(routes[0].path).toBe(\"/location\")\n    expect(routes[0].method).toBe(\"post\")\n    expect(routes[0].operationId).toBe(\"GetCurrentWeather\")\n\n    expect(functions).toHaveLength(1)\n    expect(\n      functions[0].function.parameters.properties.requestBody.properties\n        .location.type\n    ).toBe(\"string\")\n    expect(\n      functions[0].function.parameters.properties.requestBody.properties\n        .location.description\n    ).toBe(\"The city and state to retrieve the weather for\")\n  })\n})\n\nconst validSchemaBody2 = JSON.stringify({\n  openapi: \"3.1.0\",\n  info: {\n    title: \"Polygon.io Stock and Crypto Data API\",\n    description:\n      \"API schema for accessing stock and crypto data from Polygon.io.\",\n    version: \"1.0.0\"\n  },\n  servers: [\n    {\n      url: \"https://api.polygon.io\"\n    }\n  ],\n  paths: {\n    \"/v1/open-close/{stocksTicker}/{date}\": {\n      get: {\n        summary: \"Get Stock Daily Open and Close\",\n        description: \"Get the daily open and close for a specific stock.\",\n        operationId: \"getStockDailyOpenClose\",\n        parameters: [\n          {\n            name: \"stocksTicker\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          },\n          {\n            name: \"date\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\",\n              format: \"date\"\n            }\n          }\n        ]\n      }\n    },\n    \"/v2/aggs/ticker/{stocksTicker}/prev\": {\n      get: {\n        summary: \"Get Stock Previous Close\",\n        description: \"Get the previous closing data for a specific stock.\",\n        operationId: \"getStockPreviousClose\",\n        parameters: [\n          {\n            name: \"stocksTicker\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          }\n        ]\n      }\n    },\n    \"/v3/trades/{stockTicker}\": {\n      get: {\n        summary: \"Get Stock Trades\",\n        description: \"Retrieve trades for a specific stock.\",\n        operationId: \"getStockTrades\",\n        parameters: [\n          {\n            name: \"stockTicker\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          }\n        ]\n      }\n    },\n    \"/v3/trades/{optionsTicker}\": {\n      get: {\n        summary: \"Get Options Trades\",\n        description: \"Retrieve trades for a specific options ticker.\",\n        operationId: \"getOptionsTrades\",\n        parameters: [\n          {\n            name: \"optionsTicker\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          }\n        ]\n      }\n    },\n    \"/v2/last/trade/{optionsTicker}\": {\n      get: {\n        summary: \"Get Last Options Trade\",\n        description: \"Get the last trade for a specific options ticker.\",\n        operationId: \"getLastOptionsTrade\",\n        parameters: [\n          {\n            name: \"optionsTicker\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          }\n        ]\n      }\n    },\n    \"/v1/open-close/crypto/{from}/{to}/{date}\": {\n      get: {\n        summary: \"Get Crypto Daily Open and Close\",\n        description:\n          \"Get daily open and close data for a specific cryptocurrency.\",\n        operationId: \"getCryptoDailyOpenClose\",\n        parameters: [\n          {\n            name: \"from\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          },\n          {\n            name: \"to\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          },\n          {\n            name: \"date\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\",\n              format: \"date\"\n            }\n          }\n        ]\n      }\n    },\n    \"/v2/aggs/ticker/{cryptoTicker}/prev\": {\n      get: {\n        summary: \"Get Crypto Previous Close\",\n        description:\n          \"Get the previous closing data for a specific cryptocurrency.\",\n        operationId: \"getCryptoPreviousClose\",\n        parameters: [\n          {\n            name: \"cryptoTicker\",\n            in: \"path\",\n            required: true,\n            schema: {\n              type: \"string\"\n            }\n          }\n        ]\n      }\n    }\n  },\n  components: {\n    securitySchemes: {\n      BearerAuth: {\n        type: \"http\",\n        scheme: \"bearer\",\n        bearerFormat: \"API Key\"\n      }\n    }\n  },\n  security: [\n    {\n      BearerAuth: []\n    }\n  ]\n})\n\ndescribe(\"extractOpenapiData for body 2\", () => {\n  it(\"should parse a valid OpenAPI body schema for body 2\", async () => {\n    const { info, routes, functions } = await openapiToFunctions(\n      JSON.parse(validSchemaBody2)\n    )\n\n    expect(info.title).toBe(\"Polygon.io Stock and Crypto Data API\")\n    expect(info.description).toBe(\n      \"API schema for accessing stock and crypto data from Polygon.io.\"\n    )\n    expect(info.server).toBe(\"https://api.polygon.io\")\n\n    expect(routes).toHaveLength(7)\n    expect(routes[0].path).toBe(\"/v1/open-close/{stocksTicker}/{date}\")\n    expect(routes[0].method).toBe(\"get\")\n    expect(routes[0].operationId).toBe(\"getStockDailyOpenClose\")\n\n    expect(functions[0].function.parameters.properties).toHaveProperty(\n      \"stocksTicker\"\n    )\n    expect(functions[0].function.parameters.properties.stocksTicker.type).toBe(\n      \"string\"\n    )\n    expect(\n      functions[0].function.parameters.properties.stocksTicker\n    ).toHaveProperty(\"required\", true)\n    expect(functions[0].function.parameters.properties).toHaveProperty(\"date\")\n    expect(functions[0].function.parameters.properties.date.type).toBe(\"string\")\n    expect(functions[0].function.parameters.properties.date).toHaveProperty(\n      \"format\",\n      \"date\"\n    )\n    expect(functions[0].function.parameters.properties.date).toHaveProperty(\n      \"required\",\n      true\n    )\n    expect(routes[1].path).toBe(\"/v2/aggs/ticker/{stocksTicker}/prev\")\n    expect(routes[1].method).toBe(\"get\")\n    expect(routes[1].operationId).toBe(\"getStockPreviousClose\")\n    expect(functions[1].function.parameters.properties).toHaveProperty(\n      \"stocksTicker\"\n    )\n    expect(functions[1].function.parameters.properties.stocksTicker.type).toBe(\n      \"string\"\n    )\n    expect(\n      functions[1].function.parameters.properties.stocksTicker\n    ).toHaveProperty(\"required\", true)\n  })\n})\n"
  },
  {
    "path": "__tests__/playwright-test/.gitignore",
    "content": "node_modules/\n/test-results/\n/playwright-report/\n/blob-report/\n/playwright/.cache/\n"
  },
  {
    "path": "__tests__/playwright-test/package.json",
    "content": "{\n  \"name\": \"playwright-test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"integration\": \"playwright test\", \n    \"integration:open\": \"playwright test --ui\",\n    \"integration:codegen\": \"playwright codegen\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"@playwright/test\": \"^1.41.2\",\n    \"@types/node\": \"^20.11.20\"\n  }\n}\n"
  },
  {
    "path": "__tests__/playwright-test/playwright.config.ts",
    "content": "import { defineConfig, devices } from '@playwright/test';\n\n/**\n * Read environment variables from file.\n * https://github.com/motdotla/dotenv\n */\n// require('dotenv').config();\n\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nexport default defineConfig({\n  testDir: './tests',\n  /* Run tests in files in parallel */\n  fullyParallel: true,\n  /* Fail the build on CI if you accidentally left test.only in the source code. */\n  forbidOnly: !!process.env.CI,\n  /* Retry on CI only */\n  retries: process.env.CI ? 2 : 0,\n  /* Opt out of parallel tests on CI. */\n  workers: process.env.CI ? 1 : undefined,\n  /* Reporter to use. See https://playwright.dev/docs/test-reporters */\n  reporter: 'html',\n  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n  use: {\n    /* Base URL to use in actions like `await page.goto('/')`. */\n    // baseURL: 'http://127.0.0.1:3000',\n\n    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */\n    trace: 'on-first-retry',\n  },\n\n  /* Configure projects for major browsers */\n  projects: [\n    {\n      name: 'chromium',\n      use: { ...devices['Desktop Chrome'] },\n    },\n\n    {\n      name: 'firefox',\n      use: { ...devices['Desktop Firefox'] },\n    },\n\n    {\n      name: 'webkit',\n      use: { ...devices['Desktop Safari'] },\n    },\n\n    /* Test against mobile viewports. */\n    // {\n    //   name: 'Mobile Chrome',\n    //   use: { ...devices['Pixel 5'] },\n    // },\n    // {\n    //   name: 'Mobile Safari',\n    //   use: { ...devices['iPhone 12'] },\n    // },\n\n    /* Test against branded browsers. */\n    // {\n    //   name: 'Microsoft Edge',\n    //   use: { ...devices['Desktop Edge'], channel: 'msedge' },\n    // },\n    // {\n    //   name: 'Google Chrome',\n    //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },\n    // },\n  ],\n\n  /* Run your local dev server before starting the tests */\n  // webServer: {\n  //   command: 'npm run start',\n  //   url: 'http://127.0.0.1:3000',\n  //   reuseExistingServer: !process.env.CI,\n  // },\n});\n"
  },
  {
    "path": "__tests__/playwright-test/tests/login.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\ntest('start chatting is displayed', async ({ page }) => {\n  await page.goto('http://localhost:3000/');\n\n  //expect the start chatting link to be visible\n  await expect (page.getByRole('link', { name: 'Start Chatting' })).toBeVisible();\n});\n\ntest('No password error message', async ({ page }) => {\n  await page.goto('http://localhost:3000/login');\n  //fill in dummy email\n  await page.getByPlaceholder('you@example.com').fill('dummyemail@gmail.com');\n  await page.getByRole('button', { name: 'Login' }).click();\n  //wait for netwrok to be idle\n  await page.waitForLoadState('networkidle');\n  //validate that correct message is shown to the user\n  await expect(page.getByText('Invalid login credentials')).toBeVisible();\n  \n});\ntest('No password for signup', async ({ page }) => {\n  await page.goto('http://localhost:3000/login');\n  \n  await page.getByPlaceholder('you@example.com').fill('dummyEmail@Gmail.com');\n  await page.getByRole('button', { name: 'Sign Up' }).click();\n  //validate appropriate error is thrown for missing password when signing up\n  await expect(page.getByText('Signup requires a valid')).toBeVisible();\n});\ntest('invalid username for signup', async ({ page }) => {\n  await page.goto('http://localhost:3000/login');\n  \n  await page.getByPlaceholder('you@example.com').fill('dummyEmail');\n  await page.getByPlaceholder('••••••••').fill('dummypassword');\n  await page.getByRole('button', { name: 'Sign Up' }).click();\n  //validate appropriate error is thrown for invalid username when signing up\n  await expect(page.getByText('Unable to validate email')).toBeVisible();\n});\ntest('password reset message', async ({ page }) => {\n  await page.goto('http://localhost:3000/login');\n  await page.getByPlaceholder('you@example.com').fill('demo@gmail.com');\n  await page.getByRole('button', { name: 'Reset' }).click();\n  //validate appropriate message is shown\n  await expect(page.getByText('Check email to reset password')).toBeVisible();\n});\n\n//more tests can be added here"
  },
  {
    "path": "app/[locale]/[workspaceid]/chat/[chatid]/page.tsx",
    "content": "\"use client\"\n\nimport { ChatUI } from \"@/components/chat/chat-ui\"\n\nexport default function ChatIDPage() {\n  return <ChatUI />\n}\n"
  },
  {
    "path": "app/[locale]/[workspaceid]/chat/page.tsx",
    "content": "\"use client\"\n\nimport { ChatHelp } from \"@/components/chat/chat-help\"\nimport { useChatHandler } from \"@/components/chat/chat-hooks/use-chat-handler\"\nimport { ChatInput } from \"@/components/chat/chat-input\"\nimport { ChatSettings } from \"@/components/chat/chat-settings\"\nimport { ChatUI } from \"@/components/chat/chat-ui\"\nimport { QuickSettings } from \"@/components/chat/quick-settings\"\nimport { Brand } from \"@/components/ui/brand\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { useTheme } from \"next-themes\"\nimport { useContext } from \"react\"\n\nexport default function ChatPage() {\n  useHotkey(\"o\", () => handleNewChat())\n  useHotkey(\"l\", () => {\n    handleFocusChatInput()\n  })\n\n  const { chatMessages } = useContext(ChatbotUIContext)\n\n  const { handleNewChat, handleFocusChatInput } = useChatHandler()\n\n  const { theme } = useTheme()\n\n  return (\n    <>\n      {chatMessages.length === 0 ? (\n        <div className=\"relative flex h-full flex-col items-center justify-center\">\n          <div className=\"top-50% left-50% -translate-x-50% -translate-y-50% absolute mb-20\">\n            <Brand theme={theme === \"dark\" ? \"dark\" : \"light\"} />\n          </div>\n\n          <div className=\"absolute left-2 top-2\">\n            <QuickSettings />\n          </div>\n\n          <div className=\"absolute right-2 top-2\">\n            <ChatSettings />\n          </div>\n\n          <div className=\"flex grow flex-col items-center justify-center\" />\n\n          <div className=\"w-full min-w-[300px] items-end px-2 pb-3 pt-0 sm:w-[600px] sm:pb-8 sm:pt-5 md:w-[700px] lg:w-[700px] xl:w-[800px]\">\n            <ChatInput />\n          </div>\n\n          <div className=\"absolute bottom-2 right-2 hidden md:block lg:bottom-4 lg:right-4\">\n            <ChatHelp />\n          </div>\n        </div>\n      ) : (\n        <ChatUI />\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "app/[locale]/[workspaceid]/layout.tsx",
    "content": "\"use client\"\n\nimport { Dashboard } from \"@/components/ui/dashboard\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { getAssistantWorkspacesByWorkspaceId } from \"@/db/assistants\"\nimport { getChatsByWorkspaceId } from \"@/db/chats\"\nimport { getCollectionWorkspacesByWorkspaceId } from \"@/db/collections\"\nimport { getFileWorkspacesByWorkspaceId } from \"@/db/files\"\nimport { getFoldersByWorkspaceId } from \"@/db/folders\"\nimport { getModelWorkspacesByWorkspaceId } from \"@/db/models\"\nimport { getPresetWorkspacesByWorkspaceId } from \"@/db/presets\"\nimport { getPromptWorkspacesByWorkspaceId } from \"@/db/prompts\"\nimport { getAssistantImageFromStorage } from \"@/db/storage/assistant-images\"\nimport { getToolWorkspacesByWorkspaceId } from \"@/db/tools\"\nimport { getWorkspaceById } from \"@/db/workspaces\"\nimport { convertBlobToBase64 } from \"@/lib/blob-to-b64\"\nimport { supabase } from \"@/lib/supabase/browser-client\"\nimport { LLMID } from \"@/types\"\nimport { useParams, useRouter, useSearchParams } from \"next/navigation\"\nimport { ReactNode, useContext, useEffect, useState } from \"react\"\nimport Loading from \"../loading\"\n\ninterface WorkspaceLayoutProps {\n  children: ReactNode\n}\n\nexport default function WorkspaceLayout({ children }: WorkspaceLayoutProps) {\n  const router = useRouter()\n\n  const params = useParams()\n  const searchParams = useSearchParams()\n  const workspaceId = params.workspaceid as string\n\n  const {\n    setChatSettings,\n    setAssistants,\n    setAssistantImages,\n    setChats,\n    setCollections,\n    setFolders,\n    setFiles,\n    setPresets,\n    setPrompts,\n    setTools,\n    setModels,\n    selectedWorkspace,\n    setSelectedWorkspace,\n    setSelectedChat,\n    setChatMessages,\n    setUserInput,\n    setIsGenerating,\n    setFirstTokenReceived,\n    setChatFiles,\n    setChatImages,\n    setNewMessageFiles,\n    setNewMessageImages,\n    setShowFilesDisplay\n  } = useContext(ChatbotUIContext)\n\n  const [loading, setLoading] = useState(true)\n\n  useEffect(() => {\n    ;(async () => {\n      const session = (await supabase.auth.getSession()).data.session\n\n      if (!session) {\n        return router.push(\"/login\")\n      } else {\n        await fetchWorkspaceData(workspaceId)\n      }\n    })()\n  }, [])\n\n  useEffect(() => {\n    ;(async () => await fetchWorkspaceData(workspaceId))()\n\n    setUserInput(\"\")\n    setChatMessages([])\n    setSelectedChat(null)\n\n    setIsGenerating(false)\n    setFirstTokenReceived(false)\n\n    setChatFiles([])\n    setChatImages([])\n    setNewMessageFiles([])\n    setNewMessageImages([])\n    setShowFilesDisplay(false)\n  }, [workspaceId])\n\n  const fetchWorkspaceData = async (workspaceId: string) => {\n    setLoading(true)\n\n    const workspace = await getWorkspaceById(workspaceId)\n    setSelectedWorkspace(workspace)\n\n    const assistantData = await getAssistantWorkspacesByWorkspaceId(workspaceId)\n    setAssistants(assistantData.assistants)\n\n    for (const assistant of assistantData.assistants) {\n      let url = \"\"\n\n      if (assistant.image_path) {\n        url = (await getAssistantImageFromStorage(assistant.image_path)) || \"\"\n      }\n\n      if (url) {\n        const response = await fetch(url)\n        const blob = await response.blob()\n        const base64 = await convertBlobToBase64(blob)\n\n        setAssistantImages(prev => [\n          ...prev,\n          {\n            assistantId: assistant.id,\n            path: assistant.image_path,\n            base64,\n            url\n          }\n        ])\n      } else {\n        setAssistantImages(prev => [\n          ...prev,\n          {\n            assistantId: assistant.id,\n            path: assistant.image_path,\n            base64: \"\",\n            url\n          }\n        ])\n      }\n    }\n\n    const chats = await getChatsByWorkspaceId(workspaceId)\n    setChats(chats)\n\n    const collectionData =\n      await getCollectionWorkspacesByWorkspaceId(workspaceId)\n    setCollections(collectionData.collections)\n\n    const folders = await getFoldersByWorkspaceId(workspaceId)\n    setFolders(folders)\n\n    const fileData = await getFileWorkspacesByWorkspaceId(workspaceId)\n    setFiles(fileData.files)\n\n    const presetData = await getPresetWorkspacesByWorkspaceId(workspaceId)\n    setPresets(presetData.presets)\n\n    const promptData = await getPromptWorkspacesByWorkspaceId(workspaceId)\n    setPrompts(promptData.prompts)\n\n    const toolData = await getToolWorkspacesByWorkspaceId(workspaceId)\n    setTools(toolData.tools)\n\n    const modelData = await getModelWorkspacesByWorkspaceId(workspaceId)\n    setModels(modelData.models)\n\n    setChatSettings({\n      model: (searchParams.get(\"model\") ||\n        workspace?.default_model ||\n        \"gpt-4-1106-preview\") as LLMID,\n      prompt:\n        workspace?.default_prompt ||\n        \"You are a friendly, helpful AI assistant.\",\n      temperature: workspace?.default_temperature || 0.5,\n      contextLength: workspace?.default_context_length || 4096,\n      includeProfileContext: workspace?.include_profile_context || true,\n      includeWorkspaceInstructions:\n        workspace?.include_workspace_instructions || true,\n      embeddingsProvider:\n        (workspace?.embeddings_provider as \"openai\" | \"local\") || \"openai\"\n    })\n\n    setLoading(false)\n  }\n\n  if (loading) {\n    return <Loading />\n  }\n\n  return <Dashboard>{children}</Dashboard>\n}\n"
  },
  {
    "path": "app/[locale]/[workspaceid]/page.tsx",
    "content": "\"use client\"\n\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { useContext } from \"react\"\n\nexport default function WorkspacePage() {\n  const { selectedWorkspace } = useContext(ChatbotUIContext)\n\n  return (\n    <div className=\"flex h-screen w-full flex-col items-center justify-center\">\n      <div className=\"text-4xl\">{selectedWorkspace?.name}</div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/[locale]/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n::-webkit-scrollbar-track {\n  background-color: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background-color: #ccc;\n  border-radius: 10px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background-color: #aaa;\n}\n\n::-webkit-scrollbar-track:hover {\n  background-color: #f2f2f2;\n}\n\n::-webkit-scrollbar-corner {\n  background-color: transparent;\n}\n\n::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 0 0% 3.9%;\n\n    --muted: 0 0% 96.1%;\n    --muted-foreground: 0 0% 45.1%;\n\n    --popover: 0 0% 100%;\n    --popover-foreground: 0 0% 3.9%;\n\n    --card: 0 0% 100%;\n    --card-foreground: 0 0% 3.9%;\n\n    --border: 0 0% 89.8%;\n    --input: 0 0% 89.8%;\n\n    --primary: 0 0% 9%;\n    --primary-foreground: 0 0% 98%;\n\n    --secondary: 0 0% 96.1%;\n    --secondary-foreground: 0 0% 9%;\n\n    --accent: 0 0% 96.1%;\n    --accent-foreground: 0 0% 9%;\n\n    --destructive: 0 84.2% 60.2%;\n    --destructive-foreground: 0 0% 98%;\n\n    --ring: 0 0% 63.9%;\n\n    --radius: 0.5rem;\n  }\n\n  .dark {\n    --background: 0 0% 3.9%;\n    --foreground: 0 0% 98%;\n\n    --muted: 0 0% 14.9%;\n    --muted-foreground: 0 0% 63.9%;\n\n    --popover: 0 0% 3.9%;\n    --popover-foreground: 0 0% 98%;\n\n    --card: 0 0% 3.9%;\n    --card-foreground: 0 0% 98%;\n\n    --border: 0 0% 14.9%;\n    --input: 0 0% 14.9%;\n\n    --primary: 0 0% 98%;\n    --primary-foreground: 0 0% 9%;\n\n    --secondary: 0 0% 14.9%;\n    --secondary-foreground: 0 0% 98%;\n\n    --accent: 0 0% 14.9%;\n    --accent-foreground: 0 0% 98%;\n\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 0 85.7% 97.3%;\n\n    --ring: 0 0% 14.9%;\n  }\n}\n\n@layer base {\n  * {\n    @apply border-border;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n"
  },
  {
    "path": "app/[locale]/help/page.tsx",
    "content": "export default function HelpPage() {\n  return (\n    <div className=\"size-screen flex flex-col items-center justify-center\">\n      <div className=\"text-4xl\">Help under construction.</div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/[locale]/layout.tsx",
    "content": "import { Toaster } from \"@/components/ui/sonner\"\nimport { GlobalState } from \"@/components/utility/global-state\"\nimport { Providers } from \"@/components/utility/providers\"\nimport TranslationsProvider from \"@/components/utility/translations-provider\"\nimport initTranslations from \"@/lib/i18n\"\nimport { Database } from \"@/supabase/types\"\nimport { createServerClient } from \"@supabase/ssr\"\nimport { Metadata, Viewport } from \"next\"\nimport { Inter } from \"next/font/google\"\nimport { cookies } from \"next/headers\"\nimport { ReactNode } from \"react\"\nimport \"./globals.css\"\n\nconst inter = Inter({ subsets: [\"latin\"] })\nconst APP_NAME = \"Chatbot UI\"\nconst APP_DEFAULT_TITLE = \"Chatbot UI\"\nconst APP_TITLE_TEMPLATE = \"%s - Chatbot UI\"\nconst APP_DESCRIPTION = \"Chabot UI PWA!\"\n\ninterface RootLayoutProps {\n  children: ReactNode\n  params: {\n    locale: string\n  }\n}\n\nexport const metadata: Metadata = {\n  applicationName: APP_NAME,\n  title: {\n    default: APP_DEFAULT_TITLE,\n    template: APP_TITLE_TEMPLATE\n  },\n  description: APP_DESCRIPTION,\n  manifest: \"/manifest.json\",\n  appleWebApp: {\n    capable: true,\n    statusBarStyle: \"black\",\n    title: APP_DEFAULT_TITLE\n    // startUpImage: [],\n  },\n  formatDetection: {\n    telephone: false\n  },\n  openGraph: {\n    type: \"website\",\n    siteName: APP_NAME,\n    title: {\n      default: APP_DEFAULT_TITLE,\n      template: APP_TITLE_TEMPLATE\n    },\n    description: APP_DESCRIPTION\n  },\n  twitter: {\n    card: \"summary\",\n    title: {\n      default: APP_DEFAULT_TITLE,\n      template: APP_TITLE_TEMPLATE\n    },\n    description: APP_DESCRIPTION\n  }\n}\n\nexport const viewport: Viewport = {\n  themeColor: \"#000000\"\n}\n\nconst i18nNamespaces = [\"translation\"]\n\nexport default async function RootLayout({\n  children,\n  params: { locale }\n}: RootLayoutProps) {\n  const cookieStore = cookies()\n  const supabase = createServerClient<Database>(\n    process.env.NEXT_PUBLIC_SUPABASE_URL!,\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n    {\n      cookies: {\n        get(name: string) {\n          return cookieStore.get(name)?.value\n        }\n      }\n    }\n  )\n  const session = (await supabase.auth.getSession()).data.session\n\n  const { t, resources } = await initTranslations(locale, i18nNamespaces)\n\n  return (\n    <html lang=\"en\" suppressHydrationWarning>\n      <body className={inter.className}>\n        <Providers attribute=\"class\" defaultTheme=\"dark\">\n          <TranslationsProvider\n            namespaces={i18nNamespaces}\n            locale={locale}\n            resources={resources}\n          >\n            <Toaster richColors position=\"top-center\" duration={3000} />\n            <div className=\"bg-background text-foreground flex h-dvh flex-col items-center overflow-x-auto\">\n              {session ? <GlobalState>{children}</GlobalState> : children}\n            </div>\n          </TranslationsProvider>\n        </Providers>\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "app/[locale]/loading.tsx",
    "content": "import { IconLoader2 } from \"@tabler/icons-react\"\n\nexport default function Loading() {\n  return (\n    <div className=\"flex size-full flex-col items-center justify-center\">\n      <IconLoader2 className=\"mt-4 size-12 animate-spin\" />\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/[locale]/login/page.tsx",
    "content": "import { Brand } from \"@/components/ui/brand\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { SubmitButton } from \"@/components/ui/submit-button\"\nimport { createClient } from \"@/lib/supabase/server\"\nimport { Database } from \"@/supabase/types\"\nimport { createServerClient } from \"@supabase/ssr\"\nimport { get } from \"@vercel/edge-config\"\nimport { Metadata } from \"next\"\nimport { cookies, headers } from \"next/headers\"\nimport { redirect } from \"next/navigation\"\n\nexport const metadata: Metadata = {\n  title: \"Login\"\n}\n\nexport default async function Login({\n  searchParams\n}: {\n  searchParams: { message: string }\n}) {\n  const cookieStore = cookies()\n  const supabase = createServerClient<Database>(\n    process.env.NEXT_PUBLIC_SUPABASE_URL!,\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n    {\n      cookies: {\n        get(name: string) {\n          return cookieStore.get(name)?.value\n        }\n      }\n    }\n  )\n  const session = (await supabase.auth.getSession()).data.session\n\n  if (session) {\n    const { data: homeWorkspace, error } = await supabase\n      .from(\"workspaces\")\n      .select(\"*\")\n      .eq(\"user_id\", session.user.id)\n      .eq(\"is_home\", true)\n      .single()\n\n    if (!homeWorkspace) {\n      throw new Error(error.message)\n    }\n\n    return redirect(`/${homeWorkspace.id}/chat`)\n  }\n\n  const signIn = async (formData: FormData) => {\n    \"use server\"\n\n    const email = formData.get(\"email\") as string\n    const password = formData.get(\"password\") as string\n    const cookieStore = cookies()\n    const supabase = createClient(cookieStore)\n\n    const { data, error } = await supabase.auth.signInWithPassword({\n      email,\n      password\n    })\n\n    if (error) {\n      return redirect(`/login?message=${error.message}`)\n    }\n\n    const { data: homeWorkspace, error: homeWorkspaceError } = await supabase\n      .from(\"workspaces\")\n      .select(\"*\")\n      .eq(\"user_id\", data.user.id)\n      .eq(\"is_home\", true)\n      .single()\n\n    if (!homeWorkspace) {\n      throw new Error(\n        homeWorkspaceError?.message || \"An unexpected error occurred\"\n      )\n    }\n\n    return redirect(`/${homeWorkspace.id}/chat`)\n  }\n\n  const getEnvVarOrEdgeConfigValue = async (name: string) => {\n    \"use server\"\n    if (process.env.EDGE_CONFIG) {\n      return await get<string>(name)\n    }\n\n    return process.env[name]\n  }\n\n  const signUp = async (formData: FormData) => {\n    \"use server\"\n\n    const email = formData.get(\"email\") as string\n    const password = formData.get(\"password\") as string\n\n    const emailDomainWhitelistPatternsString = await getEnvVarOrEdgeConfigValue(\n      \"EMAIL_DOMAIN_WHITELIST\"\n    )\n    const emailDomainWhitelist = emailDomainWhitelistPatternsString?.trim()\n      ? emailDomainWhitelistPatternsString?.split(\",\")\n      : []\n    const emailWhitelistPatternsString =\n      await getEnvVarOrEdgeConfigValue(\"EMAIL_WHITELIST\")\n    const emailWhitelist = emailWhitelistPatternsString?.trim()\n      ? emailWhitelistPatternsString?.split(\",\")\n      : []\n\n    // If there are whitelist patterns, check if the email is allowed to sign up\n    if (emailDomainWhitelist.length > 0 || emailWhitelist.length > 0) {\n      const domainMatch = emailDomainWhitelist?.includes(email.split(\"@\")[1])\n      const emailMatch = emailWhitelist?.includes(email)\n      if (!domainMatch && !emailMatch) {\n        return redirect(\n          `/login?message=Email ${email} is not allowed to sign up.`\n        )\n      }\n    }\n\n    const cookieStore = cookies()\n    const supabase = createClient(cookieStore)\n\n    const { error } = await supabase.auth.signUp({\n      email,\n      password,\n      options: {\n        // USE IF YOU WANT TO SEND EMAIL VERIFICATION, ALSO CHANGE TOML FILE\n        // emailRedirectTo: `${origin}/auth/callback`\n      }\n    })\n\n    if (error) {\n      console.error(error)\n      return redirect(`/login?message=${error.message}`)\n    }\n\n    return redirect(\"/setup\")\n\n    // USE IF YOU WANT TO SEND EMAIL VERIFICATION, ALSO CHANGE TOML FILE\n    // return redirect(\"/login?message=Check email to continue sign in process\")\n  }\n\n  const handleResetPassword = async (formData: FormData) => {\n    \"use server\"\n\n    const origin = headers().get(\"origin\")\n    const email = formData.get(\"email\") as string\n    const cookieStore = cookies()\n    const supabase = createClient(cookieStore)\n\n    const { error } = await supabase.auth.resetPasswordForEmail(email, {\n      redirectTo: `${origin}/auth/callback?next=/login/password`\n    })\n\n    if (error) {\n      return redirect(`/login?message=${error.message}`)\n    }\n\n    return redirect(\"/login?message=Check email to reset password\")\n  }\n\n  return (\n    <div className=\"flex w-full flex-1 flex-col justify-center gap-2 px-8 sm:max-w-md\">\n      <form\n        className=\"animate-in text-foreground flex w-full flex-1 flex-col justify-center gap-2\"\n        action={signIn}\n      >\n        <Brand />\n\n        <Label className=\"text-md mt-4\" htmlFor=\"email\">\n          Email\n        </Label>\n        <Input\n          className=\"mb-3 rounded-md border bg-inherit px-4 py-2\"\n          name=\"email\"\n          placeholder=\"you@example.com\"\n          required\n        />\n\n        <Label className=\"text-md\" htmlFor=\"password\">\n          Password\n        </Label>\n        <Input\n          className=\"mb-6 rounded-md border bg-inherit px-4 py-2\"\n          type=\"password\"\n          name=\"password\"\n          placeholder=\"••••••••\"\n        />\n\n        <SubmitButton className=\"mb-2 rounded-md bg-blue-700 px-4 py-2 text-white\">\n          Login\n        </SubmitButton>\n\n        <SubmitButton\n          formAction={signUp}\n          className=\"border-foreground/20 mb-2 rounded-md border px-4 py-2\"\n        >\n          Sign Up\n        </SubmitButton>\n\n        <div className=\"text-muted-foreground mt-1 flex justify-center text-sm\">\n          <span className=\"mr-1\">Forgot your password?</span>\n          <button\n            formAction={handleResetPassword}\n            className=\"text-primary ml-1 underline hover:opacity-80\"\n          >\n            Reset\n          </button>\n        </div>\n\n        {searchParams?.message && (\n          <p className=\"bg-foreground/10 text-foreground mt-4 p-4 text-center\">\n            {searchParams.message}\n          </p>\n        )}\n      </form>\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/[locale]/login/password/page.tsx",
    "content": "\"use client\"\n\nimport { ChangePassword } from \"@/components/utility/change-password\"\nimport { supabase } from \"@/lib/supabase/browser-client\"\nimport { useRouter } from \"next/navigation\"\nimport { useEffect, useState } from \"react\"\n\nexport default function ChangePasswordPage() {\n  const [loading, setLoading] = useState(true)\n\n  const router = useRouter()\n\n  useEffect(() => {\n    ;(async () => {\n      const session = (await supabase.auth.getSession()).data.session\n\n      if (!session) {\n        router.push(\"/login\")\n      } else {\n        setLoading(false)\n      }\n    })()\n  }, [])\n\n  if (loading) {\n    return null\n  }\n\n  return <ChangePassword />\n}\n"
  },
  {
    "path": "app/[locale]/page.tsx",
    "content": "\"use client\"\n\nimport { ChatbotUISVG } from \"@/components/icons/chatbotui-svg\"\nimport { IconArrowRight } from \"@tabler/icons-react\"\nimport { useTheme } from \"next-themes\"\nimport Link from \"next/link\"\n\nexport default function HomePage() {\n  const { theme } = useTheme()\n\n  return (\n    <div className=\"flex size-full flex-col items-center justify-center\">\n      <div>\n        <ChatbotUISVG theme={theme === \"dark\" ? \"dark\" : \"light\"} scale={0.3} />\n      </div>\n\n      <div className=\"mt-2 text-4xl font-bold\">Chatbot UI</div>\n\n      <Link\n        className=\"mt-4 flex w-[200px] items-center justify-center rounded-md bg-blue-500 p-2 font-semibold\"\n        href=\"/login\"\n      >\n        Start Chatting\n        <IconArrowRight className=\"ml-1\" size={20} />\n      </Link>\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/[locale]/setup/page.tsx",
    "content": "\"use client\"\n\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { getProfileByUserId, updateProfile } from \"@/db/profile\"\nimport {\n  getHomeWorkspaceByUserId,\n  getWorkspacesByUserId\n} from \"@/db/workspaces\"\nimport {\n  fetchHostedModels,\n  fetchOpenRouterModels\n} from \"@/lib/models/fetch-models\"\nimport { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesUpdate } from \"@/supabase/types\"\nimport { useRouter } from \"next/navigation\"\nimport { useContext, useEffect, useState } from \"react\"\nimport { APIStep } from \"../../../components/setup/api-step\"\nimport { FinishStep } from \"../../../components/setup/finish-step\"\nimport { ProfileStep } from \"../../../components/setup/profile-step\"\nimport {\n  SETUP_STEP_COUNT,\n  StepContainer\n} from \"../../../components/setup/step-container\"\n\nexport default function SetupPage() {\n  const {\n    profile,\n    setProfile,\n    setWorkspaces,\n    setSelectedWorkspace,\n    setEnvKeyMap,\n    setAvailableHostedModels,\n    setAvailableOpenRouterModels\n  } = useContext(ChatbotUIContext)\n\n  const router = useRouter()\n\n  const [loading, setLoading] = useState(true)\n\n  const [currentStep, setCurrentStep] = useState(1)\n\n  // Profile Step\n  const [displayName, setDisplayName] = useState(\"\")\n  const [username, setUsername] = useState(profile?.username || \"\")\n  const [usernameAvailable, setUsernameAvailable] = useState(true)\n\n  // API Step\n  const [useAzureOpenai, setUseAzureOpenai] = useState(false)\n  const [openaiAPIKey, setOpenaiAPIKey] = useState(\"\")\n  const [openaiOrgID, setOpenaiOrgID] = useState(\"\")\n  const [azureOpenaiAPIKey, setAzureOpenaiAPIKey] = useState(\"\")\n  const [azureOpenaiEndpoint, setAzureOpenaiEndpoint] = useState(\"\")\n  const [azureOpenai35TurboID, setAzureOpenai35TurboID] = useState(\"\")\n  const [azureOpenai45TurboID, setAzureOpenai45TurboID] = useState(\"\")\n  const [azureOpenai45VisionID, setAzureOpenai45VisionID] = useState(\"\")\n  const [azureOpenaiEmbeddingsID, setAzureOpenaiEmbeddingsID] = useState(\"\")\n  const [anthropicAPIKey, setAnthropicAPIKey] = useState(\"\")\n  const [googleGeminiAPIKey, setGoogleGeminiAPIKey] = useState(\"\")\n  const [mistralAPIKey, setMistralAPIKey] = useState(\"\")\n  const [groqAPIKey, setGroqAPIKey] = useState(\"\")\n  const [perplexityAPIKey, setPerplexityAPIKey] = useState(\"\")\n  const [openrouterAPIKey, setOpenrouterAPIKey] = useState(\"\")\n\n  useEffect(() => {\n    ;(async () => {\n      const session = (await supabase.auth.getSession()).data.session\n\n      if (!session) {\n        return router.push(\"/login\")\n      } else {\n        const user = session.user\n\n        const profile = await getProfileByUserId(user.id)\n        setProfile(profile)\n        setUsername(profile.username)\n\n        if (!profile.has_onboarded) {\n          setLoading(false)\n        } else {\n          const data = await fetchHostedModels(profile)\n\n          if (!data) return\n\n          setEnvKeyMap(data.envKeyMap)\n          setAvailableHostedModels(data.hostedModels)\n\n          if (profile[\"openrouter_api_key\"] || data.envKeyMap[\"openrouter\"]) {\n            const openRouterModels = await fetchOpenRouterModels()\n            if (!openRouterModels) return\n            setAvailableOpenRouterModels(openRouterModels)\n          }\n\n          const homeWorkspaceId = await getHomeWorkspaceByUserId(\n            session.user.id\n          )\n          return router.push(`/${homeWorkspaceId}/chat`)\n        }\n      }\n    })()\n  }, [])\n\n  const handleShouldProceed = (proceed: boolean) => {\n    if (proceed) {\n      if (currentStep === SETUP_STEP_COUNT) {\n        handleSaveSetupSetting()\n      } else {\n        setCurrentStep(currentStep + 1)\n      }\n    } else {\n      setCurrentStep(currentStep - 1)\n    }\n  }\n\n  const handleSaveSetupSetting = async () => {\n    const session = (await supabase.auth.getSession()).data.session\n    if (!session) {\n      return router.push(\"/login\")\n    }\n\n    const user = session.user\n    const profile = await getProfileByUserId(user.id)\n\n    const updateProfilePayload: TablesUpdate<\"profiles\"> = {\n      ...profile,\n      has_onboarded: true,\n      display_name: displayName,\n      username,\n      openai_api_key: openaiAPIKey,\n      openai_organization_id: openaiOrgID,\n      anthropic_api_key: anthropicAPIKey,\n      google_gemini_api_key: googleGeminiAPIKey,\n      mistral_api_key: mistralAPIKey,\n      groq_api_key: groqAPIKey,\n      perplexity_api_key: perplexityAPIKey,\n      openrouter_api_key: openrouterAPIKey,\n      use_azure_openai: useAzureOpenai,\n      azure_openai_api_key: azureOpenaiAPIKey,\n      azure_openai_endpoint: azureOpenaiEndpoint,\n      azure_openai_35_turbo_id: azureOpenai35TurboID,\n      azure_openai_45_turbo_id: azureOpenai45TurboID,\n      azure_openai_45_vision_id: azureOpenai45VisionID,\n      azure_openai_embeddings_id: azureOpenaiEmbeddingsID\n    }\n\n    const updatedProfile = await updateProfile(profile.id, updateProfilePayload)\n    setProfile(updatedProfile)\n\n    const workspaces = await getWorkspacesByUserId(profile.user_id)\n    const homeWorkspace = workspaces.find(w => w.is_home)\n\n    // There will always be a home workspace\n    setSelectedWorkspace(homeWorkspace!)\n    setWorkspaces(workspaces)\n\n    return router.push(`/${homeWorkspace?.id}/chat`)\n  }\n\n  const renderStep = (stepNum: number) => {\n    switch (stepNum) {\n      // Profile Step\n      case 1:\n        return (\n          <StepContainer\n            stepDescription=\"Let's create your profile.\"\n            stepNum={currentStep}\n            stepTitle=\"Welcome to Chatbot UI\"\n            onShouldProceed={handleShouldProceed}\n            showNextButton={!!(username && usernameAvailable)}\n            showBackButton={false}\n          >\n            <ProfileStep\n              username={username}\n              usernameAvailable={usernameAvailable}\n              displayName={displayName}\n              onUsernameAvailableChange={setUsernameAvailable}\n              onUsernameChange={setUsername}\n              onDisplayNameChange={setDisplayName}\n            />\n          </StepContainer>\n        )\n\n      // API Step\n      case 2:\n        return (\n          <StepContainer\n            stepDescription=\"Enter API keys for each service you'd like to use.\"\n            stepNum={currentStep}\n            stepTitle=\"Set API Keys (optional)\"\n            onShouldProceed={handleShouldProceed}\n            showNextButton={true}\n            showBackButton={true}\n          >\n            <APIStep\n              openaiAPIKey={openaiAPIKey}\n              openaiOrgID={openaiOrgID}\n              azureOpenaiAPIKey={azureOpenaiAPIKey}\n              azureOpenaiEndpoint={azureOpenaiEndpoint}\n              azureOpenai35TurboID={azureOpenai35TurboID}\n              azureOpenai45TurboID={azureOpenai45TurboID}\n              azureOpenai45VisionID={azureOpenai45VisionID}\n              azureOpenaiEmbeddingsID={azureOpenaiEmbeddingsID}\n              anthropicAPIKey={anthropicAPIKey}\n              googleGeminiAPIKey={googleGeminiAPIKey}\n              mistralAPIKey={mistralAPIKey}\n              groqAPIKey={groqAPIKey}\n              perplexityAPIKey={perplexityAPIKey}\n              useAzureOpenai={useAzureOpenai}\n              onOpenaiAPIKeyChange={setOpenaiAPIKey}\n              onOpenaiOrgIDChange={setOpenaiOrgID}\n              onAzureOpenaiAPIKeyChange={setAzureOpenaiAPIKey}\n              onAzureOpenaiEndpointChange={setAzureOpenaiEndpoint}\n              onAzureOpenai35TurboIDChange={setAzureOpenai35TurboID}\n              onAzureOpenai45TurboIDChange={setAzureOpenai45TurboID}\n              onAzureOpenai45VisionIDChange={setAzureOpenai45VisionID}\n              onAzureOpenaiEmbeddingsIDChange={setAzureOpenaiEmbeddingsID}\n              onAnthropicAPIKeyChange={setAnthropicAPIKey}\n              onGoogleGeminiAPIKeyChange={setGoogleGeminiAPIKey}\n              onMistralAPIKeyChange={setMistralAPIKey}\n              onGroqAPIKeyChange={setGroqAPIKey}\n              onPerplexityAPIKeyChange={setPerplexityAPIKey}\n              onUseAzureOpenaiChange={setUseAzureOpenai}\n              openrouterAPIKey={openrouterAPIKey}\n              onOpenrouterAPIKeyChange={setOpenrouterAPIKey}\n            />\n          </StepContainer>\n        )\n\n      // Finish Step\n      case 3:\n        return (\n          <StepContainer\n            stepDescription=\"You are all set up!\"\n            stepNum={currentStep}\n            stepTitle=\"Setup Complete\"\n            onShouldProceed={handleShouldProceed}\n            showNextButton={true}\n            showBackButton={true}\n          >\n            <FinishStep displayName={displayName} />\n          </StepContainer>\n        )\n      default:\n        return null\n    }\n  }\n\n  if (loading) {\n    return null\n  }\n\n  return (\n    <div className=\"flex h-full items-center justify-center\">\n      {renderStep(currentStep)}\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/api/assistants/openai/route.ts",
    "content": "import { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { ServerRuntime } from \"next\"\nimport OpenAI from \"openai\"\n\nexport const runtime: ServerRuntime = \"edge\"\n\nexport async function GET() {\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.openai_api_key, \"OpenAI\")\n\n    const openai = new OpenAI({\n      apiKey: profile.openai_api_key || \"\",\n      organization: profile.openai_organization_id\n    })\n\n    const myAssistants = await openai.beta.assistants.list({\n      limit: 100\n    })\n\n    return new Response(JSON.stringify({ assistants: myAssistants.data }), {\n      status: 200\n    })\n  } catch (error: any) {\n    const errorMessage = error.error?.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/anthropic/route.ts",
    "content": "import { CHAT_SETTING_LIMITS } from \"@/lib/chat-setting-limits\"\nimport { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { getBase64FromDataURL, getMediaTypeFromDataURL } from \"@/lib/utils\"\nimport { ChatSettings } from \"@/types\"\nimport Anthropic from \"@anthropic-ai/sdk\"\nimport { AnthropicStream, StreamingTextResponse } from \"ai\"\nimport { NextRequest, NextResponse } from \"next/server\"\n\nexport const runtime = \"edge\"\n\nexport async function POST(request: NextRequest) {\n  const json = await request.json()\n  const { chatSettings, messages } = json as {\n    chatSettings: ChatSettings\n    messages: any[]\n  }\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.anthropic_api_key, \"Anthropic\")\n\n    let ANTHROPIC_FORMATTED_MESSAGES: any = messages.slice(1)\n\n    ANTHROPIC_FORMATTED_MESSAGES = ANTHROPIC_FORMATTED_MESSAGES?.map(\n      (message: any) => {\n        const messageContent =\n          typeof message?.content === \"string\"\n            ? [message.content]\n            : message?.content\n\n        return {\n          ...message,\n          content: messageContent.map((content: any) => {\n            if (typeof content === \"string\") {\n              // Handle the case where content is a string\n              return { type: \"text\", text: content }\n            } else if (\n              content?.type === \"image_url\" &&\n              content?.image_url?.url?.length\n            ) {\n              return {\n                type: \"image\",\n                source: {\n                  type: \"base64\",\n                  media_type: getMediaTypeFromDataURL(content.image_url.url),\n                  data: getBase64FromDataURL(content.image_url.url)\n                }\n              }\n            } else {\n              return content\n            }\n          })\n        }\n      }\n    )\n\n    const anthropic = new Anthropic({\n      apiKey: profile.anthropic_api_key || \"\"\n    })\n\n    try {\n      const response = await anthropic.messages.create({\n        model: chatSettings.model,\n        messages: ANTHROPIC_FORMATTED_MESSAGES,\n        temperature: chatSettings.temperature,\n        system: messages[0].content,\n        max_tokens:\n          CHAT_SETTING_LIMITS[chatSettings.model].MAX_TOKEN_OUTPUT_LENGTH,\n        stream: true\n      })\n\n      try {\n        const stream = AnthropicStream(response)\n        return new StreamingTextResponse(stream)\n      } catch (error: any) {\n        console.error(\"Error parsing Anthropic API response:\", error)\n        return new NextResponse(\n          JSON.stringify({\n            message:\n              \"An error occurred while parsing the Anthropic API response\"\n          }),\n          { status: 500 }\n        )\n      }\n    } catch (error: any) {\n      console.error(\"Error calling Anthropic API:\", error)\n      return new NextResponse(\n        JSON.stringify({\n          message: \"An error occurred while calling the Anthropic API\"\n        }),\n        { status: 500 }\n      )\n    }\n  } catch (error: any) {\n    let errorMessage = error.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n\n    if (errorMessage.toLowerCase().includes(\"api key not found\")) {\n      errorMessage =\n        \"Anthropic API Key not found. Please set it in your profile settings.\"\n    } else if (errorCode === 401) {\n      errorMessage =\n        \"Anthropic API Key is incorrect. Please fix it in your profile settings.\"\n    }\n\n    return new NextResponse(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/azure/route.ts",
    "content": "import { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { ChatAPIPayload } from \"@/types\"\nimport { OpenAIStream, StreamingTextResponse } from \"ai\"\nimport OpenAI from \"openai\"\nimport { ChatCompletionCreateParamsBase } from \"openai/resources/chat/completions.mjs\"\n\nexport const runtime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { chatSettings, messages } = json as ChatAPIPayload\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.azure_openai_api_key, \"Azure OpenAI\")\n\n    const ENDPOINT = profile.azure_openai_endpoint\n    const KEY = profile.azure_openai_api_key\n\n    let DEPLOYMENT_ID = \"\"\n    switch (chatSettings.model) {\n      case \"gpt-3.5-turbo\":\n        DEPLOYMENT_ID = profile.azure_openai_35_turbo_id || \"\"\n        break\n      case \"gpt-4-turbo-preview\":\n        DEPLOYMENT_ID = profile.azure_openai_45_turbo_id || \"\"\n        break\n      case \"gpt-4-vision-preview\":\n        DEPLOYMENT_ID = profile.azure_openai_45_vision_id || \"\"\n        break\n      default:\n        return new Response(JSON.stringify({ message: \"Model not found\" }), {\n          status: 400\n        })\n    }\n\n    if (!ENDPOINT || !KEY || !DEPLOYMENT_ID) {\n      return new Response(\n        JSON.stringify({ message: \"Azure resources not found\" }),\n        {\n          status: 400\n        }\n      )\n    }\n\n    const azureOpenai = new OpenAI({\n      apiKey: KEY,\n      baseURL: `${ENDPOINT}/openai/deployments/${DEPLOYMENT_ID}`,\n      defaultQuery: { \"api-version\": \"2023-12-01-preview\" },\n      defaultHeaders: { \"api-key\": KEY }\n    })\n\n    const response = await azureOpenai.chat.completions.create({\n      model: DEPLOYMENT_ID as ChatCompletionCreateParamsBase[\"model\"],\n      messages: messages as ChatCompletionCreateParamsBase[\"messages\"],\n      temperature: chatSettings.temperature,\n      max_tokens: chatSettings.model === \"gpt-4-vision-preview\" ? 4096 : null, // TODO: Fix\n      stream: true\n    })\n\n    const stream = OpenAIStream(response)\n\n    return new StreamingTextResponse(stream)\n  } catch (error: any) {\n    const errorMessage = error.error?.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/custom/route.ts",
    "content": "import { Database } from \"@/supabase/types\"\nimport { ChatSettings } from \"@/types\"\nimport { createClient } from \"@supabase/supabase-js\"\nimport { OpenAIStream, StreamingTextResponse } from \"ai\"\nimport { ServerRuntime } from \"next\"\nimport OpenAI from \"openai\"\nimport { ChatCompletionCreateParamsBase } from \"openai/resources/chat/completions.mjs\"\n\nexport const runtime: ServerRuntime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { chatSettings, messages, customModelId } = json as {\n    chatSettings: ChatSettings\n    messages: any[]\n    customModelId: string\n  }\n\n  try {\n    const supabaseAdmin = createClient<Database>(\n      process.env.NEXT_PUBLIC_SUPABASE_URL!,\n      process.env.SUPABASE_SERVICE_ROLE_KEY!\n    )\n\n    const { data: customModel, error } = await supabaseAdmin\n      .from(\"models\")\n      .select(\"*\")\n      .eq(\"id\", customModelId)\n      .single()\n\n    if (!customModel) {\n      throw new Error(error.message)\n    }\n\n    const custom = new OpenAI({\n      apiKey: customModel.api_key || \"\",\n      baseURL: customModel.base_url\n    })\n\n    const response = await custom.chat.completions.create({\n      model: chatSettings.model as ChatCompletionCreateParamsBase[\"model\"],\n      messages: messages as ChatCompletionCreateParamsBase[\"messages\"],\n      temperature: chatSettings.temperature,\n      stream: true\n    })\n\n    const stream = OpenAIStream(response)\n\n    return new StreamingTextResponse(stream)\n  } catch (error: any) {\n    let errorMessage = error.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n\n    if (errorMessage.toLowerCase().includes(\"api key not found\")) {\n      errorMessage =\n        \"Custom API Key not found. Please set it in your profile settings.\"\n    } else if (errorMessage.toLowerCase().includes(\"incorrect api key\")) {\n      errorMessage =\n        \"Custom API Key is incorrect. Please fix it in your profile settings.\"\n    }\n\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/google/route.ts",
    "content": "import { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { ChatSettings } from \"@/types\"\nimport { GoogleGenerativeAI } from \"@google/generative-ai\"\n\nexport const runtime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { chatSettings, messages } = json as {\n    chatSettings: ChatSettings\n    messages: any[]\n  }\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.google_gemini_api_key, \"Google\")\n\n    const genAI = new GoogleGenerativeAI(profile.google_gemini_api_key || \"\")\n    const googleModel = genAI.getGenerativeModel({ model: chatSettings.model })\n\n    const lastMessage = messages.pop()\n\n    const chat = googleModel.startChat({\n      history: messages,\n      generationConfig: {\n        temperature: chatSettings.temperature\n      }\n    })\n\n    const response = await chat.sendMessageStream(lastMessage.parts)\n\n    const encoder = new TextEncoder()\n    const readableStream = new ReadableStream({\n      async start(controller) {\n        for await (const chunk of response.stream) {\n          const chunkText = chunk.text()\n          controller.enqueue(encoder.encode(chunkText))\n        }\n        controller.close()\n      }\n    })\n\n    return new Response(readableStream, {\n      headers: { \"Content-Type\": \"text/plain\" }\n    })\n\n  } catch (error: any) {\n    let errorMessage = error.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n\n    if (errorMessage.toLowerCase().includes(\"api key not found\")) {\n      errorMessage =\n        \"Google Gemini API Key not found. Please set it in your profile settings.\"\n    } else if (errorMessage.toLowerCase().includes(\"api key not valid\")) {\n      errorMessage =\n        \"Google Gemini API Key is incorrect. Please fix it in your profile settings.\"\n    }\n\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/groq/route.ts",
    "content": "import { CHAT_SETTING_LIMITS } from \"@/lib/chat-setting-limits\"\nimport { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { ChatSettings } from \"@/types\"\nimport { OpenAIStream, StreamingTextResponse } from \"ai\"\nimport OpenAI from \"openai\"\n\nexport const runtime = \"edge\"\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { chatSettings, messages } = json as {\n    chatSettings: ChatSettings\n    messages: any[]\n  }\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.groq_api_key, \"G\")\n\n    // Groq is compatible with the OpenAI SDK\n    const groq = new OpenAI({\n      apiKey: profile.groq_api_key || \"\",\n      baseURL: \"https://api.groq.com/openai/v1\"\n    })\n\n    const response = await groq.chat.completions.create({\n      model: chatSettings.model,\n      messages,\n      max_tokens:\n        CHAT_SETTING_LIMITS[chatSettings.model].MAX_TOKEN_OUTPUT_LENGTH,\n      stream: true\n    })\n\n    // Convert the response into a friendly text-stream.\n    const stream = OpenAIStream(response)\n\n    // Respond with the stream\n    return new StreamingTextResponse(stream)\n  } catch (error: any) {\n    let errorMessage = error.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n\n    if (errorMessage.toLowerCase().includes(\"api key not found\")) {\n      errorMessage =\n        \"Groq API Key not found. Please set it in your profile settings.\"\n    } else if (errorCode === 401) {\n      errorMessage =\n        \"Groq API Key is incorrect. Please fix it in your profile settings.\"\n    }\n\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/mistral/route.ts",
    "content": "import { CHAT_SETTING_LIMITS } from \"@/lib/chat-setting-limits\"\nimport { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { ChatSettings } from \"@/types\"\nimport { OpenAIStream, StreamingTextResponse } from \"ai\"\nimport OpenAI from \"openai\"\n\nexport const runtime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { chatSettings, messages } = json as {\n    chatSettings: ChatSettings\n    messages: any[]\n  }\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.mistral_api_key, \"Mistral\")\n\n    // Mistral is compatible the OpenAI SDK\n    const mistral = new OpenAI({\n      apiKey: profile.mistral_api_key || \"\",\n      baseURL: \"https://api.mistral.ai/v1\"\n    })\n\n    const response = await mistral.chat.completions.create({\n      model: chatSettings.model,\n      messages,\n      max_tokens:\n        CHAT_SETTING_LIMITS[chatSettings.model].MAX_TOKEN_OUTPUT_LENGTH,\n      stream: true\n    })\n\n    // Convert the response into a friendly text-stream.\n    const stream = OpenAIStream(response)\n\n    // Respond with the stream\n    return new StreamingTextResponse(stream)\n  } catch (error: any) {\n    let errorMessage = error.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n\n    if (errorMessage.toLowerCase().includes(\"api key not found\")) {\n      errorMessage =\n        \"Mistral API Key not found. Please set it in your profile settings.\"\n    } else if (errorCode === 401) {\n      errorMessage =\n        \"Mistral API Key is incorrect. Please fix it in your profile settings.\"\n    }\n\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/openai/route.ts",
    "content": "import { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { ChatSettings } from \"@/types\"\nimport { OpenAIStream, StreamingTextResponse } from \"ai\"\nimport { ServerRuntime } from \"next\"\nimport OpenAI from \"openai\"\nimport { ChatCompletionCreateParamsBase } from \"openai/resources/chat/completions.mjs\"\n\nexport const runtime: ServerRuntime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { chatSettings, messages } = json as {\n    chatSettings: ChatSettings\n    messages: any[]\n  }\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.openai_api_key, \"OpenAI\")\n\n    const openai = new OpenAI({\n      apiKey: profile.openai_api_key || \"\",\n      organization: profile.openai_organization_id\n    })\n\n    const response = await openai.chat.completions.create({\n      model: chatSettings.model as ChatCompletionCreateParamsBase[\"model\"],\n      messages: messages as ChatCompletionCreateParamsBase[\"messages\"],\n      temperature: chatSettings.temperature,\n      max_tokens:\n        chatSettings.model === \"gpt-4-vision-preview\" ||\n        chatSettings.model === \"gpt-4o\"\n          ? 4096\n          : null, // TODO: Fix\n      stream: true\n    })\n\n    const stream = OpenAIStream(response)\n\n    return new StreamingTextResponse(stream)\n  } catch (error: any) {\n    let errorMessage = error.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n\n    if (errorMessage.toLowerCase().includes(\"api key not found\")) {\n      errorMessage =\n        \"OpenAI API Key not found. Please set it in your profile settings.\"\n    } else if (errorMessage.toLowerCase().includes(\"incorrect api key\")) {\n      errorMessage =\n        \"OpenAI API Key is incorrect. Please fix it in your profile settings.\"\n    }\n\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/openrouter/route.ts",
    "content": "import { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { ChatSettings } from \"@/types\"\nimport { OpenAIStream, StreamingTextResponse } from \"ai\"\nimport { ServerRuntime } from \"next\"\nimport OpenAI from \"openai\"\nimport { ChatCompletionCreateParamsBase } from \"openai/resources/chat/completions.mjs\"\n\nexport const runtime: ServerRuntime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { chatSettings, messages } = json as {\n    chatSettings: ChatSettings\n    messages: any[]\n  }\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.openrouter_api_key, \"OpenRouter\")\n\n    const openai = new OpenAI({\n      apiKey: profile.openrouter_api_key || \"\",\n      baseURL: \"https://openrouter.ai/api/v1\"\n    })\n\n    const response = await openai.chat.completions.create({\n      model: chatSettings.model as ChatCompletionCreateParamsBase[\"model\"],\n      messages: messages as ChatCompletionCreateParamsBase[\"messages\"],\n      temperature: chatSettings.temperature,\n      max_tokens: undefined,\n      stream: true\n    })\n\n    const stream = OpenAIStream(response)\n\n    return new StreamingTextResponse(stream)\n  } catch (error: any) {\n    let errorMessage = error.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n\n    if (errorMessage.toLowerCase().includes(\"api key not found\")) {\n      errorMessage =\n        \"OpenRouter API Key not found. Please set it in your profile settings.\"\n    }\n\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/perplexity/route.ts",
    "content": "import { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { ChatSettings } from \"@/types\"\nimport { OpenAIStream, StreamingTextResponse } from \"ai\"\nimport OpenAI from \"openai\"\n\nexport const runtime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { chatSettings, messages } = json as {\n    chatSettings: ChatSettings\n    messages: any[]\n  }\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.perplexity_api_key, \"Perplexity\")\n\n    // Perplexity is compatible the OpenAI SDK\n    const perplexity = new OpenAI({\n      apiKey: profile.perplexity_api_key || \"\",\n      baseURL: \"https://api.perplexity.ai/\"\n    })\n\n    const response = await perplexity.chat.completions.create({\n      model: chatSettings.model,\n      messages,\n      stream: true\n    })\n\n    const stream = OpenAIStream(response)\n\n    return new StreamingTextResponse(stream)\n  } catch (error: any) {\n    let errorMessage = error.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n\n    if (errorMessage.toLowerCase().includes(\"api key not found\")) {\n      errorMessage =\n        \"Perplexity API Key not found. Please set it in your profile settings.\"\n    } else if (errorCode === 401) {\n      errorMessage =\n        \"Perplexity API Key is incorrect. Please fix it in your profile settings.\"\n    }\n\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/chat/tools/route.ts",
    "content": "import { openapiToFunctions } from \"@/lib/openapi-conversion\"\nimport { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { Tables } from \"@/supabase/types\"\nimport { ChatSettings } from \"@/types\"\nimport { OpenAIStream, StreamingTextResponse } from \"ai\"\nimport OpenAI from \"openai\"\nimport { ChatCompletionCreateParamsBase } from \"openai/resources/chat/completions.mjs\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { chatSettings, messages, selectedTools } = json as {\n    chatSettings: ChatSettings\n    messages: any[]\n    selectedTools: Tables<\"tools\">[]\n  }\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.openai_api_key, \"OpenAI\")\n\n    const openai = new OpenAI({\n      apiKey: profile.openai_api_key || \"\",\n      organization: profile.openai_organization_id\n    })\n\n    let allTools: OpenAI.Chat.Completions.ChatCompletionTool[] = []\n    let allRouteMaps = {}\n    let schemaDetails = []\n\n    for (const selectedTool of selectedTools) {\n      try {\n        const convertedSchema = await openapiToFunctions(\n          JSON.parse(selectedTool.schema as string)\n        )\n        const tools = convertedSchema.functions || []\n        allTools = allTools.concat(tools)\n\n        const routeMap = convertedSchema.routes.reduce(\n          (map: Record<string, string>, route) => {\n            map[route.path.replace(/{(\\w+)}/g, \":$1\")] = route.operationId\n            return map\n          },\n          {}\n        )\n\n        allRouteMaps = { ...allRouteMaps, ...routeMap }\n\n        schemaDetails.push({\n          title: convertedSchema.info.title,\n          description: convertedSchema.info.description,\n          url: convertedSchema.info.server,\n          headers: selectedTool.custom_headers,\n          routeMap,\n          requestInBody: convertedSchema.routes[0].requestInBody\n        })\n      } catch (error: any) {\n        console.error(\"Error converting schema\", error)\n      }\n    }\n\n    const firstResponse = await openai.chat.completions.create({\n      model: chatSettings.model as ChatCompletionCreateParamsBase[\"model\"],\n      messages,\n      tools: allTools.length > 0 ? allTools : undefined\n    })\n\n    const message = firstResponse.choices[0].message\n    messages.push(message)\n    const toolCalls = message.tool_calls || []\n\n    if (toolCalls.length === 0) {\n      return new Response(message.content, {\n        headers: {\n          \"Content-Type\": \"application/json\"\n        }\n      })\n    }\n\n    if (toolCalls.length > 0) {\n      for (const toolCall of toolCalls) {\n        const functionCall = toolCall.function\n        const functionName = functionCall.name\n        const argumentsString = toolCall.function.arguments.trim()\n        const parsedArgs = JSON.parse(argumentsString)\n\n        // Find the schema detail that contains the function name\n        const schemaDetail = schemaDetails.find(detail =>\n          Object.values(detail.routeMap).includes(functionName)\n        )\n\n        if (!schemaDetail) {\n          throw new Error(`Function ${functionName} not found in any schema`)\n        }\n\n        const pathTemplate = Object.keys(schemaDetail.routeMap).find(\n          key => schemaDetail.routeMap[key] === functionName\n        )\n\n        if (!pathTemplate) {\n          throw new Error(`Path for function ${functionName} not found`)\n        }\n\n        const path = pathTemplate.replace(/:(\\w+)/g, (_, paramName) => {\n          const value = parsedArgs.parameters[paramName]\n          if (!value) {\n            throw new Error(\n              `Parameter ${paramName} not found for function ${functionName}`\n            )\n          }\n          return encodeURIComponent(value)\n        })\n\n        if (!path) {\n          throw new Error(`Path for function ${functionName} not found`)\n        }\n\n        // Determine if the request should be in the body or as a query\n        const isRequestInBody = schemaDetail.requestInBody\n        let data = {}\n\n        if (isRequestInBody) {\n          // If the type is set to body\n          let headers = {\n            \"Content-Type\": \"application/json\"\n          }\n\n          // Check if custom headers are set\n          const customHeaders = schemaDetail.headers // Moved this line up to the loop\n          // Check if custom headers are set and are of type string\n          if (customHeaders && typeof customHeaders === \"string\") {\n            let parsedCustomHeaders = JSON.parse(customHeaders) as Record<\n              string,\n              string\n            >\n\n            headers = {\n              ...headers,\n              ...parsedCustomHeaders\n            }\n          }\n\n          const fullUrl = schemaDetail.url + path\n\n          const bodyContent = parsedArgs.requestBody || parsedArgs\n\n          const requestInit = {\n            method: \"POST\",\n            headers,\n            body: JSON.stringify(bodyContent) // Use the extracted requestBody or the entire parsedArgs\n          }\n\n          const response = await fetch(fullUrl, requestInit)\n\n          if (!response.ok) {\n            data = {\n              error: response.statusText\n            }\n          } else {\n            data = await response.json()\n          }\n        } else {\n          // If the type is set to query\n          const queryParams = new URLSearchParams(\n            parsedArgs.parameters\n          ).toString()\n          const fullUrl =\n            schemaDetail.url + path + (queryParams ? \"?\" + queryParams : \"\")\n\n          let headers = {}\n\n          // Check if custom headers are set\n          const customHeaders = schemaDetail.headers\n          if (customHeaders && typeof customHeaders === \"string\") {\n            headers = JSON.parse(customHeaders)\n          }\n\n          const response = await fetch(fullUrl, {\n            method: \"GET\",\n            headers: headers\n          })\n\n          if (!response.ok) {\n            data = {\n              error: response.statusText\n            }\n          } else {\n            data = await response.json()\n          }\n        }\n\n        messages.push({\n          tool_call_id: toolCall.id,\n          role: \"tool\",\n          name: functionName,\n          content: JSON.stringify(data)\n        })\n      }\n    }\n\n    const secondResponse = await openai.chat.completions.create({\n      model: chatSettings.model as ChatCompletionCreateParamsBase[\"model\"],\n      messages,\n      stream: true\n    })\n\n    const stream = OpenAIStream(secondResponse)\n\n    return new StreamingTextResponse(stream)\n  } catch (error: any) {\n    console.error(error)\n    const errorMessage = error.error?.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/command/route.ts",
    "content": "import { CHAT_SETTING_LIMITS } from \"@/lib/chat-setting-limits\"\nimport { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport OpenAI from \"openai\"\n\nexport const runtime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { input } = json as {\n    input: string\n  }\n\n  try {\n    const profile = await getServerProfile()\n\n    checkApiKey(profile.openai_api_key, \"OpenAI\")\n\n    const openai = new OpenAI({\n      apiKey: profile.openai_api_key || \"\",\n      organization: profile.openai_organization_id\n    })\n\n    const response = await openai.chat.completions.create({\n      model: \"gpt-4-1106-preview\",\n      messages: [\n        {\n          role: \"system\",\n          content: \"Respond to the user.\"\n        },\n        {\n          role: \"user\",\n          content: input\n        }\n      ],\n      temperature: 0,\n      max_tokens:\n        CHAT_SETTING_LIMITS[\"gpt-4-turbo-preview\"].MAX_TOKEN_OUTPUT_LENGTH\n      //   response_format: { type: \"json_object\" }\n      //   stream: true\n    })\n\n    const content = response.choices[0].message.content\n\n    return new Response(JSON.stringify({ content }), {\n      status: 200\n    })\n  } catch (error: any) {\n    const errorMessage = error.error?.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/keys/route.ts",
    "content": "import { isUsingEnvironmentKey } from \"@/lib/envs\"\nimport { createResponse } from \"@/lib/server/server-utils\"\nimport { EnvKey } from \"@/types/key-type\"\nimport { VALID_ENV_KEYS } from \"@/types/valid-keys\"\n\nexport async function GET() {\n  const envKeyMap: Record<string, VALID_ENV_KEYS> = {\n    azure: VALID_ENV_KEYS.AZURE_OPENAI_API_KEY,\n    openai: VALID_ENV_KEYS.OPENAI_API_KEY,\n    google: VALID_ENV_KEYS.GOOGLE_GEMINI_API_KEY,\n    anthropic: VALID_ENV_KEYS.ANTHROPIC_API_KEY,\n    mistral: VALID_ENV_KEYS.MISTRAL_API_KEY,\n    groq: VALID_ENV_KEYS.GROQ_API_KEY,\n    perplexity: VALID_ENV_KEYS.PERPLEXITY_API_KEY,\n    openrouter: VALID_ENV_KEYS.OPENROUTER_API_KEY,\n\n    openai_organization_id: VALID_ENV_KEYS.OPENAI_ORGANIZATION_ID,\n\n    azure_openai_endpoint: VALID_ENV_KEYS.AZURE_OPENAI_ENDPOINT,\n    azure_gpt_35_turbo_name: VALID_ENV_KEYS.AZURE_GPT_35_TURBO_NAME,\n    azure_gpt_45_vision_name: VALID_ENV_KEYS.AZURE_GPT_45_VISION_NAME,\n    azure_gpt_45_turbo_name: VALID_ENV_KEYS.AZURE_GPT_45_TURBO_NAME,\n    azure_embeddings_name: VALID_ENV_KEYS.AZURE_EMBEDDINGS_NAME\n  }\n\n  const isUsingEnvKeyMap = Object.keys(envKeyMap).reduce<\n    Record<string, boolean>\n  >((acc, provider) => {\n    const key = envKeyMap[provider]\n\n    if (key) {\n      acc[provider] = isUsingEnvironmentKey(key as EnvKey)\n    }\n    return acc\n  }, {})\n\n  return createResponse({ isUsingEnvKeyMap }, 200)\n}\n"
  },
  {
    "path": "app/api/retrieval/process/docx/route.ts",
    "content": "import { generateLocalEmbedding } from \"@/lib/generate-local-embedding\"\nimport { processDocX } from \"@/lib/retrieval/processing\"\nimport { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { Database } from \"@/supabase/types\"\nimport { FileItemChunk } from \"@/types\"\nimport { createClient } from \"@supabase/supabase-js\"\nimport { NextResponse } from \"next/server\"\nimport OpenAI from \"openai\"\n\nexport async function POST(req: Request) {\n  const json = await req.json()\n  const { text, fileId, embeddingsProvider, fileExtension } = json as {\n    text: string\n    fileId: string\n    embeddingsProvider: \"openai\" | \"local\"\n    fileExtension: string\n  }\n\n  try {\n    const supabaseAdmin = createClient<Database>(\n      process.env.NEXT_PUBLIC_SUPABASE_URL!,\n      process.env.SUPABASE_SERVICE_ROLE_KEY!\n    )\n\n    const profile = await getServerProfile()\n\n    if (embeddingsProvider === \"openai\") {\n      if (profile.use_azure_openai) {\n        checkApiKey(profile.azure_openai_api_key, \"Azure OpenAI\")\n      } else {\n        checkApiKey(profile.openai_api_key, \"OpenAI\")\n      }\n    }\n\n    let chunks: FileItemChunk[] = []\n\n    switch (fileExtension) {\n      case \"docx\":\n        chunks = await processDocX(text)\n        break\n      default:\n        return new NextResponse(\"Unsupported file type\", {\n          status: 400\n        })\n    }\n\n    let embeddings: any = []\n\n    let openai\n    if (profile.use_azure_openai) {\n      openai = new OpenAI({\n        apiKey: profile.azure_openai_api_key || \"\",\n        baseURL: `${profile.azure_openai_endpoint}/openai/deployments/${profile.azure_openai_embeddings_id}`,\n        defaultQuery: { \"api-version\": \"2023-12-01-preview\" },\n        defaultHeaders: { \"api-key\": profile.azure_openai_api_key }\n      })\n    } else {\n      openai = new OpenAI({\n        apiKey: profile.openai_api_key || \"\",\n        organization: profile.openai_organization_id\n      })\n    }\n\n    if (embeddingsProvider === \"openai\") {\n      const response = await openai.embeddings.create({\n        model: \"text-embedding-3-small\",\n        input: chunks.map(chunk => chunk.content)\n      })\n\n      embeddings = response.data.map((item: any) => {\n        return item.embedding\n      })\n    } else if (embeddingsProvider === \"local\") {\n      const embeddingPromises = chunks.map(async chunk => {\n        try {\n          return await generateLocalEmbedding(chunk.content)\n        } catch (error) {\n          console.error(`Error generating embedding for chunk: ${chunk}`, error)\n          return null\n        }\n      })\n\n      embeddings = await Promise.all(embeddingPromises)\n    }\n\n    const file_items = chunks.map((chunk, index) => ({\n      file_id: fileId,\n      user_id: profile.user_id,\n      content: chunk.content,\n      tokens: chunk.tokens,\n      openai_embedding:\n        embeddingsProvider === \"openai\"\n          ? ((embeddings[index] || null) as any)\n          : null,\n      local_embedding:\n        embeddingsProvider === \"local\"\n          ? ((embeddings[index] || null) as any)\n          : null\n    }))\n\n    await supabaseAdmin.from(\"file_items\").upsert(file_items)\n\n    const totalTokens = file_items.reduce((acc, item) => acc + item.tokens, 0)\n\n    await supabaseAdmin\n      .from(\"files\")\n      .update({ tokens: totalTokens })\n      .eq(\"id\", fileId)\n\n    return new NextResponse(\"Embed Successful\", {\n      status: 200\n    })\n  } catch (error: any) {\n    console.error(error)\n    const errorMessage = error.error?.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/retrieval/process/route.ts",
    "content": "import { generateLocalEmbedding } from \"@/lib/generate-local-embedding\"\nimport {\n  processCSV,\n  processJSON,\n  processMarkdown,\n  processPdf,\n  processTxt\n} from \"@/lib/retrieval/processing\"\nimport { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { Database } from \"@/supabase/types\"\nimport { FileItemChunk } from \"@/types\"\nimport { createClient } from \"@supabase/supabase-js\"\nimport { NextResponse } from \"next/server\"\nimport OpenAI from \"openai\"\n\nexport async function POST(req: Request) {\n  try {\n    const supabaseAdmin = createClient<Database>(\n      process.env.NEXT_PUBLIC_SUPABASE_URL!,\n      process.env.SUPABASE_SERVICE_ROLE_KEY!\n    )\n\n    const profile = await getServerProfile()\n\n    const formData = await req.formData()\n\n    const file_id = formData.get(\"file_id\") as string\n    const embeddingsProvider = formData.get(\"embeddingsProvider\") as string\n\n    const { data: fileMetadata, error: metadataError } = await supabaseAdmin\n      .from(\"files\")\n      .select(\"*\")\n      .eq(\"id\", file_id)\n      .single()\n\n    if (metadataError) {\n      throw new Error(\n        `Failed to retrieve file metadata: ${metadataError.message}`\n      )\n    }\n\n    if (!fileMetadata) {\n      throw new Error(\"File not found\")\n    }\n\n    if (fileMetadata.user_id !== profile.user_id) {\n      throw new Error(\"Unauthorized\")\n    }\n\n    const { data: file, error: fileError } = await supabaseAdmin.storage\n      .from(\"files\")\n      .download(fileMetadata.file_path)\n\n    if (fileError)\n      throw new Error(`Failed to retrieve file: ${fileError.message}`)\n\n    const fileBuffer = Buffer.from(await file.arrayBuffer())\n    const blob = new Blob([fileBuffer])\n    const fileExtension = fileMetadata.name.split(\".\").pop()?.toLowerCase()\n\n    if (embeddingsProvider === \"openai\") {\n      try {\n        if (profile.use_azure_openai) {\n          checkApiKey(profile.azure_openai_api_key, \"Azure OpenAI\")\n        } else {\n          checkApiKey(profile.openai_api_key, \"OpenAI\")\n        }\n      } catch (error: any) {\n        error.message =\n          error.message +\n          \", make sure it is configured or else use local embeddings\"\n        throw error\n      }\n    }\n\n    let chunks: FileItemChunk[] = []\n\n    switch (fileExtension) {\n      case \"csv\":\n        chunks = await processCSV(blob)\n        break\n      case \"json\":\n        chunks = await processJSON(blob)\n        break\n      case \"md\":\n        chunks = await processMarkdown(blob)\n        break\n      case \"pdf\":\n        chunks = await processPdf(blob)\n        break\n      case \"txt\":\n        chunks = await processTxt(blob)\n        break\n      default:\n        return new NextResponse(\"Unsupported file type\", {\n          status: 400\n        })\n    }\n\n    let embeddings: any = []\n\n    let openai\n    if (profile.use_azure_openai) {\n      openai = new OpenAI({\n        apiKey: profile.azure_openai_api_key || \"\",\n        baseURL: `${profile.azure_openai_endpoint}/openai/deployments/${profile.azure_openai_embeddings_id}`,\n        defaultQuery: { \"api-version\": \"2023-12-01-preview\" },\n        defaultHeaders: { \"api-key\": profile.azure_openai_api_key }\n      })\n    } else {\n      openai = new OpenAI({\n        apiKey: profile.openai_api_key || \"\",\n        organization: profile.openai_organization_id\n      })\n    }\n\n    if (embeddingsProvider === \"openai\") {\n      const response = await openai.embeddings.create({\n        model: \"text-embedding-3-small\",\n        input: chunks.map(chunk => chunk.content)\n      })\n\n      embeddings = response.data.map((item: any) => {\n        return item.embedding\n      })\n    } else if (embeddingsProvider === \"local\") {\n      const embeddingPromises = chunks.map(async chunk => {\n        try {\n          return await generateLocalEmbedding(chunk.content)\n        } catch (error) {\n          console.error(`Error generating embedding for chunk: ${chunk}`, error)\n\n          return null\n        }\n      })\n\n      embeddings = await Promise.all(embeddingPromises)\n    }\n\n    const file_items = chunks.map((chunk, index) => ({\n      file_id,\n      user_id: profile.user_id,\n      content: chunk.content,\n      tokens: chunk.tokens,\n      openai_embedding:\n        embeddingsProvider === \"openai\"\n          ? ((embeddings[index] || null) as any)\n          : null,\n      local_embedding:\n        embeddingsProvider === \"local\"\n          ? ((embeddings[index] || null) as any)\n          : null\n    }))\n\n    await supabaseAdmin.from(\"file_items\").upsert(file_items)\n\n    const totalTokens = file_items.reduce((acc, item) => acc + item.tokens, 0)\n\n    await supabaseAdmin\n      .from(\"files\")\n      .update({ tokens: totalTokens })\n      .eq(\"id\", file_id)\n\n    return new NextResponse(\"Embed Successful\", {\n      status: 200\n    })\n  } catch (error: any) {\n    console.log(`Error in retrieval/process: ${error.stack}`)\n    const errorMessage = error?.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/retrieval/retrieve/route.ts",
    "content": "import { generateLocalEmbedding } from \"@/lib/generate-local-embedding\"\nimport { checkApiKey, getServerProfile } from \"@/lib/server/server-chat-helpers\"\nimport { Database } from \"@/supabase/types\"\nimport { createClient } from \"@supabase/supabase-js\"\nimport OpenAI from \"openai\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { userInput, fileIds, embeddingsProvider, sourceCount } = json as {\n    userInput: string\n    fileIds: string[]\n    embeddingsProvider: \"openai\" | \"local\"\n    sourceCount: number\n  }\n\n  const uniqueFileIds = [...new Set(fileIds)]\n\n  try {\n    const supabaseAdmin = createClient<Database>(\n      process.env.NEXT_PUBLIC_SUPABASE_URL!,\n      process.env.SUPABASE_SERVICE_ROLE_KEY!\n    )\n\n    const profile = await getServerProfile()\n\n    if (embeddingsProvider === \"openai\") {\n      if (profile.use_azure_openai) {\n        checkApiKey(profile.azure_openai_api_key, \"Azure OpenAI\")\n      } else {\n        checkApiKey(profile.openai_api_key, \"OpenAI\")\n      }\n    }\n\n    let chunks: any[] = []\n\n    let openai\n    if (profile.use_azure_openai) {\n      openai = new OpenAI({\n        apiKey: profile.azure_openai_api_key || \"\",\n        baseURL: `${profile.azure_openai_endpoint}/openai/deployments/${profile.azure_openai_embeddings_id}`,\n        defaultQuery: { \"api-version\": \"2023-12-01-preview\" },\n        defaultHeaders: { \"api-key\": profile.azure_openai_api_key }\n      })\n    } else {\n      openai = new OpenAI({\n        apiKey: profile.openai_api_key || \"\",\n        organization: profile.openai_organization_id\n      })\n    }\n\n    if (embeddingsProvider === \"openai\") {\n      const response = await openai.embeddings.create({\n        model: \"text-embedding-3-small\",\n        input: userInput\n      })\n\n      const openaiEmbedding = response.data.map(item => item.embedding)[0]\n\n      const { data: openaiFileItems, error: openaiError } =\n        await supabaseAdmin.rpc(\"match_file_items_openai\", {\n          query_embedding: openaiEmbedding as any,\n          match_count: sourceCount,\n          file_ids: uniqueFileIds\n        })\n\n      if (openaiError) {\n        throw openaiError\n      }\n\n      chunks = openaiFileItems\n    } else if (embeddingsProvider === \"local\") {\n      const localEmbedding = await generateLocalEmbedding(userInput)\n\n      const { data: localFileItems, error: localFileItemsError } =\n        await supabaseAdmin.rpc(\"match_file_items_local\", {\n          query_embedding: localEmbedding as any,\n          match_count: sourceCount,\n          file_ids: uniqueFileIds\n        })\n\n      if (localFileItemsError) {\n        throw localFileItemsError\n      }\n\n      chunks = localFileItems\n    }\n\n    const mostSimilarChunks = chunks?.sort(\n      (a, b) => b.similarity - a.similarity\n    )\n\n    return new Response(JSON.stringify({ results: mostSimilarChunks }), {\n      status: 200\n    })\n  } catch (error: any) {\n    const errorMessage = error.error?.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/username/available/route.ts",
    "content": "import { Database } from \"@/supabase/types\"\nimport { createClient } from \"@supabase/supabase-js\"\n\nexport const runtime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { username } = json as {\n    username: string\n  }\n\n  try {\n    const supabaseAdmin = createClient<Database>(\n      process.env.NEXT_PUBLIC_SUPABASE_URL!,\n      process.env.SUPABASE_SERVICE_ROLE_KEY!\n    )\n\n    const { data: usernames, error } = await supabaseAdmin\n      .from(\"profiles\")\n      .select(\"username\")\n      .eq(\"username\", username)\n\n    if (!usernames) {\n      throw new Error(error.message)\n    }\n\n    return new Response(JSON.stringify({ isAvailable: !usernames.length }), {\n      status: 200\n    })\n  } catch (error: any) {\n    const errorMessage = error.error?.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/api/username/get/route.ts",
    "content": "import { Database } from \"@/supabase/types\"\nimport { createClient } from \"@supabase/supabase-js\"\n\nexport const runtime = \"edge\"\n\nexport async function POST(request: Request) {\n  const json = await request.json()\n  const { userId } = json as {\n    userId: string\n  }\n\n  try {\n    const supabaseAdmin = createClient<Database>(\n      process.env.NEXT_PUBLIC_SUPABASE_URL!,\n      process.env.SUPABASE_SERVICE_ROLE_KEY!\n    )\n\n    const { data, error } = await supabaseAdmin\n      .from(\"profiles\")\n      .select(\"username\")\n      .eq(\"user_id\", userId)\n      .single()\n\n    if (!data) {\n      throw new Error(error.message)\n    }\n\n    return new Response(JSON.stringify({ username: data.username }), {\n      status: 200\n    })\n  } catch (error: any) {\n    const errorMessage = error.error?.message || \"An unexpected error occurred\"\n    const errorCode = error.status || 500\n    return new Response(JSON.stringify({ message: errorMessage }), {\n      status: errorCode\n    })\n  }\n}\n"
  },
  {
    "path": "app/auth/callback/route.ts",
    "content": "import { createClient } from \"@/lib/supabase/server\"\nimport { cookies } from \"next/headers\"\nimport { NextResponse } from \"next/server\"\n\nexport async function GET(request: Request) {\n  const requestUrl = new URL(request.url)\n  const code = requestUrl.searchParams.get(\"code\")\n  const next = requestUrl.searchParams.get(\"next\")\n\n  if (code) {\n    const cookieStore = cookies()\n    const supabase = createClient(cookieStore)\n    await supabase.auth.exchangeCodeForSession(code)\n  }\n\n  if (next) {\n    return NextResponse.redirect(requestUrl.origin + next)\n  } else {\n    return NextResponse.redirect(requestUrl.origin)\n  }\n}\n"
  },
  {
    "path": "components/chat/assistant-picker.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconRobotFace } from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { FC, useContext, useEffect, useRef } from \"react\"\nimport { usePromptAndCommand } from \"./chat-hooks/use-prompt-and-command\"\n\ninterface AssistantPickerProps {}\n\nexport const AssistantPicker: FC<AssistantPickerProps> = ({}) => {\n  const {\n    assistants,\n    assistantImages,\n    focusAssistant,\n    atCommand,\n    isAssistantPickerOpen,\n    setIsAssistantPickerOpen\n  } = useContext(ChatbotUIContext)\n\n  const { handleSelectAssistant } = usePromptAndCommand()\n\n  const itemsRef = useRef<(HTMLDivElement | null)[]>([])\n\n  useEffect(() => {\n    if (focusAssistant && itemsRef.current[0]) {\n      itemsRef.current[0].focus()\n    }\n  }, [focusAssistant])\n\n  const filteredAssistants = assistants.filter(assistant =>\n    assistant.name.toLowerCase().includes(atCommand.toLowerCase())\n  )\n\n  const handleOpenChange = (isOpen: boolean) => {\n    setIsAssistantPickerOpen(isOpen)\n  }\n\n  const callSelectAssistant = (assistant: Tables<\"assistants\">) => {\n    handleSelectAssistant(assistant)\n    handleOpenChange(false)\n  }\n\n  const getKeyDownHandler =\n    (index: number) => (e: React.KeyboardEvent<HTMLDivElement>) => {\n      if (e.key === \"Backspace\") {\n        e.preventDefault()\n        handleOpenChange(false)\n      } else if (e.key === \"Enter\") {\n        e.preventDefault()\n        callSelectAssistant(filteredAssistants[index])\n      } else if (\n        (e.key === \"Tab\" || e.key === \"ArrowDown\") &&\n        !e.shiftKey &&\n        index === filteredAssistants.length - 1\n      ) {\n        e.preventDefault()\n        itemsRef.current[0]?.focus()\n      } else if (e.key === \"ArrowUp\" && !e.shiftKey && index === 0) {\n        // go to last element if arrow up is pressed on first element\n        e.preventDefault()\n        itemsRef.current[itemsRef.current.length - 1]?.focus()\n      } else if (e.key === \"ArrowUp\") {\n        e.preventDefault()\n        const prevIndex =\n          index - 1 >= 0 ? index - 1 : itemsRef.current.length - 1\n        itemsRef.current[prevIndex]?.focus()\n      } else if (e.key === \"ArrowDown\") {\n        e.preventDefault()\n        const nextIndex = index + 1 < itemsRef.current.length ? index + 1 : 0\n        itemsRef.current[nextIndex]?.focus()\n      }\n    }\n\n  return (\n    <>\n      {isAssistantPickerOpen && (\n        <div className=\"bg-background flex flex-col space-y-1 rounded-xl border-2 p-2 text-sm\">\n          {filteredAssistants.length === 0 ? (\n            <div className=\"text-md flex h-14 cursor-pointer items-center justify-center italic hover:opacity-50\">\n              No matching assistants.\n            </div>\n          ) : (\n            <>\n              {filteredAssistants.map((item, index) => (\n                <div\n                  key={item.id}\n                  ref={ref => {\n                    itemsRef.current[index] = ref\n                  }}\n                  tabIndex={0}\n                  className=\"hover:bg-accent focus:bg-accent flex cursor-pointer items-center rounded p-2 focus:outline-none\"\n                  onClick={() =>\n                    callSelectAssistant(item as Tables<\"assistants\">)\n                  }\n                  onKeyDown={getKeyDownHandler(index)}\n                >\n                  {item.image_path ? (\n                    <Image\n                      src={\n                        assistantImages.find(\n                          image => image.path === item.image_path\n                        )?.url || \"\"\n                      }\n                      alt={item.name}\n                      width={32}\n                      height={32}\n                      className=\"rounded\"\n                    />\n                  ) : (\n                    <IconRobotFace size={32} />\n                  )}\n\n                  <div className=\"ml-3 flex flex-col\">\n                    <div className=\"font-bold\">{item.name}</div>\n\n                    <div className=\"truncate text-sm opacity-80\">\n                      {item.description || \"No description.\"}\n                    </div>\n                  </div>\n                </div>\n              ))}\n            </>\n          )}\n        </div>\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "components/chat/chat-command-input.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { FC, useContext } from \"react\"\nimport { AssistantPicker } from \"./assistant-picker\"\nimport { usePromptAndCommand } from \"./chat-hooks/use-prompt-and-command\"\nimport { FilePicker } from \"./file-picker\"\nimport { PromptPicker } from \"./prompt-picker\"\nimport { ToolPicker } from \"./tool-picker\"\n\ninterface ChatCommandInputProps {}\n\nexport const ChatCommandInput: FC<ChatCommandInputProps> = ({}) => {\n  const {\n    newMessageFiles,\n    chatFiles,\n    slashCommand,\n    isFilePickerOpen,\n    setIsFilePickerOpen,\n    hashtagCommand,\n    focusPrompt,\n    focusFile\n  } = useContext(ChatbotUIContext)\n\n  const { handleSelectUserFile, handleSelectUserCollection } =\n    usePromptAndCommand()\n\n  return (\n    <>\n      <PromptPicker />\n\n      <FilePicker\n        isOpen={isFilePickerOpen}\n        searchQuery={hashtagCommand}\n        onOpenChange={setIsFilePickerOpen}\n        selectedFileIds={[...newMessageFiles, ...chatFiles].map(\n          file => file.id\n        )}\n        selectedCollectionIds={[]}\n        onSelectFile={handleSelectUserFile}\n        onSelectCollection={handleSelectUserCollection}\n        isFocused={focusFile}\n      />\n\n      <ToolPicker />\n\n      <AssistantPicker />\n    </>\n  )\n}\n"
  },
  {
    "path": "components/chat/chat-files-display.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { getFileFromStorage } from \"@/db/storage/files\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { cn } from \"@/lib/utils\"\nimport { ChatFile, MessageImage } from \"@/types\"\nimport {\n  IconCircleFilled,\n  IconFileFilled,\n  IconFileTypeCsv,\n  IconFileTypeDocx,\n  IconFileTypePdf,\n  IconFileTypeTxt,\n  IconJson,\n  IconLoader2,\n  IconMarkdown,\n  IconX\n} from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { FC, useContext, useState } from \"react\"\nimport { Button } from \"../ui/button\"\nimport { FilePreview } from \"../ui/file-preview\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\nimport { ChatRetrievalSettings } from \"./chat-retrieval-settings\"\n\ninterface ChatFilesDisplayProps {}\n\nexport const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => {\n  useHotkey(\"f\", () => setShowFilesDisplay(prev => !prev))\n  useHotkey(\"e\", () => setUseRetrieval(prev => !prev))\n\n  const {\n    files,\n    newMessageImages,\n    setNewMessageImages,\n    newMessageFiles,\n    setNewMessageFiles,\n    setShowFilesDisplay,\n    showFilesDisplay,\n    chatFiles,\n    chatImages,\n    setChatImages,\n    setChatFiles,\n    setUseRetrieval\n  } = useContext(ChatbotUIContext)\n\n  const [selectedFile, setSelectedFile] = useState<ChatFile | null>(null)\n  const [selectedImage, setSelectedImage] = useState<MessageImage | null>(null)\n  const [showPreview, setShowPreview] = useState(false)\n\n  const messageImages = [\n    ...newMessageImages.filter(\n      image =>\n        !chatImages.some(chatImage => chatImage.messageId === image.messageId)\n    )\n  ]\n\n  const combinedChatFiles = [\n    ...newMessageFiles.filter(\n      file => !chatFiles.some(chatFile => chatFile.id === file.id)\n    ),\n    ...chatFiles\n  ]\n\n  const combinedMessageFiles = [...messageImages, ...combinedChatFiles]\n\n  const getLinkAndView = async (file: ChatFile) => {\n    const fileRecord = files.find(f => f.id === file.id)\n\n    if (!fileRecord) return\n\n    const link = await getFileFromStorage(fileRecord.file_path)\n    window.open(link, \"_blank\")\n  }\n\n  return showFilesDisplay && combinedMessageFiles.length > 0 ? (\n    <>\n      {showPreview && selectedImage && (\n        <FilePreview\n          type=\"image\"\n          item={selectedImage}\n          isOpen={showPreview}\n          onOpenChange={(isOpen: boolean) => {\n            setShowPreview(isOpen)\n            setSelectedImage(null)\n          }}\n        />\n      )}\n\n      {showPreview && selectedFile && (\n        <FilePreview\n          type=\"file\"\n          item={selectedFile}\n          isOpen={showPreview}\n          onOpenChange={(isOpen: boolean) => {\n            setShowPreview(isOpen)\n            setSelectedFile(null)\n          }}\n        />\n      )}\n\n      <div className=\"space-y-2\">\n        <div className=\"flex w-full items-center justify-center\">\n          <Button\n            className=\"flex h-[32px] w-[140px] space-x-2\"\n            onClick={() => setShowFilesDisplay(false)}\n          >\n            <RetrievalToggle />\n\n            <div>Hide files</div>\n\n            <div onClick={e => e.stopPropagation()}>\n              <ChatRetrievalSettings />\n            </div>\n          </Button>\n        </div>\n\n        <div className=\"overflow-auto\">\n          <div className=\"flex gap-2 overflow-auto pt-2\">\n            {messageImages.map((image, index) => (\n              <div\n                key={index}\n                className=\"relative flex h-[64px] cursor-pointer items-center space-x-4 rounded-xl hover:opacity-50\"\n              >\n                <Image\n                  className=\"rounded\"\n                  // Force the image to be 56px by 56px\n                  style={{\n                    minWidth: \"56px\",\n                    minHeight: \"56px\",\n                    maxHeight: \"56px\",\n                    maxWidth: \"56px\"\n                  }}\n                  src={image.base64} // Preview images will always be base64\n                  alt=\"File image\"\n                  width={56}\n                  height={56}\n                  onClick={() => {\n                    setSelectedImage(image)\n                    setShowPreview(true)\n                  }}\n                />\n\n                <IconX\n                  className=\"bg-muted-foreground border-primary absolute right-[-6px] top-[-2px] flex size-5 cursor-pointer items-center justify-center rounded-full border-DEFAULT text-[10px] hover:border-red-500 hover:bg-white hover:text-red-500\"\n                  onClick={e => {\n                    e.stopPropagation()\n                    setNewMessageImages(\n                      newMessageImages.filter(\n                        f => f.messageId !== image.messageId\n                      )\n                    )\n                    setChatImages(\n                      chatImages.filter(f => f.messageId !== image.messageId)\n                    )\n                  }}\n                />\n              </div>\n            ))}\n\n            {combinedChatFiles.map((file, index) =>\n              file.id === \"loading\" ? (\n                <div\n                  key={index}\n                  className=\"relative flex h-[64px] items-center space-x-4 rounded-xl border-2 px-4 py-3\"\n                >\n                  <div className=\"rounded bg-blue-500 p-2\">\n                    <IconLoader2 className=\"animate-spin\" />\n                  </div>\n\n                  <div className=\"truncate text-sm\">\n                    <div className=\"truncate\">{file.name}</div>\n                    <div className=\"truncate opacity-50\">{file.type}</div>\n                  </div>\n                </div>\n              ) : (\n                <div\n                  key={file.id}\n                  className=\"relative flex h-[64px] cursor-pointer items-center space-x-4 rounded-xl border-2 px-4 py-3 hover:opacity-50\"\n                  onClick={() => getLinkAndView(file)}\n                >\n                  <div className=\"rounded bg-blue-500 p-2\">\n                    {(() => {\n                      let fileExtension = file.type.includes(\"/\")\n                        ? file.type.split(\"/\")[1]\n                        : file.type\n\n                      switch (fileExtension) {\n                        case \"pdf\":\n                          return <IconFileTypePdf />\n                        case \"markdown\":\n                          return <IconMarkdown />\n                        case \"txt\":\n                          return <IconFileTypeTxt />\n                        case \"json\":\n                          return <IconJson />\n                        case \"csv\":\n                          return <IconFileTypeCsv />\n                        case \"docx\":\n                          return <IconFileTypeDocx />\n                        default:\n                          return <IconFileFilled />\n                      }\n                    })()}\n                  </div>\n\n                  <div className=\"truncate text-sm\">\n                    <div className=\"truncate\">{file.name}</div>\n                  </div>\n\n                  <IconX\n                    className=\"bg-muted-foreground border-primary absolute right-[-6px] top-[-6px] flex size-5 cursor-pointer items-center justify-center rounded-full border-DEFAULT text-[10px] hover:border-red-500 hover:bg-white hover:text-red-500\"\n                    onClick={e => {\n                      e.stopPropagation()\n                      setNewMessageFiles(\n                        newMessageFiles.filter(f => f.id !== file.id)\n                      )\n                      setChatFiles(chatFiles.filter(f => f.id !== file.id))\n                    }}\n                  />\n                </div>\n              )\n            )}\n          </div>\n        </div>\n      </div>\n    </>\n  ) : (\n    combinedMessageFiles.length > 0 && (\n      <div className=\"flex w-full items-center justify-center space-x-2\">\n        <Button\n          className=\"flex h-[32px] w-[140px] space-x-2\"\n          onClick={() => setShowFilesDisplay(true)}\n        >\n          <RetrievalToggle />\n\n          <div>\n            {\" \"}\n            View {combinedMessageFiles.length} file\n            {combinedMessageFiles.length > 1 ? \"s\" : \"\"}\n          </div>\n\n          <div onClick={e => e.stopPropagation()}>\n            <ChatRetrievalSettings />\n          </div>\n        </Button>\n      </div>\n    )\n  )\n}\n\nconst RetrievalToggle = ({}) => {\n  const { useRetrieval, setUseRetrieval } = useContext(ChatbotUIContext)\n\n  return (\n    <div className=\"flex items-center\">\n      <WithTooltip\n        delayDuration={0}\n        side=\"top\"\n        display={\n          <div>\n            {useRetrieval\n              ? \"File retrieval is enabled on the selected files for this message. Click the indicator to disable.\"\n              : \"Click the indicator to enable file retrieval for this message.\"}\n          </div>\n        }\n        trigger={\n          <IconCircleFilled\n            className={cn(\n              \"p-1\",\n              useRetrieval ? \"text-green-500\" : \"text-red-500\",\n              useRetrieval ? \"hover:text-green-200\" : \"hover:text-red-200\"\n            )}\n            size={24}\n            onClick={e => {\n              e.stopPropagation()\n              setUseRetrieval(prev => !prev)\n            }}\n          />\n        }\n      />\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/chat/chat-help.tsx",
    "content": "import useHotkey from \"@/lib/hooks/use-hotkey\"\nimport {\n  IconBrandGithub,\n  IconBrandX,\n  IconHelpCircle,\n  IconQuestionMark\n} from \"@tabler/icons-react\"\nimport Link from \"next/link\"\nimport { FC, useState } from \"react\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger\n} from \"../ui/dropdown-menu\"\nimport { Announcements } from \"../utility/announcements\"\n\ninterface ChatHelpProps {}\n\nexport const ChatHelp: FC<ChatHelpProps> = ({}) => {\n  useHotkey(\"/\", () => setIsOpen(prevState => !prevState))\n\n  const [isOpen, setIsOpen] = useState(false)\n\n  return (\n    <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>\n      <DropdownMenuTrigger asChild>\n        <IconQuestionMark className=\"bg-primary text-secondary size-[24px] cursor-pointer rounded-full p-0.5 opacity-60 hover:opacity-50 lg:size-[30px] lg:p-1\" />\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuLabel className=\"flex items-center justify-between\">\n          <div className=\"flex space-x-2\">\n            <Link\n              className=\"cursor-pointer hover:opacity-50\"\n              href=\"https://twitter.com/ChatbotUI\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              <IconBrandX />\n            </Link>\n\n            <Link\n              className=\"cursor-pointer hover:opacity-50\"\n              href=\"https://github.com/mckaywrigley/chatbot-ui\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              <IconBrandGithub />\n            </Link>\n          </div>\n\n          <div className=\"flex space-x-2\">\n            <Announcements />\n\n            <Link\n              className=\"cursor-pointer hover:opacity-50\"\n              href=\"/help\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              <IconHelpCircle size={24} />\n            </Link>\n          </div>\n        </DropdownMenuLabel>\n\n        <DropdownMenuSeparator />\n\n        <DropdownMenuItem className=\"flex justify-between\">\n          <div>Show Help</div>\n          <div className=\"flex opacity-60\">\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ⌘\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              Shift\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              /\n            </div>\n          </div>\n        </DropdownMenuItem>\n\n        <DropdownMenuItem className=\"flex justify-between\">\n          <div>Show Workspaces</div>\n          <div className=\"flex opacity-60\">\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ⌘\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              Shift\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ;\n            </div>\n          </div>\n        </DropdownMenuItem>\n\n        <DropdownMenuItem className=\"flex w-[300px] justify-between\">\n          <div>New Chat</div>\n          <div className=\"flex opacity-60\">\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ⌘\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              Shift\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              O\n            </div>\n          </div>\n        </DropdownMenuItem>\n\n        <DropdownMenuItem className=\"flex justify-between\">\n          <div>Focus Chat</div>\n          <div className=\"flex opacity-60\">\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ⌘\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              Shift\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              L\n            </div>\n          </div>\n        </DropdownMenuItem>\n\n        <DropdownMenuItem className=\"flex justify-between\">\n          <div>Toggle Files</div>\n          <div className=\"flex opacity-60\">\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ⌘\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              Shift\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              F\n            </div>\n          </div>\n        </DropdownMenuItem>\n\n        <DropdownMenuItem className=\"flex justify-between\">\n          <div>Toggle Retrieval</div>\n          <div className=\"flex opacity-60\">\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ⌘\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              Shift\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              E\n            </div>\n          </div>\n        </DropdownMenuItem>\n\n        <DropdownMenuItem className=\"flex justify-between\">\n          <div>Open Settings</div>\n          <div className=\"flex opacity-60\">\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ⌘\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              Shift\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              I\n            </div>\n          </div>\n        </DropdownMenuItem>\n\n        <DropdownMenuItem className=\"flex justify-between\">\n          <div>Open Quick Settings</div>\n          <div className=\"flex opacity-60\">\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ⌘\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              Shift\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              P\n            </div>\n          </div>\n        </DropdownMenuItem>\n\n        <DropdownMenuItem className=\"flex justify-between\">\n          <div>Toggle Sidebar</div>\n          <div className=\"flex opacity-60\">\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              ⌘\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              Shift\n            </div>\n            <div className=\"min-w-[30px] rounded border-DEFAULT p-1 text-center\">\n              S\n            </div>\n          </div>\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n"
  },
  {
    "path": "components/chat/chat-helpers/index.ts",
    "content": "// Only used in use-chat-handler.tsx to keep it clean\n\nimport { createChatFiles } from \"@/db/chat-files\"\nimport { createChat } from \"@/db/chats\"\nimport { createMessageFileItems } from \"@/db/message-file-items\"\nimport { createMessages, updateMessage } from \"@/db/messages\"\nimport { uploadMessageImage } from \"@/db/storage/message-images\"\nimport {\n  buildFinalMessages,\n  adaptMessagesForGoogleGemini\n} from \"@/lib/build-prompt\"\nimport { consumeReadableStream } from \"@/lib/consume-stream\"\nimport { Tables, TablesInsert } from \"@/supabase/types\"\nimport {\n  ChatFile,\n  ChatMessage,\n  ChatPayload,\n  ChatSettings,\n  LLM,\n  MessageImage\n} from \"@/types\"\nimport React from \"react\"\nimport { toast } from \"sonner\"\nimport { v4 as uuidv4 } from \"uuid\"\n\nexport const validateChatSettings = (\n  chatSettings: ChatSettings | null,\n  modelData: LLM | undefined,\n  profile: Tables<\"profiles\"> | null,\n  selectedWorkspace: Tables<\"workspaces\"> | null,\n  messageContent: string\n) => {\n  if (!chatSettings) {\n    throw new Error(\"Chat settings not found\")\n  }\n\n  if (!modelData) {\n    throw new Error(\"Model not found\")\n  }\n\n  if (!profile) {\n    throw new Error(\"Profile not found\")\n  }\n\n  if (!selectedWorkspace) {\n    throw new Error(\"Workspace not found\")\n  }\n\n  if (!messageContent) {\n    throw new Error(\"Message content not found\")\n  }\n}\n\nexport const handleRetrieval = async (\n  userInput: string,\n  newMessageFiles: ChatFile[],\n  chatFiles: ChatFile[],\n  embeddingsProvider: \"openai\" | \"local\",\n  sourceCount: number\n) => {\n  const response = await fetch(\"/api/retrieval/retrieve\", {\n    method: \"POST\",\n    body: JSON.stringify({\n      userInput,\n      fileIds: [...newMessageFiles, ...chatFiles].map(file => file.id),\n      embeddingsProvider,\n      sourceCount\n    })\n  })\n\n  if (!response.ok) {\n    console.error(\"Error retrieving:\", response)\n  }\n\n  const { results } = (await response.json()) as {\n    results: Tables<\"file_items\">[]\n  }\n\n  return results\n}\n\nexport const createTempMessages = (\n  messageContent: string,\n  chatMessages: ChatMessage[],\n  chatSettings: ChatSettings,\n  b64Images: string[],\n  isRegeneration: boolean,\n  setChatMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>,\n  selectedAssistant: Tables<\"assistants\"> | null\n) => {\n  let tempUserChatMessage: ChatMessage = {\n    message: {\n      chat_id: \"\",\n      assistant_id: null,\n      content: messageContent,\n      created_at: \"\",\n      id: uuidv4(),\n      image_paths: b64Images,\n      model: chatSettings.model,\n      role: \"user\",\n      sequence_number: chatMessages.length,\n      updated_at: \"\",\n      user_id: \"\"\n    },\n    fileItems: []\n  }\n\n  let tempAssistantChatMessage: ChatMessage = {\n    message: {\n      chat_id: \"\",\n      assistant_id: selectedAssistant?.id || null,\n      content: \"\",\n      created_at: \"\",\n      id: uuidv4(),\n      image_paths: [],\n      model: chatSettings.model,\n      role: \"assistant\",\n      sequence_number: chatMessages.length + 1,\n      updated_at: \"\",\n      user_id: \"\"\n    },\n    fileItems: []\n  }\n\n  let newMessages = []\n\n  if (isRegeneration) {\n    const lastMessageIndex = chatMessages.length - 1\n    chatMessages[lastMessageIndex].message.content = \"\"\n    newMessages = [...chatMessages]\n  } else {\n    newMessages = [\n      ...chatMessages,\n      tempUserChatMessage,\n      tempAssistantChatMessage\n    ]\n  }\n\n  setChatMessages(newMessages)\n\n  return {\n    tempUserChatMessage,\n    tempAssistantChatMessage\n  }\n}\n\nexport const handleLocalChat = async (\n  payload: ChatPayload,\n  profile: Tables<\"profiles\">,\n  chatSettings: ChatSettings,\n  tempAssistantMessage: ChatMessage,\n  isRegeneration: boolean,\n  newAbortController: AbortController,\n  setIsGenerating: React.Dispatch<React.SetStateAction<boolean>>,\n  setFirstTokenReceived: React.Dispatch<React.SetStateAction<boolean>>,\n  setChatMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>,\n  setToolInUse: React.Dispatch<React.SetStateAction<string>>\n) => {\n  const formattedMessages = await buildFinalMessages(payload, profile, [])\n\n  // Ollama API: https://github.com/jmorganca/ollama/blob/main/docs/api.md\n  const response = await fetchChatResponse(\n    process.env.NEXT_PUBLIC_OLLAMA_URL + \"/api/chat\",\n    {\n      model: chatSettings.model,\n      messages: formattedMessages,\n      options: {\n        temperature: payload.chatSettings.temperature\n      }\n    },\n    false,\n    newAbortController,\n    setIsGenerating,\n    setChatMessages\n  )\n\n  return await processResponse(\n    response,\n    isRegeneration\n      ? payload.chatMessages[payload.chatMessages.length - 1]\n      : tempAssistantMessage,\n    false,\n    newAbortController,\n    setFirstTokenReceived,\n    setChatMessages,\n    setToolInUse\n  )\n}\n\nexport const handleHostedChat = async (\n  payload: ChatPayload,\n  profile: Tables<\"profiles\">,\n  modelData: LLM,\n  tempAssistantChatMessage: ChatMessage,\n  isRegeneration: boolean,\n  newAbortController: AbortController,\n  newMessageImages: MessageImage[],\n  chatImages: MessageImage[],\n  setIsGenerating: React.Dispatch<React.SetStateAction<boolean>>,\n  setFirstTokenReceived: React.Dispatch<React.SetStateAction<boolean>>,\n  setChatMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>,\n  setToolInUse: React.Dispatch<React.SetStateAction<string>>\n) => {\n  const provider =\n    modelData.provider === \"openai\" && profile.use_azure_openai\n      ? \"azure\"\n      : modelData.provider\n\n  let draftMessages = await buildFinalMessages(payload, profile, chatImages)\n\n  let formattedMessages : any[] = []\n  if (provider === \"google\") {\n    formattedMessages = await adaptMessagesForGoogleGemini(payload, draftMessages)\n  } else {\n    formattedMessages = draftMessages\n  }\n\n  const apiEndpoint =\n    provider === \"custom\" ? \"/api/chat/custom\" : `/api/chat/${provider}`\n\n  const requestBody = {\n    chatSettings: payload.chatSettings,\n    messages: formattedMessages,\n    customModelId: provider === \"custom\" ? modelData.hostedId : \"\"\n  }\n\n  const response = await fetchChatResponse(\n    apiEndpoint,\n    requestBody,\n    true,\n    newAbortController,\n    setIsGenerating,\n    setChatMessages\n  )\n\n  return await processResponse(\n    response,\n    isRegeneration\n      ? payload.chatMessages[payload.chatMessages.length - 1]\n      : tempAssistantChatMessage,\n    true,\n    newAbortController,\n    setFirstTokenReceived,\n    setChatMessages,\n    setToolInUse\n  )\n}\n\nexport const fetchChatResponse = async (\n  url: string,\n  body: object,\n  isHosted: boolean,\n  controller: AbortController,\n  setIsGenerating: React.Dispatch<React.SetStateAction<boolean>>,\n  setChatMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>\n) => {\n  const response = await fetch(url, {\n    method: \"POST\",\n    body: JSON.stringify(body),\n    signal: controller.signal\n  })\n\n  if (!response.ok) {\n    if (response.status === 404 && !isHosted) {\n      toast.error(\n        \"Model not found. Make sure you have it downloaded via Ollama.\"\n      )\n    }\n\n    const errorData = await response.json()\n\n    toast.error(errorData.message)\n\n    setIsGenerating(false)\n    setChatMessages(prevMessages => prevMessages.slice(0, -2))\n  }\n\n  return response\n}\n\nexport const processResponse = async (\n  response: Response,\n  lastChatMessage: ChatMessage,\n  isHosted: boolean,\n  controller: AbortController,\n  setFirstTokenReceived: React.Dispatch<React.SetStateAction<boolean>>,\n  setChatMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>,\n  setToolInUse: React.Dispatch<React.SetStateAction<string>>\n) => {\n  let fullText = \"\"\n  let contentToAdd = \"\"\n\n  if (response.body) {\n    await consumeReadableStream(\n      response.body,\n      chunk => {\n        setFirstTokenReceived(true)\n        setToolInUse(\"none\")\n\n        try {\n          contentToAdd = isHosted\n            ? chunk\n            : // Ollama's streaming endpoint returns new-line separated JSON\n              // objects. A chunk may have more than one of these objects, so we\n              // need to split the chunk by new-lines and handle each one\n              // separately.\n              chunk\n                .trimEnd()\n                .split(\"\\n\")\n                .reduce(\n                  (acc, line) => acc + JSON.parse(line).message.content,\n                  \"\"\n                )\n          fullText += contentToAdd\n        } catch (error) {\n          console.error(\"Error parsing JSON:\", error)\n        }\n\n        setChatMessages(prev =>\n          prev.map(chatMessage => {\n            if (chatMessage.message.id === lastChatMessage.message.id) {\n              const updatedChatMessage: ChatMessage = {\n                message: {\n                  ...chatMessage.message,\n                  content: fullText\n                },\n                fileItems: chatMessage.fileItems\n              }\n\n              return updatedChatMessage\n            }\n\n            return chatMessage\n          })\n        )\n      },\n      controller.signal\n    )\n\n    return fullText\n  } else {\n    throw new Error(\"Response body is null\")\n  }\n}\n\nexport const handleCreateChat = async (\n  chatSettings: ChatSettings,\n  profile: Tables<\"profiles\">,\n  selectedWorkspace: Tables<\"workspaces\">,\n  messageContent: string,\n  selectedAssistant: Tables<\"assistants\">,\n  newMessageFiles: ChatFile[],\n  setSelectedChat: React.Dispatch<React.SetStateAction<Tables<\"chats\"> | null>>,\n  setChats: React.Dispatch<React.SetStateAction<Tables<\"chats\">[]>>,\n  setChatFiles: React.Dispatch<React.SetStateAction<ChatFile[]>>\n) => {\n  const createdChat = await createChat({\n    user_id: profile.user_id,\n    workspace_id: selectedWorkspace.id,\n    assistant_id: selectedAssistant?.id || null,\n    context_length: chatSettings.contextLength,\n    include_profile_context: chatSettings.includeProfileContext,\n    include_workspace_instructions: chatSettings.includeWorkspaceInstructions,\n    model: chatSettings.model,\n    name: messageContent.substring(0, 100),\n    prompt: chatSettings.prompt,\n    temperature: chatSettings.temperature,\n    embeddings_provider: chatSettings.embeddingsProvider\n  })\n\n  setSelectedChat(createdChat)\n  setChats(chats => [createdChat, ...chats])\n\n  await createChatFiles(\n    newMessageFiles.map(file => ({\n      user_id: profile.user_id,\n      chat_id: createdChat.id,\n      file_id: file.id\n    }))\n  )\n\n  setChatFiles(prev => [...prev, ...newMessageFiles])\n\n  return createdChat\n}\n\nexport const handleCreateMessages = async (\n  chatMessages: ChatMessage[],\n  currentChat: Tables<\"chats\">,\n  profile: Tables<\"profiles\">,\n  modelData: LLM,\n  messageContent: string,\n  generatedText: string,\n  newMessageImages: MessageImage[],\n  isRegeneration: boolean,\n  retrievedFileItems: Tables<\"file_items\">[],\n  setChatMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>,\n  setChatFileItems: React.Dispatch<\n    React.SetStateAction<Tables<\"file_items\">[]>\n  >,\n  setChatImages: React.Dispatch<React.SetStateAction<MessageImage[]>>,\n  selectedAssistant: Tables<\"assistants\"> | null\n) => {\n  const finalUserMessage: TablesInsert<\"messages\"> = {\n    chat_id: currentChat.id,\n    assistant_id: null,\n    user_id: profile.user_id,\n    content: messageContent,\n    model: modelData.modelId,\n    role: \"user\",\n    sequence_number: chatMessages.length,\n    image_paths: []\n  }\n\n  const finalAssistantMessage: TablesInsert<\"messages\"> = {\n    chat_id: currentChat.id,\n    assistant_id: selectedAssistant?.id || null,\n    user_id: profile.user_id,\n    content: generatedText,\n    model: modelData.modelId,\n    role: \"assistant\",\n    sequence_number: chatMessages.length + 1,\n    image_paths: []\n  }\n\n  let finalChatMessages: ChatMessage[] = []\n\n  if (isRegeneration) {\n    const lastStartingMessage = chatMessages[chatMessages.length - 1].message\n\n    const updatedMessage = await updateMessage(lastStartingMessage.id, {\n      ...lastStartingMessage,\n      content: generatedText\n    })\n\n    chatMessages[chatMessages.length - 1].message = updatedMessage\n\n    finalChatMessages = [...chatMessages]\n\n    setChatMessages(finalChatMessages)\n  } else {\n    const createdMessages = await createMessages([\n      finalUserMessage,\n      finalAssistantMessage\n    ])\n\n    // Upload each image (stored in newMessageImages) for the user message to message_images bucket\n    const uploadPromises = newMessageImages\n      .filter(obj => obj.file !== null)\n      .map(obj => {\n        let filePath = `${profile.user_id}/${currentChat.id}/${\n          createdMessages[0].id\n        }/${uuidv4()}`\n\n        return uploadMessageImage(filePath, obj.file as File).catch(error => {\n          console.error(`Failed to upload image at ${filePath}:`, error)\n          return null\n        })\n      })\n\n    const paths = (await Promise.all(uploadPromises)).filter(\n      Boolean\n    ) as string[]\n\n    setChatImages(prevImages => [\n      ...prevImages,\n      ...newMessageImages.map((obj, index) => ({\n        ...obj,\n        messageId: createdMessages[0].id,\n        path: paths[index]\n      }))\n    ])\n\n    const updatedMessage = await updateMessage(createdMessages[0].id, {\n      ...createdMessages[0],\n      image_paths: paths\n    })\n\n    const createdMessageFileItems = await createMessageFileItems(\n      retrievedFileItems.map(fileItem => {\n        return {\n          user_id: profile.user_id,\n          message_id: createdMessages[1].id,\n          file_item_id: fileItem.id\n        }\n      })\n    )\n\n    finalChatMessages = [\n      ...chatMessages,\n      {\n        message: updatedMessage,\n        fileItems: []\n      },\n      {\n        message: createdMessages[1],\n        fileItems: retrievedFileItems.map(fileItem => fileItem.id)\n      }\n    ]\n\n    setChatFileItems(prevFileItems => {\n      const newFileItems = retrievedFileItems.filter(\n        fileItem => !prevFileItems.some(prevItem => prevItem.id === fileItem.id)\n      )\n\n      return [...prevFileItems, ...newFileItems]\n    })\n\n    setChatMessages(finalChatMessages)\n  }\n}\n"
  },
  {
    "path": "components/chat/chat-hooks/use-chat-handler.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { getAssistantCollectionsByAssistantId } from \"@/db/assistant-collections\"\nimport { getAssistantFilesByAssistantId } from \"@/db/assistant-files\"\nimport { getAssistantToolsByAssistantId } from \"@/db/assistant-tools\"\nimport { updateChat } from \"@/db/chats\"\nimport { getCollectionFilesByCollectionId } from \"@/db/collection-files\"\nimport { deleteMessagesIncludingAndAfter } from \"@/db/messages\"\nimport { buildFinalMessages } from \"@/lib/build-prompt\"\nimport { Tables } from \"@/supabase/types\"\nimport { ChatMessage, ChatPayload, LLMID, ModelProvider } from \"@/types\"\nimport { useRouter } from \"next/navigation\"\nimport { useContext, useEffect, useRef } from \"react\"\nimport { LLM_LIST } from \"../../../lib/models/llm/llm-list\"\nimport {\n  createTempMessages,\n  handleCreateChat,\n  handleCreateMessages,\n  handleHostedChat,\n  handleLocalChat,\n  handleRetrieval,\n  processResponse,\n  validateChatSettings\n} from \"../chat-helpers\"\n\nexport const useChatHandler = () => {\n  const router = useRouter()\n\n  const {\n    userInput,\n    chatFiles,\n    setUserInput,\n    setNewMessageImages,\n    profile,\n    setIsGenerating,\n    setChatMessages,\n    setFirstTokenReceived,\n    selectedChat,\n    selectedWorkspace,\n    setSelectedChat,\n    setChats,\n    setSelectedTools,\n    availableLocalModels,\n    availableOpenRouterModels,\n    abortController,\n    setAbortController,\n    chatSettings,\n    newMessageImages,\n    selectedAssistant,\n    chatMessages,\n    chatImages,\n    setChatImages,\n    setChatFiles,\n    setNewMessageFiles,\n    setShowFilesDisplay,\n    newMessageFiles,\n    chatFileItems,\n    setChatFileItems,\n    setToolInUse,\n    useRetrieval,\n    sourceCount,\n    setIsPromptPickerOpen,\n    setIsFilePickerOpen,\n    selectedTools,\n    selectedPreset,\n    setChatSettings,\n    models,\n    isPromptPickerOpen,\n    isFilePickerOpen,\n    isToolPickerOpen\n  } = useContext(ChatbotUIContext)\n\n  const chatInputRef = useRef<HTMLTextAreaElement>(null)\n\n  useEffect(() => {\n    if (!isPromptPickerOpen || !isFilePickerOpen || !isToolPickerOpen) {\n      chatInputRef.current?.focus()\n    }\n  }, [isPromptPickerOpen, isFilePickerOpen, isToolPickerOpen])\n\n  const handleNewChat = async () => {\n    if (!selectedWorkspace) return\n\n    setUserInput(\"\")\n    setChatMessages([])\n    setSelectedChat(null)\n    setChatFileItems([])\n\n    setIsGenerating(false)\n    setFirstTokenReceived(false)\n\n    setChatFiles([])\n    setChatImages([])\n    setNewMessageFiles([])\n    setNewMessageImages([])\n    setShowFilesDisplay(false)\n    setIsPromptPickerOpen(false)\n    setIsFilePickerOpen(false)\n\n    setSelectedTools([])\n    setToolInUse(\"none\")\n\n    if (selectedAssistant) {\n      setChatSettings({\n        model: selectedAssistant.model as LLMID,\n        prompt: selectedAssistant.prompt,\n        temperature: selectedAssistant.temperature,\n        contextLength: selectedAssistant.context_length,\n        includeProfileContext: selectedAssistant.include_profile_context,\n        includeWorkspaceInstructions:\n          selectedAssistant.include_workspace_instructions,\n        embeddingsProvider: selectedAssistant.embeddings_provider as\n          | \"openai\"\n          | \"local\"\n      })\n\n      let allFiles = []\n\n      const assistantFiles = (\n        await getAssistantFilesByAssistantId(selectedAssistant.id)\n      ).files\n      allFiles = [...assistantFiles]\n      const assistantCollections = (\n        await getAssistantCollectionsByAssistantId(selectedAssistant.id)\n      ).collections\n      for (const collection of assistantCollections) {\n        const collectionFiles = (\n          await getCollectionFilesByCollectionId(collection.id)\n        ).files\n        allFiles = [...allFiles, ...collectionFiles]\n      }\n      const assistantTools = (\n        await getAssistantToolsByAssistantId(selectedAssistant.id)\n      ).tools\n\n      setSelectedTools(assistantTools)\n      setChatFiles(\n        allFiles.map(file => ({\n          id: file.id,\n          name: file.name,\n          type: file.type,\n          file: null\n        }))\n      )\n\n      if (allFiles.length > 0) setShowFilesDisplay(true)\n    } else if (selectedPreset) {\n      setChatSettings({\n        model: selectedPreset.model as LLMID,\n        prompt: selectedPreset.prompt,\n        temperature: selectedPreset.temperature,\n        contextLength: selectedPreset.context_length,\n        includeProfileContext: selectedPreset.include_profile_context,\n        includeWorkspaceInstructions:\n          selectedPreset.include_workspace_instructions,\n        embeddingsProvider: selectedPreset.embeddings_provider as\n          | \"openai\"\n          | \"local\"\n      })\n    } else if (selectedWorkspace) {\n      // setChatSettings({\n      //   model: (selectedWorkspace.default_model ||\n      //     \"gpt-4-1106-preview\") as LLMID,\n      //   prompt:\n      //     selectedWorkspace.default_prompt ||\n      //     \"You are a friendly, helpful AI assistant.\",\n      //   temperature: selectedWorkspace.default_temperature || 0.5,\n      //   contextLength: selectedWorkspace.default_context_length || 4096,\n      //   includeProfileContext:\n      //     selectedWorkspace.include_profile_context || true,\n      //   includeWorkspaceInstructions:\n      //     selectedWorkspace.include_workspace_instructions || true,\n      //   embeddingsProvider:\n      //     (selectedWorkspace.embeddings_provider as \"openai\" | \"local\") ||\n      //     \"openai\"\n      // })\n    }\n\n    return router.push(`/${selectedWorkspace.id}/chat`)\n  }\n\n  const handleFocusChatInput = () => {\n    chatInputRef.current?.focus()\n  }\n\n  const handleStopMessage = () => {\n    if (abortController) {\n      abortController.abort()\n    }\n  }\n\n  const handleSendMessage = async (\n    messageContent: string,\n    chatMessages: ChatMessage[],\n    isRegeneration: boolean\n  ) => {\n    const startingInput = messageContent\n\n    try {\n      setUserInput(\"\")\n      setIsGenerating(true)\n      setIsPromptPickerOpen(false)\n      setIsFilePickerOpen(false)\n      setNewMessageImages([])\n\n      const newAbortController = new AbortController()\n      setAbortController(newAbortController)\n\n      const modelData = [\n        ...models.map(model => ({\n          modelId: model.model_id as LLMID,\n          modelName: model.name,\n          provider: \"custom\" as ModelProvider,\n          hostedId: model.id,\n          platformLink: \"\",\n          imageInput: false\n        })),\n        ...LLM_LIST,\n        ...availableLocalModels,\n        ...availableOpenRouterModels\n      ].find(llm => llm.modelId === chatSettings?.model)\n\n      validateChatSettings(\n        chatSettings,\n        modelData,\n        profile,\n        selectedWorkspace,\n        messageContent\n      )\n\n      let currentChat = selectedChat ? { ...selectedChat } : null\n\n      const b64Images = newMessageImages.map(image => image.base64)\n\n      let retrievedFileItems: Tables<\"file_items\">[] = []\n\n      if (\n        (newMessageFiles.length > 0 || chatFiles.length > 0) &&\n        useRetrieval\n      ) {\n        setToolInUse(\"retrieval\")\n\n        retrievedFileItems = await handleRetrieval(\n          userInput,\n          newMessageFiles,\n          chatFiles,\n          chatSettings!.embeddingsProvider,\n          sourceCount\n        )\n      }\n\n      const { tempUserChatMessage, tempAssistantChatMessage } =\n        createTempMessages(\n          messageContent,\n          chatMessages,\n          chatSettings!,\n          b64Images,\n          isRegeneration,\n          setChatMessages,\n          selectedAssistant\n        )\n\n      let payload: ChatPayload = {\n        chatSettings: chatSettings!,\n        workspaceInstructions: selectedWorkspace!.instructions || \"\",\n        chatMessages: isRegeneration\n          ? [...chatMessages]\n          : [...chatMessages, tempUserChatMessage],\n        assistant: selectedChat?.assistant_id ? selectedAssistant : null,\n        messageFileItems: retrievedFileItems,\n        chatFileItems: chatFileItems\n      }\n\n      let generatedText = \"\"\n\n      if (selectedTools.length > 0) {\n        setToolInUse(\"Tools\")\n\n        const formattedMessages = await buildFinalMessages(\n          payload,\n          profile!,\n          chatImages\n        )\n\n        const response = await fetch(\"/api/chat/tools\", {\n          method: \"POST\",\n          headers: {\n            \"Content-Type\": \"application/json\"\n          },\n          body: JSON.stringify({\n            chatSettings: payload.chatSettings,\n            messages: formattedMessages,\n            selectedTools\n          })\n        })\n\n        setToolInUse(\"none\")\n\n        generatedText = await processResponse(\n          response,\n          isRegeneration\n            ? payload.chatMessages[payload.chatMessages.length - 1]\n            : tempAssistantChatMessage,\n          true,\n          newAbortController,\n          setFirstTokenReceived,\n          setChatMessages,\n          setToolInUse\n        )\n      } else {\n        if (modelData!.provider === \"ollama\") {\n          generatedText = await handleLocalChat(\n            payload,\n            profile!,\n            chatSettings!,\n            tempAssistantChatMessage,\n            isRegeneration,\n            newAbortController,\n            setIsGenerating,\n            setFirstTokenReceived,\n            setChatMessages,\n            setToolInUse\n          )\n        } else {\n          generatedText = await handleHostedChat(\n            payload,\n            profile!,\n            modelData!,\n            tempAssistantChatMessage,\n            isRegeneration,\n            newAbortController,\n            newMessageImages,\n            chatImages,\n            setIsGenerating,\n            setFirstTokenReceived,\n            setChatMessages,\n            setToolInUse\n          )\n        }\n      }\n\n      if (!currentChat) {\n        currentChat = await handleCreateChat(\n          chatSettings!,\n          profile!,\n          selectedWorkspace!,\n          messageContent,\n          selectedAssistant!,\n          newMessageFiles,\n          setSelectedChat,\n          setChats,\n          setChatFiles\n        )\n      } else {\n        const updatedChat = await updateChat(currentChat.id, {\n          updated_at: new Date().toISOString()\n        })\n\n        setChats(prevChats => {\n          const updatedChats = prevChats.map(prevChat =>\n            prevChat.id === updatedChat.id ? updatedChat : prevChat\n          )\n\n          return updatedChats\n        })\n      }\n\n      await handleCreateMessages(\n        chatMessages,\n        currentChat,\n        profile!,\n        modelData!,\n        messageContent,\n        generatedText,\n        newMessageImages,\n        isRegeneration,\n        retrievedFileItems,\n        setChatMessages,\n        setChatFileItems,\n        setChatImages,\n        selectedAssistant\n      )\n\n      setIsGenerating(false)\n      setFirstTokenReceived(false)\n    } catch (error) {\n      setIsGenerating(false)\n      setFirstTokenReceived(false)\n      setUserInput(startingInput)\n    }\n  }\n\n  const handleSendEdit = async (\n    editedContent: string,\n    sequenceNumber: number\n  ) => {\n    if (!selectedChat) return\n\n    await deleteMessagesIncludingAndAfter(\n      selectedChat.user_id,\n      selectedChat.id,\n      sequenceNumber\n    )\n\n    const filteredMessages = chatMessages.filter(\n      chatMessage => chatMessage.message.sequence_number < sequenceNumber\n    )\n\n    setChatMessages(filteredMessages)\n\n    handleSendMessage(editedContent, filteredMessages, false)\n  }\n\n  return {\n    chatInputRef,\n    prompt,\n    handleNewChat,\n    handleSendMessage,\n    handleFocusChatInput,\n    handleStopMessage,\n    handleSendEdit\n  }\n}\n"
  },
  {
    "path": "components/chat/chat-hooks/use-chat-history.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { useContext, useEffect, useState } from \"react\"\n\n/**\n * Custom hook for handling chat history in the chat component.\n * It provides functions to set the new message content to the previous or next user message in the chat history.\n *\n * @returns An object containing the following functions:\n *   - setNewMessageContentToPreviousUserMessage: Sets the new message content to the previous user message.\n *   - setNewMessageContentToNextUserMessage: Sets the new message content to the next user message in the chat history.\n */\nexport const useChatHistoryHandler = () => {\n  const { setUserInput, chatMessages, isGenerating } =\n    useContext(ChatbotUIContext)\n  const userRoleString = \"user\"\n\n  const [messageHistoryIndex, setMessageHistoryIndex] = useState<number>(\n    chatMessages.length\n  )\n\n  useEffect(() => {\n    // If messages get deleted the history index pointed could be out of bounds\n    if (!isGenerating && messageHistoryIndex > chatMessages.length)\n      setMessageHistoryIndex(chatMessages.length)\n  }, [chatMessages, isGenerating, messageHistoryIndex])\n\n  /**\n   * Sets the new message content to the previous user message.\n   */\n  const setNewMessageContentToPreviousUserMessage = () => {\n    let tempIndex = messageHistoryIndex\n    while (\n      tempIndex > 0 &&\n      chatMessages[tempIndex - 1].message.role !== userRoleString\n    ) {\n      tempIndex--\n    }\n\n    const previousUserMessage =\n      chatMessages.length > 0 && tempIndex > 0\n        ? chatMessages[tempIndex - 1]\n        : null\n    if (previousUserMessage) {\n      setUserInput(previousUserMessage.message.content)\n      setMessageHistoryIndex(tempIndex - 1)\n    }\n  }\n\n  /**\n   * Sets the new message content to the next user message in the chat history.\n   * If there is a next user message, it updates the user input and message history index accordingly.\n   * If there is no next user message, it resets the user input and sets the message history index to the end of the chat history.\n   */\n  const setNewMessageContentToNextUserMessage = () => {\n    let tempIndex = messageHistoryIndex\n    while (\n      tempIndex < chatMessages.length - 1 &&\n      chatMessages[tempIndex + 1].message.role !== userRoleString\n    ) {\n      tempIndex++\n    }\n\n    const nextUserMessage =\n      chatMessages.length > 0 && tempIndex < chatMessages.length - 1\n        ? chatMessages[tempIndex + 1]\n        : null\n    setUserInput(nextUserMessage?.message.content || \"\")\n    setMessageHistoryIndex(\n      nextUserMessage ? tempIndex + 1 : chatMessages.length\n    )\n  }\n\n  return {\n    setNewMessageContentToPreviousUserMessage,\n    setNewMessageContentToNextUserMessage\n  }\n}\n"
  },
  {
    "path": "components/chat/chat-hooks/use-prompt-and-command.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { getAssistantCollectionsByAssistantId } from \"@/db/assistant-collections\"\nimport { getAssistantFilesByAssistantId } from \"@/db/assistant-files\"\nimport { getAssistantToolsByAssistantId } from \"@/db/assistant-tools\"\nimport { getCollectionFilesByCollectionId } from \"@/db/collection-files\"\nimport { Tables } from \"@/supabase/types\"\nimport { LLMID } from \"@/types\"\nimport { useContext } from \"react\"\n\nexport const usePromptAndCommand = () => {\n  const {\n    chatFiles,\n    setNewMessageFiles,\n    userInput,\n    setUserInput,\n    setShowFilesDisplay,\n    setIsPromptPickerOpen,\n    setIsFilePickerOpen,\n    setSlashCommand,\n    setHashtagCommand,\n    setUseRetrieval,\n    setToolCommand,\n    setIsToolPickerOpen,\n    setSelectedTools,\n    setAtCommand,\n    setIsAssistantPickerOpen,\n    setSelectedAssistant,\n    setChatSettings,\n    setChatFiles\n  } = useContext(ChatbotUIContext)\n\n  const handleInputChange = (value: string) => {\n    const atTextRegex = /@([^ ]*)$/\n    const slashTextRegex = /\\/([^ ]*)$/\n    const hashtagTextRegex = /#([^ ]*)$/\n    const toolTextRegex = /!([^ ]*)$/\n    const atMatch = value.match(atTextRegex)\n    const slashMatch = value.match(slashTextRegex)\n    const hashtagMatch = value.match(hashtagTextRegex)\n    const toolMatch = value.match(toolTextRegex)\n\n    if (atMatch) {\n      setIsAssistantPickerOpen(true)\n      setAtCommand(atMatch[1])\n    } else if (slashMatch) {\n      setIsPromptPickerOpen(true)\n      setSlashCommand(slashMatch[1])\n    } else if (hashtagMatch) {\n      setIsFilePickerOpen(true)\n      setHashtagCommand(hashtagMatch[1])\n    } else if (toolMatch) {\n      setIsToolPickerOpen(true)\n      setToolCommand(toolMatch[1])\n    } else {\n      setIsPromptPickerOpen(false)\n      setIsFilePickerOpen(false)\n      setIsToolPickerOpen(false)\n      setIsAssistantPickerOpen(false)\n      setSlashCommand(\"\")\n      setHashtagCommand(\"\")\n      setToolCommand(\"\")\n      setAtCommand(\"\")\n    }\n\n    setUserInput(value)\n  }\n\n  const handleSelectPrompt = (prompt: Tables<\"prompts\">) => {\n    setIsPromptPickerOpen(false)\n    setUserInput(userInput.replace(/\\/[^ ]*$/, \"\") + prompt.content)\n  }\n\n  const handleSelectUserFile = async (file: Tables<\"files\">) => {\n    setShowFilesDisplay(true)\n    setIsFilePickerOpen(false)\n    setUseRetrieval(true)\n\n    setNewMessageFiles(prev => {\n      const fileAlreadySelected =\n        prev.some(prevFile => prevFile.id === file.id) ||\n        chatFiles.some(chatFile => chatFile.id === file.id)\n\n      if (!fileAlreadySelected) {\n        return [\n          ...prev,\n          {\n            id: file.id,\n            name: file.name,\n            type: file.type,\n            file: null\n          }\n        ]\n      }\n      return prev\n    })\n\n    setUserInput(userInput.replace(/#[^ ]*$/, \"\"))\n  }\n\n  const handleSelectUserCollection = async (\n    collection: Tables<\"collections\">\n  ) => {\n    setShowFilesDisplay(true)\n    setIsFilePickerOpen(false)\n    setUseRetrieval(true)\n\n    const collectionFiles = await getCollectionFilesByCollectionId(\n      collection.id\n    )\n\n    setNewMessageFiles(prev => {\n      const newFiles = collectionFiles.files\n        .filter(\n          file =>\n            !prev.some(prevFile => prevFile.id === file.id) &&\n            !chatFiles.some(chatFile => chatFile.id === file.id)\n        )\n        .map(file => ({\n          id: file.id,\n          name: file.name,\n          type: file.type,\n          file: null\n        }))\n\n      return [...prev, ...newFiles]\n    })\n\n    setUserInput(userInput.replace(/#[^ ]*$/, \"\"))\n  }\n\n  const handleSelectTool = (tool: Tables<\"tools\">) => {\n    setIsToolPickerOpen(false)\n    setUserInput(userInput.replace(/![^ ]*$/, \"\"))\n    setSelectedTools(prev => [...prev, tool])\n  }\n\n  const handleSelectAssistant = async (assistant: Tables<\"assistants\">) => {\n    setIsAssistantPickerOpen(false)\n    setUserInput(userInput.replace(/@[^ ]*$/, \"\"))\n    setSelectedAssistant(assistant)\n\n    setChatSettings({\n      model: assistant.model as LLMID,\n      prompt: assistant.prompt,\n      temperature: assistant.temperature,\n      contextLength: assistant.context_length,\n      includeProfileContext: assistant.include_profile_context,\n      includeWorkspaceInstructions: assistant.include_workspace_instructions,\n      embeddingsProvider: assistant.embeddings_provider as \"openai\" | \"local\"\n    })\n\n    let allFiles = []\n\n    const assistantFiles = (await getAssistantFilesByAssistantId(assistant.id))\n      .files\n    allFiles = [...assistantFiles]\n    const assistantCollections = (\n      await getAssistantCollectionsByAssistantId(assistant.id)\n    ).collections\n    for (const collection of assistantCollections) {\n      const collectionFiles = (\n        await getCollectionFilesByCollectionId(collection.id)\n      ).files\n      allFiles = [...allFiles, ...collectionFiles]\n    }\n    const assistantTools = (await getAssistantToolsByAssistantId(assistant.id))\n      .tools\n\n    setSelectedTools(assistantTools)\n    setChatFiles(\n      allFiles.map(file => ({\n        id: file.id,\n        name: file.name,\n        type: file.type,\n        file: null\n      }))\n    )\n\n    if (allFiles.length > 0) setShowFilesDisplay(true)\n  }\n\n  return {\n    handleInputChange,\n    handleSelectPrompt,\n    handleSelectUserFile,\n    handleSelectUserCollection,\n    handleSelectTool,\n    handleSelectAssistant\n  }\n}\n"
  },
  {
    "path": "components/chat/chat-hooks/use-scroll.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport {\n  type UIEventHandler,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState\n} from \"react\"\n\nexport const useScroll = () => {\n  const { isGenerating, chatMessages } = useContext(ChatbotUIContext)\n\n  const messagesStartRef = useRef<HTMLDivElement>(null)\n  const messagesEndRef = useRef<HTMLDivElement>(null)\n  const isAutoScrolling = useRef(false)\n\n  const [isAtTop, setIsAtTop] = useState(false)\n  const [isAtBottom, setIsAtBottom] = useState(true)\n  const [userScrolled, setUserScrolled] = useState(false)\n  const [isOverflowing, setIsOverflowing] = useState(false)\n\n  useEffect(() => {\n    setUserScrolled(false)\n\n    if (!isGenerating && userScrolled) {\n      setUserScrolled(false)\n    }\n  }, [isGenerating])\n\n  useEffect(() => {\n    if (isGenerating && !userScrolled) {\n      scrollToBottom()\n    }\n  }, [chatMessages])\n\n  const handleScroll: UIEventHandler<HTMLDivElement> = useCallback(e => {\n    const target = e.target as HTMLDivElement\n    const bottom =\n      Math.round(target.scrollHeight) - Math.round(target.scrollTop) ===\n      Math.round(target.clientHeight)\n    setIsAtBottom(bottom)\n\n    const top = target.scrollTop === 0\n    setIsAtTop(top)\n\n    if (!bottom && !isAutoScrolling.current) {\n      setUserScrolled(true)\n    } else {\n      setUserScrolled(false)\n    }\n\n    const isOverflow = target.scrollHeight > target.clientHeight\n    setIsOverflowing(isOverflow)\n  }, [])\n\n  const scrollToTop = useCallback(() => {\n    if (messagesStartRef.current) {\n      messagesStartRef.current.scrollIntoView({ behavior: \"instant\" })\n    }\n  }, [])\n\n  const scrollToBottom = useCallback(() => {\n    isAutoScrolling.current = true\n\n    setTimeout(() => {\n      if (messagesEndRef.current) {\n        messagesEndRef.current.scrollIntoView({ behavior: \"instant\" })\n      }\n\n      isAutoScrolling.current = false\n    }, 100)\n  }, [])\n\n  return {\n    messagesStartRef,\n    messagesEndRef,\n    isAtTop,\n    isAtBottom,\n    userScrolled,\n    isOverflowing,\n    handleScroll,\n    scrollToTop,\n    scrollToBottom,\n    setIsAtBottom\n  }\n}\n"
  },
  {
    "path": "components/chat/chat-hooks/use-select-file-handler.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { createDocXFile, createFile } from \"@/db/files\"\nimport { LLM_LIST } from \"@/lib/models/llm/llm-list\"\nimport mammoth from \"mammoth\"\nimport { useContext, useEffect, useState } from \"react\"\nimport { toast } from \"sonner\"\n\nexport const ACCEPTED_FILE_TYPES = [\n  \"text/csv\",\n  \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n  \"application/json\",\n  \"text/markdown\",\n  \"application/pdf\",\n  \"text/plain\"\n].join(\",\")\n\nexport const useSelectFileHandler = () => {\n  const {\n    selectedWorkspace,\n    profile,\n    chatSettings,\n    setNewMessageImages,\n    setNewMessageFiles,\n    setShowFilesDisplay,\n    setFiles,\n    setUseRetrieval\n  } = useContext(ChatbotUIContext)\n\n  const [filesToAccept, setFilesToAccept] = useState(ACCEPTED_FILE_TYPES)\n\n  useEffect(() => {\n    handleFilesToAccept()\n  }, [chatSettings?.model])\n\n  const handleFilesToAccept = () => {\n    const model = chatSettings?.model\n    const FULL_MODEL = LLM_LIST.find(llm => llm.modelId === model)\n\n    if (!FULL_MODEL) return\n\n    setFilesToAccept(\n      FULL_MODEL.imageInput\n        ? `${ACCEPTED_FILE_TYPES},image/*`\n        : ACCEPTED_FILE_TYPES\n    )\n  }\n\n  const handleSelectDeviceFile = async (file: File) => {\n    if (!profile || !selectedWorkspace || !chatSettings) return\n\n    setShowFilesDisplay(true)\n    setUseRetrieval(true)\n\n    if (file) {\n      let simplifiedFileType = file.type.split(\"/\")[1]\n\n      let reader = new FileReader()\n\n      if (file.type.includes(\"image\")) {\n        reader.readAsDataURL(file)\n      } else if (ACCEPTED_FILE_TYPES.split(\",\").includes(file.type)) {\n        if (simplifiedFileType.includes(\"vnd.adobe.pdf\")) {\n          simplifiedFileType = \"pdf\"\n        } else if (\n          simplifiedFileType.includes(\n            \"vnd.openxmlformats-officedocument.wordprocessingml.document\" ||\n              \"docx\"\n          )\n        ) {\n          simplifiedFileType = \"docx\"\n        }\n\n        setNewMessageFiles(prev => [\n          ...prev,\n          {\n            id: \"loading\",\n            name: file.name,\n            type: simplifiedFileType,\n            file: file\n          }\n        ])\n\n        // Handle docx files\n        if (\n          file.type.includes(\n            \"vnd.openxmlformats-officedocument.wordprocessingml.document\" ||\n              \"docx\"\n          )\n        ) {\n          const arrayBuffer = await file.arrayBuffer()\n          const result = await mammoth.extractRawText({\n            arrayBuffer\n          })\n\n          const createdFile = await createDocXFile(\n            result.value,\n            file,\n            {\n              user_id: profile.user_id,\n              description: \"\",\n              file_path: \"\",\n              name: file.name,\n              size: file.size,\n              tokens: 0,\n              type: simplifiedFileType\n            },\n            selectedWorkspace.id,\n            chatSettings.embeddingsProvider\n          )\n\n          setFiles(prev => [...prev, createdFile])\n\n          setNewMessageFiles(prev =>\n            prev.map(item =>\n              item.id === \"loading\"\n                ? {\n                    id: createdFile.id,\n                    name: createdFile.name,\n                    type: createdFile.type,\n                    file: file\n                  }\n                : item\n            )\n          )\n\n          reader.onloadend = null\n\n          return\n        } else {\n          // Use readAsArrayBuffer for PDFs and readAsText for other types\n          file.type.includes(\"pdf\")\n            ? reader.readAsArrayBuffer(file)\n            : reader.readAsText(file)\n        }\n      } else {\n        throw new Error(\"Unsupported file type\")\n      }\n\n      reader.onloadend = async function () {\n        try {\n          if (file.type.includes(\"image\")) {\n            // Create a temp url for the image file\n            const imageUrl = URL.createObjectURL(file)\n\n            // This is a temporary image for display purposes in the chat input\n            setNewMessageImages(prev => [\n              ...prev,\n              {\n                messageId: \"temp\",\n                path: \"\",\n                base64: reader.result, // base64 image\n                url: imageUrl,\n                file\n              }\n            ])\n          } else {\n            const createdFile = await createFile(\n              file,\n              {\n                user_id: profile.user_id,\n                description: \"\",\n                file_path: \"\",\n                name: file.name,\n                size: file.size,\n                tokens: 0,\n                type: simplifiedFileType\n              },\n              selectedWorkspace.id,\n              chatSettings.embeddingsProvider\n            )\n\n            setFiles(prev => [...prev, createdFile])\n\n            setNewMessageFiles(prev =>\n              prev.map(item =>\n                item.id === \"loading\"\n                  ? {\n                      id: createdFile.id,\n                      name: createdFile.name,\n                      type: createdFile.type,\n                      file: file\n                    }\n                  : item\n              )\n            )\n          }\n        } catch (error: any) {\n          toast.error(\"Failed to upload. \" + error?.message, {\n            duration: 10000\n          })\n          setNewMessageImages(prev =>\n            prev.filter(img => img.messageId !== \"temp\")\n          )\n          setNewMessageFiles(prev => prev.filter(file => file.id !== \"loading\"))\n        }\n      }\n    }\n  }\n\n  return {\n    handleSelectDeviceFile,\n    filesToAccept\n  }\n}\n"
  },
  {
    "path": "components/chat/chat-input.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { LLM_LIST } from \"@/lib/models/llm/llm-list\"\nimport { cn } from \"@/lib/utils\"\nimport {\n  IconBolt,\n  IconCirclePlus,\n  IconPlayerStopFilled,\n  IconSend\n} from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\nimport { useTranslation } from \"react-i18next\"\nimport { toast } from \"sonner\"\nimport { Input } from \"../ui/input\"\nimport { TextareaAutosize } from \"../ui/textarea-autosize\"\nimport { ChatCommandInput } from \"./chat-command-input\"\nimport { ChatFilesDisplay } from \"./chat-files-display\"\nimport { useChatHandler } from \"./chat-hooks/use-chat-handler\"\nimport { useChatHistoryHandler } from \"./chat-hooks/use-chat-history\"\nimport { usePromptAndCommand } from \"./chat-hooks/use-prompt-and-command\"\nimport { useSelectFileHandler } from \"./chat-hooks/use-select-file-handler\"\n\ninterface ChatInputProps {}\n\nexport const ChatInput: FC<ChatInputProps> = ({}) => {\n  const { t } = useTranslation()\n\n  useHotkey(\"l\", () => {\n    handleFocusChatInput()\n  })\n\n  const [isTyping, setIsTyping] = useState<boolean>(false)\n\n  const {\n    isAssistantPickerOpen,\n    focusAssistant,\n    setFocusAssistant,\n    userInput,\n    chatMessages,\n    isGenerating,\n    selectedPreset,\n    selectedAssistant,\n    focusPrompt,\n    setFocusPrompt,\n    focusFile,\n    focusTool,\n    setFocusTool,\n    isToolPickerOpen,\n    isPromptPickerOpen,\n    setIsPromptPickerOpen,\n    isFilePickerOpen,\n    setFocusFile,\n    chatSettings,\n    selectedTools,\n    setSelectedTools,\n    assistantImages\n  } = useContext(ChatbotUIContext)\n\n  const {\n    chatInputRef,\n    handleSendMessage,\n    handleStopMessage,\n    handleFocusChatInput\n  } = useChatHandler()\n\n  const { handleInputChange } = usePromptAndCommand()\n\n  const { filesToAccept, handleSelectDeviceFile } = useSelectFileHandler()\n\n  const {\n    setNewMessageContentToNextUserMessage,\n    setNewMessageContentToPreviousUserMessage\n  } = useChatHistoryHandler()\n\n  const fileInputRef = useRef<HTMLInputElement>(null)\n\n  useEffect(() => {\n    setTimeout(() => {\n      handleFocusChatInput()\n    }, 200) // FIX: hacky\n  }, [selectedPreset, selectedAssistant])\n\n  const handleKeyDown = (event: React.KeyboardEvent) => {\n    if (!isTyping && event.key === \"Enter\" && !event.shiftKey) {\n      event.preventDefault()\n      setIsPromptPickerOpen(false)\n      handleSendMessage(userInput, chatMessages, false)\n    }\n\n    // Consolidate conditions to avoid TypeScript error\n    if (\n      isPromptPickerOpen ||\n      isFilePickerOpen ||\n      isToolPickerOpen ||\n      isAssistantPickerOpen\n    ) {\n      if (\n        event.key === \"Tab\" ||\n        event.key === \"ArrowUp\" ||\n        event.key === \"ArrowDown\"\n      ) {\n        event.preventDefault()\n        // Toggle focus based on picker type\n        if (isPromptPickerOpen) setFocusPrompt(!focusPrompt)\n        if (isFilePickerOpen) setFocusFile(!focusFile)\n        if (isToolPickerOpen) setFocusTool(!focusTool)\n        if (isAssistantPickerOpen) setFocusAssistant(!focusAssistant)\n      }\n    }\n\n    if (event.key === \"ArrowUp\" && event.shiftKey && event.ctrlKey) {\n      event.preventDefault()\n      setNewMessageContentToPreviousUserMessage()\n    }\n\n    if (event.key === \"ArrowDown\" && event.shiftKey && event.ctrlKey) {\n      event.preventDefault()\n      setNewMessageContentToNextUserMessage()\n    }\n\n    //use shift+ctrl+up and shift+ctrl+down to navigate through chat history\n    if (event.key === \"ArrowUp\" && event.shiftKey && event.ctrlKey) {\n      event.preventDefault()\n      setNewMessageContentToPreviousUserMessage()\n    }\n\n    if (event.key === \"ArrowDown\" && event.shiftKey && event.ctrlKey) {\n      event.preventDefault()\n      setNewMessageContentToNextUserMessage()\n    }\n\n    if (\n      isAssistantPickerOpen &&\n      (event.key === \"Tab\" ||\n        event.key === \"ArrowUp\" ||\n        event.key === \"ArrowDown\")\n    ) {\n      event.preventDefault()\n      setFocusAssistant(!focusAssistant)\n    }\n  }\n\n  const handlePaste = (event: React.ClipboardEvent) => {\n    const imagesAllowed = LLM_LIST.find(\n      llm => llm.modelId === chatSettings?.model\n    )?.imageInput\n\n    const items = event.clipboardData.items\n    for (const item of items) {\n      if (item.type.indexOf(\"image\") === 0) {\n        if (!imagesAllowed) {\n          toast.error(\n            `Images are not supported for this model. Use models like GPT-4 Vision instead.`\n          )\n          return\n        }\n        const file = item.getAsFile()\n        if (!file) return\n        handleSelectDeviceFile(file)\n      }\n    }\n  }\n\n  return (\n    <>\n      <div className=\"flex flex-col flex-wrap justify-center gap-2\">\n        <ChatFilesDisplay />\n\n        {selectedTools &&\n          selectedTools.map((tool, index) => (\n            <div\n              key={index}\n              className=\"flex justify-center\"\n              onClick={() =>\n                setSelectedTools(\n                  selectedTools.filter(\n                    selectedTool => selectedTool.id !== tool.id\n                  )\n                )\n              }\n            >\n              <div className=\"flex cursor-pointer items-center justify-center space-x-1 rounded-lg bg-purple-600 px-3 py-1 hover:opacity-50\">\n                <IconBolt size={20} />\n\n                <div>{tool.name}</div>\n              </div>\n            </div>\n          ))}\n\n        {selectedAssistant && (\n          <div className=\"border-primary mx-auto flex w-fit items-center space-x-2 rounded-lg border p-1.5\">\n            {selectedAssistant.image_path && (\n              <Image\n                className=\"rounded\"\n                src={\n                  assistantImages.find(\n                    img => img.path === selectedAssistant.image_path\n                  )?.base64\n                }\n                width={28}\n                height={28}\n                alt={selectedAssistant.name}\n              />\n            )}\n\n            <div className=\"text-sm font-bold\">\n              Talking to {selectedAssistant.name}\n            </div>\n          </div>\n        )}\n      </div>\n\n      <div className=\"border-input relative mt-3 flex min-h-[60px] w-full items-center justify-center rounded-xl border-2\">\n        <div className=\"absolute bottom-[76px] left-0 max-h-[300px] w-full overflow-auto rounded-xl dark:border-none\">\n          <ChatCommandInput />\n        </div>\n\n        <>\n          <IconCirclePlus\n            className=\"absolute bottom-[12px] left-3 cursor-pointer p-1 hover:opacity-50\"\n            size={32}\n            onClick={() => fileInputRef.current?.click()}\n          />\n\n          {/* Hidden input to select files from device */}\n          <Input\n            ref={fileInputRef}\n            className=\"hidden\"\n            type=\"file\"\n            onChange={e => {\n              if (!e.target.files) return\n              handleSelectDeviceFile(e.target.files[0])\n            }}\n            accept={filesToAccept}\n          />\n        </>\n\n        <TextareaAutosize\n          textareaRef={chatInputRef}\n          className=\"ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring text-md flex w-full resize-none rounded-md border-none bg-transparent px-14 py-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n          placeholder={t(\n            // `Ask anything. Type \"@\" for assistants, \"/\" for prompts, \"#\" for files, and \"!\" for tools.`\n            `Ask anything. Type @  /  #  !`\n          )}\n          onValueChange={handleInputChange}\n          value={userInput}\n          minRows={1}\n          maxRows={18}\n          onKeyDown={handleKeyDown}\n          onPaste={handlePaste}\n          onCompositionStart={() => setIsTyping(true)}\n          onCompositionEnd={() => setIsTyping(false)}\n        />\n\n        <div className=\"absolute bottom-[14px] right-3 cursor-pointer hover:opacity-50\">\n          {isGenerating ? (\n            <IconPlayerStopFilled\n              className=\"hover:bg-background animate-pulse rounded bg-transparent p-1\"\n              onClick={handleStopMessage}\n              size={30}\n            />\n          ) : (\n            <IconSend\n              className={cn(\n                \"bg-primary text-secondary rounded p-1\",\n                !userInput && \"cursor-not-allowed opacity-50\"\n              )}\n              onClick={() => {\n                if (!userInput) return\n\n                handleSendMessage(userInput, chatMessages, false)\n              }}\n              size={30}\n            />\n          )}\n        </div>\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "components/chat/chat-messages.tsx",
    "content": "import { useChatHandler } from \"@/components/chat/chat-hooks/use-chat-handler\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { Tables } from \"@/supabase/types\"\nimport { FC, useContext, useState } from \"react\"\nimport { Message } from \"../messages/message\"\n\ninterface ChatMessagesProps {}\n\nexport const ChatMessages: FC<ChatMessagesProps> = ({}) => {\n  const { chatMessages, chatFileItems } = useContext(ChatbotUIContext)\n\n  const { handleSendEdit } = useChatHandler()\n\n  const [editingMessage, setEditingMessage] = useState<Tables<\"messages\">>()\n\n  return chatMessages\n    .sort((a, b) => a.message.sequence_number - b.message.sequence_number)\n    .map((chatMessage, index, array) => {\n      const messageFileItems = chatFileItems.filter(\n        (chatFileItem, _, self) =>\n          chatMessage.fileItems.includes(chatFileItem.id) &&\n          self.findIndex(item => item.id === chatFileItem.id) === _\n      )\n\n      return (\n        <Message\n          key={chatMessage.message.sequence_number}\n          message={chatMessage.message}\n          fileItems={messageFileItems}\n          isEditing={editingMessage?.id === chatMessage.message.id}\n          isLast={index === array.length - 1}\n          onStartEdit={setEditingMessage}\n          onCancelEdit={() => setEditingMessage(undefined)}\n          onSubmitEdit={handleSendEdit}\n        />\n      )\n    })\n}\n"
  },
  {
    "path": "components/chat/chat-retrieval-settings.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { IconAdjustmentsHorizontal } from \"@tabler/icons-react\"\nimport { FC, useContext, useState } from \"react\"\nimport { Button } from \"../ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogFooter,\n  DialogTrigger\n} from \"../ui/dialog\"\nimport { Label } from \"../ui/label\"\nimport { Slider } from \"../ui/slider\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\n\ninterface ChatRetrievalSettingsProps {}\n\nexport const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {\n  const { sourceCount, setSourceCount } = useContext(ChatbotUIContext)\n\n  const [isOpen, setIsOpen] = useState(false)\n\n  return (\n    <Dialog open={isOpen} onOpenChange={setIsOpen}>\n      <DialogTrigger>\n        <WithTooltip\n          delayDuration={0}\n          side=\"top\"\n          display={<div>Adjust retrieval settings.</div>}\n          trigger={\n            <IconAdjustmentsHorizontal\n              className=\"cursor-pointer pt-[4px] hover:opacity-50\"\n              size={24}\n            />\n          }\n        />\n      </DialogTrigger>\n\n      <DialogContent>\n        <div className=\"space-y-3\">\n          <Label className=\"flex items-center space-x-1\">\n            <div>Source Count:</div>\n\n            <div>{sourceCount}</div>\n          </Label>\n\n          <Slider\n            value={[sourceCount]}\n            onValueChange={values => {\n              setSourceCount(values[0])\n            }}\n            min={1}\n            max={10}\n            step={1}\n          />\n        </div>\n\n        <DialogFooter>\n          <Button size=\"sm\" onClick={() => setIsOpen(false)}>\n            Save & Close\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "components/chat/chat-scroll-buttons.tsx",
    "content": "import {\n  IconCircleArrowDownFilled,\n  IconCircleArrowUpFilled\n} from \"@tabler/icons-react\"\nimport { FC } from \"react\"\n\ninterface ChatScrollButtonsProps {\n  isAtTop: boolean\n  isAtBottom: boolean\n  isOverflowing: boolean\n  scrollToTop: () => void\n  scrollToBottom: () => void\n}\n\nexport const ChatScrollButtons: FC<ChatScrollButtonsProps> = ({\n  isAtTop,\n  isAtBottom,\n  isOverflowing,\n  scrollToTop,\n  scrollToBottom\n}) => {\n  return (\n    <>\n      {!isAtTop && isOverflowing && (\n        <IconCircleArrowUpFilled\n          className=\"cursor-pointer opacity-50 hover:opacity-100\"\n          size={32}\n          onClick={scrollToTop}\n        />\n      )}\n\n      {!isAtBottom && isOverflowing && (\n        <IconCircleArrowDownFilled\n          className=\"cursor-pointer opacity-50 hover:opacity-100\"\n          size={32}\n          onClick={scrollToBottom}\n        />\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "components/chat/chat-secondary-buttons.tsx",
    "content": "import { useChatHandler } from \"@/components/chat/chat-hooks/use-chat-handler\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { IconInfoCircle, IconMessagePlus } from \"@tabler/icons-react\"\nimport { FC, useContext } from \"react\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\n\ninterface ChatSecondaryButtonsProps {}\n\nexport const ChatSecondaryButtons: FC<ChatSecondaryButtonsProps> = ({}) => {\n  const { selectedChat } = useContext(ChatbotUIContext)\n\n  const { handleNewChat } = useChatHandler()\n\n  return (\n    <>\n      {selectedChat && (\n        <>\n          <WithTooltip\n            delayDuration={200}\n            display={\n              <div>\n                <div className=\"text-xl font-bold\">Chat Info</div>\n\n                <div className=\"mx-auto mt-2 max-w-xs space-y-2 sm:max-w-sm md:max-w-md lg:max-w-lg\">\n                  <div>Model: {selectedChat.model}</div>\n                  <div>Prompt: {selectedChat.prompt}</div>\n\n                  <div>Temperature: {selectedChat.temperature}</div>\n                  <div>Context Length: {selectedChat.context_length}</div>\n\n                  <div>\n                    Profile Context:{\" \"}\n                    {selectedChat.include_profile_context\n                      ? \"Enabled\"\n                      : \"Disabled\"}\n                  </div>\n                  <div>\n                    {\" \"}\n                    Workspace Instructions:{\" \"}\n                    {selectedChat.include_workspace_instructions\n                      ? \"Enabled\"\n                      : \"Disabled\"}\n                  </div>\n\n                  <div>\n                    Embeddings Provider: {selectedChat.embeddings_provider}\n                  </div>\n                </div>\n              </div>\n            }\n            trigger={\n              <div className=\"mt-1\">\n                <IconInfoCircle\n                  className=\"cursor-default hover:opacity-50\"\n                  size={24}\n                />\n              </div>\n            }\n          />\n\n          <WithTooltip\n            delayDuration={200}\n            display={<div>Start a new chat</div>}\n            trigger={\n              <div className=\"mt-1\">\n                <IconMessagePlus\n                  className=\"cursor-pointer hover:opacity-50\"\n                  size={24}\n                  onClick={handleNewChat}\n                />\n              </div>\n            }\n          />\n        </>\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "components/chat/chat-settings.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { CHAT_SETTING_LIMITS } from \"@/lib/chat-setting-limits\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { LLMID, ModelProvider } from \"@/types\"\nimport { IconAdjustmentsHorizontal } from \"@tabler/icons-react\"\nimport { FC, useContext, useEffect, useRef } from \"react\"\nimport { Button } from \"../ui/button\"\nimport { ChatSettingsForm } from \"../ui/chat-settings-form\"\nimport { Popover, PopoverContent, PopoverTrigger } from \"../ui/popover\"\n\ninterface ChatSettingsProps {}\n\nexport const ChatSettings: FC<ChatSettingsProps> = ({}) => {\n  useHotkey(\"i\", () => handleClick())\n\n  const {\n    chatSettings,\n    setChatSettings,\n    models,\n    availableHostedModels,\n    availableLocalModels,\n    availableOpenRouterModels\n  } = useContext(ChatbotUIContext)\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const handleClick = () => {\n    if (buttonRef.current) {\n      buttonRef.current.click()\n    }\n  }\n\n  useEffect(() => {\n    if (!chatSettings) return\n\n    setChatSettings({\n      ...chatSettings,\n      temperature: Math.min(\n        chatSettings.temperature,\n        CHAT_SETTING_LIMITS[chatSettings.model]?.MAX_TEMPERATURE || 1\n      ),\n      contextLength: Math.min(\n        chatSettings.contextLength,\n        CHAT_SETTING_LIMITS[chatSettings.model]?.MAX_CONTEXT_LENGTH || 4096\n      )\n    })\n  }, [chatSettings?.model])\n\n  if (!chatSettings) return null\n\n  const allModels = [\n    ...models.map(model => ({\n      modelId: model.model_id as LLMID,\n      modelName: model.name,\n      provider: \"custom\" as ModelProvider,\n      hostedId: model.id,\n      platformLink: \"\",\n      imageInput: false\n    })),\n    ...availableHostedModels,\n    ...availableLocalModels,\n    ...availableOpenRouterModels\n  ]\n\n  const fullModel = allModels.find(llm => llm.modelId === chatSettings.model)\n\n  return (\n    <Popover>\n      <PopoverTrigger>\n        <Button\n          ref={buttonRef}\n          className=\"flex items-center space-x-2\"\n          variant=\"ghost\"\n        >\n          <div className=\"max-w-[120px] truncate text-lg sm:max-w-[300px] lg:max-w-[500px]\">\n            {fullModel?.modelName || chatSettings.model}\n          </div>\n\n          <IconAdjustmentsHorizontal size={28} />\n        </Button>\n      </PopoverTrigger>\n\n      <PopoverContent\n        className=\"bg-background border-input relative flex max-h-[calc(100vh-60px)] w-[300px] flex-col space-y-4 overflow-auto rounded-lg border-2 p-6 sm:w-[350px] md:w-[400px] lg:w-[500px] dark:border-none\"\n        align=\"end\"\n      >\n        <ChatSettingsForm\n          chatSettings={chatSettings}\n          onChangeChatSettings={setChatSettings}\n        />\n      </PopoverContent>\n    </Popover>\n  )\n}\n"
  },
  {
    "path": "components/chat/chat-ui.tsx",
    "content": "import Loading from \"@/app/[locale]/loading\"\nimport { useChatHandler } from \"@/components/chat/chat-hooks/use-chat-handler\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { getAssistantToolsByAssistantId } from \"@/db/assistant-tools\"\nimport { getChatFilesByChatId } from \"@/db/chat-files\"\nimport { getChatById } from \"@/db/chats\"\nimport { getMessageFileItemsByMessageId } from \"@/db/message-file-items\"\nimport { getMessagesByChatId } from \"@/db/messages\"\nimport { getMessageImageFromStorage } from \"@/db/storage/message-images\"\nimport { convertBlobToBase64 } from \"@/lib/blob-to-b64\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { LLMID, MessageImage } from \"@/types\"\nimport { useParams } from \"next/navigation\"\nimport { FC, useContext, useEffect, useState } from \"react\"\nimport { ChatHelp } from \"./chat-help\"\nimport { useScroll } from \"./chat-hooks/use-scroll\"\nimport { ChatInput } from \"./chat-input\"\nimport { ChatMessages } from \"./chat-messages\"\nimport { ChatScrollButtons } from \"./chat-scroll-buttons\"\nimport { ChatSecondaryButtons } from \"./chat-secondary-buttons\"\n\ninterface ChatUIProps {}\n\nexport const ChatUI: FC<ChatUIProps> = ({}) => {\n  useHotkey(\"o\", () => handleNewChat())\n\n  const params = useParams()\n\n  const {\n    setChatMessages,\n    selectedChat,\n    setSelectedChat,\n    setChatSettings,\n    setChatImages,\n    assistants,\n    setSelectedAssistant,\n    setChatFileItems,\n    setChatFiles,\n    setShowFilesDisplay,\n    setUseRetrieval,\n    setSelectedTools\n  } = useContext(ChatbotUIContext)\n\n  const { handleNewChat, handleFocusChatInput } = useChatHandler()\n\n  const {\n    messagesStartRef,\n    messagesEndRef,\n    handleScroll,\n    scrollToBottom,\n    setIsAtBottom,\n    isAtTop,\n    isAtBottom,\n    isOverflowing,\n    scrollToTop\n  } = useScroll()\n\n  const [loading, setLoading] = useState(true)\n\n  useEffect(() => {\n    const fetchData = async () => {\n      await fetchMessages()\n      await fetchChat()\n\n      scrollToBottom()\n      setIsAtBottom(true)\n    }\n\n    if (params.chatid) {\n      fetchData().then(() => {\n        handleFocusChatInput()\n        setLoading(false)\n      })\n    } else {\n      setLoading(false)\n    }\n  }, [])\n\n  const fetchMessages = async () => {\n    const fetchedMessages = await getMessagesByChatId(params.chatid as string)\n\n    const imagePromises: Promise<MessageImage>[] = fetchedMessages.flatMap(\n      message =>\n        message.image_paths\n          ? message.image_paths.map(async imagePath => {\n              const url = await getMessageImageFromStorage(imagePath)\n\n              if (url) {\n                const response = await fetch(url)\n                const blob = await response.blob()\n                const base64 = await convertBlobToBase64(blob)\n\n                return {\n                  messageId: message.id,\n                  path: imagePath,\n                  base64,\n                  url,\n                  file: null\n                }\n              }\n\n              return {\n                messageId: message.id,\n                path: imagePath,\n                base64: \"\",\n                url,\n                file: null\n              }\n            })\n          : []\n    )\n\n    const images: MessageImage[] = await Promise.all(imagePromises.flat())\n    setChatImages(images)\n\n    const messageFileItemPromises = fetchedMessages.map(\n      async message => await getMessageFileItemsByMessageId(message.id)\n    )\n\n    const messageFileItems = await Promise.all(messageFileItemPromises)\n\n    const uniqueFileItems = messageFileItems.flatMap(item => item.file_items)\n    setChatFileItems(uniqueFileItems)\n\n    const chatFiles = await getChatFilesByChatId(params.chatid as string)\n\n    setChatFiles(\n      chatFiles.files.map(file => ({\n        id: file.id,\n        name: file.name,\n        type: file.type,\n        file: null\n      }))\n    )\n\n    setUseRetrieval(true)\n    setShowFilesDisplay(true)\n\n    const fetchedChatMessages = fetchedMessages.map(message => {\n      return {\n        message,\n        fileItems: messageFileItems\n          .filter(messageFileItem => messageFileItem.id === message.id)\n          .flatMap(messageFileItem =>\n            messageFileItem.file_items.map(fileItem => fileItem.id)\n          )\n      }\n    })\n\n    setChatMessages(fetchedChatMessages)\n  }\n\n  const fetchChat = async () => {\n    const chat = await getChatById(params.chatid as string)\n    if (!chat) return\n\n    if (chat.assistant_id) {\n      const assistant = assistants.find(\n        assistant => assistant.id === chat.assistant_id\n      )\n\n      if (assistant) {\n        setSelectedAssistant(assistant)\n\n        const assistantTools = (\n          await getAssistantToolsByAssistantId(assistant.id)\n        ).tools\n        setSelectedTools(assistantTools)\n      }\n    }\n\n    setSelectedChat(chat)\n    setChatSettings({\n      model: chat.model as LLMID,\n      prompt: chat.prompt,\n      temperature: chat.temperature,\n      contextLength: chat.context_length,\n      includeProfileContext: chat.include_profile_context,\n      includeWorkspaceInstructions: chat.include_workspace_instructions,\n      embeddingsProvider: chat.embeddings_provider as \"openai\" | \"local\"\n    })\n  }\n\n  if (loading) {\n    return <Loading />\n  }\n\n  return (\n    <div className=\"relative flex h-full flex-col items-center\">\n      <div className=\"absolute left-4 top-2.5 flex justify-center\">\n        <ChatScrollButtons\n          isAtTop={isAtTop}\n          isAtBottom={isAtBottom}\n          isOverflowing={isOverflowing}\n          scrollToTop={scrollToTop}\n          scrollToBottom={scrollToBottom}\n        />\n      </div>\n\n      <div className=\"absolute right-4 top-1 flex h-[40px] items-center space-x-2\">\n        <ChatSecondaryButtons />\n      </div>\n\n      <div className=\"bg-secondary flex max-h-[50px] min-h-[50px] w-full items-center justify-center border-b-2 font-bold\">\n        <div className=\"max-w-[200px] truncate sm:max-w-[400px] md:max-w-[500px] lg:max-w-[600px] xl:max-w-[700px]\">\n          {selectedChat?.name || \"Chat\"}\n        </div>\n      </div>\n\n      <div\n        className=\"flex size-full flex-col overflow-auto border-b\"\n        onScroll={handleScroll}\n      >\n        <div ref={messagesStartRef} />\n\n        <ChatMessages />\n\n        <div ref={messagesEndRef} />\n      </div>\n\n      <div className=\"relative w-full min-w-[300px] items-end px-2 pb-3 pt-0 sm:w-[600px] sm:pb-8 sm:pt-5 md:w-[700px] lg:w-[700px] xl:w-[800px]\">\n        <ChatInput />\n      </div>\n\n      <div className=\"absolute bottom-2 right-2 hidden md:block lg:bottom-4 lg:right-4\">\n        <ChatHelp />\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/chat/file-picker.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconBooks } from \"@tabler/icons-react\"\nimport { FC, useContext, useEffect, useRef } from \"react\"\nimport { FileIcon } from \"../ui/file-icon\"\n\ninterface FilePickerProps {\n  isOpen: boolean\n  searchQuery: string\n  onOpenChange: (isOpen: boolean) => void\n  selectedFileIds: string[]\n  selectedCollectionIds: string[]\n  onSelectFile: (file: Tables<\"files\">) => void\n  onSelectCollection: (collection: Tables<\"collections\">) => void\n  isFocused: boolean\n}\n\nexport const FilePicker: FC<FilePickerProps> = ({\n  isOpen,\n  searchQuery,\n  onOpenChange,\n  selectedFileIds,\n  selectedCollectionIds,\n  onSelectFile,\n  onSelectCollection,\n  isFocused\n}) => {\n  const { files, collections, setIsFilePickerOpen } =\n    useContext(ChatbotUIContext)\n\n  const itemsRef = useRef<(HTMLDivElement | null)[]>([])\n\n  useEffect(() => {\n    if (isFocused && itemsRef.current[0]) {\n      itemsRef.current[0].focus()\n    }\n  }, [isFocused])\n\n  const filteredFiles = files.filter(\n    file =>\n      file.name.toLowerCase().includes(searchQuery.toLowerCase()) &&\n      !selectedFileIds.includes(file.id)\n  )\n\n  const filteredCollections = collections.filter(\n    collection =>\n      collection.name.toLowerCase().includes(searchQuery.toLowerCase()) &&\n      !selectedCollectionIds.includes(collection.id)\n  )\n\n  const handleOpenChange = (isOpen: boolean) => {\n    onOpenChange(isOpen)\n  }\n\n  const handleSelectFile = (file: Tables<\"files\">) => {\n    onSelectFile(file)\n    handleOpenChange(false)\n  }\n\n  const handleSelectCollection = (collection: Tables<\"collections\">) => {\n    onSelectCollection(collection)\n    handleOpenChange(false)\n  }\n\n  const getKeyDownHandler =\n    (index: number, type: \"file\" | \"collection\", item: any) =>\n    (e: React.KeyboardEvent<HTMLDivElement>) => {\n      if (e.key === \"Escape\") {\n        e.preventDefault()\n        setIsFilePickerOpen(false)\n      } else if (e.key === \"Backspace\") {\n        e.preventDefault()\n      } else if (e.key === \"Enter\") {\n        e.preventDefault()\n\n        if (type === \"file\") {\n          handleSelectFile(item)\n        } else {\n          handleSelectCollection(item)\n        }\n      } else if (\n        (e.key === \"Tab\" || e.key === \"ArrowDown\") &&\n        !e.shiftKey &&\n        index === filteredFiles.length + filteredCollections.length - 1\n      ) {\n        e.preventDefault()\n        itemsRef.current[0]?.focus()\n      } else if (e.key === \"ArrowUp\" && !e.shiftKey && index === 0) {\n        // go to last element if arrow up is pressed on first element\n        e.preventDefault()\n        itemsRef.current[itemsRef.current.length - 1]?.focus()\n      } else if (e.key === \"ArrowUp\") {\n        e.preventDefault()\n        const prevIndex =\n          index - 1 >= 0 ? index - 1 : itemsRef.current.length - 1\n        itemsRef.current[prevIndex]?.focus()\n      } else if (e.key === \"ArrowDown\") {\n        e.preventDefault()\n        const nextIndex = index + 1 < itemsRef.current.length ? index + 1 : 0\n        itemsRef.current[nextIndex]?.focus()\n      }\n    }\n\n  return (\n    <>\n      {isOpen && (\n        <div className=\"bg-background flex flex-col space-y-1 rounded-xl border-2 p-2 text-sm\">\n          {filteredFiles.length === 0 && filteredCollections.length === 0 ? (\n            <div className=\"text-md flex h-14 cursor-pointer items-center justify-center italic hover:opacity-50\">\n              No matching files.\n            </div>\n          ) : (\n            <>\n              {[...filteredFiles, ...filteredCollections].map((item, index) => (\n                <div\n                  key={item.id}\n                  ref={ref => {\n                    itemsRef.current[index] = ref\n                  }}\n                  tabIndex={0}\n                  className=\"hover:bg-accent focus:bg-accent flex cursor-pointer items-center rounded p-2 focus:outline-none\"\n                  onClick={() => {\n                    if (\"type\" in item) {\n                      handleSelectFile(item as Tables<\"files\">)\n                    } else {\n                      handleSelectCollection(item)\n                    }\n                  }}\n                  onKeyDown={e =>\n                    getKeyDownHandler(\n                      index,\n                      \"type\" in item ? \"file\" : \"collection\",\n                      item\n                    )(e)\n                  }\n                >\n                  {\"type\" in item ? (\n                    <FileIcon type={(item as Tables<\"files\">).type} size={32} />\n                  ) : (\n                    <IconBooks size={32} />\n                  )}\n\n                  <div className=\"ml-3 flex flex-col\">\n                    <div className=\"font-bold\">{item.name}</div>\n\n                    <div className=\"truncate text-sm opacity-80\">\n                      {item.description || \"No description.\"}\n                    </div>\n                  </div>\n                </div>\n              ))}\n            </>\n          )}\n        </div>\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "components/chat/prompt-picker.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { Tables } from \"@/supabase/types\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\nimport { Button } from \"../ui/button\"\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from \"../ui/dialog\"\nimport { Label } from \"../ui/label\"\nimport { TextareaAutosize } from \"../ui/textarea-autosize\"\nimport { usePromptAndCommand } from \"./chat-hooks/use-prompt-and-command\"\n\ninterface PromptPickerProps {}\n\nexport const PromptPicker: FC<PromptPickerProps> = ({}) => {\n  const {\n    prompts,\n    isPromptPickerOpen,\n    setIsPromptPickerOpen,\n    focusPrompt,\n    slashCommand\n  } = useContext(ChatbotUIContext)\n\n  const { handleSelectPrompt } = usePromptAndCommand()\n\n  const itemsRef = useRef<(HTMLDivElement | null)[]>([])\n\n  const [promptVariables, setPromptVariables] = useState<\n    {\n      promptId: string\n      name: string\n      value: string\n    }[]\n  >([])\n  const [showPromptVariables, setShowPromptVariables] = useState(false)\n\n  useEffect(() => {\n    if (focusPrompt && itemsRef.current[0]) {\n      itemsRef.current[0].focus()\n    }\n  }, [focusPrompt])\n\n  const [isTyping, setIsTyping] = useState(false)\n\n  const filteredPrompts = prompts.filter(prompt =>\n    prompt.name.toLowerCase().includes(slashCommand.toLowerCase())\n  )\n\n  const handleOpenChange = (isOpen: boolean) => {\n    setIsPromptPickerOpen(isOpen)\n  }\n\n  const callSelectPrompt = (prompt: Tables<\"prompts\">) => {\n    const regex = /\\{\\{.*?\\}\\}/g\n    const matches = prompt.content.match(regex)\n\n    if (matches) {\n      const newPromptVariables = matches.map(match => ({\n        promptId: prompt.id,\n        name: match.replace(/\\{\\{|\\}\\}/g, \"\"),\n        value: \"\"\n      }))\n\n      setPromptVariables(newPromptVariables)\n      setShowPromptVariables(true)\n    } else {\n      handleSelectPrompt(prompt)\n      handleOpenChange(false)\n    }\n  }\n\n  const getKeyDownHandler =\n    (index: number) => (e: React.KeyboardEvent<HTMLDivElement>) => {\n      if (e.key === \"Backspace\") {\n        e.preventDefault()\n        handleOpenChange(false)\n      } else if (e.key === \"Enter\") {\n        e.preventDefault()\n        callSelectPrompt(filteredPrompts[index])\n      } else if (\n        (e.key === \"Tab\" || e.key === \"ArrowDown\") &&\n        !e.shiftKey &&\n        index === filteredPrompts.length - 1\n      ) {\n        e.preventDefault()\n        itemsRef.current[0]?.focus()\n      } else if (e.key === \"ArrowUp\" && !e.shiftKey && index === 0) {\n        // go to last element if arrow up is pressed on first element\n        e.preventDefault()\n        itemsRef.current[itemsRef.current.length - 1]?.focus()\n      } else if (e.key === \"ArrowUp\") {\n        e.preventDefault()\n        const prevIndex =\n          index - 1 >= 0 ? index - 1 : itemsRef.current.length - 1\n        itemsRef.current[prevIndex]?.focus()\n      } else if (e.key === \"ArrowDown\") {\n        e.preventDefault()\n        const nextIndex = index + 1 < itemsRef.current.length ? index + 1 : 0\n        itemsRef.current[nextIndex]?.focus()\n      }\n    }\n\n  const handleSubmitPromptVariables = () => {\n    const newPromptContent = promptVariables.reduce(\n      (prevContent, variable) =>\n        prevContent.replace(\n          new RegExp(`\\\\{\\\\{${variable.name}\\\\}\\\\}`, \"g\"),\n          variable.value\n        ),\n      prompts.find(prompt => prompt.id === promptVariables[0].promptId)\n        ?.content || \"\"\n    )\n\n    const newPrompt: any = {\n      ...prompts.find(prompt => prompt.id === promptVariables[0].promptId),\n      content: newPromptContent\n    }\n\n    handleSelectPrompt(newPrompt)\n    handleOpenChange(false)\n    setShowPromptVariables(false)\n    setPromptVariables([])\n  }\n\n  const handleCancelPromptVariables = () => {\n    setShowPromptVariables(false)\n    setPromptVariables([])\n  }\n\n  const handleKeydownPromptVariables = (\n    e: React.KeyboardEvent<HTMLDivElement>\n  ) => {\n    if (!isTyping && e.key === \"Enter\" && !e.shiftKey) {\n      e.preventDefault()\n      handleSubmitPromptVariables()\n    }\n  }\n\n  return (\n    <>\n      {isPromptPickerOpen && (\n        <div className=\"bg-background flex flex-col space-y-1 rounded-xl border-2 p-2 text-sm\">\n          {showPromptVariables ? (\n            <Dialog\n              open={showPromptVariables}\n              onOpenChange={setShowPromptVariables}\n            >\n              <DialogContent onKeyDown={handleKeydownPromptVariables}>\n                <DialogHeader>\n                  <DialogTitle>Enter Prompt Variables</DialogTitle>\n                </DialogHeader>\n\n                <div className=\"mt-2 space-y-6\">\n                  {promptVariables.map((variable, index) => (\n                    <div key={index} className=\"flex flex-col space-y-2\">\n                      <Label>{variable.name}</Label>\n\n                      <TextareaAutosize\n                        placeholder={`Enter a value for ${variable.name}...`}\n                        value={variable.value}\n                        onValueChange={value => {\n                          const newPromptVariables = [...promptVariables]\n                          newPromptVariables[index].value = value\n                          setPromptVariables(newPromptVariables)\n                        }}\n                        minRows={3}\n                        maxRows={5}\n                        onCompositionStart={() => setIsTyping(true)}\n                        onCompositionEnd={() => setIsTyping(false)}\n                      />\n                    </div>\n                  ))}\n                </div>\n\n                <div className=\"mt-2 flex justify-end space-x-2\">\n                  <Button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    onClick={handleCancelPromptVariables}\n                  >\n                    Cancel\n                  </Button>\n\n                  <Button size=\"sm\" onClick={handleSubmitPromptVariables}>\n                    Submit\n                  </Button>\n                </div>\n              </DialogContent>\n            </Dialog>\n          ) : filteredPrompts.length === 0 ? (\n            <div className=\"text-md flex h-14 cursor-pointer items-center justify-center italic hover:opacity-50\">\n              No matching prompts.\n            </div>\n          ) : (\n            filteredPrompts.map((prompt, index) => (\n              <div\n                key={prompt.id}\n                ref={ref => {\n                  itemsRef.current[index] = ref\n                }}\n                tabIndex={0}\n                className=\"hover:bg-accent focus:bg-accent flex cursor-pointer flex-col rounded p-2 focus:outline-none\"\n                onClick={() => callSelectPrompt(prompt)}\n                onKeyDown={getKeyDownHandler(index)}\n              >\n                <div className=\"font-bold\">{prompt.name}</div>\n\n                <div className=\"truncate text-sm opacity-80\">\n                  {prompt.content}\n                </div>\n              </div>\n            ))\n          )}\n        </div>\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "components/chat/quick-setting-option.tsx",
    "content": "import { LLM_LIST } from \"@/lib/models/llm/llm-list\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconCircleCheckFilled, IconRobotFace } from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { FC } from \"react\"\nimport { ModelIcon } from \"../models/model-icon\"\nimport { DropdownMenuItem } from \"../ui/dropdown-menu\"\n\ninterface QuickSettingOptionProps {\n  contentType: \"presets\" | \"assistants\"\n  isSelected: boolean\n  item: Tables<\"presets\"> | Tables<\"assistants\">\n  onSelect: () => void\n  image: string\n}\n\nexport const QuickSettingOption: FC<QuickSettingOptionProps> = ({\n  contentType,\n  isSelected,\n  item,\n  onSelect,\n  image\n}) => {\n  const modelDetails = LLM_LIST.find(model => model.modelId === item.model)\n\n  return (\n    <DropdownMenuItem\n      tabIndex={0}\n      className=\"cursor-pointer items-center\"\n      onSelect={onSelect}\n    >\n      <div className=\"w-[32px]\">\n        {contentType === \"presets\" ? (\n          <ModelIcon\n            provider={modelDetails?.provider || \"custom\"}\n            width={32}\n            height={32}\n          />\n        ) : image ? (\n          <Image\n            style={{ width: \"32px\", height: \"32px\" }}\n            className=\"rounded\"\n            src={image}\n            alt=\"Assistant\"\n            width={32}\n            height={32}\n          />\n        ) : (\n          <IconRobotFace\n            className=\"bg-primary text-secondary border-primary rounded border-DEFAULT p-1\"\n            size={32}\n          />\n        )}\n      </div>\n\n      <div className=\"ml-4 flex grow flex-col space-y-1\">\n        <div className=\"text-md font-bold\">{item.name}</div>\n\n        {item.description && (\n          <div className=\"text-sm font-light\">{item.description}</div>\n        )}\n      </div>\n\n      <div className=\"min-w-[40px]\">\n        {isSelected ? (\n          <IconCircleCheckFilled className=\"ml-4\" size={20} />\n        ) : null}\n      </div>\n    </DropdownMenuItem>\n  )\n}\n"
  },
  {
    "path": "components/chat/quick-settings.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { getAssistantCollectionsByAssistantId } from \"@/db/assistant-collections\"\nimport { getAssistantFilesByAssistantId } from \"@/db/assistant-files\"\nimport { getAssistantToolsByAssistantId } from \"@/db/assistant-tools\"\nimport { getCollectionFilesByCollectionId } from \"@/db/collection-files\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { LLM_LIST } from \"@/lib/models/llm/llm-list\"\nimport { Tables } from \"@/supabase/types\"\nimport { LLMID } from \"@/types\"\nimport { IconChevronDown, IconRobotFace } from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\nimport { useTranslation } from \"react-i18next\"\nimport { ModelIcon } from \"../models/model-icon\"\nimport { Button } from \"../ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuTrigger\n} from \"../ui/dropdown-menu\"\nimport { Input } from \"../ui/input\"\nimport { QuickSettingOption } from \"./quick-setting-option\"\nimport { set } from \"date-fns\"\n\ninterface QuickSettingsProps {}\n\nexport const QuickSettings: FC<QuickSettingsProps> = ({}) => {\n  const { t } = useTranslation()\n\n  useHotkey(\"p\", () => setIsOpen(prevState => !prevState))\n\n  const {\n    presets,\n    assistants,\n    selectedAssistant,\n    selectedPreset,\n    chatSettings,\n    setSelectedPreset,\n    setSelectedAssistant,\n    setChatSettings,\n    assistantImages,\n    setChatFiles,\n    setSelectedTools,\n    setShowFilesDisplay,\n    selectedWorkspace\n  } = useContext(ChatbotUIContext)\n\n  const inputRef = useRef<HTMLInputElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n  const [search, setSearch] = useState(\"\")\n  const [loading, setLoading] = useState(false)\n\n  useEffect(() => {\n    if (isOpen) {\n      setTimeout(() => {\n        inputRef.current?.focus()\n      }, 100) // FIX: hacky\n    }\n  }, [isOpen])\n\n  const handleSelectQuickSetting = async (\n    item: Tables<\"presets\"> | Tables<\"assistants\"> | null,\n    contentType: \"presets\" | \"assistants\" | \"remove\"\n  ) => {\n    console.log({ item, contentType })\n    if (contentType === \"assistants\" && item) {\n      setSelectedAssistant(item as Tables<\"assistants\">)\n      setLoading(true)\n      let allFiles = []\n      const assistantFiles = (await getAssistantFilesByAssistantId(item.id))\n        .files\n      allFiles = [...assistantFiles]\n      const assistantCollections = (\n        await getAssistantCollectionsByAssistantId(item.id)\n      ).collections\n      for (const collection of assistantCollections) {\n        const collectionFiles = (\n          await getCollectionFilesByCollectionId(collection.id)\n        ).files\n        allFiles = [...allFiles, ...collectionFiles]\n      }\n      const assistantTools = (await getAssistantToolsByAssistantId(item.id))\n        .tools\n      setSelectedTools(assistantTools)\n      setChatFiles(\n        allFiles.map(file => ({\n          id: file.id,\n          name: file.name,\n          type: file.type,\n          file: null\n        }))\n      )\n      if (allFiles.length > 0) setShowFilesDisplay(true)\n      setLoading(false)\n      setSelectedPreset(null)\n    } else if (contentType === \"presets\" && item) {\n      setSelectedPreset(item as Tables<\"presets\">)\n      setSelectedAssistant(null)\n      setChatFiles([])\n      setSelectedTools([])\n    } else {\n      setSelectedPreset(null)\n      setSelectedAssistant(null)\n      setChatFiles([])\n      setSelectedTools([])\n      if (selectedWorkspace) {\n        setChatSettings({\n          model: selectedWorkspace.default_model as LLMID,\n          prompt: selectedWorkspace.default_prompt,\n          temperature: selectedWorkspace.default_temperature,\n          contextLength: selectedWorkspace.default_context_length,\n          includeProfileContext: selectedWorkspace.include_profile_context,\n          includeWorkspaceInstructions:\n            selectedWorkspace.include_workspace_instructions,\n          embeddingsProvider: selectedWorkspace.embeddings_provider as\n            | \"openai\"\n            | \"local\"\n        })\n      }\n      return\n    }\n\n    setChatSettings({\n      model: item.model as LLMID,\n      prompt: item.prompt,\n      temperature: item.temperature,\n      contextLength: item.context_length,\n      includeProfileContext: item.include_profile_context,\n      includeWorkspaceInstructions: item.include_workspace_instructions,\n      embeddingsProvider: item.embeddings_provider as \"openai\" | \"local\"\n    })\n  }\n\n  const checkIfModified = () => {\n    if (!chatSettings) return false\n\n    if (selectedPreset) {\n      return (\n        selectedPreset.include_profile_context !==\n          chatSettings?.includeProfileContext ||\n        selectedPreset.include_workspace_instructions !==\n          chatSettings.includeWorkspaceInstructions ||\n        selectedPreset.context_length !== chatSettings.contextLength ||\n        selectedPreset.model !== chatSettings.model ||\n        selectedPreset.prompt !== chatSettings.prompt ||\n        selectedPreset.temperature !== chatSettings.temperature\n      )\n    } else if (selectedAssistant) {\n      return (\n        selectedAssistant.include_profile_context !==\n          chatSettings.includeProfileContext ||\n        selectedAssistant.include_workspace_instructions !==\n          chatSettings.includeWorkspaceInstructions ||\n        selectedAssistant.context_length !== chatSettings.contextLength ||\n        selectedAssistant.model !== chatSettings.model ||\n        selectedAssistant.prompt !== chatSettings.prompt ||\n        selectedAssistant.temperature !== chatSettings.temperature\n      )\n    }\n\n    return false\n  }\n\n  const isModified = checkIfModified()\n\n  const items = [\n    ...presets.map(preset => ({ ...preset, contentType: \"presets\" })),\n    ...assistants.map(assistant => ({\n      ...assistant,\n      contentType: \"assistants\"\n    }))\n  ]\n\n  const selectedAssistantImage = selectedPreset\n    ? \"\"\n    : assistantImages.find(\n        image => image.path === selectedAssistant?.image_path\n      )?.base64 || \"\"\n\n  const modelDetails = LLM_LIST.find(\n    model => model.modelId === selectedPreset?.model\n  )\n\n  return (\n    <DropdownMenu\n      open={isOpen}\n      onOpenChange={isOpen => {\n        setIsOpen(isOpen)\n        setSearch(\"\")\n      }}\n    >\n      <DropdownMenuTrigger asChild className=\"max-w-[400px]\" disabled={loading}>\n        <Button variant=\"ghost\" className=\"flex space-x-3 text-lg\">\n          {selectedPreset && (\n            <ModelIcon\n              provider={modelDetails?.provider || \"custom\"}\n              width={32}\n              height={32}\n            />\n          )}\n\n          {selectedAssistant &&\n            (selectedAssistantImage ? (\n              <Image\n                className=\"rounded\"\n                src={selectedAssistantImage}\n                alt=\"Assistant\"\n                width={28}\n                height={28}\n              />\n            ) : (\n              <IconRobotFace\n                className=\"bg-primary text-secondary border-primary rounded border-DEFAULT p-1\"\n                size={28}\n              />\n            ))}\n\n          {loading ? (\n            <div className=\"animate-pulse\">Loading assistant...</div>\n          ) : (\n            <>\n              <div className=\"overflow-hidden text-ellipsis\">\n                {isModified &&\n                  (selectedPreset || selectedAssistant) &&\n                  \"Modified \"}\n\n                {selectedPreset?.name ||\n                  selectedAssistant?.name ||\n                  t(\"Quick Settings\")}\n              </div>\n\n              <IconChevronDown className=\"ml-1\" />\n            </>\n          )}\n        </Button>\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent\n        className=\"min-w-[300px] max-w-[500px] space-y-4\"\n        align=\"start\"\n      >\n        {presets.length === 0 && assistants.length === 0 ? (\n          <div className=\"p-8 text-center\">No items found.</div>\n        ) : (\n          <>\n            <Input\n              ref={inputRef}\n              className=\"w-full\"\n              placeholder=\"Search...\"\n              value={search}\n              onChange={e => setSearch(e.target.value)}\n              onKeyDown={e => e.stopPropagation()}\n            />\n\n            {!!(selectedPreset || selectedAssistant) && (\n              <QuickSettingOption\n                contentType={selectedPreset ? \"presets\" : \"assistants\"}\n                isSelected={true}\n                item={\n                  selectedPreset ||\n                  (selectedAssistant as\n                    | Tables<\"presets\">\n                    | Tables<\"assistants\">)\n                }\n                onSelect={() => {\n                  handleSelectQuickSetting(null, \"remove\")\n                }}\n                image={selectedPreset ? \"\" : selectedAssistantImage}\n              />\n            )}\n\n            {items\n              .filter(\n                item =>\n                  item.name.toLowerCase().includes(search.toLowerCase()) &&\n                  item.id !== selectedPreset?.id &&\n                  item.id !== selectedAssistant?.id\n              )\n              .map(({ contentType, ...item }) => (\n                <QuickSettingOption\n                  key={item.id}\n                  contentType={contentType as \"presets\" | \"assistants\"}\n                  isSelected={false}\n                  item={item}\n                  onSelect={() =>\n                    handleSelectQuickSetting(\n                      item,\n                      contentType as \"presets\" | \"assistants\"\n                    )\n                  }\n                  image={\n                    contentType === \"assistants\"\n                      ? assistantImages.find(\n                          image =>\n                            image.path ===\n                            (item as Tables<\"assistants\">).image_path\n                        )?.base64 || \"\"\n                      : \"\"\n                  }\n                />\n              ))}\n          </>\n        )}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n"
  },
  {
    "path": "components/chat/tool-picker.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconBolt } from \"@tabler/icons-react\"\nimport { FC, useContext, useEffect, useRef } from \"react\"\nimport { usePromptAndCommand } from \"./chat-hooks/use-prompt-and-command\"\n\ninterface ToolPickerProps {}\n\nexport const ToolPicker: FC<ToolPickerProps> = ({}) => {\n  const {\n    tools,\n    focusTool,\n    toolCommand,\n    isToolPickerOpen,\n    setIsToolPickerOpen\n  } = useContext(ChatbotUIContext)\n\n  const { handleSelectTool } = usePromptAndCommand()\n\n  const itemsRef = useRef<(HTMLDivElement | null)[]>([])\n\n  useEffect(() => {\n    if (focusTool && itemsRef.current[0]) {\n      itemsRef.current[0].focus()\n    }\n  }, [focusTool])\n\n  const filteredTools = tools.filter(tool =>\n    tool.name.toLowerCase().includes(toolCommand.toLowerCase())\n  )\n\n  const handleOpenChange = (isOpen: boolean) => {\n    setIsToolPickerOpen(isOpen)\n  }\n\n  const callSelectTool = (tool: Tables<\"tools\">) => {\n    handleSelectTool(tool)\n    handleOpenChange(false)\n  }\n\n  const getKeyDownHandler =\n    (index: number) => (e: React.KeyboardEvent<HTMLDivElement>) => {\n      if (e.key === \"Backspace\") {\n        e.preventDefault()\n        handleOpenChange(false)\n      } else if (e.key === \"Enter\") {\n        e.preventDefault()\n        callSelectTool(filteredTools[index])\n      } else if (\n        (e.key === \"Tab\" || e.key === \"ArrowDown\") &&\n        !e.shiftKey &&\n        index === filteredTools.length - 1\n      ) {\n        e.preventDefault()\n        itemsRef.current[0]?.focus()\n      } else if (e.key === \"ArrowUp\" && !e.shiftKey && index === 0) {\n        // go to last element if arrow up is pressed on first element\n        e.preventDefault()\n        itemsRef.current[itemsRef.current.length - 1]?.focus()\n      } else if (e.key === \"ArrowUp\") {\n        e.preventDefault()\n        const prevIndex =\n          index - 1 >= 0 ? index - 1 : itemsRef.current.length - 1\n        itemsRef.current[prevIndex]?.focus()\n      } else if (e.key === \"ArrowDown\") {\n        e.preventDefault()\n        const nextIndex = index + 1 < itemsRef.current.length ? index + 1 : 0\n        itemsRef.current[nextIndex]?.focus()\n      }\n    }\n\n  return (\n    <>\n      {isToolPickerOpen && (\n        <div className=\"bg-background flex flex-col space-y-1 rounded-xl border-2 p-2 text-sm\">\n          {filteredTools.length === 0 ? (\n            <div className=\"text-md flex h-14 cursor-pointer items-center justify-center italic hover:opacity-50\">\n              No matching tools.\n            </div>\n          ) : (\n            <>\n              {filteredTools.map((item, index) => (\n                <div\n                  key={item.id}\n                  ref={ref => {\n                    itemsRef.current[index] = ref\n                  }}\n                  tabIndex={0}\n                  className=\"hover:bg-accent focus:bg-accent flex cursor-pointer items-center rounded p-2 focus:outline-none\"\n                  onClick={() => callSelectTool(item as Tables<\"tools\">)}\n                  onKeyDown={getKeyDownHandler(index)}\n                >\n                  <IconBolt size={32} />\n\n                  <div className=\"ml-3 flex flex-col\">\n                    <div className=\"font-bold\">{item.name}</div>\n\n                    <div className=\"truncate text-sm opacity-80\">\n                      {item.description || \"No description.\"}\n                    </div>\n                  </div>\n                </div>\n              ))}\n            </>\n          )}\n        </div>\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "components/icons/anthropic-svg.tsx",
    "content": "import { FC } from \"react\"\n\ninterface AnthropicSVGProps {\n  height?: number\n  width?: number\n  className?: string\n}\n\nexport const AnthropicSVG: FC<AnthropicSVGProps> = ({\n  height = 40,\n  width = 40,\n  className\n}) => {\n  return (\n    <svg\n      className={className}\n      width={width}\n      height={height}\n      viewBox=\"0 0 24 16\"\n      overflow=\"visible\"\n    >\n      <g\n        style={{\n          transform: \"translateX(13px) rotateZ(0deg)\",\n          transformOrigin: \"4.775px 7.73501px\"\n        }}\n      >\n        <path\n          fill=\"currentColor\"\n          d=\" M0,0 C0,0 6.1677093505859375,15.470022201538086 6.1677093505859375,15.470022201538086 C6.1677093505859375,15.470022201538086 9.550004005432129,15.470022201538086 9.550004005432129,15.470022201538086 C9.550004005432129,15.470022201538086 3.382294178009033,0 3.382294178009033,0 C3.382294178009033,0 0,0 0,0 C0,0 0,0 0,0z\"\n        ></path>\n      </g>\n      <g\n        style={{ transform: \"none\", transformOrigin: \"7.935px 7.73501px\" }}\n        opacity=\"1\"\n      >\n        <path\n          fill=\"currentColor\"\n          d=\" M5.824605464935303,9.348296165466309 C5.824605464935303,9.348296165466309 7.93500280380249,3.911694288253784 7.93500280380249,3.911694288253784 C7.93500280380249,3.911694288253784 10.045400619506836,9.348296165466309 10.045400619506836,9.348296165466309 C10.045400619506836,9.348296165466309 5.824605464935303,9.348296165466309 5.824605464935303,9.348296165466309 C5.824605464935303,9.348296165466309 5.824605464935303,9.348296165466309 5.824605464935303,9.348296165466309z M6.166755199432373,0 C6.166755199432373,0 0,15.470022201538086 0,15.470022201538086 C0,15.470022201538086 3.4480772018432617,15.470022201538086 3.4480772018432617,15.470022201538086 C3.4480772018432617,15.470022201538086 4.709278583526611,12.22130012512207 4.709278583526611,12.22130012512207 C4.709278583526611,12.22130012512207 11.16093635559082,12.22130012512207 11.16093635559082,12.22130012512207 C11.16093635559082,12.22130012512207 12.421928405761719,15.470022201538086 12.421928405761719,15.470022201538086 C12.421928405761719,15.470022201538086 15.87000560760498,15.470022201538086 15.87000560760498,15.470022201538086 C15.87000560760498,15.470022201538086 9.703250885009766,0 9.703250885009766,0 C9.703250885009766,0 6.166755199432373,0 6.166755199432373,0 C6.166755199432373,0 6.166755199432373,0 6.166755199432373,0z\"\n        ></path>\n      </g>\n    </svg>\n  )\n}\n"
  },
  {
    "path": "components/icons/chatbotui-svg.tsx",
    "content": "import { FC } from \"react\"\n\ninterface ChatbotUISVGProps {\n  theme: \"dark\" | \"light\"\n  scale?: number\n}\n\nexport const ChatbotUISVG: FC<ChatbotUISVGProps> = ({ theme, scale = 1 }) => {\n  return (\n    <svg\n      width={189 * scale}\n      height={194 * scale}\n      viewBox=\"0 0 189 194\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <rect\n        x=\"12.5\"\n        y=\"12.5\"\n        width=\"164\"\n        height=\"127\"\n        rx=\"37.5\"\n        fill={`${theme === \"dark\" ? \"#000\" : \"#fff\"}`}\n        stroke={`${theme === \"dark\" ? \"#fff\" : \"#000\"}`}\n        strokeWidth=\"25\"\n      />\n      <path\n        d=\"M72.7643 143.457C77.2953 143.443 79.508 148.98 76.2146 152.092L42.7738 183.69C39.5361 186.749 34.2157 184.366 34.3419 179.914L35.2341 148.422C35.3106 145.723 37.5158 143.572 40.2158 143.564L72.7643 143.457Z\"\n        fill={`${theme === \"dark\" ? \"#fff\" : \"#000\"}`}\n      />\n      <path\n        d=\"M59.6722 51.6H75.5122V84C75.5122 86.016 76.0162 87.672 77.0242 88.968C78.0802 90.216 79.6882 90.84 81.8482 90.84C84.0082 90.84 85.5922 90.216 86.6002 88.968C87.6562 87.672 88.1842 86.016 88.1842 84V51.6H104.024V85.44C104.024 89.04 103.424 92.088 102.224 94.584C101.072 97.032 99.4642 99.024 97.4002 100.56C95.3362 102.048 92.9602 103.128 90.2722 103.8C87.6322 104.52 84.8242 104.88 81.8482 104.88C78.8722 104.88 76.0402 104.52 73.3522 103.8C70.7122 103.128 68.3602 102.048 66.2962 100.56C64.2322 99.024 62.6002 97.032 61.4002 94.584C60.2482 92.088 59.6722 89.04 59.6722 85.44V51.6ZM113.751 51.6H129.951V102H113.751V51.6Z\"\n        fill={`${theme === \"dark\" ? \"#fff\" : \"#000\"}`}\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "components/icons/google-svg.tsx",
    "content": "import { FC } from \"react\"\n\ninterface GoogleSVGProps {\n  height?: number\n  width?: number\n  className?: string\n}\n\nexport const GoogleSVG: FC<GoogleSVGProps> = ({\n  height = 40,\n  width = 40,\n  className\n}) => {\n  return (\n    <svg\n      className={className}\n      width={width}\n      height={height}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 48 48\"\n    >\n      <path\n        fill=\"#FFC107\"\n        d=\"M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z\"\n      ></path>\n      <path\n        fill=\"#FF3D00\"\n        d=\"M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z\"\n      ></path>\n      <path\n        fill=\"#4CAF50\"\n        d=\"M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z\"\n      ></path>\n      <path\n        fill=\"#1976D2\"\n        d=\"M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z\"\n      ></path>\n    </svg>\n  )\n}\n"
  },
  {
    "path": "components/icons/openai-svg.tsx",
    "content": "import { FC } from \"react\"\n\ninterface OpenAISVGProps {\n  height?: number\n  width?: number\n  className?: string\n}\n\nexport const OpenAISVG: FC<OpenAISVGProps> = ({\n  height = 40,\n  width = 40,\n  className\n}) => {\n  return (\n    <svg\n      className={className}\n      width={width}\n      height={height}\n      viewBox=\"0 0 41 41\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      strokeWidth=\"1.5\"\n      role=\"img\"\n    >\n      <path\n        d=\"M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9098 3.40259 25.8215 3.85078C24.8796 2.7893 23.7219 1.94125 22.4257 1.36341C21.1295 0.785575 19.7249 0.491269 18.3058 0.500197C16.1708 0.495044 14.0893 1.16803 12.3614 2.42214C10.6335 3.67624 9.34853 5.44666 8.6917 7.47815C7.30085 7.76286 5.98686 8.3414 4.8377 9.17505C3.68854 10.0087 2.73073 11.0782 2.02839 12.312C0.956464 14.1591 0.498905 16.2988 0.721698 18.4228C0.944492 20.5467 1.83612 22.5449 3.268 24.1293C2.81966 25.4759 2.66413 26.9026 2.81182 28.3141C2.95951 29.7256 3.40701 31.0892 4.12437 32.3138C5.18791 34.1659 6.8123 35.6322 8.76321 36.5013C10.7141 37.3704 12.8907 37.5973 14.9789 37.1492C15.9208 38.2107 17.0786 39.0587 18.3747 39.6366C19.6709 40.2144 21.0755 40.5087 22.4946 40.4998C24.6307 40.5054 26.7133 39.8321 28.4418 38.5772C30.1704 37.3223 31.4556 35.5506 32.1119 33.5179C33.5027 33.2332 34.8167 32.6547 35.9659 31.821C37.115 30.9874 38.0728 29.9178 38.7752 28.684C39.8458 26.8371 40.3023 24.6979 40.0789 22.5748C39.8556 20.4517 38.9639 18.4544 37.5324 16.8707ZM22.4978 37.8849C20.7443 37.8874 19.0459 37.2733 17.6994 36.1501C17.7601 36.117 17.8666 36.0586 17.936 36.0161L25.9004 31.4156C26.1003 31.3019 26.2663 31.137 26.3813 30.9378C26.4964 30.7386 26.5563 30.5124 26.5549 30.2825V19.0542L29.9213 20.998C29.9389 21.0068 29.9541 21.0198 29.9656 21.0359C29.977 21.052 29.9842 21.0707 29.9867 21.0902V30.3889C29.9842 32.375 29.1946 34.2791 27.7909 35.6841C26.3872 37.0892 24.4838 37.8806 22.4978 37.8849ZM6.39227 31.0064C5.51397 29.4888 5.19742 27.7107 5.49804 25.9832C5.55718 26.0187 5.66048 26.0818 5.73461 26.1244L13.699 30.7248C13.8975 30.8408 14.1233 30.902 14.3532 30.902C14.583 30.902 14.8088 30.8408 15.0073 30.7248L24.731 25.1103V28.9979C24.7321 29.0177 24.7283 29.0376 24.7199 29.0556C24.7115 29.0736 24.6988 29.0893 24.6829 29.1012L16.6317 33.7497C14.9096 34.7416 12.8643 35.0097 10.9447 34.4954C9.02506 33.9811 7.38785 32.7263 6.39227 31.0064ZM4.29707 13.6194C5.17156 12.0998 6.55279 10.9364 8.19885 10.3327C8.19885 10.4013 8.19491 10.5228 8.19491 10.6071V19.808C8.19351 20.0378 8.25334 20.2638 8.36823 20.4629C8.48312 20.6619 8.64893 20.8267 8.84863 20.9404L18.5723 26.5542L15.206 28.4979C15.1894 28.5089 15.1703 28.5155 15.1505 28.5173C15.1307 28.5191 15.1107 28.516 15.0924 28.5082L7.04046 23.8557C5.32135 22.8601 4.06716 21.2235 3.55289 19.3046C3.03862 17.3858 3.30624 15.3413 4.29707 13.6194ZM31.955 20.0556L22.2312 14.4411L25.5976 12.4981C25.6142 12.4872 25.6333 12.4805 25.6531 12.4787C25.6729 12.4769 25.6928 12.4801 25.7111 12.4879L33.7631 17.1364C34.9967 17.849 36.0017 18.8982 36.6606 20.1613C37.3194 21.4244 37.6047 22.849 37.4832 24.2684C37.3617 25.6878 36.8382 27.0432 35.9743 28.1759C35.1103 29.3086 33.9415 30.1717 32.6047 30.6641C32.6047 30.5947 32.6047 30.4733 32.6047 30.3889V21.188C32.6066 20.9586 32.5474 20.7328 32.4332 20.5338C32.319 20.3348 32.154 20.1698 31.955 20.0556ZM35.3055 15.0128C35.2464 14.9765 35.1431 14.9142 35.069 14.8717L27.1045 10.2712C26.906 10.1554 26.6803 10.0943 26.4504 10.0943C26.2206 10.0943 25.9948 10.1554 25.7963 10.2712L16.0726 15.8858V11.9982C16.0715 11.9783 16.0753 11.9585 16.0837 11.9405C16.0921 11.9225 16.1048 11.9068 16.1207 11.8949L24.1719 7.25025C25.4053 6.53903 26.8158 6.19376 28.2383 6.25482C29.6608 6.31589 31.0364 6.78077 32.2044 7.59508C33.3723 8.40939 34.2842 9.53945 34.8334 10.8531C35.3826 12.1667 35.5464 13.6095 35.3055 15.0128ZM14.2424 21.9419L10.8752 19.9981C10.8576 19.9893 10.8423 19.9763 10.8309 19.9602C10.8195 19.9441 10.8122 19.9254 10.8098 19.9058V10.6071C10.8107 9.18295 11.2173 7.78848 11.9819 6.58696C12.7466 5.38544 13.8377 4.42659 15.1275 3.82264C16.4173 3.21869 17.8524 2.99464 19.2649 3.1767C20.6775 3.35876 22.0089 3.93941 23.1034 4.85067C23.0427 4.88379 22.937 4.94215 22.8668 4.98473L14.9024 9.58517C14.7025 9.69878 14.5366 9.86356 14.4215 10.0626C14.3065 10.2616 14.2466 10.4877 14.2479 10.7175L14.2424 21.9419ZM16.071 17.9991L20.4018 15.4978L24.7325 17.9975V22.9985L20.4018 25.4983L16.071 22.9985V17.9991Z\"\n        fill=\"currentColor\"\n      ></path>\n    </svg>\n  )\n}\n"
  },
  {
    "path": "components/messages/message-actions.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { IconCheck, IconCopy, IconEdit, IconRepeat } from \"@tabler/icons-react\"\nimport { FC, useContext, useEffect, useState } from \"react\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\n\nexport const MESSAGE_ICON_SIZE = 18\n\ninterface MessageActionsProps {\n  isAssistant: boolean\n  isLast: boolean\n  isEditing: boolean\n  isHovering: boolean\n  onCopy: () => void\n  onEdit: () => void\n  onRegenerate: () => void\n}\n\nexport const MessageActions: FC<MessageActionsProps> = ({\n  isAssistant,\n  isLast,\n  isEditing,\n  isHovering,\n  onCopy,\n  onEdit,\n  onRegenerate\n}) => {\n  const { isGenerating } = useContext(ChatbotUIContext)\n\n  const [showCheckmark, setShowCheckmark] = useState(false)\n\n  const handleCopy = () => {\n    onCopy()\n    setShowCheckmark(true)\n  }\n\n  const handleForkChat = async () => {}\n\n  useEffect(() => {\n    if (showCheckmark) {\n      const timer = setTimeout(() => {\n        setShowCheckmark(false)\n      }, 2000)\n\n      return () => clearTimeout(timer)\n    }\n  }, [showCheckmark])\n\n  return (isLast && isGenerating) || isEditing ? null : (\n    <div className=\"text-muted-foreground flex items-center space-x-2\">\n      {/* {((isAssistant && isHovering) || isLast) && (\n        <WithTooltip\n          delayDuration={1000}\n          side=\"bottom\"\n          display={<div>Fork Chat</div>}\n          trigger={\n            <IconGitFork\n              className=\"cursor-pointer hover:opacity-50\"\n              size={MESSAGE_ICON_SIZE}\n              onClick={handleForkChat}\n            />\n          }\n        />\n      )} */}\n\n      {!isAssistant && isHovering && (\n        <WithTooltip\n          delayDuration={1000}\n          side=\"bottom\"\n          display={<div>Edit</div>}\n          trigger={\n            <IconEdit\n              className=\"cursor-pointer hover:opacity-50\"\n              size={MESSAGE_ICON_SIZE}\n              onClick={onEdit}\n            />\n          }\n        />\n      )}\n\n      {(isHovering || isLast) && (\n        <WithTooltip\n          delayDuration={1000}\n          side=\"bottom\"\n          display={<div>Copy</div>}\n          trigger={\n            showCheckmark ? (\n              <IconCheck size={MESSAGE_ICON_SIZE} />\n            ) : (\n              <IconCopy\n                className=\"cursor-pointer hover:opacity-50\"\n                size={MESSAGE_ICON_SIZE}\n                onClick={handleCopy}\n              />\n            )\n          }\n        />\n      )}\n\n      {isLast && (\n        <WithTooltip\n          delayDuration={1000}\n          side=\"bottom\"\n          display={<div>Regenerate</div>}\n          trigger={\n            <IconRepeat\n              className=\"cursor-pointer hover:opacity-50\"\n              size={MESSAGE_ICON_SIZE}\n              onClick={onRegenerate}\n            />\n          }\n        />\n      )}\n\n      {/* {1 > 0 && isAssistant && <MessageReplies />} */}\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/messages/message-codeblock.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport { useCopyToClipboard } from \"@/lib/hooks/use-copy-to-clipboard\"\nimport { IconCheck, IconCopy, IconDownload } from \"@tabler/icons-react\"\nimport { FC, memo } from \"react\"\nimport { Prism as SyntaxHighlighter } from \"react-syntax-highlighter\"\nimport { oneDark } from \"react-syntax-highlighter/dist/cjs/styles/prism\"\n\ninterface MessageCodeBlockProps {\n  language: string\n  value: string\n}\n\ninterface languageMap {\n  [key: string]: string | undefined\n}\n\nexport const programmingLanguages: languageMap = {\n  javascript: \".js\",\n  python: \".py\",\n  java: \".java\",\n  c: \".c\",\n  cpp: \".cpp\",\n  \"c++\": \".cpp\",\n  \"c#\": \".cs\",\n  ruby: \".rb\",\n  php: \".php\",\n  swift: \".swift\",\n  \"objective-c\": \".m\",\n  kotlin: \".kt\",\n  typescript: \".ts\",\n  go: \".go\",\n  perl: \".pl\",\n  rust: \".rs\",\n  scala: \".scala\",\n  haskell: \".hs\",\n  lua: \".lua\",\n  shell: \".sh\",\n  sql: \".sql\",\n  html: \".html\",\n  css: \".css\"\n}\n\nexport const generateRandomString = (length: number, lowercase = false) => {\n  const chars = \"ABCDEFGHJKLMNPQRSTUVWXY3456789\" // excluding similar looking characters like Z, 2, I, 1, O, 0\n  let result = \"\"\n  for (let i = 0; i < length; i++) {\n    result += chars.charAt(Math.floor(Math.random() * chars.length))\n  }\n  return lowercase ? result.toLowerCase() : result\n}\n\nexport const MessageCodeBlock: FC<MessageCodeBlockProps> = memo(\n  ({ language, value }) => {\n    const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })\n\n    const downloadAsFile = () => {\n      if (typeof window === \"undefined\") {\n        return\n      }\n      const fileExtension = programmingLanguages[language] || \".file\"\n      const suggestedFileName = `file-${generateRandomString(\n        3,\n        true\n      )}${fileExtension}`\n      const fileName = window.prompt(\"Enter file name\" || \"\", suggestedFileName)\n\n      if (!fileName) {\n        return\n      }\n\n      const blob = new Blob([value], { type: \"text/plain\" })\n      const url = URL.createObjectURL(blob)\n      const link = document.createElement(\"a\")\n      link.download = fileName\n      link.href = url\n      link.style.display = \"none\"\n      document.body.appendChild(link)\n      link.click()\n      document.body.removeChild(link)\n      URL.revokeObjectURL(url)\n    }\n\n    const onCopy = () => {\n      if (isCopied) return\n      copyToClipboard(value)\n    }\n\n    return (\n      <div className=\"codeblock relative w-full bg-zinc-950 font-sans\">\n        <div className=\"flex w-full items-center justify-between bg-zinc-700 px-4 text-white\">\n          <span className=\"text-xs lowercase\">{language}</span>\n          <div className=\"flex items-center space-x-1\">\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0\"\n              onClick={downloadAsFile}\n            >\n              <IconDownload size={16} />\n            </Button>\n\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"text-xs hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0\"\n              onClick={onCopy}\n            >\n              {isCopied ? <IconCheck size={16} /> : <IconCopy size={16} />}\n            </Button>\n          </div>\n        </div>\n        <SyntaxHighlighter\n          language={language}\n          style={oneDark}\n          // showLineNumbers\n          customStyle={{\n            margin: 0,\n            width: \"100%\",\n            background: \"transparent\"\n          }}\n          codeTagProps={{\n            style: {\n              fontSize: \"14px\",\n              fontFamily: \"var(--font-mono)\"\n            }\n          }}\n        >\n          {value}\n        </SyntaxHighlighter>\n      </div>\n    )\n  }\n)\n\nMessageCodeBlock.displayName = \"MessageCodeBlock\"\n"
  },
  {
    "path": "components/messages/message-markdown-memoized.tsx",
    "content": "import { FC, memo } from \"react\"\nimport ReactMarkdown, { Options } from \"react-markdown\"\n\nexport const MessageMarkdownMemoized: FC<Options> = memo(\n  ReactMarkdown,\n  (prevProps, nextProps) =>\n    prevProps.children === nextProps.children &&\n    prevProps.className === nextProps.className\n)\n"
  },
  {
    "path": "components/messages/message-markdown.tsx",
    "content": "import React, { FC } from \"react\"\nimport remarkGfm from \"remark-gfm\"\nimport remarkMath from \"remark-math\"\nimport { MessageCodeBlock } from \"./message-codeblock\"\nimport { MessageMarkdownMemoized } from \"./message-markdown-memoized\"\n\ninterface MessageMarkdownProps {\n  content: string\n}\n\nexport const MessageMarkdown: FC<MessageMarkdownProps> = ({ content }) => {\n  return (\n    <MessageMarkdownMemoized\n      className=\"prose dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 min-w-full space-y-6 break-words\"\n      remarkPlugins={[remarkGfm, remarkMath]}\n      components={{\n        p({ children }) {\n          return <p className=\"mb-2 last:mb-0\">{children}</p>\n        },\n        img({ node, ...props }) {\n          return <img className=\"max-w-[67%]\" {...props} />\n        },\n        code({ node, className, children, ...props }) {\n          const childArray = React.Children.toArray(children)\n          const firstChild = childArray[0] as React.ReactElement\n          const firstChildAsString = React.isValidElement(firstChild)\n            ? (firstChild as React.ReactElement).props.children\n            : firstChild\n\n          if (firstChildAsString === \"▍\") {\n            return <span className=\"mt-1 animate-pulse cursor-default\">▍</span>\n          }\n\n          if (typeof firstChildAsString === \"string\") {\n            childArray[0] = firstChildAsString.replace(\"`▍`\", \"▍\")\n          }\n\n          const match = /language-(\\w+)/.exec(className || \"\")\n\n          if (\n            typeof firstChildAsString === \"string\" &&\n            !firstChildAsString.includes(\"\\n\")\n          ) {\n            return (\n              <code className={className} {...props}>\n                {childArray}\n              </code>\n            )\n          }\n\n          return (\n            <MessageCodeBlock\n              key={Math.random()}\n              language={(match && match[1]) || \"\"}\n              value={String(childArray).replace(/\\n$/, \"\")}\n              {...props}\n            />\n          )\n        }\n      }}\n    >\n      {content}\n    </MessageMarkdownMemoized>\n  )\n}\n"
  },
  {
    "path": "components/messages/message-replies.tsx",
    "content": "import { IconMessage } from \"@tabler/icons-react\"\nimport { FC, useState } from \"react\"\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n  SheetTrigger\n} from \"../ui/sheet\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\nimport { MESSAGE_ICON_SIZE } from \"./message-actions\"\n\ninterface MessageRepliesProps {}\n\nexport const MessageReplies: FC<MessageRepliesProps> = ({}) => {\n  const [isOpen, setIsOpen] = useState(false)\n\n  return (\n    <Sheet open={isOpen} onOpenChange={setIsOpen}>\n      <SheetTrigger asChild>\n        <WithTooltip\n          delayDuration={1000}\n          side=\"bottom\"\n          display={<div>View Replies</div>}\n          trigger={\n            <div\n              className=\"relative cursor-pointer hover:opacity-50\"\n              onClick={() => setIsOpen(true)}\n            >\n              <IconMessage size={MESSAGE_ICON_SIZE} />\n              <div className=\"notification-indicator absolute right-[-4px] top-[-4px] flex size-3 items-center justify-center rounded-full bg-red-600 text-[8px] text-white\">\n                {1}\n              </div>\n            </div>\n          }\n        />\n      </SheetTrigger>\n\n      <SheetContent>\n        <SheetHeader>\n          <SheetTitle>Are you sure absolutely sure?</SheetTitle>\n          <SheetDescription>\n            This action cannot be undone. This will permanently delete your\n            account and remove your data from our servers.\n          </SheetDescription>\n        </SheetHeader>\n      </SheetContent>\n    </Sheet>\n  )\n}\n"
  },
  {
    "path": "components/messages/message.tsx",
    "content": "import { useChatHandler } from \"@/components/chat/chat-hooks/use-chat-handler\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { LLM_LIST } from \"@/lib/models/llm/llm-list\"\nimport { cn } from \"@/lib/utils\"\nimport { Tables } from \"@/supabase/types\"\nimport { LLM, LLMID, MessageImage, ModelProvider } from \"@/types\"\nimport {\n  IconBolt,\n  IconCaretDownFilled,\n  IconCaretRightFilled,\n  IconCircleFilled,\n  IconFileText,\n  IconMoodSmile,\n  IconPencil\n} from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\nimport { ModelIcon } from \"../models/model-icon\"\nimport { Button } from \"../ui/button\"\nimport { FileIcon } from \"../ui/file-icon\"\nimport { FilePreview } from \"../ui/file-preview\"\nimport { TextareaAutosize } from \"../ui/textarea-autosize\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\nimport { MessageActions } from \"./message-actions\"\nimport { MessageMarkdown } from \"./message-markdown\"\n\nconst ICON_SIZE = 32\n\ninterface MessageProps {\n  message: Tables<\"messages\">\n  fileItems: Tables<\"file_items\">[]\n  isEditing: boolean\n  isLast: boolean\n  onStartEdit: (message: Tables<\"messages\">) => void\n  onCancelEdit: () => void\n  onSubmitEdit: (value: string, sequenceNumber: number) => void\n}\n\nexport const Message: FC<MessageProps> = ({\n  message,\n  fileItems,\n  isEditing,\n  isLast,\n  onStartEdit,\n  onCancelEdit,\n  onSubmitEdit\n}) => {\n  const {\n    assistants,\n    profile,\n    isGenerating,\n    setIsGenerating,\n    firstTokenReceived,\n    availableLocalModels,\n    availableOpenRouterModels,\n    chatMessages,\n    selectedAssistant,\n    chatImages,\n    assistantImages,\n    toolInUse,\n    files,\n    models\n  } = useContext(ChatbotUIContext)\n\n  const { handleSendMessage } = useChatHandler()\n\n  const editInputRef = useRef<HTMLTextAreaElement>(null)\n\n  const [isHovering, setIsHovering] = useState(false)\n  const [editedMessage, setEditedMessage] = useState(message.content)\n\n  const [showImagePreview, setShowImagePreview] = useState(false)\n  const [selectedImage, setSelectedImage] = useState<MessageImage | null>(null)\n\n  const [showFileItemPreview, setShowFileItemPreview] = useState(false)\n  const [selectedFileItem, setSelectedFileItem] =\n    useState<Tables<\"file_items\"> | null>(null)\n\n  const [viewSources, setViewSources] = useState(false)\n\n  const handleCopy = () => {\n    if (navigator.clipboard) {\n      navigator.clipboard.writeText(message.content)\n    } else {\n      const textArea = document.createElement(\"textarea\")\n      textArea.value = message.content\n      document.body.appendChild(textArea)\n      textArea.focus()\n      textArea.select()\n      document.execCommand(\"copy\")\n      document.body.removeChild(textArea)\n    }\n  }\n\n  const handleSendEdit = () => {\n    onSubmitEdit(editedMessage, message.sequence_number)\n    onCancelEdit()\n  }\n\n  const handleKeyDown = (event: React.KeyboardEvent) => {\n    if (isEditing && event.key === \"Enter\" && event.metaKey) {\n      handleSendEdit()\n    }\n  }\n\n  const handleRegenerate = async () => {\n    setIsGenerating(true)\n    await handleSendMessage(\n      editedMessage || chatMessages[chatMessages.length - 2].message.content,\n      chatMessages,\n      true\n    )\n  }\n\n  const handleStartEdit = () => {\n    onStartEdit(message)\n  }\n\n  useEffect(() => {\n    setEditedMessage(message.content)\n\n    if (isEditing && editInputRef.current) {\n      const input = editInputRef.current\n      input.focus()\n      input.setSelectionRange(input.value.length, input.value.length)\n    }\n  }, [isEditing])\n\n  const MODEL_DATA = [\n    ...models.map(model => ({\n      modelId: model.model_id as LLMID,\n      modelName: model.name,\n      provider: \"custom\" as ModelProvider,\n      hostedId: model.id,\n      platformLink: \"\",\n      imageInput: false\n    })),\n    ...LLM_LIST,\n    ...availableLocalModels,\n    ...availableOpenRouterModels\n  ].find(llm => llm.modelId === message.model) as LLM\n\n  const messageAssistantImage = assistantImages.find(\n    image => image.assistantId === message.assistant_id\n  )?.base64\n\n  const selectedAssistantImage = assistantImages.find(\n    image => image.path === selectedAssistant?.image_path\n  )?.base64\n\n  const modelDetails = LLM_LIST.find(model => model.modelId === message.model)\n\n  const fileAccumulator: Record<\n    string,\n    {\n      id: string\n      name: string\n      count: number\n      type: string\n      description: string\n    }\n  > = {}\n\n  const fileSummary = fileItems.reduce((acc, fileItem) => {\n    const parentFile = files.find(file => file.id === fileItem.file_id)\n    if (parentFile) {\n      if (!acc[parentFile.id]) {\n        acc[parentFile.id] = {\n          id: parentFile.id,\n          name: parentFile.name,\n          count: 1,\n          type: parentFile.type,\n          description: parentFile.description\n        }\n      } else {\n        acc[parentFile.id].count += 1\n      }\n    }\n    return acc\n  }, fileAccumulator)\n\n  return (\n    <div\n      className={cn(\n        \"flex w-full justify-center\",\n        message.role === \"user\" ? \"\" : \"bg-secondary\"\n      )}\n      onMouseEnter={() => setIsHovering(true)}\n      onMouseLeave={() => setIsHovering(false)}\n      onKeyDown={handleKeyDown}\n    >\n      <div className=\"relative flex w-full flex-col p-6 sm:w-[550px] sm:px-0 md:w-[650px] lg:w-[650px] xl:w-[700px]\">\n        <div className=\"absolute right-5 top-7 sm:right-0\">\n          <MessageActions\n            onCopy={handleCopy}\n            onEdit={handleStartEdit}\n            isAssistant={message.role === \"assistant\"}\n            isLast={isLast}\n            isEditing={isEditing}\n            isHovering={isHovering}\n            onRegenerate={handleRegenerate}\n          />\n        </div>\n        <div className=\"space-y-3\">\n          {message.role === \"system\" ? (\n            <div className=\"flex items-center space-x-4\">\n              <IconPencil\n                className=\"border-primary bg-primary text-secondary rounded border-DEFAULT p-1\"\n                size={ICON_SIZE}\n              />\n\n              <div className=\"text-lg font-semibold\">Prompt</div>\n            </div>\n          ) : (\n            <div className=\"flex items-center space-x-3\">\n              {message.role === \"assistant\" ? (\n                messageAssistantImage ? (\n                  <Image\n                    style={{\n                      width: `${ICON_SIZE}px`,\n                      height: `${ICON_SIZE}px`\n                    }}\n                    className=\"rounded\"\n                    src={messageAssistantImage}\n                    alt=\"assistant image\"\n                    height={ICON_SIZE}\n                    width={ICON_SIZE}\n                  />\n                ) : (\n                  <WithTooltip\n                    display={<div>{MODEL_DATA?.modelName}</div>}\n                    trigger={\n                      <ModelIcon\n                        provider={modelDetails?.provider || \"custom\"}\n                        height={ICON_SIZE}\n                        width={ICON_SIZE}\n                      />\n                    }\n                  />\n                )\n              ) : profile?.image_url ? (\n                <Image\n                  className={`size-[32px] rounded`}\n                  src={profile?.image_url}\n                  height={32}\n                  width={32}\n                  alt=\"user image\"\n                />\n              ) : (\n                <IconMoodSmile\n                  className=\"bg-primary text-secondary border-primary rounded border-DEFAULT p-1\"\n                  size={ICON_SIZE}\n                />\n              )}\n\n              <div className=\"font-semibold\">\n                {message.role === \"assistant\"\n                  ? message.assistant_id\n                    ? assistants.find(\n                        assistant => assistant.id === message.assistant_id\n                      )?.name\n                    : selectedAssistant\n                      ? selectedAssistant?.name\n                      : MODEL_DATA?.modelName\n                  : profile?.display_name ?? profile?.username}\n              </div>\n            </div>\n          )}\n          {!firstTokenReceived &&\n          isGenerating &&\n          isLast &&\n          message.role === \"assistant\" ? (\n            <>\n              {(() => {\n                switch (toolInUse) {\n                  case \"none\":\n                    return (\n                      <IconCircleFilled className=\"animate-pulse\" size={20} />\n                    )\n                  case \"retrieval\":\n                    return (\n                      <div className=\"flex animate-pulse items-center space-x-2\">\n                        <IconFileText size={20} />\n\n                        <div>Searching files...</div>\n                      </div>\n                    )\n                  default:\n                    return (\n                      <div className=\"flex animate-pulse items-center space-x-2\">\n                        <IconBolt size={20} />\n\n                        <div>Using {toolInUse}...</div>\n                      </div>\n                    )\n                }\n              })()}\n            </>\n          ) : isEditing ? (\n            <TextareaAutosize\n              textareaRef={editInputRef}\n              className=\"text-md\"\n              value={editedMessage}\n              onValueChange={setEditedMessage}\n              maxRows={20}\n            />\n          ) : (\n            <MessageMarkdown content={message.content} />\n          )}\n        </div>\n\n        {fileItems.length > 0 && (\n          <div className=\"border-primary mt-6 border-t pt-4 font-bold\">\n            {!viewSources ? (\n              <div\n                className=\"flex cursor-pointer items-center text-lg hover:opacity-50\"\n                onClick={() => setViewSources(true)}\n              >\n                {fileItems.length}\n                {fileItems.length > 1 ? \" Sources \" : \" Source \"}\n                from {Object.keys(fileSummary).length}{\" \"}\n                {Object.keys(fileSummary).length > 1 ? \"Files\" : \"File\"}{\" \"}\n                <IconCaretRightFilled className=\"ml-1\" />\n              </div>\n            ) : (\n              <>\n                <div\n                  className=\"flex cursor-pointer items-center text-lg hover:opacity-50\"\n                  onClick={() => setViewSources(false)}\n                >\n                  {fileItems.length}\n                  {fileItems.length > 1 ? \" Sources \" : \" Source \"}\n                  from {Object.keys(fileSummary).length}{\" \"}\n                  {Object.keys(fileSummary).length > 1 ? \"Files\" : \"File\"}{\" \"}\n                  <IconCaretDownFilled className=\"ml-1\" />\n                </div>\n\n                <div className=\"mt-3 space-y-4\">\n                  {Object.values(fileSummary).map((file, index) => (\n                    <div key={index}>\n                      <div className=\"flex items-center space-x-2\">\n                        <div>\n                          <FileIcon type={file.type} />\n                        </div>\n\n                        <div className=\"truncate\">{file.name}</div>\n                      </div>\n\n                      {fileItems\n                        .filter(fileItem => {\n                          const parentFile = files.find(\n                            parentFile => parentFile.id === fileItem.file_id\n                          )\n                          return parentFile?.id === file.id\n                        })\n                        .map((fileItem, index) => (\n                          <div\n                            key={index}\n                            className=\"ml-8 mt-1.5 flex cursor-pointer items-center space-x-2 hover:opacity-50\"\n                            onClick={() => {\n                              setSelectedFileItem(fileItem)\n                              setShowFileItemPreview(true)\n                            }}\n                          >\n                            <div className=\"text-sm font-normal\">\n                              <span className=\"mr-1 text-lg font-bold\">-</span>{\" \"}\n                              {fileItem.content.substring(0, 200)}...\n                            </div>\n                          </div>\n                        ))}\n                    </div>\n                  ))}\n                </div>\n              </>\n            )}\n          </div>\n        )}\n\n        <div className=\"mt-3 flex flex-wrap gap-2\">\n          {message.image_paths.map((path, index) => {\n            const item = chatImages.find(image => image.path === path)\n\n            return (\n              <Image\n                key={index}\n                className=\"cursor-pointer rounded hover:opacity-50\"\n                src={path.startsWith(\"data\") ? path : item?.base64}\n                alt=\"message image\"\n                width={300}\n                height={300}\n                onClick={() => {\n                  setSelectedImage({\n                    messageId: message.id,\n                    path,\n                    base64: path.startsWith(\"data\") ? path : item?.base64 || \"\",\n                    url: path.startsWith(\"data\") ? \"\" : item?.url || \"\",\n                    file: null\n                  })\n\n                  setShowImagePreview(true)\n                }}\n                loading=\"lazy\"\n              />\n            )\n          })}\n        </div>\n        {isEditing && (\n          <div className=\"mt-4 flex justify-center space-x-2\">\n            <Button size=\"sm\" onClick={handleSendEdit}>\n              Save & Send\n            </Button>\n\n            <Button size=\"sm\" variant=\"outline\" onClick={onCancelEdit}>\n              Cancel\n            </Button>\n          </div>\n        )}\n      </div>\n\n      {showImagePreview && selectedImage && (\n        <FilePreview\n          type=\"image\"\n          item={selectedImage}\n          isOpen={showImagePreview}\n          onOpenChange={(isOpen: boolean) => {\n            setShowImagePreview(isOpen)\n            setSelectedImage(null)\n          }}\n        />\n      )}\n\n      {showFileItemPreview && selectedFileItem && (\n        <FilePreview\n          type=\"file_item\"\n          item={selectedFileItem}\n          isOpen={showFileItemPreview}\n          onOpenChange={(isOpen: boolean) => {\n            setShowFileItemPreview(isOpen)\n            setSelectedFileItem(null)\n          }}\n        />\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/models/model-icon.tsx",
    "content": "import { cn } from \"@/lib/utils\"\nimport mistral from \"@/public/providers/mistral.png\"\nimport groq from \"@/public/providers/groq.png\"\nimport perplexity from \"@/public/providers/perplexity.png\"\nimport { ModelProvider } from \"@/types\"\nimport { IconSparkles } from \"@tabler/icons-react\"\nimport { useTheme } from \"next-themes\"\nimport Image from \"next/image\"\nimport { FC, HTMLAttributes } from \"react\"\nimport { AnthropicSVG } from \"../icons/anthropic-svg\"\nimport { GoogleSVG } from \"../icons/google-svg\"\nimport { OpenAISVG } from \"../icons/openai-svg\"\n\ninterface ModelIconProps extends HTMLAttributes<HTMLDivElement> {\n  provider: ModelProvider\n  height: number\n  width: number\n}\n\nexport const ModelIcon: FC<ModelIconProps> = ({\n  provider,\n  height,\n  width,\n  ...props\n}) => {\n  const { theme } = useTheme()\n\n  switch (provider as ModelProvider) {\n    case \"openai\":\n      return (\n        <OpenAISVG\n          className={cn(\n            \"rounded-sm bg-white p-1 text-black\",\n            props.className,\n            theme === \"dark\" ? \"bg-white\" : \"border-DEFAULT border-black\"\n          )}\n          width={width}\n          height={height}\n        />\n      )\n    case \"mistral\":\n      return (\n        <Image\n          className={cn(\n            \"rounded-sm p-1\",\n            theme === \"dark\" ? \"bg-white\" : \"border-DEFAULT border-black\"\n          )}\n          src={mistral.src}\n          alt=\"Mistral\"\n          width={width}\n          height={height}\n        />\n      )\n    case \"groq\":\n      return (\n        <Image\n          className={cn(\n            \"rounded-sm p-0\",\n            theme === \"dark\" ? \"bg-white\" : \"border-DEFAULT border-black\"\n          )}\n          src={groq.src}\n          alt=\"Groq\"\n          width={width}\n          height={height}\n        />\n      )\n    case \"anthropic\":\n      return (\n        <AnthropicSVG\n          className={cn(\n            \"rounded-sm bg-white p-1 text-black\",\n            props.className,\n            theme === \"dark\" ? \"bg-white\" : \"border-DEFAULT border-black\"\n          )}\n          width={width}\n          height={height}\n        />\n      )\n    case \"google\":\n      return (\n        <GoogleSVG\n          className={cn(\n            \"rounded-sm bg-white p-1 text-black\",\n            props.className,\n            theme === \"dark\" ? \"bg-white\" : \"border-DEFAULT border-black\"\n          )}\n          width={width}\n          height={height}\n        />\n      )\n    case \"perplexity\":\n      return (\n        <Image\n          className={cn(\n            \"rounded-sm p-1\",\n            theme === \"dark\" ? \"bg-white\" : \"border-DEFAULT border-black\"\n          )}\n          src={perplexity.src}\n          alt=\"Mistral\"\n          width={width}\n          height={height}\n        />\n      )\n    default:\n      return <IconSparkles size={width} />\n  }\n}\n"
  },
  {
    "path": "components/models/model-option.tsx",
    "content": "import { LLM } from \"@/types\"\nimport { FC } from \"react\"\nimport { ModelIcon } from \"./model-icon\"\nimport { IconInfoCircle } from \"@tabler/icons-react\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\n\ninterface ModelOptionProps {\n  model: LLM\n  onSelect: () => void\n}\n\nexport const ModelOption: FC<ModelOptionProps> = ({ model, onSelect }) => {\n  return (\n    <WithTooltip\n      display={\n        <div>\n          {model.provider !== \"ollama\" && model.pricing && (\n            <div className=\"space-y-1 text-sm\">\n              <div>\n                <span className=\"font-semibold\">Input Cost:</span>{\" \"}\n                {model.pricing.inputCost} {model.pricing.currency} per{\" \"}\n                {model.pricing.unit}\n              </div>\n              {model.pricing.outputCost && (\n                <div>\n                  <span className=\"font-semibold\">Output Cost:</span>{\" \"}\n                  {model.pricing.outputCost} {model.pricing.currency} per{\" \"}\n                  {model.pricing.unit}\n                </div>\n              )}\n            </div>\n          )}\n        </div>\n      }\n      side=\"bottom\"\n      trigger={\n        <div\n          className=\"hover:bg-accent flex w-full cursor-pointer justify-start space-x-3 truncate rounded p-2 hover:opacity-50\"\n          onClick={onSelect}\n        >\n          <div className=\"flex items-center space-x-2\">\n            <ModelIcon provider={model.provider} width={28} height={28} />\n            <div className=\"text-sm font-semibold\">{model.modelName}</div>\n          </div>\n        </div>\n      }\n    />\n  )\n}\n"
  },
  {
    "path": "components/models/model-select.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { LLM, LLMID, ModelProvider } from \"@/types\"\nimport { IconCheck, IconChevronDown } from \"@tabler/icons-react\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\nimport { Button } from \"../ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuTrigger\n} from \"../ui/dropdown-menu\"\nimport { Input } from \"../ui/input\"\nimport { Tabs, TabsList, TabsTrigger } from \"../ui/tabs\"\nimport { ModelIcon } from \"./model-icon\"\nimport { ModelOption } from \"./model-option\"\n\ninterface ModelSelectProps {\n  selectedModelId: string\n  onSelectModel: (modelId: LLMID) => void\n}\n\nexport const ModelSelect: FC<ModelSelectProps> = ({\n  selectedModelId,\n  onSelectModel\n}) => {\n  const {\n    profile,\n    models,\n    availableHostedModels,\n    availableLocalModels,\n    availableOpenRouterModels\n  } = useContext(ChatbotUIContext)\n\n  const inputRef = useRef<HTMLInputElement>(null)\n  const triggerRef = useRef<HTMLButtonElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n  const [search, setSearch] = useState(\"\")\n  const [tab, setTab] = useState<\"hosted\" | \"local\">(\"hosted\")\n\n  useEffect(() => {\n    if (isOpen) {\n      setTimeout(() => {\n        inputRef.current?.focus()\n      }, 100) // FIX: hacky\n    }\n  }, [isOpen])\n\n  const handleSelectModel = (modelId: LLMID) => {\n    onSelectModel(modelId)\n    setIsOpen(false)\n  }\n\n  const allModels = [\n    ...models.map(model => ({\n      modelId: model.model_id as LLMID,\n      modelName: model.name,\n      provider: \"custom\" as ModelProvider,\n      hostedId: model.id,\n      platformLink: \"\",\n      imageInput: false\n    })),\n    ...availableHostedModels,\n    ...availableLocalModels,\n    ...availableOpenRouterModels\n  ]\n\n  const groupedModels = allModels.reduce<Record<string, LLM[]>>(\n    (groups, model) => {\n      const key = model.provider\n      if (!groups[key]) {\n        groups[key] = []\n      }\n      groups[key].push(model)\n      return groups\n    },\n    {}\n  )\n\n  const selectedModel = allModels.find(\n    model => model.modelId === selectedModelId\n  )\n\n  if (!profile) return null\n\n  return (\n    <DropdownMenu\n      open={isOpen}\n      onOpenChange={isOpen => {\n        setIsOpen(isOpen)\n        setSearch(\"\")\n      }}\n    >\n      <DropdownMenuTrigger\n        className=\"bg-background w-full justify-start border-2 px-3 py-5\"\n        asChild\n        disabled={allModels.length === 0}\n      >\n        {allModels.length === 0 ? (\n          <div className=\"rounded text-sm font-bold\">\n            Unlock models by entering API keys in your profile settings.\n          </div>\n        ) : (\n          <Button\n            ref={triggerRef}\n            className=\"flex items-center justify-between\"\n            variant=\"ghost\"\n          >\n            <div className=\"flex items-center\">\n              {selectedModel ? (\n                <>\n                  <ModelIcon\n                    provider={selectedModel?.provider}\n                    width={26}\n                    height={26}\n                  />\n                  <div className=\"ml-2 flex items-center\">\n                    {selectedModel?.modelName}\n                  </div>\n                </>\n              ) : (\n                <div className=\"flex items-center\">Select a model</div>\n              )}\n            </div>\n\n            <IconChevronDown />\n          </Button>\n        )}\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent\n        className=\"space-y-2 overflow-auto p-2\"\n        style={{ width: triggerRef.current?.offsetWidth }}\n        align=\"start\"\n      >\n        <Tabs value={tab} onValueChange={(value: any) => setTab(value)}>\n          {availableLocalModels.length > 0 && (\n            <TabsList defaultValue=\"hosted\" className=\"grid grid-cols-2\">\n              <TabsTrigger value=\"hosted\">Hosted</TabsTrigger>\n\n              <TabsTrigger value=\"local\">Local</TabsTrigger>\n            </TabsList>\n          )}\n        </Tabs>\n\n        <Input\n          ref={inputRef}\n          className=\"w-full\"\n          placeholder=\"Search models...\"\n          value={search}\n          onChange={e => setSearch(e.target.value)}\n        />\n\n        <div className=\"max-h-[300px] overflow-auto\">\n          {Object.entries(groupedModels).map(([provider, models]) => {\n            const filteredModels = models\n              .filter(model => {\n                if (tab === \"hosted\") return model.provider !== \"ollama\"\n                if (tab === \"local\") return model.provider === \"ollama\"\n                if (tab === \"openrouter\") return model.provider === \"openrouter\"\n              })\n              .filter(model =>\n                model.modelName.toLowerCase().includes(search.toLowerCase())\n              )\n              .sort((a, b) => a.provider.localeCompare(b.provider))\n\n            if (filteredModels.length === 0) return null\n\n            return (\n              <div key={provider}>\n                <div className=\"mb-1 ml-2 text-xs font-bold tracking-wide opacity-50\">\n                  {provider === \"openai\" && profile.use_azure_openai\n                    ? \"AZURE OPENAI\"\n                    : provider.toLocaleUpperCase()}\n                </div>\n\n                <div className=\"mb-4\">\n                  {filteredModels.map(model => {\n                    return (\n                      <div\n                        key={model.modelId}\n                        className=\"flex items-center space-x-1\"\n                      >\n                        {selectedModelId === model.modelId && (\n                          <IconCheck className=\"ml-2\" size={32} />\n                        )}\n\n                        <ModelOption\n                          key={model.modelId}\n                          model={model}\n                          onSelect={() => handleSelectModel(model.modelId)}\n                        />\n                      </div>\n                    )\n                  })}\n                </div>\n              </div>\n            )\n          })}\n        </div>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n"
  },
  {
    "path": "components/setup/api-step.tsx",
    "content": "import { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { FC } from \"react\"\nimport { Button } from \"../ui/button\"\n\ninterface APIStepProps {\n  openaiAPIKey: string\n  openaiOrgID: string\n  azureOpenaiAPIKey: string\n  azureOpenaiEndpoint: string\n  azureOpenai35TurboID: string\n  azureOpenai45TurboID: string\n  azureOpenai45VisionID: string\n  azureOpenaiEmbeddingsID: string\n  anthropicAPIKey: string\n  googleGeminiAPIKey: string\n  mistralAPIKey: string\n  groqAPIKey: string\n  perplexityAPIKey: string\n  useAzureOpenai: boolean\n  openrouterAPIKey: string\n  onOpenrouterAPIKeyChange: (value: string) => void\n  onOpenaiAPIKeyChange: (value: string) => void\n  onOpenaiOrgIDChange: (value: string) => void\n  onAzureOpenaiAPIKeyChange: (value: string) => void\n  onAzureOpenaiEndpointChange: (value: string) => void\n  onAzureOpenai35TurboIDChange: (value: string) => void\n  onAzureOpenai45TurboIDChange: (value: string) => void\n  onAzureOpenai45VisionIDChange: (value: string) => void\n  onAzureOpenaiEmbeddingsIDChange: (value: string) => void\n  onAnthropicAPIKeyChange: (value: string) => void\n  onGoogleGeminiAPIKeyChange: (value: string) => void\n  onMistralAPIKeyChange: (value: string) => void\n  onGroqAPIKeyChange: (value: string) => void\n  onPerplexityAPIKeyChange: (value: string) => void\n  onUseAzureOpenaiChange: (value: boolean) => void\n}\n\nexport const APIStep: FC<APIStepProps> = ({\n  openaiAPIKey,\n  openaiOrgID,\n  azureOpenaiAPIKey,\n  azureOpenaiEndpoint,\n  azureOpenai35TurboID,\n  azureOpenai45TurboID,\n  azureOpenai45VisionID,\n  azureOpenaiEmbeddingsID,\n  anthropicAPIKey,\n  googleGeminiAPIKey,\n  mistralAPIKey,\n  groqAPIKey,\n  perplexityAPIKey,\n  openrouterAPIKey,\n  useAzureOpenai,\n  onOpenaiAPIKeyChange,\n  onOpenaiOrgIDChange,\n  onAzureOpenaiAPIKeyChange,\n  onAzureOpenaiEndpointChange,\n  onAzureOpenai35TurboIDChange,\n  onAzureOpenai45TurboIDChange,\n  onAzureOpenai45VisionIDChange,\n  onAzureOpenaiEmbeddingsIDChange,\n  onAnthropicAPIKeyChange,\n  onGoogleGeminiAPIKeyChange,\n  onMistralAPIKeyChange,\n  onGroqAPIKeyChange,\n  onPerplexityAPIKeyChange,\n  onUseAzureOpenaiChange,\n  onOpenrouterAPIKeyChange\n}) => {\n  return (\n    <>\n      <div className=\"mt-5 space-y-2\">\n        <Label className=\"flex items-center\">\n          <div>\n            {useAzureOpenai ? \"Azure OpenAI API Key\" : \"OpenAI API Key\"}\n          </div>\n\n          <Button\n            className=\"ml-3 h-[18px] w-[150px] text-[11px]\"\n            onClick={() => onUseAzureOpenaiChange(!useAzureOpenai)}\n          >\n            {useAzureOpenai\n              ? \"Switch To Standard OpenAI\"\n              : \"Switch To Azure OpenAI\"}\n          </Button>\n        </Label>\n\n        <Input\n          placeholder={\n            useAzureOpenai ? \"Azure OpenAI API Key\" : \"OpenAI API Key\"\n          }\n          type=\"password\"\n          value={useAzureOpenai ? azureOpenaiAPIKey : openaiAPIKey}\n          onChange={e =>\n            useAzureOpenai\n              ? onAzureOpenaiAPIKeyChange(e.target.value)\n              : onOpenaiAPIKeyChange(e.target.value)\n          }\n        />\n      </div>\n\n      <div className=\"ml-8 space-y-3\">\n        {useAzureOpenai ? (\n          <>\n            <div className=\"space-y-1\">\n              <Label>Azure OpenAI Endpoint</Label>\n\n              <Input\n                placeholder=\"https://your-endpoint.openai.azure.com\"\n                type=\"password\"\n                value={azureOpenaiEndpoint}\n                onChange={e => onAzureOpenaiEndpointChange(e.target.value)}\n              />\n            </div>\n\n            <div className=\"space-y-1\">\n              <Label>Azure OpenAI GPT-3.5 Turbo ID</Label>\n\n              <Input\n                placeholder=\"Azure OpenAI GPT-3.5 Turbo ID\"\n                type=\"password\"\n                value={azureOpenai35TurboID}\n                onChange={e => onAzureOpenai35TurboIDChange(e.target.value)}\n              />\n            </div>\n\n            <div className=\"space-y-1\">\n              <Label>Azure OpenAI GPT-4.5 Turbo ID</Label>\n\n              <Input\n                placeholder=\"Azure OpenAI GPT-4.5 Turbo ID\"\n                type=\"password\"\n                value={azureOpenai45TurboID}\n                onChange={e => onAzureOpenai45TurboIDChange(e.target.value)}\n              />\n            </div>\n\n            <div className=\"space-y-1\">\n              <Label>Azure OpenAI GPT-4.5 Vision ID</Label>\n\n              <Input\n                placeholder=\"Azure OpenAI GPT-4.5 Vision ID\"\n                type=\"password\"\n                value={azureOpenai45VisionID}\n                onChange={e => onAzureOpenai45VisionIDChange(e.target.value)}\n              />\n            </div>\n\n            <div className=\"space-y-1\">\n              <Label>Azure OpenAI Embeddings ID</Label>\n\n              <Input\n                placeholder=\"Azure OpenAI Embeddings ID\"\n                type=\"password\"\n                value={azureOpenaiEmbeddingsID}\n                onChange={e => onAzureOpenaiEmbeddingsIDChange(e.target.value)}\n              />\n            </div>\n          </>\n        ) : (\n          <>\n            <div className=\"space-y-1\">\n              <Label>OpenAI Organization ID</Label>\n\n              <Input\n                placeholder=\"OpenAI Organization ID (optional)\"\n                type=\"password\"\n                value={openaiOrgID}\n                onChange={e => onOpenaiOrgIDChange(e.target.value)}\n              />\n            </div>\n          </>\n        )}\n      </div>\n\n      <div className=\"space-y-1\">\n        <Label>Anthropic API Key</Label>\n\n        <Input\n          placeholder=\"Anthropic API Key\"\n          type=\"password\"\n          value={anthropicAPIKey}\n          onChange={e => onAnthropicAPIKeyChange(e.target.value)}\n        />\n      </div>\n\n      <div className=\"space-y-1\">\n        <Label>Google Gemini API Key</Label>\n\n        <Input\n          placeholder=\"Google Gemini API Key\"\n          type=\"password\"\n          value={googleGeminiAPIKey}\n          onChange={e => onGoogleGeminiAPIKeyChange(e.target.value)}\n        />\n      </div>\n\n      <div className=\"space-y-1\">\n        <Label>Mistral API Key</Label>\n\n        <Input\n          placeholder=\"Mistral API Key\"\n          type=\"password\"\n          value={mistralAPIKey}\n          onChange={e => onMistralAPIKeyChange(e.target.value)}\n        />\n      </div>\n\n      <div className=\"space-y-1\">\n        <Label>Groq API Key</Label>\n\n        <Input\n          placeholder=\"Groq API Key\"\n          type=\"password\"\n          value={groqAPIKey}\n          onChange={e => onGroqAPIKeyChange(e.target.value)}\n        />\n      </div>\n\n      <div className=\"space-y-1\">\n        <Label>Perplexity API Key</Label>\n\n        <Input\n          placeholder=\"Perplexity API Key\"\n          type=\"password\"\n          value={perplexityAPIKey}\n          onChange={e => onPerplexityAPIKeyChange(e.target.value)}\n        />\n      </div>\n      <div className=\"space-y-1\">\n        <Label>OpenRouter API Key</Label>\n\n        <Input\n          placeholder=\"OpenRouter API Key\"\n          type=\"password\"\n          value={openrouterAPIKey}\n          onChange={e => onOpenrouterAPIKeyChange(e.target.value)}\n        />\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "components/setup/finish-step.tsx",
    "content": "import { FC } from \"react\"\n\ninterface FinishStepProps {\n  displayName: string\n}\n\nexport const FinishStep: FC<FinishStepProps> = ({ displayName }) => {\n  return (\n    <div className=\"space-y-4\">\n      <div>\n        Welcome to Chatbot UI\n        {displayName.length > 0 ? `, ${displayName.split(\" \")[0]}` : null}!\n      </div>\n\n      <div>Click next to start chatting.</div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/setup/profile-step.tsx",
    "content": "import { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport {\n  PROFILE_DISPLAY_NAME_MAX,\n  PROFILE_USERNAME_MAX,\n  PROFILE_USERNAME_MIN\n} from \"@/db/limits\"\nimport {\n  IconCircleCheckFilled,\n  IconCircleXFilled,\n  IconLoader2\n} from \"@tabler/icons-react\"\nimport { FC, useCallback, useState } from \"react\"\nimport { LimitDisplay } from \"../ui/limit-display\"\nimport { toast } from \"sonner\"\n\ninterface ProfileStepProps {\n  username: string\n  usernameAvailable: boolean\n  displayName: string\n  onUsernameAvailableChange: (isAvailable: boolean) => void\n  onUsernameChange: (username: string) => void\n  onDisplayNameChange: (name: string) => void\n}\n\nexport const ProfileStep: FC<ProfileStepProps> = ({\n  username,\n  usernameAvailable,\n  displayName,\n  onUsernameAvailableChange,\n  onUsernameChange,\n  onDisplayNameChange\n}) => {\n  const [loading, setLoading] = useState(false)\n\n  const debounce = (func: (...args: any[]) => void, wait: number) => {\n    let timeout: NodeJS.Timeout | null\n\n    return (...args: any[]) => {\n      const later = () => {\n        if (timeout) clearTimeout(timeout)\n        func(...args)\n      }\n\n      if (timeout) clearTimeout(timeout)\n      timeout = setTimeout(later, wait)\n    }\n  }\n\n  const checkUsernameAvailability = useCallback(\n    debounce(async (username: string) => {\n      if (!username) return\n\n      if (username.length < PROFILE_USERNAME_MIN) {\n        onUsernameAvailableChange(false)\n        return\n      }\n\n      if (username.length > PROFILE_USERNAME_MAX) {\n        onUsernameAvailableChange(false)\n        return\n      }\n\n      const usernameRegex = /^[a-zA-Z0-9_]+$/\n      if (!usernameRegex.test(username)) {\n        onUsernameAvailableChange(false)\n        toast.error(\n          \"Username must be letters, numbers, or underscores only - no other characters or spacing allowed.\"\n        )\n        return\n      }\n\n      setLoading(true)\n\n      const response = await fetch(`/api/username/available`, {\n        method: \"POST\",\n        body: JSON.stringify({ username })\n      })\n\n      const data = await response.json()\n      const isAvailable = data.isAvailable\n\n      onUsernameAvailableChange(isAvailable)\n\n      setLoading(false)\n    }, 500),\n    []\n  )\n\n  return (\n    <>\n      <div className=\"space-y-1\">\n        <div className=\"flex items-center space-x-2\">\n          <Label>Username</Label>\n\n          <div className=\"text-xs\">\n            {usernameAvailable ? (\n              <div className=\"text-green-500\">AVAILABLE</div>\n            ) : (\n              <div className=\"text-red-500\">UNAVAILABLE</div>\n            )}\n          </div>\n        </div>\n\n        <div className=\"relative\">\n          <Input\n            className=\"pr-10\"\n            placeholder=\"username\"\n            value={username}\n            onChange={e => {\n              onUsernameChange(e.target.value)\n              checkUsernameAvailability(e.target.value)\n            }}\n            minLength={PROFILE_USERNAME_MIN}\n            maxLength={PROFILE_USERNAME_MAX}\n          />\n\n          <div className=\"absolute inset-y-0 right-0 flex items-center pr-3\">\n            {loading ? (\n              <IconLoader2 className=\"animate-spin\" />\n            ) : usernameAvailable ? (\n              <IconCircleCheckFilled className=\"text-green-500\" />\n            ) : (\n              <IconCircleXFilled className=\"text-red-500\" />\n            )}\n          </div>\n        </div>\n\n        <LimitDisplay used={username.length} limit={PROFILE_USERNAME_MAX} />\n      </div>\n\n      <div className=\"space-y-1\">\n        <Label>Chat Display Name</Label>\n\n        <Input\n          placeholder=\"Your Name\"\n          value={displayName}\n          onChange={e => onDisplayNameChange(e.target.value)}\n          maxLength={PROFILE_DISPLAY_NAME_MAX}\n        />\n\n        <LimitDisplay\n          used={displayName.length}\n          limit={PROFILE_DISPLAY_NAME_MAX}\n        />\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "components/setup/step-container.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle\n} from \"@/components/ui/card\"\nimport { FC, useRef } from \"react\"\n\nexport const SETUP_STEP_COUNT = 3\n\ninterface StepContainerProps {\n  stepDescription: string\n  stepNum: number\n  stepTitle: string\n  onShouldProceed: (shouldProceed: boolean) => void\n  children?: React.ReactNode\n  showBackButton?: boolean\n  showNextButton?: boolean\n}\n\nexport const StepContainer: FC<StepContainerProps> = ({\n  stepDescription,\n  stepNum,\n  stepTitle,\n  onShouldProceed,\n  children,\n  showBackButton = false,\n  showNextButton = true\n}) => {\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\" && !e.shiftKey) {\n      if (buttonRef.current) {\n        buttonRef.current.click()\n      }\n    }\n  }\n\n  return (\n    <Card\n      className=\"max-h-[calc(100vh-60px)] w-[600px] overflow-auto\"\n      onKeyDown={handleKeyDown}\n    >\n      <CardHeader>\n        <CardTitle className=\"flex justify-between\">\n          <div>{stepTitle}</div>\n\n          <div className=\"text-sm\">\n            {stepNum} / {SETUP_STEP_COUNT}\n          </div>\n        </CardTitle>\n\n        <CardDescription>{stepDescription}</CardDescription>\n      </CardHeader>\n\n      <CardContent className=\"space-y-4\">{children}</CardContent>\n\n      <CardFooter className=\"flex justify-between\">\n        <div>\n          {showBackButton && (\n            <Button\n              size=\"sm\"\n              variant=\"outline\"\n              onClick={() => onShouldProceed(false)}\n            >\n              Back\n            </Button>\n          )}\n        </div>\n\n        <div>\n          {showNextButton && (\n            <Button\n              ref={buttonRef}\n              size=\"sm\"\n              onClick={() => onShouldProceed(true)}\n            >\n              Next\n            </Button>\n          )}\n        </div>\n      </CardFooter>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/all/sidebar-create-item.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  Sheet,\n  SheetContent,\n  SheetFooter,\n  SheetHeader,\n  SheetTitle\n} from \"@/components/ui/sheet\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { createAssistantCollections } from \"@/db/assistant-collections\"\nimport { createAssistantFiles } from \"@/db/assistant-files\"\nimport { createAssistantTools } from \"@/db/assistant-tools\"\nimport { createAssistant, updateAssistant } from \"@/db/assistants\"\nimport { createChat } from \"@/db/chats\"\nimport { createCollectionFiles } from \"@/db/collection-files\"\nimport { createCollection } from \"@/db/collections\"\nimport { createFileBasedOnExtension } from \"@/db/files\"\nimport { createModel } from \"@/db/models\"\nimport { createPreset } from \"@/db/presets\"\nimport { createPrompt } from \"@/db/prompts\"\nimport {\n  getAssistantImageFromStorage,\n  uploadAssistantImage\n} from \"@/db/storage/assistant-images\"\nimport { createTool } from \"@/db/tools\"\nimport { convertBlobToBase64 } from \"@/lib/blob-to-b64\"\nimport { Tables, TablesInsert } from \"@/supabase/types\"\nimport { ContentType } from \"@/types\"\nimport { FC, useContext, useRef, useState } from \"react\"\nimport { toast } from \"sonner\"\n\ninterface SidebarCreateItemProps {\n  isOpen: boolean\n  isTyping: boolean\n  onOpenChange: (isOpen: boolean) => void\n  contentType: ContentType\n  renderInputs: () => JSX.Element\n  createState: any\n}\n\nexport const SidebarCreateItem: FC<SidebarCreateItemProps> = ({\n  isOpen,\n  onOpenChange,\n  contentType,\n  renderInputs,\n  createState,\n  isTyping\n}) => {\n  const {\n    selectedWorkspace,\n    setChats,\n    setPresets,\n    setPrompts,\n    setFiles,\n    setCollections,\n    setAssistants,\n    setAssistantImages,\n    setTools,\n    setModels\n  } = useContext(ChatbotUIContext)\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [creating, setCreating] = useState(false)\n\n  const createFunctions = {\n    chats: createChat,\n    presets: createPreset,\n    prompts: createPrompt,\n    files: async (\n      createState: { file: File } & TablesInsert<\"files\">,\n      workspaceId: string\n    ) => {\n      if (!selectedWorkspace) return\n\n      const { file, ...rest } = createState\n\n      const createdFile = await createFileBasedOnExtension(\n        file,\n        rest,\n        workspaceId,\n        selectedWorkspace.embeddings_provider as \"openai\" | \"local\"\n      )\n\n      return createdFile\n    },\n    collections: async (\n      createState: {\n        image: File\n        collectionFiles: TablesInsert<\"collection_files\">[]\n      } & Tables<\"collections\">,\n      workspaceId: string\n    ) => {\n      const { collectionFiles, ...rest } = createState\n\n      const createdCollection = await createCollection(rest, workspaceId)\n\n      const finalCollectionFiles = collectionFiles.map(collectionFile => ({\n        ...collectionFile,\n        collection_id: createdCollection.id\n      }))\n\n      await createCollectionFiles(finalCollectionFiles)\n\n      return createdCollection\n    },\n    assistants: async (\n      createState: {\n        image: File\n        files: Tables<\"files\">[]\n        collections: Tables<\"collections\">[]\n        tools: Tables<\"tools\">[]\n      } & Tables<\"assistants\">,\n      workspaceId: string\n    ) => {\n      const { image, files, collections, tools, ...rest } = createState\n\n      const createdAssistant = await createAssistant(rest, workspaceId)\n\n      let updatedAssistant = createdAssistant\n\n      if (image) {\n        const filePath = await uploadAssistantImage(createdAssistant, image)\n\n        updatedAssistant = await updateAssistant(createdAssistant.id, {\n          image_path: filePath\n        })\n\n        const url = (await getAssistantImageFromStorage(filePath)) || \"\"\n\n        if (url) {\n          const response = await fetch(url)\n          const blob = await response.blob()\n          const base64 = await convertBlobToBase64(blob)\n\n          setAssistantImages(prev => [\n            ...prev,\n            {\n              assistantId: updatedAssistant.id,\n              path: filePath,\n              base64,\n              url\n            }\n          ])\n        }\n      }\n\n      const assistantFiles = files.map(file => ({\n        user_id: rest.user_id,\n        assistant_id: createdAssistant.id,\n        file_id: file.id\n      }))\n\n      const assistantCollections = collections.map(collection => ({\n        user_id: rest.user_id,\n        assistant_id: createdAssistant.id,\n        collection_id: collection.id\n      }))\n\n      const assistantTools = tools.map(tool => ({\n        user_id: rest.user_id,\n        assistant_id: createdAssistant.id,\n        tool_id: tool.id\n      }))\n\n      await createAssistantFiles(assistantFiles)\n      await createAssistantCollections(assistantCollections)\n      await createAssistantTools(assistantTools)\n\n      return updatedAssistant\n    },\n    tools: createTool,\n    models: createModel\n  }\n\n  const stateUpdateFunctions = {\n    chats: setChats,\n    presets: setPresets,\n    prompts: setPrompts,\n    files: setFiles,\n    collections: setCollections,\n    assistants: setAssistants,\n    tools: setTools,\n    models: setModels\n  }\n\n  const handleCreate = async () => {\n    try {\n      if (!selectedWorkspace) return\n      if (isTyping) return // Prevent creation while typing\n\n      const createFunction = createFunctions[contentType]\n      const setStateFunction = stateUpdateFunctions[contentType]\n\n      if (!createFunction || !setStateFunction) return\n\n      setCreating(true)\n\n      const newItem = await createFunction(createState, selectedWorkspace.id)\n\n      setStateFunction((prevItems: any) => [...prevItems, newItem])\n\n      onOpenChange(false)\n      setCreating(false)\n    } catch (error) {\n      toast.error(`Error creating ${contentType.slice(0, -1)}. ${error}.`)\n      setCreating(false)\n    }\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (!isTyping && e.key === \"Enter\" && !e.shiftKey) {\n      e.preventDefault()\n      buttonRef.current?.click()\n    }\n  }\n\n  return (\n    <Sheet open={isOpen} onOpenChange={onOpenChange}>\n      <SheetContent\n        className=\"flex min-w-[450px] flex-col justify-between overflow-auto\"\n        side=\"left\"\n        onKeyDown={handleKeyDown}\n      >\n        <div className=\"grow overflow-auto\">\n          <SheetHeader>\n            <SheetTitle className=\"text-2xl font-bold\">\n              Create{\" \"}\n              {contentType.charAt(0).toUpperCase() + contentType.slice(1, -1)}\n            </SheetTitle>\n          </SheetHeader>\n\n          <div className=\"mt-4 space-y-3\">{renderInputs()}</div>\n        </div>\n\n        <SheetFooter className=\"mt-2 flex justify-between\">\n          <div className=\"flex grow justify-end space-x-2\">\n            <Button\n              disabled={creating}\n              variant=\"outline\"\n              onClick={() => onOpenChange(false)}\n            >\n              Cancel\n            </Button>\n\n            <Button disabled={creating} ref={buttonRef} onClick={handleCreate}>\n              {creating ? \"Creating...\" : \"Create\"}\n            </Button>\n          </div>\n        </SheetFooter>\n      </SheetContent>\n    </Sheet>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/all/sidebar-delete-item.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from \"@/components/ui/dialog\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { deleteAssistant } from \"@/db/assistants\"\nimport { deleteChat } from \"@/db/chats\"\nimport { deleteCollection } from \"@/db/collections\"\nimport { deleteFile } from \"@/db/files\"\nimport { deleteModel } from \"@/db/models\"\nimport { deletePreset } from \"@/db/presets\"\nimport { deletePrompt } from \"@/db/prompts\"\nimport { deleteFileFromStorage } from \"@/db/storage/files\"\nimport { deleteTool } from \"@/db/tools\"\nimport { Tables } from \"@/supabase/types\"\nimport { ContentType, DataItemType } from \"@/types\"\nimport { FC, useContext, useRef, useState } from \"react\"\n\ninterface SidebarDeleteItemProps {\n  item: DataItemType\n  contentType: ContentType\n}\n\nexport const SidebarDeleteItem: FC<SidebarDeleteItemProps> = ({\n  item,\n  contentType\n}) => {\n  const {\n    setChats,\n    setPresets,\n    setPrompts,\n    setFiles,\n    setCollections,\n    setAssistants,\n    setTools,\n    setModels\n  } = useContext(ChatbotUIContext)\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [showDialog, setShowDialog] = useState(false)\n\n  const deleteFunctions = {\n    chats: async (chat: Tables<\"chats\">) => {\n      await deleteChat(chat.id)\n    },\n    presets: async (preset: Tables<\"presets\">) => {\n      await deletePreset(preset.id)\n    },\n    prompts: async (prompt: Tables<\"prompts\">) => {\n      await deletePrompt(prompt.id)\n    },\n    files: async (file: Tables<\"files\">) => {\n      await deleteFileFromStorage(file.file_path)\n      await deleteFile(file.id)\n    },\n    collections: async (collection: Tables<\"collections\">) => {\n      await deleteCollection(collection.id)\n    },\n    assistants: async (assistant: Tables<\"assistants\">) => {\n      await deleteAssistant(assistant.id)\n      setChats(prevState =>\n        prevState.filter(chat => chat.assistant_id !== assistant.id)\n      )\n    },\n    tools: async (tool: Tables<\"tools\">) => {\n      await deleteTool(tool.id)\n    },\n    models: async (model: Tables<\"models\">) => {\n      await deleteModel(model.id)\n    }\n  }\n\n  const stateUpdateFunctions = {\n    chats: setChats,\n    presets: setPresets,\n    prompts: setPrompts,\n    files: setFiles,\n    collections: setCollections,\n    assistants: setAssistants,\n    tools: setTools,\n    models: setModels\n  }\n\n  const handleDelete = async () => {\n    const deleteFunction = deleteFunctions[contentType]\n    const setStateFunction = stateUpdateFunctions[contentType]\n\n    if (!deleteFunction || !setStateFunction) return\n\n    await deleteFunction(item as any)\n\n    setStateFunction((prevItems: any) =>\n      prevItems.filter((prevItem: any) => prevItem.id !== item.id)\n    )\n\n    setShowDialog(false)\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\") {\n      e.stopPropagation()\n      buttonRef.current?.click()\n    }\n  }\n\n  return (\n    <Dialog open={showDialog} onOpenChange={setShowDialog}>\n      <DialogTrigger asChild>\n        <Button className=\"text-red-500\" variant=\"ghost\">\n          Delete\n        </Button>\n      </DialogTrigger>\n\n      <DialogContent onKeyDown={handleKeyDown}>\n        <DialogHeader>\n          <DialogTitle>Delete {contentType.slice(0, -1)}</DialogTitle>\n\n          <DialogDescription>\n            Are you sure you want to delete {item.name}?\n          </DialogDescription>\n        </DialogHeader>\n\n        <DialogFooter>\n          <Button variant=\"ghost\" onClick={() => setShowDialog(false)}>\n            Cancel\n          </Button>\n\n          <Button ref={buttonRef} variant=\"destructive\" onClick={handleDelete}>\n            Delete\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/all/sidebar-display-item.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { createChat } from \"@/db/chats\"\nimport { cn } from \"@/lib/utils\"\nimport { Tables } from \"@/supabase/types\"\nimport { ContentType, DataItemType } from \"@/types\"\nimport { useRouter } from \"next/navigation\"\nimport { FC, useContext, useRef, useState } from \"react\"\nimport { SidebarUpdateItem } from \"./sidebar-update-item\"\n\ninterface SidebarItemProps {\n  item: DataItemType\n  isTyping: boolean\n  contentType: ContentType\n  icon: React.ReactNode\n  updateState: any\n  renderInputs: (renderState: any) => JSX.Element\n}\n\nexport const SidebarItem: FC<SidebarItemProps> = ({\n  item,\n  contentType,\n  updateState,\n  renderInputs,\n  icon,\n  isTyping\n}) => {\n  const { selectedWorkspace, setChats, setSelectedAssistant } =\n    useContext(ChatbotUIContext)\n\n  const router = useRouter()\n\n  const itemRef = useRef<HTMLDivElement>(null)\n\n  const [isHovering, setIsHovering] = useState(false)\n\n  const actionMap = {\n    chats: async (item: any) => {},\n    presets: async (item: any) => {},\n    prompts: async (item: any) => {},\n    files: async (item: any) => {},\n    collections: async (item: any) => {},\n    assistants: async (assistant: Tables<\"assistants\">) => {\n      if (!selectedWorkspace) return\n\n      const createdChat = await createChat({\n        user_id: assistant.user_id,\n        workspace_id: selectedWorkspace.id,\n        assistant_id: assistant.id,\n        context_length: assistant.context_length,\n        include_profile_context: assistant.include_profile_context,\n        include_workspace_instructions:\n          assistant.include_workspace_instructions,\n        model: assistant.model,\n        name: `Chat with ${assistant.name}`,\n        prompt: assistant.prompt,\n        temperature: assistant.temperature,\n        embeddings_provider: assistant.embeddings_provider\n      })\n\n      setChats(prevState => [createdChat, ...prevState])\n      setSelectedAssistant(assistant)\n\n      return router.push(`/${selectedWorkspace.id}/chat/${createdChat.id}`)\n    },\n    tools: async (item: any) => {},\n    models: async (item: any) => {}\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\") {\n      e.stopPropagation()\n      itemRef.current?.click()\n    }\n  }\n\n  // const handleClickAction = async (\n  //   e: React.MouseEvent<SVGSVGElement, MouseEvent>\n  // ) => {\n  //   e.stopPropagation()\n\n  //   const action = actionMap[contentType]\n\n  //   await action(item as any)\n  // }\n\n  return (\n    <SidebarUpdateItem\n      item={item}\n      isTyping={isTyping}\n      contentType={contentType}\n      updateState={updateState}\n      renderInputs={renderInputs}\n    >\n      <div\n        ref={itemRef}\n        className={cn(\n          \"hover:bg-accent flex w-full cursor-pointer items-center rounded p-2 hover:opacity-50 focus:outline-none\"\n        )}\n        tabIndex={0}\n        onKeyDown={handleKeyDown}\n        onMouseEnter={() => setIsHovering(true)}\n        onMouseLeave={() => setIsHovering(false)}\n      >\n        {icon}\n\n        <div className=\"ml-3 flex-1 truncate text-sm font-semibold\">\n          {item.name}\n        </div>\n\n        {/* TODO */}\n        {/* {isHovering && (\n          <WithTooltip\n            delayDuration={1000}\n            display={<div>Start chat with {contentType.slice(0, -1)}</div>}\n            trigger={\n              <IconSquarePlus\n                className=\"cursor-pointer hover:text-blue-500\"\n                size={20}\n                onClick={handleClickAction}\n              />\n            }\n          />\n        )} */}\n      </div>\n    </SidebarUpdateItem>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/all/sidebar-update-item.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport { Label } from \"@/components/ui/label\"\nimport {\n  Sheet,\n  SheetContent,\n  SheetFooter,\n  SheetHeader,\n  SheetTitle,\n  SheetTrigger\n} from \"@/components/ui/sheet\"\nimport { AssignWorkspaces } from \"@/components/workspace/assign-workspaces\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport {\n  createAssistantCollection,\n  deleteAssistantCollection,\n  getAssistantCollectionsByAssistantId\n} from \"@/db/assistant-collections\"\nimport {\n  createAssistantFile,\n  deleteAssistantFile,\n  getAssistantFilesByAssistantId\n} from \"@/db/assistant-files\"\nimport {\n  createAssistantTool,\n  deleteAssistantTool,\n  getAssistantToolsByAssistantId\n} from \"@/db/assistant-tools\"\nimport {\n  createAssistantWorkspaces,\n  deleteAssistantWorkspace,\n  getAssistantWorkspacesByAssistantId,\n  updateAssistant\n} from \"@/db/assistants\"\nimport { updateChat } from \"@/db/chats\"\nimport {\n  createCollectionFile,\n  deleteCollectionFile,\n  getCollectionFilesByCollectionId\n} from \"@/db/collection-files\"\nimport {\n  createCollectionWorkspaces,\n  deleteCollectionWorkspace,\n  getCollectionWorkspacesByCollectionId,\n  updateCollection\n} from \"@/db/collections\"\nimport {\n  createFileWorkspaces,\n  deleteFileWorkspace,\n  getFileWorkspacesByFileId,\n  updateFile\n} from \"@/db/files\"\nimport {\n  createModelWorkspaces,\n  deleteModelWorkspace,\n  getModelWorkspacesByModelId,\n  updateModel\n} from \"@/db/models\"\nimport {\n  createPresetWorkspaces,\n  deletePresetWorkspace,\n  getPresetWorkspacesByPresetId,\n  updatePreset\n} from \"@/db/presets\"\nimport {\n  createPromptWorkspaces,\n  deletePromptWorkspace,\n  getPromptWorkspacesByPromptId,\n  updatePrompt\n} from \"@/db/prompts\"\nimport {\n  getAssistantImageFromStorage,\n  uploadAssistantImage\n} from \"@/db/storage/assistant-images\"\nimport {\n  createToolWorkspaces,\n  deleteToolWorkspace,\n  getToolWorkspacesByToolId,\n  updateTool\n} from \"@/db/tools\"\nimport { convertBlobToBase64 } from \"@/lib/blob-to-b64\"\nimport { Tables, TablesUpdate } from \"@/supabase/types\"\nimport { CollectionFile, ContentType, DataItemType } from \"@/types\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\nimport profile from \"react-syntax-highlighter/dist/esm/languages/hljs/profile\"\nimport { toast } from \"sonner\"\nimport { SidebarDeleteItem } from \"./sidebar-delete-item\"\n\ninterface SidebarUpdateItemProps {\n  isTyping: boolean\n  item: DataItemType\n  contentType: ContentType\n  children: React.ReactNode\n  renderInputs: (renderState: any) => JSX.Element\n  updateState: any\n}\n\nexport const SidebarUpdateItem: FC<SidebarUpdateItemProps> = ({\n  item,\n  contentType,\n  children,\n  renderInputs,\n  updateState,\n  isTyping\n}) => {\n  const {\n    workspaces,\n    selectedWorkspace,\n    setChats,\n    setPresets,\n    setPrompts,\n    setFiles,\n    setCollections,\n    setAssistants,\n    setTools,\n    setModels,\n    setAssistantImages\n  } = useContext(ChatbotUIContext)\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n  const [startingWorkspaces, setStartingWorkspaces] = useState<\n    Tables<\"workspaces\">[]\n  >([])\n  const [selectedWorkspaces, setSelectedWorkspaces] = useState<\n    Tables<\"workspaces\">[]\n  >([])\n\n  // Collections Render State\n  const [startingCollectionFiles, setStartingCollectionFiles] = useState<\n    CollectionFile[]\n  >([])\n  const [selectedCollectionFiles, setSelectedCollectionFiles] = useState<\n    CollectionFile[]\n  >([])\n\n  // Assistants Render State\n  const [startingAssistantFiles, setStartingAssistantFiles] = useState<\n    Tables<\"files\">[]\n  >([])\n  const [startingAssistantCollections, setStartingAssistantCollections] =\n    useState<Tables<\"collections\">[]>([])\n  const [startingAssistantTools, setStartingAssistantTools] = useState<\n    Tables<\"tools\">[]\n  >([])\n  const [selectedAssistantFiles, setSelectedAssistantFiles] = useState<\n    Tables<\"files\">[]\n  >([])\n  const [selectedAssistantCollections, setSelectedAssistantCollections] =\n    useState<Tables<\"collections\">[]>([])\n  const [selectedAssistantTools, setSelectedAssistantTools] = useState<\n    Tables<\"tools\">[]\n  >([])\n\n  useEffect(() => {\n    if (isOpen) {\n      const fetchData = async () => {\n        if (workspaces.length > 1) {\n          const workspaces = await fetchSelectedWorkspaces()\n          setStartingWorkspaces(workspaces)\n          setSelectedWorkspaces(workspaces)\n        }\n\n        const fetchDataFunction = fetchDataFunctions[contentType]\n        if (!fetchDataFunction) return\n        await fetchDataFunction(item.id)\n      }\n\n      fetchData()\n    }\n  }, [isOpen])\n\n  const renderState = {\n    chats: null,\n    presets: null,\n    prompts: null,\n    files: null,\n    collections: {\n      startingCollectionFiles,\n      setStartingCollectionFiles,\n      selectedCollectionFiles,\n      setSelectedCollectionFiles\n    },\n    assistants: {\n      startingAssistantFiles,\n      setStartingAssistantFiles,\n      startingAssistantCollections,\n      setStartingAssistantCollections,\n      startingAssistantTools,\n      setStartingAssistantTools,\n      selectedAssistantFiles,\n      setSelectedAssistantFiles,\n      selectedAssistantCollections,\n      setSelectedAssistantCollections,\n      selectedAssistantTools,\n      setSelectedAssistantTools\n    },\n    tools: null,\n    models: null\n  }\n\n  const fetchDataFunctions = {\n    chats: null,\n    presets: null,\n    prompts: null,\n    files: null,\n    collections: async (collectionId: string) => {\n      const collectionFiles =\n        await getCollectionFilesByCollectionId(collectionId)\n      setStartingCollectionFiles(collectionFiles.files)\n      setSelectedCollectionFiles([])\n    },\n    assistants: async (assistantId: string) => {\n      const assistantFiles = await getAssistantFilesByAssistantId(assistantId)\n      setStartingAssistantFiles(assistantFiles.files)\n\n      const assistantCollections =\n        await getAssistantCollectionsByAssistantId(assistantId)\n      setStartingAssistantCollections(assistantCollections.collections)\n\n      const assistantTools = await getAssistantToolsByAssistantId(assistantId)\n      setStartingAssistantTools(assistantTools.tools)\n\n      setSelectedAssistantFiles([])\n      setSelectedAssistantCollections([])\n      setSelectedAssistantTools([])\n    },\n    tools: null,\n    models: null\n  }\n\n  const fetchWorkpaceFunctions = {\n    chats: null,\n    presets: async (presetId: string) => {\n      const item = await getPresetWorkspacesByPresetId(presetId)\n      return item.workspaces\n    },\n    prompts: async (promptId: string) => {\n      const item = await getPromptWorkspacesByPromptId(promptId)\n      return item.workspaces\n    },\n    files: async (fileId: string) => {\n      const item = await getFileWorkspacesByFileId(fileId)\n      return item.workspaces\n    },\n    collections: async (collectionId: string) => {\n      const item = await getCollectionWorkspacesByCollectionId(collectionId)\n      return item.workspaces\n    },\n    assistants: async (assistantId: string) => {\n      const item = await getAssistantWorkspacesByAssistantId(assistantId)\n      return item.workspaces\n    },\n    tools: async (toolId: string) => {\n      const item = await getToolWorkspacesByToolId(toolId)\n      return item.workspaces\n    },\n    models: async (modelId: string) => {\n      const item = await getModelWorkspacesByModelId(modelId)\n      return item.workspaces\n    }\n  }\n\n  const fetchSelectedWorkspaces = async () => {\n    const fetchFunction = fetchWorkpaceFunctions[contentType]\n\n    if (!fetchFunction) return []\n\n    const workspaces = await fetchFunction(item.id)\n\n    return workspaces\n  }\n\n  const handleWorkspaceUpdates = async (\n    startingWorkspaces: Tables<\"workspaces\">[],\n    selectedWorkspaces: Tables<\"workspaces\">[],\n    itemId: string,\n    deleteWorkspaceFn: (\n      itemId: string,\n      workspaceId: string\n    ) => Promise<boolean>,\n    createWorkspaceFn: (\n      workspaces: { user_id: string; item_id: string; workspace_id: string }[]\n    ) => Promise<void>,\n    itemIdKey: string\n  ) => {\n    if (!selectedWorkspace) return\n\n    const deleteList = startingWorkspaces.filter(\n      startingWorkspace =>\n        !selectedWorkspaces.some(\n          selectedWorkspace => selectedWorkspace.id === startingWorkspace.id\n        )\n    )\n\n    for (const workspace of deleteList) {\n      await deleteWorkspaceFn(itemId, workspace.id)\n    }\n\n    if (deleteList.map(w => w.id).includes(selectedWorkspace.id)) {\n      const setStateFunction = stateUpdateFunctions[contentType]\n\n      if (setStateFunction) {\n        setStateFunction((prevItems: any) =>\n          prevItems.filter((prevItem: any) => prevItem.id !== item.id)\n        )\n      }\n    }\n\n    const createList = selectedWorkspaces.filter(\n      selectedWorkspace =>\n        !startingWorkspaces.some(\n          startingWorkspace => startingWorkspace.id === selectedWorkspace.id\n        )\n    )\n\n    await createWorkspaceFn(\n      createList.map(workspace => {\n        return {\n          user_id: workspace.user_id,\n          [itemIdKey]: itemId,\n          workspace_id: workspace.id\n        } as any\n      })\n    )\n  }\n\n  const updateFunctions = {\n    chats: updateChat,\n    presets: async (presetId: string, updateState: TablesUpdate<\"presets\">) => {\n      const updatedPreset = await updatePreset(presetId, updateState)\n\n      await handleWorkspaceUpdates(\n        startingWorkspaces,\n        selectedWorkspaces,\n        presetId,\n        deletePresetWorkspace,\n        createPresetWorkspaces as any,\n        \"preset_id\"\n      )\n\n      return updatedPreset\n    },\n    prompts: async (promptId: string, updateState: TablesUpdate<\"prompts\">) => {\n      const updatedPrompt = await updatePrompt(promptId, updateState)\n\n      await handleWorkspaceUpdates(\n        startingWorkspaces,\n        selectedWorkspaces,\n        promptId,\n        deletePromptWorkspace,\n        createPromptWorkspaces as any,\n        \"prompt_id\"\n      )\n\n      return updatedPrompt\n    },\n    files: async (fileId: string, updateState: TablesUpdate<\"files\">) => {\n      const updatedFile = await updateFile(fileId, updateState)\n\n      await handleWorkspaceUpdates(\n        startingWorkspaces,\n        selectedWorkspaces,\n        fileId,\n        deleteFileWorkspace,\n        createFileWorkspaces as any,\n        \"file_id\"\n      )\n\n      return updatedFile\n    },\n    collections: async (\n      collectionId: string,\n      updateState: TablesUpdate<\"assistants\">\n    ) => {\n      if (!profile) return\n\n      const { ...rest } = updateState\n\n      const filesToAdd = selectedCollectionFiles.filter(\n        selectedFile =>\n          !startingCollectionFiles.some(\n            startingFile => startingFile.id === selectedFile.id\n          )\n      )\n\n      const filesToRemove = startingCollectionFiles.filter(startingFile =>\n        selectedCollectionFiles.some(\n          selectedFile => selectedFile.id === startingFile.id\n        )\n      )\n\n      for (const file of filesToAdd) {\n        await createCollectionFile({\n          user_id: item.user_id,\n          collection_id: collectionId,\n          file_id: file.id\n        })\n      }\n\n      for (const file of filesToRemove) {\n        await deleteCollectionFile(collectionId, file.id)\n      }\n\n      const updatedCollection = await updateCollection(collectionId, rest)\n\n      await handleWorkspaceUpdates(\n        startingWorkspaces,\n        selectedWorkspaces,\n        collectionId,\n        deleteCollectionWorkspace,\n        createCollectionWorkspaces as any,\n        \"collection_id\"\n      )\n\n      return updatedCollection\n    },\n    assistants: async (\n      assistantId: string,\n      updateState: {\n        assistantId: string\n        image: File\n      } & TablesUpdate<\"assistants\">\n    ) => {\n      const { image, ...rest } = updateState\n\n      const filesToAdd = selectedAssistantFiles.filter(\n        selectedFile =>\n          !startingAssistantFiles.some(\n            startingFile => startingFile.id === selectedFile.id\n          )\n      )\n\n      const filesToRemove = startingAssistantFiles.filter(startingFile =>\n        selectedAssistantFiles.some(\n          selectedFile => selectedFile.id === startingFile.id\n        )\n      )\n\n      for (const file of filesToAdd) {\n        await createAssistantFile({\n          user_id: item.user_id,\n          assistant_id: assistantId,\n          file_id: file.id\n        })\n      }\n\n      for (const file of filesToRemove) {\n        await deleteAssistantFile(assistantId, file.id)\n      }\n\n      const collectionsToAdd = selectedAssistantCollections.filter(\n        selectedCollection =>\n          !startingAssistantCollections.some(\n            startingCollection =>\n              startingCollection.id === selectedCollection.id\n          )\n      )\n\n      const collectionsToRemove = startingAssistantCollections.filter(\n        startingCollection =>\n          selectedAssistantCollections.some(\n            selectedCollection =>\n              selectedCollection.id === startingCollection.id\n          )\n      )\n\n      for (const collection of collectionsToAdd) {\n        await createAssistantCollection({\n          user_id: item.user_id,\n          assistant_id: assistantId,\n          collection_id: collection.id\n        })\n      }\n\n      for (const collection of collectionsToRemove) {\n        await deleteAssistantCollection(assistantId, collection.id)\n      }\n\n      const toolsToAdd = selectedAssistantTools.filter(\n        selectedTool =>\n          !startingAssistantTools.some(\n            startingTool => startingTool.id === selectedTool.id\n          )\n      )\n\n      const toolsToRemove = startingAssistantTools.filter(startingTool =>\n        selectedAssistantTools.some(\n          selectedTool => selectedTool.id === startingTool.id\n        )\n      )\n\n      for (const tool of toolsToAdd) {\n        await createAssistantTool({\n          user_id: item.user_id,\n          assistant_id: assistantId,\n          tool_id: tool.id\n        })\n      }\n\n      for (const tool of toolsToRemove) {\n        await deleteAssistantTool(assistantId, tool.id)\n      }\n\n      let updatedAssistant = await updateAssistant(assistantId, rest)\n\n      if (image) {\n        const filePath = await uploadAssistantImage(updatedAssistant, image)\n\n        updatedAssistant = await updateAssistant(assistantId, {\n          image_path: filePath\n        })\n\n        const url = (await getAssistantImageFromStorage(filePath)) || \"\"\n\n        if (url) {\n          const response = await fetch(url)\n          const blob = await response.blob()\n          const base64 = await convertBlobToBase64(blob)\n\n          setAssistantImages(prev => [\n            ...prev,\n            {\n              assistantId: updatedAssistant.id,\n              path: filePath,\n              base64,\n              url\n            }\n          ])\n        }\n      }\n\n      await handleWorkspaceUpdates(\n        startingWorkspaces,\n        selectedWorkspaces,\n        assistantId,\n        deleteAssistantWorkspace,\n        createAssistantWorkspaces as any,\n        \"assistant_id\"\n      )\n\n      return updatedAssistant\n    },\n    tools: async (toolId: string, updateState: TablesUpdate<\"tools\">) => {\n      const updatedTool = await updateTool(toolId, updateState)\n\n      await handleWorkspaceUpdates(\n        startingWorkspaces,\n        selectedWorkspaces,\n        toolId,\n        deleteToolWorkspace,\n        createToolWorkspaces as any,\n        \"tool_id\"\n      )\n\n      return updatedTool\n    },\n    models: async (modelId: string, updateState: TablesUpdate<\"models\">) => {\n      const updatedModel = await updateModel(modelId, updateState)\n\n      await handleWorkspaceUpdates(\n        startingWorkspaces,\n        selectedWorkspaces,\n        modelId,\n        deleteModelWorkspace,\n        createModelWorkspaces as any,\n        \"model_id\"\n      )\n\n      return updatedModel\n    }\n  }\n\n  const stateUpdateFunctions = {\n    chats: setChats,\n    presets: setPresets,\n    prompts: setPrompts,\n    files: setFiles,\n    collections: setCollections,\n    assistants: setAssistants,\n    tools: setTools,\n    models: setModels\n  }\n\n  const handleUpdate = async () => {\n    try {\n      const updateFunction = updateFunctions[contentType]\n      const setStateFunction = stateUpdateFunctions[contentType]\n\n      if (!updateFunction || !setStateFunction) return\n      if (isTyping) return // Prevent update while typing\n\n      const updatedItem = await updateFunction(item.id, updateState)\n\n      setStateFunction((prevItems: any) =>\n        prevItems.map((prevItem: any) =>\n          prevItem.id === item.id ? updatedItem : prevItem\n        )\n      )\n\n      setIsOpen(false)\n\n      toast.success(`${contentType.slice(0, -1)} updated successfully`)\n    } catch (error) {\n      toast.error(`Error updating ${contentType.slice(0, -1)}. ${error}`)\n    }\n  }\n\n  const handleSelectWorkspace = (workspace: Tables<\"workspaces\">) => {\n    setSelectedWorkspaces(prevState => {\n      const isWorkspaceAlreadySelected = prevState.find(\n        selectedWorkspace => selectedWorkspace.id === workspace.id\n      )\n\n      if (isWorkspaceAlreadySelected) {\n        return prevState.filter(\n          selectedWorkspace => selectedWorkspace.id !== workspace.id\n        )\n      } else {\n        return [...prevState, workspace]\n      }\n    })\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (!isTyping && e.key === \"Enter\" && !e.shiftKey) {\n      e.preventDefault()\n      buttonRef.current?.click()\n    }\n  }\n\n  return (\n    <Sheet open={isOpen} onOpenChange={setIsOpen}>\n      <SheetTrigger asChild>{children}</SheetTrigger>\n\n      <SheetContent\n        className=\"flex min-w-[450px] flex-col justify-between\"\n        side=\"left\"\n        onKeyDown={handleKeyDown}\n      >\n        <div className=\"grow overflow-auto\">\n          <SheetHeader>\n            <SheetTitle className=\"text-2xl font-bold\">\n              Edit {contentType.slice(0, -1)}\n            </SheetTitle>\n          </SheetHeader>\n\n          <div className=\"mt-4 space-y-3\">\n            {workspaces.length > 1 && (\n              <div className=\"space-y-1\">\n                <Label>Assigned Workspaces</Label>\n\n                <AssignWorkspaces\n                  selectedWorkspaces={selectedWorkspaces}\n                  onSelectWorkspace={handleSelectWorkspace}\n                />\n              </div>\n            )}\n\n            {renderInputs(renderState[contentType])}\n          </div>\n        </div>\n\n        <SheetFooter className=\"mt-2 flex justify-between\">\n          <SidebarDeleteItem item={item} contentType={contentType} />\n\n          <div className=\"flex grow justify-end space-x-2\">\n            <Button variant=\"outline\" onClick={() => setIsOpen(false)}>\n              Cancel\n            </Button>\n\n            <Button ref={buttonRef} onClick={handleUpdate}>\n              Save\n            </Button>\n          </div>\n        </SheetFooter>\n      </SheetContent>\n    </Sheet>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/assistants/assistant-item.tsx",
    "content": "import { ChatSettingsForm } from \"@/components/ui/chat-settings-form\"\nimport ImagePicker from \"@/components/ui/image-picker\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { ASSISTANT_DESCRIPTION_MAX, ASSISTANT_NAME_MAX } from \"@/db/limits\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconRobotFace } from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { FC, useContext, useEffect, useState } from \"react\"\nimport profile from \"react-syntax-highlighter/dist/esm/languages/hljs/profile\"\nimport { SidebarItem } from \"../all/sidebar-display-item\"\nimport { AssistantRetrievalSelect } from \"./assistant-retrieval-select\"\nimport { AssistantToolSelect } from \"./assistant-tool-select\"\n\ninterface AssistantItemProps {\n  assistant: Tables<\"assistants\">\n}\n\nexport const AssistantItem: FC<AssistantItemProps> = ({ assistant }) => {\n  const { selectedWorkspace, assistantImages } = useContext(ChatbotUIContext)\n\n  const [name, setName] = useState(assistant.name)\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(assistant.description)\n  const [assistantChatSettings, setAssistantChatSettings] = useState({\n    model: assistant.model,\n    prompt: assistant.prompt,\n    temperature: assistant.temperature,\n    contextLength: assistant.context_length,\n    includeProfileContext: assistant.include_profile_context,\n    includeWorkspaceInstructions: assistant.include_workspace_instructions\n  })\n  const [selectedImage, setSelectedImage] = useState<File | null>(null)\n  const [imageLink, setImageLink] = useState(\"\")\n\n  useEffect(() => {\n    const assistantImage =\n      assistantImages.find(image => image.path === assistant.image_path)\n        ?.base64 || \"\"\n    setImageLink(assistantImage)\n  }, [assistant, assistantImages])\n\n  const handleFileSelect = (\n    file: Tables<\"files\">,\n    setSelectedAssistantFiles: React.Dispatch<\n      React.SetStateAction<Tables<\"files\">[]>\n    >\n  ) => {\n    setSelectedAssistantFiles(prevState => {\n      const isFileAlreadySelected = prevState.find(\n        selectedFile => selectedFile.id === file.id\n      )\n\n      if (isFileAlreadySelected) {\n        return prevState.filter(selectedFile => selectedFile.id !== file.id)\n      } else {\n        return [...prevState, file]\n      }\n    })\n  }\n\n  const handleCollectionSelect = (\n    collection: Tables<\"collections\">,\n    setSelectedAssistantCollections: React.Dispatch<\n      React.SetStateAction<Tables<\"collections\">[]>\n    >\n  ) => {\n    setSelectedAssistantCollections(prevState => {\n      const isCollectionAlreadySelected = prevState.find(\n        selectedCollection => selectedCollection.id === collection.id\n      )\n\n      if (isCollectionAlreadySelected) {\n        return prevState.filter(\n          selectedCollection => selectedCollection.id !== collection.id\n        )\n      } else {\n        return [...prevState, collection]\n      }\n    })\n  }\n\n  const handleToolSelect = (\n    tool: Tables<\"tools\">,\n    setSelectedAssistantTools: React.Dispatch<\n      React.SetStateAction<Tables<\"tools\">[]>\n    >\n  ) => {\n    setSelectedAssistantTools(prevState => {\n      const isToolAlreadySelected = prevState.find(\n        selectedTool => selectedTool.id === tool.id\n      )\n\n      if (isToolAlreadySelected) {\n        return prevState.filter(selectedTool => selectedTool.id !== tool.id)\n      } else {\n        return [...prevState, tool]\n      }\n    })\n  }\n\n  if (!profile) return null\n  if (!selectedWorkspace) return null\n\n  return (\n    <SidebarItem\n      item={assistant}\n      contentType=\"assistants\"\n      isTyping={isTyping}\n      icon={\n        imageLink ? (\n          <Image\n            style={{ width: \"30px\", height: \"30px\" }}\n            className=\"rounded\"\n            src={imageLink}\n            alt={assistant.name}\n            width={30}\n            height={30}\n          />\n        ) : (\n          <IconRobotFace\n            className=\"bg-primary text-secondary border-primary rounded border-DEFAULT p-1\"\n            size={30}\n          />\n        )\n      }\n      updateState={{\n        image: selectedImage,\n        user_id: assistant.user_id,\n        name,\n        description,\n        include_profile_context: assistantChatSettings.includeProfileContext,\n        include_workspace_instructions:\n          assistantChatSettings.includeWorkspaceInstructions,\n        context_length: assistantChatSettings.contextLength,\n        model: assistantChatSettings.model,\n        image_path: assistant.image_path,\n        prompt: assistantChatSettings.prompt,\n        temperature: assistantChatSettings.temperature\n      }}\n      renderInputs={(renderState: {\n        startingAssistantFiles: Tables<\"files\">[]\n        setStartingAssistantFiles: React.Dispatch<\n          React.SetStateAction<Tables<\"files\">[]>\n        >\n        selectedAssistantFiles: Tables<\"files\">[]\n        setSelectedAssistantFiles: React.Dispatch<\n          React.SetStateAction<Tables<\"files\">[]>\n        >\n        startingAssistantCollections: Tables<\"collections\">[]\n        setStartingAssistantCollections: React.Dispatch<\n          React.SetStateAction<Tables<\"collections\">[]>\n        >\n        selectedAssistantCollections: Tables<\"collections\">[]\n        setSelectedAssistantCollections: React.Dispatch<\n          React.SetStateAction<Tables<\"collections\">[]>\n        >\n        startingAssistantTools: Tables<\"tools\">[]\n        setStartingAssistantTools: React.Dispatch<\n          React.SetStateAction<Tables<\"tools\">[]>\n        >\n        selectedAssistantTools: Tables<\"tools\">[]\n        setSelectedAssistantTools: React.Dispatch<\n          React.SetStateAction<Tables<\"tools\">[]>\n        >\n      }) => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Assistant name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={ASSISTANT_NAME_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1 pt-2\">\n            <Label>Description</Label>\n\n            <Input\n              placeholder=\"Assistant description...\"\n              value={description}\n              onChange={e => setDescription(e.target.value)}\n              maxLength={ASSISTANT_DESCRIPTION_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Image</Label>\n\n            <ImagePicker\n              src={imageLink}\n              image={selectedImage}\n              onSrcChange={setImageLink}\n              onImageChange={setSelectedImage}\n              width={100}\n              height={100}\n            />\n          </div>\n\n          <ChatSettingsForm\n            chatSettings={assistantChatSettings as any}\n            onChangeChatSettings={setAssistantChatSettings}\n            useAdvancedDropdown={true}\n          />\n\n          <div className=\"space-y-1 pt-2\">\n            <Label>Files & Collections</Label>\n\n            <AssistantRetrievalSelect\n              selectedAssistantRetrievalItems={\n                [\n                  ...renderState.selectedAssistantFiles,\n                  ...renderState.selectedAssistantCollections\n                ].length === 0\n                  ? [\n                      ...renderState.startingAssistantFiles,\n                      ...renderState.startingAssistantCollections\n                    ]\n                  : [\n                      ...renderState.startingAssistantFiles.filter(\n                        startingFile =>\n                          ![\n                            ...renderState.selectedAssistantFiles,\n                            ...renderState.selectedAssistantCollections\n                          ].some(\n                            selectedFile => selectedFile.id === startingFile.id\n                          )\n                      ),\n                      ...renderState.selectedAssistantFiles.filter(\n                        selectedFile =>\n                          !renderState.startingAssistantFiles.some(\n                            startingFile => startingFile.id === selectedFile.id\n                          )\n                      ),\n                      ...renderState.startingAssistantCollections.filter(\n                        startingCollection =>\n                          ![\n                            ...renderState.selectedAssistantFiles,\n                            ...renderState.selectedAssistantCollections\n                          ].some(\n                            selectedCollection =>\n                              selectedCollection.id === startingCollection.id\n                          )\n                      ),\n                      ...renderState.selectedAssistantCollections.filter(\n                        selectedCollection =>\n                          !renderState.startingAssistantCollections.some(\n                            startingCollection =>\n                              startingCollection.id === selectedCollection.id\n                          )\n                      )\n                    ]\n              }\n              onAssistantRetrievalItemsSelect={item =>\n                \"type\" in item\n                  ? handleFileSelect(\n                      item,\n                      renderState.setSelectedAssistantFiles\n                    )\n                  : handleCollectionSelect(\n                      item,\n                      renderState.setSelectedAssistantCollections\n                    )\n              }\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Tools</Label>\n\n            <AssistantToolSelect\n              selectedAssistantTools={\n                renderState.selectedAssistantTools.length === 0\n                  ? renderState.startingAssistantTools\n                  : [\n                      ...renderState.startingAssistantTools.filter(\n                        startingTool =>\n                          !renderState.selectedAssistantTools.some(\n                            selectedTool => selectedTool.id === startingTool.id\n                          )\n                      ),\n                      ...renderState.selectedAssistantTools.filter(\n                        selectedTool =>\n                          !renderState.startingAssistantTools.some(\n                            startingTool => startingTool.id === selectedTool.id\n                          )\n                      )\n                    ]\n              }\n              onAssistantToolsSelect={tool =>\n                handleToolSelect(tool, renderState.setSelectedAssistantTools)\n              }\n            />\n          </div>\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/assistants/assistant-retrieval-select.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuTrigger\n} from \"@/components/ui/dropdown-menu\"\nimport { Input } from \"@/components/ui/input\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { Tables } from \"@/supabase/types\"\nimport {\n  IconBooks,\n  IconChevronDown,\n  IconCircleCheckFilled\n} from \"@tabler/icons-react\"\nimport { FileIcon } from \"lucide-react\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\n\ninterface AssistantRetrievalSelectProps {\n  selectedAssistantRetrievalItems: Tables<\"files\">[] | Tables<\"collections\">[]\n  onAssistantRetrievalItemsSelect: (\n    item: Tables<\"files\"> | Tables<\"collections\">\n  ) => void\n}\n\nexport const AssistantRetrievalSelect: FC<AssistantRetrievalSelectProps> = ({\n  selectedAssistantRetrievalItems,\n  onAssistantRetrievalItemsSelect\n}) => {\n  const { files, collections } = useContext(ChatbotUIContext)\n\n  const inputRef = useRef<HTMLInputElement>(null)\n  const triggerRef = useRef<HTMLButtonElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n  const [search, setSearch] = useState(\"\")\n\n  useEffect(() => {\n    if (isOpen) {\n      setTimeout(() => {\n        inputRef.current?.focus()\n      }, 100) // FIX: hacky\n    }\n  }, [isOpen])\n\n  const handleItemSelect = (item: Tables<\"files\"> | Tables<\"collections\">) => {\n    onAssistantRetrievalItemsSelect(item)\n  }\n\n  if (!files || !collections) return null\n\n  return (\n    <DropdownMenu\n      open={isOpen}\n      onOpenChange={isOpen => {\n        setIsOpen(isOpen)\n        setSearch(\"\")\n      }}\n    >\n      <DropdownMenuTrigger\n        className=\"bg-background w-full justify-start border-2 px-3 py-5\"\n        asChild\n      >\n        <Button\n          ref={triggerRef}\n          className=\"flex items-center justify-between\"\n          variant=\"ghost\"\n        >\n          <div className=\"flex items-center\">\n            <div className=\"ml-2 flex items-center\">\n              {selectedAssistantRetrievalItems.length} files selected\n            </div>\n          </div>\n\n          <IconChevronDown />\n        </Button>\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent\n        style={{ width: triggerRef.current?.offsetWidth }}\n        className=\"space-y-2 overflow-auto p-2\"\n        align=\"start\"\n      >\n        <Input\n          ref={inputRef}\n          placeholder=\"Search files...\"\n          value={search}\n          onChange={e => setSearch(e.target.value)}\n          onKeyDown={e => e.stopPropagation()}\n        />\n\n        {selectedAssistantRetrievalItems\n          .filter(item =>\n            item.name.toLowerCase().includes(search.toLowerCase())\n          )\n          .map(item => (\n            <AssistantRetrievalItemOption\n              key={item.id}\n              contentType={\n                item.hasOwnProperty(\"type\") ? \"files\" : \"collections\"\n              }\n              item={item as Tables<\"files\"> | Tables<\"collections\">}\n              selected={selectedAssistantRetrievalItems.some(\n                selectedAssistantRetrieval =>\n                  selectedAssistantRetrieval.id === item.id\n              )}\n              onSelect={handleItemSelect}\n            />\n          ))}\n\n        {files\n          .filter(\n            file =>\n              !selectedAssistantRetrievalItems.some(\n                selectedAssistantRetrieval =>\n                  selectedAssistantRetrieval.id === file.id\n              ) && file.name.toLowerCase().includes(search.toLowerCase())\n          )\n          .map(file => (\n            <AssistantRetrievalItemOption\n              key={file.id}\n              item={file}\n              contentType=\"files\"\n              selected={selectedAssistantRetrievalItems.some(\n                selectedAssistantRetrieval =>\n                  selectedAssistantRetrieval.id === file.id\n              )}\n              onSelect={handleItemSelect}\n            />\n          ))}\n\n        {collections\n          .filter(\n            collection =>\n              !selectedAssistantRetrievalItems.some(\n                selectedAssistantRetrieval =>\n                  selectedAssistantRetrieval.id === collection.id\n              ) && collection.name.toLowerCase().includes(search.toLowerCase())\n          )\n          .map(collection => (\n            <AssistantRetrievalItemOption\n              key={collection.id}\n              contentType=\"collections\"\n              item={collection}\n              selected={selectedAssistantRetrievalItems.some(\n                selectedAssistantRetrieval =>\n                  selectedAssistantRetrieval.id === collection.id\n              )}\n              onSelect={handleItemSelect}\n            />\n          ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\ninterface AssistantRetrievalOptionItemProps {\n  contentType: \"files\" | \"collections\"\n  item: Tables<\"files\"> | Tables<\"collections\">\n  selected: boolean\n  onSelect: (item: Tables<\"files\"> | Tables<\"collections\">) => void\n}\n\nconst AssistantRetrievalItemOption: FC<AssistantRetrievalOptionItemProps> = ({\n  contentType,\n  item,\n  selected,\n  onSelect\n}) => {\n  const handleSelect = () => {\n    onSelect(item)\n  }\n\n  return (\n    <div\n      className=\"flex cursor-pointer items-center justify-between py-0.5 hover:opacity-50\"\n      onClick={handleSelect}\n    >\n      <div className=\"flex grow items-center truncate\">\n        {contentType === \"files\" ? (\n          <div className=\"mr-2 min-w-[24px]\">\n            <FileIcon type={(item as Tables<\"files\">).type} size={24} />\n          </div>\n        ) : (\n          <div className=\"mr-2 min-w-[24px]\">\n            <IconBooks size={24} />\n          </div>\n        )}\n\n        <div className=\"truncate\">{item.name}</div>\n      </div>\n\n      {selected && (\n        <IconCircleCheckFilled size={20} className=\"min-w-[30px] flex-none\" />\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/assistants/assistant-tool-select.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuTrigger\n} from \"@/components/ui/dropdown-menu\"\nimport { Input } from \"@/components/ui/input\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { Tables } from \"@/supabase/types\"\nimport {\n  IconBolt,\n  IconChevronDown,\n  IconCircleCheckFilled\n} from \"@tabler/icons-react\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\n\ninterface AssistantToolSelectProps {\n  selectedAssistantTools: Tables<\"tools\">[]\n  onAssistantToolsSelect: (tool: Tables<\"tools\">) => void\n}\n\nexport const AssistantToolSelect: FC<AssistantToolSelectProps> = ({\n  selectedAssistantTools,\n  onAssistantToolsSelect\n}) => {\n  const { tools } = useContext(ChatbotUIContext)\n\n  const inputRef = useRef<HTMLInputElement>(null)\n  const triggerRef = useRef<HTMLButtonElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n  const [search, setSearch] = useState(\"\")\n\n  useEffect(() => {\n    if (isOpen) {\n      setTimeout(() => {\n        inputRef.current?.focus()\n      }, 100) // FIX: hacky\n    }\n  }, [isOpen])\n\n  const handleToolSelect = (tool: Tables<\"tools\">) => {\n    onAssistantToolsSelect(tool)\n  }\n\n  if (!tools) return null\n\n  return (\n    <DropdownMenu\n      open={isOpen}\n      onOpenChange={isOpen => {\n        setIsOpen(isOpen)\n        setSearch(\"\")\n      }}\n    >\n      <DropdownMenuTrigger\n        className=\"bg-background w-full justify-start border-2 px-3 py-5\"\n        asChild\n      >\n        <Button\n          ref={triggerRef}\n          className=\"flex items-center justify-between\"\n          variant=\"ghost\"\n        >\n          <div className=\"flex items-center\">\n            <div className=\"ml-2 flex items-center\">\n              {selectedAssistantTools.length} tools selected\n            </div>\n          </div>\n\n          <IconChevronDown />\n        </Button>\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent\n        style={{ width: triggerRef.current?.offsetWidth }}\n        className=\"space-y-2 overflow-auto p-2\"\n        align=\"start\"\n      >\n        <Input\n          ref={inputRef}\n          placeholder=\"Search tools...\"\n          value={search}\n          onChange={e => setSearch(e.target.value)}\n          onKeyDown={e => e.stopPropagation()}\n        />\n\n        {selectedAssistantTools\n          .filter(item =>\n            item.name.toLowerCase().includes(search.toLowerCase())\n          )\n          .map(tool => (\n            <AssistantToolItem\n              key={tool.id}\n              tool={tool}\n              selected={selectedAssistantTools.some(\n                selectedAssistantRetrieval =>\n                  selectedAssistantRetrieval.id === tool.id\n              )}\n              onSelect={handleToolSelect}\n            />\n          ))}\n\n        {tools\n          .filter(\n            tool =>\n              !selectedAssistantTools.some(\n                selectedAssistantRetrieval =>\n                  selectedAssistantRetrieval.id === tool.id\n              ) && tool.name.toLowerCase().includes(search.toLowerCase())\n          )\n          .map(tool => (\n            <AssistantToolItem\n              key={tool.id}\n              tool={tool}\n              selected={selectedAssistantTools.some(\n                selectedAssistantRetrieval =>\n                  selectedAssistantRetrieval.id === tool.id\n              )}\n              onSelect={handleToolSelect}\n            />\n          ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\ninterface AssistantToolItemProps {\n  tool: Tables<\"tools\">\n  selected: boolean\n  onSelect: (tool: Tables<\"tools\">) => void\n}\n\nconst AssistantToolItem: FC<AssistantToolItemProps> = ({\n  tool,\n  selected,\n  onSelect\n}) => {\n  const handleSelect = () => {\n    onSelect(tool)\n  }\n\n  return (\n    <div\n      className=\"flex cursor-pointer items-center justify-between py-0.5 hover:opacity-50\"\n      onClick={handleSelect}\n    >\n      <div className=\"flex grow items-center truncate\">\n        <div className=\"mr-2 min-w-[24px]\">\n          <IconBolt size={24} />\n        </div>\n\n        <div className=\"truncate\">{tool.name}</div>\n      </div>\n\n      {selected && (\n        <IconCircleCheckFilled size={20} className=\"min-w-[30px] flex-none\" />\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/assistants/create-assistant.tsx",
    "content": "import { SidebarCreateItem } from \"@/components/sidebar/items/all/sidebar-create-item\"\nimport { ChatSettingsForm } from \"@/components/ui/chat-settings-form\"\nimport ImagePicker from \"@/components/ui/image-picker\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { ASSISTANT_DESCRIPTION_MAX, ASSISTANT_NAME_MAX } from \"@/db/limits\"\nimport { Tables, TablesInsert } from \"@/supabase/types\"\nimport { FC, useContext, useEffect, useState } from \"react\"\nimport { AssistantRetrievalSelect } from \"./assistant-retrieval-select\"\nimport { AssistantToolSelect } from \"./assistant-tool-select\"\n\ninterface CreateAssistantProps {\n  isOpen: boolean\n  onOpenChange: (isOpen: boolean) => void\n}\n\nexport const CreateAssistant: FC<CreateAssistantProps> = ({\n  isOpen,\n  onOpenChange\n}) => {\n  const { profile, selectedWorkspace } = useContext(ChatbotUIContext)\n\n  const [name, setName] = useState(\"\")\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(\"\")\n  const [assistantChatSettings, setAssistantChatSettings] = useState({\n    model: selectedWorkspace?.default_model,\n    prompt: selectedWorkspace?.default_prompt,\n    temperature: selectedWorkspace?.default_temperature,\n    contextLength: selectedWorkspace?.default_context_length,\n    includeProfileContext: false,\n    includeWorkspaceInstructions: false,\n    embeddingsProvider: selectedWorkspace?.embeddings_provider\n  })\n  const [selectedImage, setSelectedImage] = useState<File | null>(null)\n  const [imageLink, setImageLink] = useState(\"\")\n  const [selectedAssistantRetrievalItems, setSelectedAssistantRetrievalItems] =\n    useState<Tables<\"files\">[] | Tables<\"collections\">[]>([])\n  const [selectedAssistantToolItems, setSelectedAssistantToolItems] = useState<\n    Tables<\"tools\">[]\n  >([])\n\n  useEffect(() => {\n    setAssistantChatSettings(prevSettings => {\n      const previousPrompt = prevSettings.prompt || \"\"\n      const previousPromptParts = previousPrompt.split(\". \")\n\n      previousPromptParts[0] = name ? `You are ${name}` : \"\"\n\n      return {\n        ...prevSettings,\n        prompt: previousPromptParts.join(\". \")\n      }\n    })\n  }, [name])\n\n  const handleRetrievalItemSelect = (\n    item: Tables<\"files\"> | Tables<\"collections\">\n  ) => {\n    setSelectedAssistantRetrievalItems(prevState => {\n      const isItemAlreadySelected = prevState.find(\n        selectedItem => selectedItem.id === item.id\n      )\n\n      if (isItemAlreadySelected) {\n        return prevState.filter(selectedItem => selectedItem.id !== item.id)\n      } else {\n        return [...prevState, item]\n      }\n    })\n  }\n\n  const handleToolSelect = (item: Tables<\"tools\">) => {\n    setSelectedAssistantToolItems(prevState => {\n      const isItemAlreadySelected = prevState.find(\n        selectedItem => selectedItem.id === item.id\n      )\n\n      if (isItemAlreadySelected) {\n        return prevState.filter(selectedItem => selectedItem.id !== item.id)\n      } else {\n        return [...prevState, item]\n      }\n    })\n  }\n\n  const checkIfModelIsToolCompatible = () => {\n    if (!assistantChatSettings.model) return false\n\n    const compatibleModels = [\n      \"gpt-4-turbo-preview\",\n      \"gpt-4-vision-preview\",\n      \"gpt-3.5-turbo-1106\",\n      \"gpt-4\"\n    ]\n    const isModelCompatible = compatibleModels.includes(\n      assistantChatSettings.model\n    )\n\n    return isModelCompatible\n  }\n\n  if (!profile) return null\n  if (!selectedWorkspace) return null\n\n  return (\n    <SidebarCreateItem\n      contentType=\"assistants\"\n      createState={\n        {\n          image: selectedImage,\n          user_id: profile.user_id,\n          name,\n          description,\n          include_profile_context: assistantChatSettings.includeProfileContext,\n          include_workspace_instructions:\n            assistantChatSettings.includeWorkspaceInstructions,\n          context_length: assistantChatSettings.contextLength,\n          model: assistantChatSettings.model,\n          image_path: \"\",\n          prompt: assistantChatSettings.prompt,\n          temperature: assistantChatSettings.temperature,\n          embeddings_provider: assistantChatSettings.embeddingsProvider,\n          files: selectedAssistantRetrievalItems.filter(item =>\n            item.hasOwnProperty(\"type\")\n          ) as Tables<\"files\">[],\n          collections: selectedAssistantRetrievalItems.filter(\n            item => !item.hasOwnProperty(\"type\")\n          ) as Tables<\"collections\">[],\n          tools: selectedAssistantToolItems\n        } as TablesInsert<\"assistants\">\n      }\n      isOpen={isOpen}\n      isTyping={isTyping}\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Assistant name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={ASSISTANT_NAME_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1 pt-2\">\n            <Label>Description</Label>\n\n            <Input\n              placeholder=\"Assistant description...\"\n              value={description}\n              onChange={e => setDescription(e.target.value)}\n              maxLength={ASSISTANT_DESCRIPTION_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1 pt-2\">\n            <Label className=\"flex space-x-1\">\n              <div>Image</div>\n\n              <div className=\"text-xs\">(optional)</div>\n            </Label>\n\n            <ImagePicker\n              src={imageLink}\n              image={selectedImage}\n              onSrcChange={setImageLink}\n              onImageChange={setSelectedImage}\n              width={100}\n              height={100}\n            />\n          </div>\n\n          <ChatSettingsForm\n            chatSettings={assistantChatSettings as any}\n            onChangeChatSettings={setAssistantChatSettings}\n            useAdvancedDropdown={true}\n          />\n\n          <div className=\"space-y-1 pt-2\">\n            <Label>Files & Collections</Label>\n\n            <AssistantRetrievalSelect\n              selectedAssistantRetrievalItems={selectedAssistantRetrievalItems}\n              onAssistantRetrievalItemsSelect={handleRetrievalItemSelect}\n            />\n          </div>\n\n          {checkIfModelIsToolCompatible() ? (\n            <div className=\"space-y-1\">\n              <Label>Tools</Label>\n\n              <AssistantToolSelect\n                selectedAssistantTools={selectedAssistantToolItems}\n                onAssistantToolsSelect={handleToolSelect}\n              />\n            </div>\n          ) : (\n            <div className=\"pt-1 font-semibold\">\n              Model is not compatible with tools.\n            </div>\n          )}\n        </>\n      )}\n      onOpenChange={onOpenChange}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/chat/chat-item.tsx",
    "content": "import { ModelIcon } from \"@/components/models/model-icon\"\nimport { WithTooltip } from \"@/components/ui/with-tooltip\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { LLM_LIST } from \"@/lib/models/llm/llm-list\"\nimport { cn } from \"@/lib/utils\"\nimport { Tables } from \"@/supabase/types\"\nimport { LLM } from \"@/types\"\nimport { IconRobotFace } from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { useParams, useRouter } from \"next/navigation\"\nimport { FC, useContext, useRef } from \"react\"\nimport { DeleteChat } from \"./delete-chat\"\nimport { UpdateChat } from \"./update-chat\"\n\ninterface ChatItemProps {\n  chat: Tables<\"chats\">\n}\n\nexport const ChatItem: FC<ChatItemProps> = ({ chat }) => {\n  const {\n    selectedWorkspace,\n    selectedChat,\n    availableLocalModels,\n    assistantImages,\n    availableOpenRouterModels\n  } = useContext(ChatbotUIContext)\n\n  const router = useRouter()\n  const params = useParams()\n  const isActive = params.chatid === chat.id || selectedChat?.id === chat.id\n\n  const itemRef = useRef<HTMLDivElement>(null)\n\n  const handleClick = () => {\n    if (!selectedWorkspace) return\n    return router.push(`/${selectedWorkspace.id}/chat/${chat.id}`)\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\") {\n      e.stopPropagation()\n      itemRef.current?.click()\n    }\n  }\n\n  const MODEL_DATA = [\n    ...LLM_LIST,\n    ...availableLocalModels,\n    ...availableOpenRouterModels\n  ].find(llm => llm.modelId === chat.model) as LLM\n\n  const assistantImage = assistantImages.find(\n    image => image.assistantId === chat.assistant_id\n  )?.base64\n\n  return (\n    <div\n      ref={itemRef}\n      className={cn(\n        \"hover:bg-accent focus:bg-accent group flex w-full cursor-pointer items-center rounded p-2 hover:opacity-50 focus:outline-none\",\n        isActive && \"bg-accent\"\n      )}\n      tabIndex={0}\n      onKeyDown={handleKeyDown}\n      onClick={handleClick}\n    >\n      {chat.assistant_id ? (\n        assistantImage ? (\n          <Image\n            style={{ width: \"30px\", height: \"30px\" }}\n            className=\"rounded\"\n            src={assistantImage}\n            alt=\"Assistant image\"\n            width={30}\n            height={30}\n          />\n        ) : (\n          <IconRobotFace\n            className=\"bg-primary text-secondary border-primary rounded border-DEFAULT p-1\"\n            size={30}\n          />\n        )\n      ) : (\n        <WithTooltip\n          delayDuration={200}\n          display={<div>{MODEL_DATA?.modelName}</div>}\n          trigger={\n            <ModelIcon provider={MODEL_DATA?.provider} height={30} width={30} />\n          }\n        />\n      )}\n\n      <div className=\"ml-3 flex-1 truncate text-sm font-semibold\">\n        {chat.name}\n      </div>\n\n      <div\n        onClick={e => {\n          e.stopPropagation()\n          e.preventDefault()\n        }}\n        className={`ml-2 flex space-x-2 ${!isActive && \"w-11 opacity-0 group-hover:opacity-100\"}`}\n      >\n        <UpdateChat chat={chat} />\n\n        <DeleteChat chat={chat} />\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/chat/delete-chat.tsx",
    "content": "import { useChatHandler } from \"@/components/chat/chat-hooks/use-chat-handler\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from \"@/components/ui/dialog\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { deleteChat } from \"@/db/chats\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconTrash } from \"@tabler/icons-react\"\nimport { FC, useContext, useRef, useState } from \"react\"\n\ninterface DeleteChatProps {\n  chat: Tables<\"chats\">\n}\n\nexport const DeleteChat: FC<DeleteChatProps> = ({ chat }) => {\n  useHotkey(\"Backspace\", () => setShowChatDialog(true))\n\n  const { setChats } = useContext(ChatbotUIContext)\n  const { handleNewChat } = useChatHandler()\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [showChatDialog, setShowChatDialog] = useState(false)\n\n  const handleDeleteChat = async () => {\n    await deleteChat(chat.id)\n\n    setChats(prevState => prevState.filter(c => c.id !== chat.id))\n\n    setShowChatDialog(false)\n\n    handleNewChat()\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\") {\n      buttonRef.current?.click()\n    }\n  }\n\n  return (\n    <Dialog open={showChatDialog} onOpenChange={setShowChatDialog}>\n      <DialogTrigger asChild>\n        <IconTrash className=\"hover:opacity-50\" size={18} />\n      </DialogTrigger>\n\n      <DialogContent onKeyDown={handleKeyDown}>\n        <DialogHeader>\n          <DialogTitle>Delete {chat.name}</DialogTitle>\n\n          <DialogDescription>\n            Are you sure you want to delete this chat?\n          </DialogDescription>\n        </DialogHeader>\n\n        <DialogFooter>\n          <Button variant=\"ghost\" onClick={() => setShowChatDialog(false)}>\n            Cancel\n          </Button>\n\n          <Button\n            ref={buttonRef}\n            variant=\"destructive\"\n            onClick={handleDeleteChat}\n          >\n            Delete\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/chat/update-chat.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from \"@/components/ui/dialog\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { updateChat } from \"@/db/chats\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconEdit } from \"@tabler/icons-react\"\nimport { FC, useContext, useRef, useState } from \"react\"\n\ninterface UpdateChatProps {\n  chat: Tables<\"chats\">\n}\n\nexport const UpdateChat: FC<UpdateChatProps> = ({ chat }) => {\n  const { setChats } = useContext(ChatbotUIContext)\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [showChatDialog, setShowChatDialog] = useState(false)\n  const [name, setName] = useState(chat.name)\n\n  const handleUpdateChat = async (e: React.MouseEvent<HTMLButtonElement>) => {\n    const updatedChat = await updateChat(chat.id, {\n      name\n    })\n    setChats(prevState =>\n      prevState.map(c => (c.id === chat.id ? updatedChat : c))\n    )\n\n    setShowChatDialog(false)\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\") {\n      buttonRef.current?.click()\n    }\n  }\n\n  return (\n    <Dialog open={showChatDialog} onOpenChange={setShowChatDialog}>\n      <DialogTrigger asChild>\n        <IconEdit className=\"hover:opacity-50\" size={18} />\n      </DialogTrigger>\n\n      <DialogContent onKeyDown={handleKeyDown}>\n        <DialogHeader>\n          <DialogTitle>Edit Chat</DialogTitle>\n        </DialogHeader>\n\n        <div className=\"space-y-1\">\n          <Label>Name</Label>\n\n          <Input value={name} onChange={e => setName(e.target.value)} />\n        </div>\n\n        <DialogFooter>\n          <Button variant=\"ghost\" onClick={() => setShowChatDialog(false)}>\n            Cancel\n          </Button>\n\n          <Button ref={buttonRef} onClick={handleUpdateChat}>\n            Save\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/collections/collection-file-select.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuTrigger\n} from \"@/components/ui/dropdown-menu\"\nimport { FileIcon } from \"@/components/ui/file-icon\"\nimport { Input } from \"@/components/ui/input\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { CollectionFile } from \"@/types\"\nimport { IconChevronDown, IconCircleCheckFilled } from \"@tabler/icons-react\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\n\ninterface CollectionFileSelectProps {\n  selectedCollectionFiles: CollectionFile[]\n  onCollectionFileSelect: (file: CollectionFile) => void\n}\n\nexport const CollectionFileSelect: FC<CollectionFileSelectProps> = ({\n  selectedCollectionFiles,\n  onCollectionFileSelect\n}) => {\n  const { files } = useContext(ChatbotUIContext)\n\n  const inputRef = useRef<HTMLInputElement>(null)\n  const triggerRef = useRef<HTMLButtonElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n  const [search, setSearch] = useState(\"\")\n\n  useEffect(() => {\n    if (isOpen) {\n      setTimeout(() => {\n        inputRef.current?.focus()\n      }, 100) // FIX: hacky\n    }\n  }, [isOpen])\n\n  const handleFileSelect = (file: CollectionFile) => {\n    onCollectionFileSelect(file)\n  }\n\n  if (!files) return null\n\n  return (\n    <DropdownMenu\n      open={isOpen}\n      onOpenChange={isOpen => {\n        setIsOpen(isOpen)\n        setSearch(\"\")\n      }}\n    >\n      <DropdownMenuTrigger\n        className=\"bg-background w-full justify-start border-2 px-3 py-5\"\n        asChild\n      >\n        <Button\n          ref={triggerRef}\n          className=\"flex items-center justify-between\"\n          variant=\"ghost\"\n        >\n          <div className=\"flex items-center\">\n            <div className=\"ml-2 flex items-center\">\n              {selectedCollectionFiles.length} files selected\n            </div>\n          </div>\n\n          <IconChevronDown />\n        </Button>\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent\n        style={{ width: triggerRef.current?.offsetWidth }}\n        className=\"space-y-2 overflow-auto p-2\"\n        align=\"start\"\n      >\n        <Input\n          ref={inputRef}\n          placeholder=\"Search files...\"\n          value={search}\n          onChange={e => setSearch(e.target.value)}\n          onKeyDown={e => e.stopPropagation()}\n        />\n\n        {selectedCollectionFiles\n          .filter(file =>\n            file.name.toLowerCase().includes(search.toLowerCase())\n          )\n          .map(file => (\n            <CollectionFileItem\n              key={file.id}\n              file={file}\n              selected={selectedCollectionFiles.some(\n                selectedCollectionFile => selectedCollectionFile.id === file.id\n              )}\n              onSelect={handleFileSelect}\n            />\n          ))}\n\n        {files\n          .filter(\n            file =>\n              !selectedCollectionFiles.some(\n                selectedCollectionFile => selectedCollectionFile.id === file.id\n              ) && file.name.toLowerCase().includes(search.toLowerCase())\n          )\n          .map(file => (\n            <CollectionFileItem\n              key={file.id}\n              file={file}\n              selected={selectedCollectionFiles.some(\n                selectedCollectionFile => selectedCollectionFile.id === file.id\n              )}\n              onSelect={handleFileSelect}\n            />\n          ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\ninterface CollectionFileItemProps {\n  file: CollectionFile\n  selected: boolean\n  onSelect: (file: CollectionFile) => void\n}\n\nconst CollectionFileItem: FC<CollectionFileItemProps> = ({\n  file,\n  selected,\n  onSelect\n}) => {\n  const handleSelect = () => {\n    onSelect(file)\n  }\n\n  return (\n    <div\n      className=\"flex cursor-pointer items-center justify-between py-0.5 hover:opacity-50\"\n      onClick={handleSelect}\n    >\n      <div className=\"flex grow items-center truncate\">\n        <div className=\"mr-2 min-w-[24px]\">\n          <FileIcon type={file.type} size={24} />\n        </div>\n\n        <div className=\"truncate\">{file.name}</div>\n      </div>\n\n      {selected && (\n        <IconCircleCheckFilled size={20} className=\"min-w-[30px] flex-none\" />\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/collections/collection-item.tsx",
    "content": "import { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { COLLECTION_DESCRIPTION_MAX, COLLECTION_NAME_MAX } from \"@/db/limits\"\nimport { Tables } from \"@/supabase/types\"\nimport { CollectionFile } from \"@/types\"\nimport { IconBooks } from \"@tabler/icons-react\"\nimport { FC, useState } from \"react\"\nimport { SidebarItem } from \"../all/sidebar-display-item\"\nimport { CollectionFileSelect } from \"./collection-file-select\"\n\ninterface CollectionItemProps {\n  collection: Tables<\"collections\">\n}\n\nexport const CollectionItem: FC<CollectionItemProps> = ({ collection }) => {\n  const [name, setName] = useState(collection.name)\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(collection.description)\n\n  const handleFileSelect = (\n    file: CollectionFile,\n    setSelectedCollectionFiles: React.Dispatch<\n      React.SetStateAction<CollectionFile[]>\n    >\n  ) => {\n    setSelectedCollectionFiles(prevState => {\n      const isFileAlreadySelected = prevState.find(\n        selectedFile => selectedFile.id === file.id\n      )\n\n      if (isFileAlreadySelected) {\n        return prevState.filter(selectedFile => selectedFile.id !== file.id)\n      } else {\n        return [...prevState, file]\n      }\n    })\n  }\n\n  return (\n    <SidebarItem\n      item={collection}\n      isTyping={isTyping}\n      contentType=\"collections\"\n      icon={<IconBooks size={30} />}\n      updateState={{\n        name,\n        description\n      }}\n      renderInputs={(renderState: {\n        startingCollectionFiles: CollectionFile[]\n        setStartingCollectionFiles: React.Dispatch<\n          React.SetStateAction<CollectionFile[]>\n        >\n        selectedCollectionFiles: CollectionFile[]\n        setSelectedCollectionFiles: React.Dispatch<\n          React.SetStateAction<CollectionFile[]>\n        >\n      }) => {\n        return (\n          <>\n            <div className=\"space-y-1\">\n              <Label>Files</Label>\n\n              <CollectionFileSelect\n                selectedCollectionFiles={\n                  renderState.selectedCollectionFiles.length === 0\n                    ? renderState.startingCollectionFiles\n                    : [\n                        ...renderState.startingCollectionFiles.filter(\n                          startingFile =>\n                            !renderState.selectedCollectionFiles.some(\n                              selectedFile =>\n                                selectedFile.id === startingFile.id\n                            )\n                        ),\n                        ...renderState.selectedCollectionFiles.filter(\n                          selectedFile =>\n                            !renderState.startingCollectionFiles.some(\n                              startingFile =>\n                                startingFile.id === selectedFile.id\n                            )\n                        )\n                      ]\n                }\n                onCollectionFileSelect={file =>\n                  handleFileSelect(file, renderState.setSelectedCollectionFiles)\n                }\n              />\n            </div>\n\n            <div className=\"space-y-1\">\n              <Label>Name</Label>\n\n              <Input\n                placeholder=\"Collection name...\"\n                value={name}\n                onChange={e => setName(e.target.value)}\n                maxLength={COLLECTION_NAME_MAX}\n              />\n            </div>\n\n            <div className=\"space-y-1\">\n              <Label>Description</Label>\n\n              <Input\n                placeholder=\"Collection description...\"\n                value={description}\n                onChange={e => setDescription(e.target.value)}\n                maxLength={COLLECTION_DESCRIPTION_MAX}\n              />\n            </div>\n          </>\n        )\n      }}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/collections/create-collection.tsx",
    "content": "import { SidebarCreateItem } from \"@/components/sidebar/items/all/sidebar-create-item\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { COLLECTION_DESCRIPTION_MAX, COLLECTION_NAME_MAX } from \"@/db/limits\"\nimport { TablesInsert } from \"@/supabase/types\"\nimport { CollectionFile } from \"@/types\"\nimport { FC, useContext, useState } from \"react\"\nimport { CollectionFileSelect } from \"./collection-file-select\"\n\ninterface CreateCollectionProps {\n  isOpen: boolean\n  onOpenChange: (isOpen: boolean) => void\n}\n\nexport const CreateCollection: FC<CreateCollectionProps> = ({\n  isOpen,\n  onOpenChange\n}) => {\n  const { profile, selectedWorkspace } = useContext(ChatbotUIContext)\n\n  const [name, setName] = useState(\"\")\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(\"\")\n  const [selectedCollectionFiles, setSelectedCollectionFiles] = useState<\n    CollectionFile[]\n  >([])\n\n  const handleFileSelect = (file: CollectionFile) => {\n    setSelectedCollectionFiles(prevState => {\n      const isFileAlreadySelected = prevState.find(\n        selectedFile => selectedFile.id === file.id\n      )\n\n      if (isFileAlreadySelected) {\n        return prevState.filter(selectedFile => selectedFile.id !== file.id)\n      } else {\n        return [...prevState, file]\n      }\n    })\n  }\n\n  if (!profile) return null\n  if (!selectedWorkspace) return null\n\n  return (\n    <SidebarCreateItem\n      contentType=\"collections\"\n      createState={\n        {\n          collectionFiles: selectedCollectionFiles.map(file => ({\n            user_id: profile.user_id,\n            collection_id: \"\",\n            file_id: file.id\n          })),\n          user_id: profile.user_id,\n          name,\n          description\n        } as TablesInsert<\"collections\">\n      }\n      isOpen={isOpen}\n      isTyping={isTyping}\n      onOpenChange={onOpenChange}\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Files</Label>\n\n            <CollectionFileSelect\n              selectedCollectionFiles={selectedCollectionFiles}\n              onCollectionFileSelect={handleFileSelect}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Collection name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={COLLECTION_NAME_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Description</Label>\n\n            <Input\n              placeholder=\"Collection description...\"\n              value={description}\n              onChange={e => setDescription(e.target.value)}\n              maxLength={COLLECTION_DESCRIPTION_MAX}\n            />\n          </div>\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/files/create-file.tsx",
    "content": "import { ACCEPTED_FILE_TYPES } from \"@/components/chat/chat-hooks/use-select-file-handler\"\nimport { SidebarCreateItem } from \"@/components/sidebar/items/all/sidebar-create-item\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { FILE_DESCRIPTION_MAX, FILE_NAME_MAX } from \"@/db/limits\"\nimport { TablesInsert } from \"@/supabase/types\"\nimport { FC, useContext, useState } from \"react\"\n\ninterface CreateFileProps {\n  isOpen: boolean\n  onOpenChange: (isOpen: boolean) => void\n}\n\nexport const CreateFile: FC<CreateFileProps> = ({ isOpen, onOpenChange }) => {\n  const { profile, selectedWorkspace } = useContext(ChatbotUIContext)\n\n  const [name, setName] = useState(\"\")\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(\"\")\n  const [selectedFile, setSelectedFile] = useState<File | null>(null)\n\n  const handleSelectedFile = async (e: React.ChangeEvent<HTMLInputElement>) => {\n    if (!e.target.files) return\n\n    const file = e.target.files[0]\n\n    if (!file) return\n\n    setSelectedFile(file)\n    const fileNameWithoutExtension = file.name.split(\".\").slice(0, -1).join(\".\")\n    setName(fileNameWithoutExtension)\n  }\n\n  if (!profile) return null\n  if (!selectedWorkspace) return null\n\n  return (\n    <SidebarCreateItem\n      contentType=\"files\"\n      createState={\n        {\n          file: selectedFile,\n          user_id: profile.user_id,\n          name,\n          description,\n          file_path: \"\",\n          size: selectedFile?.size || 0,\n          tokens: 0,\n          type: selectedFile?.type || 0\n        } as TablesInsert<\"files\">\n      }\n      isOpen={isOpen}\n      isTyping={isTyping}\n      onOpenChange={onOpenChange}\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>File</Label>\n\n            <Input\n              type=\"file\"\n              onChange={handleSelectedFile}\n              accept={ACCEPTED_FILE_TYPES}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"File name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={FILE_NAME_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Description</Label>\n\n            <Input\n              placeholder=\"File description...\"\n              value={name}\n              onChange={e => setDescription(e.target.value)}\n              maxLength={FILE_DESCRIPTION_MAX}\n            />\n          </div>\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/files/file-item.tsx",
    "content": "import { FileIcon } from \"@/components/ui/file-icon\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { FILE_DESCRIPTION_MAX, FILE_NAME_MAX } from \"@/db/limits\"\nimport { getFileFromStorage } from \"@/db/storage/files\"\nimport { Tables } from \"@/supabase/types\"\nimport { FC, useState } from \"react\"\nimport { SidebarItem } from \"../all/sidebar-display-item\"\n\ninterface FileItemProps {\n  file: Tables<\"files\">\n}\n\nexport const FileItem: FC<FileItemProps> = ({ file }) => {\n  const [name, setName] = useState(file.name)\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(file.description)\n\n  const getLinkAndView = async () => {\n    const link = await getFileFromStorage(file.file_path)\n    window.open(link, \"_blank\")\n  }\n\n  return (\n    <SidebarItem\n      item={file}\n      isTyping={isTyping}\n      contentType=\"files\"\n      icon={<FileIcon type={file.type} size={30} />}\n      updateState={{ name, description }}\n      renderInputs={() => (\n        <>\n          <div\n            className=\"cursor-pointer underline hover:opacity-50\"\n            onClick={getLinkAndView}\n          >\n            View {file.name}\n          </div>\n\n          <div className=\"flex flex-col justify-between\">\n            <div>{file.type}</div>\n\n            <div>{formatFileSize(file.size)}</div>\n\n            <div>{file.tokens.toLocaleString()} tokens</div>\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"File name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={FILE_NAME_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Description</Label>\n\n            <Input\n              placeholder=\"File description...\"\n              value={description}\n              onChange={e => setDescription(e.target.value)}\n              maxLength={FILE_DESCRIPTION_MAX}\n            />\n          </div>\n        </>\n      )}\n    />\n  )\n}\n\nexport const formatFileSize = (sizeInBytes: number): string => {\n  let size = sizeInBytes\n  let unit = \"bytes\"\n\n  if (size >= 1024) {\n    size /= 1024\n    unit = \"KB\"\n  }\n\n  if (size >= 1024) {\n    size /= 1024\n    unit = \"MB\"\n  }\n\n  if (size >= 1024) {\n    size /= 1024\n    unit = \"GB\"\n  }\n\n  return `${size.toFixed(2)} ${unit}`\n}\n"
  },
  {
    "path": "components/sidebar/items/folders/delete-folder.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from \"@/components/ui/dialog\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { deleteFolder } from \"@/db/folders\"\nimport { supabase } from \"@/lib/supabase/browser-client\"\nimport { Tables } from \"@/supabase/types\"\nimport { ContentType } from \"@/types\"\nimport { IconTrash } from \"@tabler/icons-react\"\nimport { FC, useContext, useRef, useState } from \"react\"\nimport { toast } from \"sonner\"\n\ninterface DeleteFolderProps {\n  folder: Tables<\"folders\">\n  contentType: ContentType\n}\n\nexport const DeleteFolder: FC<DeleteFolderProps> = ({\n  folder,\n  contentType\n}) => {\n  const {\n    setChats,\n    setFolders,\n    setPresets,\n    setPrompts,\n    setFiles,\n    setCollections,\n    setAssistants,\n    setTools,\n    setModels\n  } = useContext(ChatbotUIContext)\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [showFolderDialog, setShowFolderDialog] = useState(false)\n\n  const stateUpdateFunctions = {\n    chats: setChats,\n    presets: setPresets,\n    prompts: setPrompts,\n    files: setFiles,\n    collections: setCollections,\n    assistants: setAssistants,\n    tools: setTools,\n    models: setModels\n  }\n\n  const handleDeleteFolderOnly = async () => {\n    await deleteFolder(folder.id)\n\n    setFolders(prevState => prevState.filter(c => c.id !== folder.id))\n\n    setShowFolderDialog(false)\n\n    const setStateFunction = stateUpdateFunctions[contentType]\n\n    if (!setStateFunction) return\n\n    setStateFunction((prevItems: any) =>\n      prevItems.map((item: any) => {\n        if (item.folder_id === folder.id) {\n          return {\n            ...item,\n            folder_id: null\n          }\n        }\n\n        return item\n      })\n    )\n  }\n\n  const handleDeleteFolderAndItems = async () => {\n    const setStateFunction = stateUpdateFunctions[contentType]\n\n    if (!setStateFunction) return\n\n    const { error } = await supabase\n      .from(contentType)\n      .delete()\n      .eq(\"folder_id\", folder.id)\n\n    if (error) {\n      toast.error(error.message)\n    }\n\n    setStateFunction((prevItems: any) =>\n      prevItems.filter((item: any) => item.folder_id !== folder.id)\n    )\n\n    handleDeleteFolderOnly()\n  }\n\n  return (\n    <Dialog open={showFolderDialog} onOpenChange={setShowFolderDialog}>\n      <DialogTrigger asChild>\n        <IconTrash className=\"hover:opacity-50\" size={18} />\n      </DialogTrigger>\n\n      <DialogContent className=\"min-w-[550px]\">\n        <DialogHeader>\n          <DialogTitle>Delete {folder.name}</DialogTitle>\n\n          <DialogDescription>\n            Are you sure you want to delete this folder?\n          </DialogDescription>\n        </DialogHeader>\n\n        <DialogFooter>\n          <Button variant=\"ghost\" onClick={() => setShowFolderDialog(false)}>\n            Cancel\n          </Button>\n\n          <Button\n            ref={buttonRef}\n            variant=\"destructive\"\n            onClick={handleDeleteFolderAndItems}\n          >\n            Delete Folder & Included Items\n          </Button>\n\n          <Button\n            ref={buttonRef}\n            variant=\"destructive\"\n            onClick={handleDeleteFolderOnly}\n          >\n            Delete Folder Only\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/folders/folder-item.tsx",
    "content": "import { cn } from \"@/lib/utils\"\nimport { Tables } from \"@/supabase/types\"\nimport { ContentType } from \"@/types\"\nimport { IconChevronDown, IconChevronRight } from \"@tabler/icons-react\"\nimport { FC, useRef, useState } from \"react\"\nimport { DeleteFolder } from \"./delete-folder\"\nimport { UpdateFolder } from \"./update-folder\"\n\ninterface FolderProps {\n  folder: Tables<\"folders\">\n  contentType: ContentType\n  children: React.ReactNode\n  onUpdateFolder: (itemId: string, folderId: string | null) => void\n}\n\nexport const Folder: FC<FolderProps> = ({\n  folder,\n  contentType,\n  children,\n  onUpdateFolder\n}) => {\n  const itemRef = useRef<HTMLDivElement>(null)\n\n  const [isDragOver, setIsDragOver] = useState(false)\n  const [isExpanded, setIsExpanded] = useState(false)\n  const [isHovering, setIsHovering] = useState(false)\n\n  const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {\n    e.preventDefault()\n    setIsDragOver(true)\n  }\n\n  const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {\n    e.preventDefault()\n    setIsDragOver(false)\n  }\n\n  const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {\n    e.preventDefault()\n    setIsDragOver(true)\n  }\n\n  const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {\n    e.preventDefault()\n\n    setIsDragOver(false)\n    const itemId = e.dataTransfer.getData(\"text/plain\")\n    onUpdateFolder(itemId, folder.id)\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\") {\n      e.stopPropagation()\n      itemRef.current?.click()\n    }\n  }\n\n  const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {\n    setIsExpanded(!isExpanded)\n  }\n\n  return (\n    <div\n      ref={itemRef}\n      id=\"folder\"\n      className={cn(\"rounded focus:outline-none\", isDragOver && \"bg-accent\")}\n      onDragEnter={handleDragEnter}\n      onDragLeave={handleDragLeave}\n      onDragOver={handleDragOver}\n      onDrop={handleDrop}\n      onKeyDown={handleKeyDown}\n      onMouseEnter={() => setIsHovering(true)}\n      onMouseLeave={() => setIsHovering(false)}\n    >\n      <div\n        tabIndex={0}\n        className={cn(\n          \"hover:bg-accent focus:bg-accent flex w-full cursor-pointer items-center justify-between rounded p-2 hover:opacity-50 focus:outline-none\"\n        )}\n        onClick={handleClick}\n      >\n        <div className=\"flex w-full items-center justify-between\">\n          <div className=\"flex items-center space-x-2\">\n            {isExpanded ? (\n              <IconChevronDown stroke={3} />\n            ) : (\n              <IconChevronRight stroke={3} />\n            )}\n\n            <div>{folder.name}</div>\n          </div>\n\n          {isHovering && (\n            <div\n              onClick={e => {\n                e.stopPropagation()\n                e.preventDefault()\n              }}\n              className=\"ml-2 flex space-x-2\"\n            >\n              <UpdateFolder folder={folder} />\n\n              <DeleteFolder folder={folder} contentType={contentType} />\n            </div>\n          )}\n        </div>\n      </div>\n\n      {isExpanded && (\n        <div className=\"ml-5 mt-2 space-y-2 border-l-2 pl-4\">{children}</div>\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/folders/update-folder.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from \"@/components/ui/dialog\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { updateFolder } from \"@/db/folders\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconEdit } from \"@tabler/icons-react\"\nimport { FC, useContext, useRef, useState } from \"react\"\n\ninterface UpdateFolderProps {\n  folder: Tables<\"folders\">\n}\n\nexport const UpdateFolder: FC<UpdateFolderProps> = ({ folder }) => {\n  const { setFolders } = useContext(ChatbotUIContext)\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [showFolderDialog, setShowFolderDialog] = useState(false)\n  const [name, setName] = useState(folder.name)\n\n  const handleUpdateFolder = async (e: React.MouseEvent<HTMLButtonElement>) => {\n    const updatedFolder = await updateFolder(folder.id, {\n      name\n    })\n    setFolders(prevState =>\n      prevState.map(c => (c.id === folder.id ? updatedFolder : c))\n    )\n\n    setShowFolderDialog(false)\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\") {\n      buttonRef.current?.click()\n    }\n  }\n\n  return (\n    <Dialog open={showFolderDialog} onOpenChange={setShowFolderDialog}>\n      <DialogTrigger asChild>\n        <IconEdit className=\"hover:opacity-50\" size={18} />\n      </DialogTrigger>\n\n      <DialogContent onKeyDown={handleKeyDown}>\n        <DialogHeader>\n          <DialogTitle>Edit Folder</DialogTitle>\n        </DialogHeader>\n\n        <div className=\"space-y-1\">\n          <Label>Name</Label>\n\n          <Input value={name} onChange={e => setName(e.target.value)} />\n        </div>\n\n        <DialogFooter>\n          <Button variant=\"ghost\" onClick={() => setShowFolderDialog(false)}>\n            Cancel\n          </Button>\n\n          <Button ref={buttonRef} onClick={handleUpdateFolder}>\n            Save\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/models/create-model.tsx",
    "content": "import { SidebarCreateItem } from \"@/components/sidebar/items/all/sidebar-create-item\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { MODEL_NAME_MAX } from \"@/db/limits\"\nimport { TablesInsert } from \"@/supabase/types\"\nimport { FC, useContext, useState } from \"react\"\n\ninterface CreateModelProps {\n  isOpen: boolean\n  onOpenChange: (isOpen: boolean) => void\n}\n\nexport const CreateModel: FC<CreateModelProps> = ({ isOpen, onOpenChange }) => {\n  const { profile, selectedWorkspace } = useContext(ChatbotUIContext)\n\n  const [isTyping, setIsTyping] = useState(false)\n\n  const [apiKey, setApiKey] = useState(\"\")\n  const [baseUrl, setBaseUrl] = useState(\"\")\n  const [description, setDescription] = useState(\"\")\n  const [modelId, setModelId] = useState(\"\")\n  const [name, setName] = useState(\"\")\n  const [contextLength, setContextLength] = useState(4096)\n\n  if (!profile || !selectedWorkspace) return null\n\n  return (\n    <SidebarCreateItem\n      contentType=\"models\"\n      isOpen={isOpen}\n      isTyping={isTyping}\n      onOpenChange={onOpenChange}\n      createState={\n        {\n          user_id: profile.user_id,\n          api_key: apiKey,\n          base_url: baseUrl,\n          description,\n          context_length: contextLength,\n          model_id: modelId,\n          name\n        } as TablesInsert<\"models\">\n      }\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1.5 text-sm\">\n            <div>Create a custom model.</div>\n\n            <div>\n              Your API <span className=\"font-bold\">*must*</span> be compatible\n              with the OpenAI SDK.\n            </div>\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Model name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={MODEL_NAME_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Model ID</Label>\n\n            <Input\n              placeholder=\"Model ID...\"\n              value={modelId}\n              onChange={e => setModelId(e.target.value)}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Base URL</Label>\n\n            <Input\n              placeholder=\"Base URL...\"\n              value={baseUrl}\n              onChange={e => setBaseUrl(e.target.value)}\n            />\n\n            <div className=\"pt-1 text-xs italic\">\n              Your API must be compatible with the OpenAI SDK.\n            </div>\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>API Key</Label>\n\n            <Input\n              type=\"password\"\n              placeholder=\"API Key...\"\n              value={apiKey}\n              onChange={e => setApiKey(e.target.value)}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Max Context Length</Label>\n\n            <Input\n              type=\"number\"\n              placeholder=\"4096\"\n              min={0}\n              value={contextLength}\n              onChange={e => setContextLength(parseInt(e.target.value))}\n            />\n          </div>\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/models/model-item.tsx",
    "content": "import { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { MODEL_NAME_MAX } from \"@/db/limits\"\nimport { Tables, TablesUpdate } from \"@/supabase/types\"\nimport { IconSparkles } from \"@tabler/icons-react\"\nimport { FC, useState } from \"react\"\nimport { SidebarItem } from \"../all/sidebar-display-item\"\n\ninterface ModelItemProps {\n  model: Tables<\"models\">\n}\n\nexport const ModelItem: FC<ModelItemProps> = ({ model }) => {\n  const [isTyping, setIsTyping] = useState(false)\n\n  const [apiKey, setApiKey] = useState(model.api_key)\n  const [baseUrl, setBaseUrl] = useState(model.base_url)\n  const [description, setDescription] = useState(model.description)\n  const [modelId, setModelId] = useState(model.model_id)\n  const [name, setName] = useState(model.name)\n  const [contextLength, setContextLength] = useState(model.context_length)\n\n  return (\n    <SidebarItem\n      item={model}\n      isTyping={isTyping}\n      contentType=\"models\"\n      icon={<IconSparkles height={30} width={30} />}\n      updateState={\n        {\n          api_key: apiKey,\n          base_url: baseUrl,\n          description,\n          context_length: contextLength,\n          model_id: modelId,\n          name\n        } as TablesUpdate<\"models\">\n      }\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Model name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={MODEL_NAME_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Model ID</Label>\n\n            <Input\n              placeholder=\"Model ID...\"\n              value={modelId}\n              onChange={e => setModelId(e.target.value)}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Base URL</Label>\n\n            <Input\n              placeholder=\"Base URL...\"\n              value={baseUrl}\n              onChange={e => setBaseUrl(e.target.value)}\n            />\n\n            <div className=\"pt-1 text-xs italic\">\n              Your API must be compatible with the OpenAI SDK.\n            </div>\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>API Key</Label>\n\n            <Input\n              type=\"password\"\n              placeholder=\"API Key...\"\n              value={apiKey}\n              onChange={e => setApiKey(e.target.value)}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Max Context Length</Label>\n\n            <Input\n              type=\"number\"\n              placeholder=\"4096\"\n              min={0}\n              value={contextLength}\n              onChange={e => setContextLength(parseInt(e.target.value))}\n            />\n          </div>\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/presets/create-preset.tsx",
    "content": "import { SidebarCreateItem } from \"@/components/sidebar/items/all/sidebar-create-item\"\nimport { ChatSettingsForm } from \"@/components/ui/chat-settings-form\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { PRESET_NAME_MAX } from \"@/db/limits\"\nimport { TablesInsert } from \"@/supabase/types\"\nimport { FC, useContext, useState } from \"react\"\n\ninterface CreatePresetProps {\n  isOpen: boolean\n  onOpenChange: (isOpen: boolean) => void\n}\n\nexport const CreatePreset: FC<CreatePresetProps> = ({\n  isOpen,\n  onOpenChange\n}) => {\n  const { profile, selectedWorkspace } = useContext(ChatbotUIContext)\n\n  const [name, setName] = useState(\"\")\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(\"\")\n  const [presetChatSettings, setPresetChatSettings] = useState({\n    model: selectedWorkspace?.default_model,\n    prompt: selectedWorkspace?.default_prompt,\n    temperature: selectedWorkspace?.default_temperature,\n    contextLength: selectedWorkspace?.default_context_length,\n    includeProfileContext: selectedWorkspace?.include_profile_context,\n    includeWorkspaceInstructions:\n      selectedWorkspace?.include_workspace_instructions,\n    embeddingsProvider: selectedWorkspace?.embeddings_provider\n  })\n\n  if (!profile) return null\n  if (!selectedWorkspace) return null\n\n  return (\n    <SidebarCreateItem\n      contentType=\"presets\"\n      isOpen={isOpen}\n      isTyping={isTyping}\n      onOpenChange={onOpenChange}\n      createState={\n        {\n          user_id: profile.user_id,\n          name,\n          description,\n          include_profile_context: presetChatSettings.includeProfileContext,\n          include_workspace_instructions:\n            presetChatSettings.includeWorkspaceInstructions,\n          context_length: presetChatSettings.contextLength,\n          model: presetChatSettings.model,\n          prompt: presetChatSettings.prompt,\n          temperature: presetChatSettings.temperature,\n          embeddings_provider: presetChatSettings.embeddingsProvider\n        } as TablesInsert<\"presets\">\n      }\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Preset name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={PRESET_NAME_MAX}\n            />\n          </div>\n\n          <ChatSettingsForm\n            chatSettings={presetChatSettings as any}\n            onChangeChatSettings={setPresetChatSettings}\n            useAdvancedDropdown={true}\n          />\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/presets/preset-item.tsx",
    "content": "import { ModelIcon } from \"@/components/models/model-icon\"\nimport { ChatSettingsForm } from \"@/components/ui/chat-settings-form\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { PRESET_NAME_MAX } from \"@/db/limits\"\nimport { LLM_LIST } from \"@/lib/models/llm/llm-list\"\nimport { Tables } from \"@/supabase/types\"\nimport { FC, useState } from \"react\"\nimport { SidebarItem } from \"../all/sidebar-display-item\"\n\ninterface PresetItemProps {\n  preset: Tables<\"presets\">\n}\n\nexport const PresetItem: FC<PresetItemProps> = ({ preset }) => {\n  const [name, setName] = useState(preset.name)\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(preset.description)\n  const [presetChatSettings, setPresetChatSettings] = useState({\n    model: preset.model,\n    prompt: preset.prompt,\n    temperature: preset.temperature,\n    contextLength: preset.context_length,\n    includeProfileContext: preset.include_profile_context,\n    includeWorkspaceInstructions: preset.include_workspace_instructions\n  })\n\n  const modelDetails = LLM_LIST.find(model => model.modelId === preset.model)\n\n  return (\n    <SidebarItem\n      item={preset}\n      isTyping={isTyping}\n      contentType=\"presets\"\n      icon={\n        <ModelIcon\n          provider={modelDetails?.provider || \"custom\"}\n          height={30}\n          width={30}\n        />\n      }\n      updateState={{\n        name,\n        description,\n        include_profile_context: presetChatSettings.includeProfileContext,\n        include_workspace_instructions:\n          presetChatSettings.includeWorkspaceInstructions,\n        context_length: presetChatSettings.contextLength,\n        model: presetChatSettings.model,\n        prompt: presetChatSettings.prompt,\n        temperature: presetChatSettings.temperature\n      }}\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Preset name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={PRESET_NAME_MAX}\n            />\n          </div>\n\n          <ChatSettingsForm\n            chatSettings={presetChatSettings as any}\n            onChangeChatSettings={setPresetChatSettings}\n            useAdvancedDropdown={true}\n          />\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/prompts/create-prompt.tsx",
    "content": "import { SidebarCreateItem } from \"@/components/sidebar/items/all/sidebar-create-item\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { TextareaAutosize } from \"@/components/ui/textarea-autosize\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { PROMPT_NAME_MAX } from \"@/db/limits\"\nimport { TablesInsert } from \"@/supabase/types\"\nimport { FC, useContext, useState } from \"react\"\n\ninterface CreatePromptProps {\n  isOpen: boolean\n  onOpenChange: (isOpen: boolean) => void\n}\n\nexport const CreatePrompt: FC<CreatePromptProps> = ({\n  isOpen,\n  onOpenChange\n}) => {\n  const { profile, selectedWorkspace } = useContext(ChatbotUIContext)\n  const [isTyping, setIsTyping] = useState(false)\n  const [name, setName] = useState(\"\")\n  const [content, setContent] = useState(\"\")\n\n  if (!profile) return null\n  if (!selectedWorkspace) return null\n\n  return (\n    <SidebarCreateItem\n      contentType=\"prompts\"\n      isOpen={isOpen}\n      isTyping={isTyping}\n      onOpenChange={onOpenChange}\n      createState={\n        {\n          user_id: profile.user_id,\n          name,\n          content\n        } as TablesInsert<\"prompts\">\n      }\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Prompt name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={PROMPT_NAME_MAX}\n              onCompositionStart={() => setIsTyping(true)}\n              onCompositionEnd={() => setIsTyping(false)}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Prompt</Label>\n\n            <TextareaAutosize\n              placeholder=\"Prompt content...\"\n              value={content}\n              onValueChange={setContent}\n              minRows={6}\n              maxRows={20}\n              onCompositionStart={() => setIsTyping(true)}\n              onCompositionEnd={() => setIsTyping(false)}\n            />\n          </div>\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/prompts/prompt-item.tsx",
    "content": "import { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { TextareaAutosize } from \"@/components/ui/textarea-autosize\"\nimport { PROMPT_NAME_MAX } from \"@/db/limits\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconPencil } from \"@tabler/icons-react\"\nimport { FC, useState } from \"react\"\nimport { SidebarItem } from \"../all/sidebar-display-item\"\n\ninterface PromptItemProps {\n  prompt: Tables<\"prompts\">\n}\n\nexport const PromptItem: FC<PromptItemProps> = ({ prompt }) => {\n  const [name, setName] = useState(prompt.name)\n  const [content, setContent] = useState(prompt.content)\n  const [isTyping, setIsTyping] = useState(false)\n  return (\n    <SidebarItem\n      item={prompt}\n      isTyping={isTyping}\n      contentType=\"prompts\"\n      icon={<IconPencil size={30} />}\n      updateState={{ name, content }}\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Prompt name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={PROMPT_NAME_MAX}\n              onCompositionStart={() => setIsTyping(true)}\n              onCompositionEnd={() => setIsTyping(false)}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Prompt</Label>\n\n            <TextareaAutosize\n              placeholder=\"Prompt...\"\n              value={content}\n              onValueChange={setContent}\n              minRows={6}\n              maxRows={20}\n              onCompositionStart={() => setIsTyping(true)}\n              onCompositionEnd={() => setIsTyping(false)}\n            />\n          </div>\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/tools/create-tool.tsx",
    "content": "import { SidebarCreateItem } from \"@/components/sidebar/items/all/sidebar-create-item\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { TextareaAutosize } from \"@/components/ui/textarea-autosize\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { TOOL_DESCRIPTION_MAX, TOOL_NAME_MAX } from \"@/db/limits\"\nimport { validateOpenAPI } from \"@/lib/openapi-conversion\"\nimport { TablesInsert } from \"@/supabase/types\"\nimport { FC, useContext, useState } from \"react\"\n\ninterface CreateToolProps {\n  isOpen: boolean\n  onOpenChange: (isOpen: boolean) => void\n}\n\nexport const CreateTool: FC<CreateToolProps> = ({ isOpen, onOpenChange }) => {\n  const { profile, selectedWorkspace } = useContext(ChatbotUIContext)\n\n  const [name, setName] = useState(\"\")\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(\"\")\n  const [url, setUrl] = useState(\"\")\n  const [customHeaders, setCustomHeaders] = useState(\"\")\n  const [schema, setSchema] = useState(\"\")\n  const [schemaError, setSchemaError] = useState(\"\")\n\n  if (!profile || !selectedWorkspace) return null\n\n  return (\n    <SidebarCreateItem\n      contentType=\"tools\"\n      createState={\n        {\n          user_id: profile.user_id,\n          name,\n          description,\n          url,\n          custom_headers: customHeaders,\n          schema\n        } as TablesInsert<\"tools\">\n      }\n      isOpen={isOpen}\n      isTyping={isTyping}\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Tool name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={TOOL_NAME_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Description</Label>\n\n            <Input\n              placeholder=\"Tool description...\"\n              value={description}\n              onChange={e => setDescription(e.target.value)}\n              maxLength={TOOL_DESCRIPTION_MAX}\n            />\n          </div>\n\n          {/* <div className=\"space-y-1\">\n            <Label>URL</Label>\n\n            <Input\n              placeholder=\"Tool url...\"\n              value={url}\n              onChange={e => setUrl(e.target.value)}\n            />\n          </div> */}\n\n          {/* <div className=\"space-y-3 pt-4 pb-3\">\n            <div className=\"space-x-2 flex items-center\">\n              <Checkbox />\n\n              <Label>Web Browsing</Label>\n            </div>\n\n            <div className=\"space-x-2 flex items-center\">\n              <Checkbox />\n\n              <Label>Image Generation</Label>\n            </div>\n\n            <div className=\"space-x-2 flex items-center\">\n              <Checkbox />\n\n              <Label>Code Interpreter</Label>\n            </div>\n          </div> */}\n\n          <div className=\"space-y-1\">\n            <Label>Custom Headers</Label>\n\n            <TextareaAutosize\n              placeholder={`{\"X-api-key\": \"1234567890\"}`}\n              value={customHeaders}\n              onValueChange={setCustomHeaders}\n              minRows={1}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Schema</Label>\n\n            <TextareaAutosize\n              placeholder={`{\n                \"openapi\": \"3.1.0\",\n                \"info\": {\n                  \"title\": \"Get weather data\",\n                  \"description\": \"Retrieves current weather data for a location.\",\n                  \"version\": \"v1.0.0\"\n                },\n                \"servers\": [\n                  {\n                    \"url\": \"https://weather.example.com\"\n                  }\n                ],\n                \"paths\": {\n                  \"/location\": {\n                    \"get\": {\n                      \"description\": \"Get temperature for a specific location\",\n                      \"operationId\": \"GetCurrentWeather\",\n                      \"parameters\": [\n                        {\n                          \"name\": \"location\",\n                          \"in\": \"query\",\n                          \"description\": \"The city and state to retrieve the weather for\",\n                          \"required\": true,\n                          \"schema\": {\n                            \"type\": \"string\"\n                          }\n                        }\n                      ],\n                      \"deprecated\": false\n                    }\n                  }\n                },\n                \"components\": {\n                  \"schemas\": {}\n                }\n              }`}\n              value={schema}\n              onValueChange={value => {\n                setSchema(value)\n\n                try {\n                  const parsedSchema = JSON.parse(value)\n                  validateOpenAPI(parsedSchema)\n                    .then(() => setSchemaError(\"\")) // Clear error if validation is successful\n                    .catch(error => setSchemaError(error.message)) // Set specific validation error message\n                } catch (error) {\n                  setSchemaError(\"Invalid JSON format\") // Set error for invalid JSON format\n                }\n              }}\n              minRows={15}\n            />\n\n            <div className=\"text-xs text-red-500\">{schemaError}</div>\n          </div>\n        </>\n      )}\n      onOpenChange={onOpenChange}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/items/tools/tool-item.tsx",
    "content": "import { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { TextareaAutosize } from \"@/components/ui/textarea-autosize\"\nimport { TOOL_DESCRIPTION_MAX, TOOL_NAME_MAX } from \"@/db/limits\"\nimport { validateOpenAPI } from \"@/lib/openapi-conversion\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconBolt } from \"@tabler/icons-react\"\nimport { FC, useState } from \"react\"\nimport { SidebarItem } from \"../all/sidebar-display-item\"\n\ninterface ToolItemProps {\n  tool: Tables<\"tools\">\n}\n\nexport const ToolItem: FC<ToolItemProps> = ({ tool }) => {\n  const [name, setName] = useState(tool.name)\n  const [isTyping, setIsTyping] = useState(false)\n  const [description, setDescription] = useState(tool.description)\n  const [url, setUrl] = useState(tool.url)\n  const [customHeaders, setCustomHeaders] = useState(\n    tool.custom_headers as string\n  )\n  const [schema, setSchema] = useState(tool.schema as string)\n  const [schemaError, setSchemaError] = useState(\"\")\n\n  return (\n    <SidebarItem\n      item={tool}\n      isTyping={isTyping}\n      contentType=\"tools\"\n      icon={<IconBolt size={30} />}\n      updateState={{\n        name,\n        description,\n        url,\n        custom_headers: customHeaders,\n        schema\n      }}\n      renderInputs={() => (\n        <>\n          <div className=\"space-y-1\">\n            <Label>Name</Label>\n\n            <Input\n              placeholder=\"Tool name...\"\n              value={name}\n              onChange={e => setName(e.target.value)}\n              maxLength={TOOL_NAME_MAX}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Description</Label>\n\n            <Input\n              placeholder=\"Tool description...\"\n              value={description}\n              onChange={e => setDescription(e.target.value)}\n              maxLength={TOOL_DESCRIPTION_MAX}\n            />\n          </div>\n\n          {/* <div className=\"space-y-1\">\n            <Label>URL</Label>\n\n            <Input\n              placeholder=\"Tool url...\"\n              value={url}\n              onChange={e => setUrl(e.target.value)}\n            />\n          </div> */}\n\n          {/* <div className=\"space-y-3 pt-4 pb-3\">\n            <div className=\"space-x-2 flex items-center\">\n              <Checkbox />\n\n              <Label>Web Browsing</Label>\n            </div>\n\n            <div className=\"space-x-2 flex items-center\">\n              <Checkbox />\n\n              <Label>Image Generation</Label>\n            </div>\n\n            <div className=\"space-x-2 flex items-center\">\n              <Checkbox />\n\n              <Label>Code Interpreter</Label>\n            </div>\n          </div> */}\n\n          <div className=\"space-y-1\">\n            <Label>Custom Headers</Label>\n\n            <TextareaAutosize\n              placeholder={`{\"X-api-key\": \"1234567890\"}`}\n              value={customHeaders}\n              onValueChange={setCustomHeaders}\n              minRows={1}\n            />\n          </div>\n\n          <div className=\"space-y-1\">\n            <Label>Schema</Label>\n\n            <TextareaAutosize\n              placeholder={`{\n                \"openapi\": \"3.1.0\",\n                \"info\": {\n                  \"title\": \"Get weather data\",\n                  \"description\": \"Retrieves current weather data for a location.\",\n                  \"version\": \"v1.0.0\"\n                },\n                \"servers\": [\n                  {\n                    \"url\": \"https://weather.example.com\"\n                  }\n                ],\n                \"paths\": {\n                  \"/location\": {\n                    \"get\": {\n                      \"description\": \"Get temperature for a specific location\",\n                      \"operationId\": \"GetCurrentWeather\",\n                      \"parameters\": [\n                        {\n                          \"name\": \"location\",\n                          \"in\": \"query\",\n                          \"description\": \"The city and state to retrieve the weather for\",\n                          \"required\": true,\n                          \"schema\": {\n                            \"type\": \"string\"\n                          }\n                        }\n                      ],\n                      \"deprecated\": false\n                    }\n                  }\n                },\n                \"components\": {\n                  \"schemas\": {}\n                }\n              }`}\n              value={schema}\n              onValueChange={value => {\n                setSchema(value)\n\n                try {\n                  const parsedSchema = JSON.parse(value)\n                  validateOpenAPI(parsedSchema)\n                    .then(() => setSchemaError(\"\")) // Clear error if validation is successful\n                    .catch(error => setSchemaError(error.message)) // Set specific validation error message\n                } catch (error) {\n                  setSchemaError(\"Invalid JSON format\") // Set error for invalid JSON format\n                }\n              }}\n              minRows={15}\n            />\n\n            <div className=\"text-xs text-red-500\">{schemaError}</div>\n          </div>\n        </>\n      )}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/sidebar-content.tsx",
    "content": "import { Tables } from \"@/supabase/types\"\nimport { ContentType, DataListType } from \"@/types\"\nimport { FC, useState } from \"react\"\nimport { SidebarCreateButtons } from \"./sidebar-create-buttons\"\nimport { SidebarDataList } from \"./sidebar-data-list\"\nimport { SidebarSearch } from \"./sidebar-search\"\n\ninterface SidebarContentProps {\n  contentType: ContentType\n  data: DataListType\n  folders: Tables<\"folders\">[]\n}\n\nexport const SidebarContent: FC<SidebarContentProps> = ({\n  contentType,\n  data,\n  folders\n}) => {\n  const [searchTerm, setSearchTerm] = useState(\"\")\n\n  const filteredData: any = data.filter(item =>\n    item.name.toLowerCase().includes(searchTerm.toLowerCase())\n  )\n\n  return (\n    // Subtract 50px for the height of the workspace settings\n    <div className=\"flex max-h-[calc(100%-50px)] grow flex-col\">\n      <div className=\"mt-2 flex items-center\">\n        <SidebarCreateButtons\n          contentType={contentType}\n          hasData={data.length > 0}\n        />\n      </div>\n\n      <div className=\"mt-2\">\n        <SidebarSearch\n          contentType={contentType}\n          searchTerm={searchTerm}\n          setSearchTerm={setSearchTerm}\n        />\n      </div>\n\n      <SidebarDataList\n        contentType={contentType}\n        data={filteredData}\n        folders={folders}\n      />\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/sidebar-create-buttons.tsx",
    "content": "import { useChatHandler } from \"@/components/chat/chat-hooks/use-chat-handler\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { createFolder } from \"@/db/folders\"\nimport { ContentType } from \"@/types\"\nimport { IconFolderPlus, IconPlus } from \"@tabler/icons-react\"\nimport { FC, useContext, useState } from \"react\"\nimport { Button } from \"../ui/button\"\nimport { CreateAssistant } from \"./items/assistants/create-assistant\"\nimport { CreateCollection } from \"./items/collections/create-collection\"\nimport { CreateFile } from \"./items/files/create-file\"\nimport { CreateModel } from \"./items/models/create-model\"\nimport { CreatePreset } from \"./items/presets/create-preset\"\nimport { CreatePrompt } from \"./items/prompts/create-prompt\"\nimport { CreateTool } from \"./items/tools/create-tool\"\n\ninterface SidebarCreateButtonsProps {\n  contentType: ContentType\n  hasData: boolean\n}\n\nexport const SidebarCreateButtons: FC<SidebarCreateButtonsProps> = ({\n  contentType,\n  hasData\n}) => {\n  const { profile, selectedWorkspace, folders, setFolders } =\n    useContext(ChatbotUIContext)\n  const { handleNewChat } = useChatHandler()\n\n  const [isCreatingPrompt, setIsCreatingPrompt] = useState(false)\n  const [isCreatingPreset, setIsCreatingPreset] = useState(false)\n  const [isCreatingFile, setIsCreatingFile] = useState(false)\n  const [isCreatingCollection, setIsCreatingCollection] = useState(false)\n  const [isCreatingAssistant, setIsCreatingAssistant] = useState(false)\n  const [isCreatingTool, setIsCreatingTool] = useState(false)\n  const [isCreatingModel, setIsCreatingModel] = useState(false)\n\n  const handleCreateFolder = async () => {\n    if (!profile) return\n    if (!selectedWorkspace) return\n\n    const createdFolder = await createFolder({\n      user_id: profile.user_id,\n      workspace_id: selectedWorkspace.id,\n      name: \"New Folder\",\n      description: \"\",\n      type: contentType\n    })\n    setFolders([...folders, createdFolder])\n  }\n\n  const getCreateFunction = () => {\n    switch (contentType) {\n      case \"chats\":\n        return async () => {\n          handleNewChat()\n        }\n\n      case \"presets\":\n        return async () => {\n          setIsCreatingPreset(true)\n        }\n\n      case \"prompts\":\n        return async () => {\n          setIsCreatingPrompt(true)\n        }\n\n      case \"files\":\n        return async () => {\n          setIsCreatingFile(true)\n        }\n\n      case \"collections\":\n        return async () => {\n          setIsCreatingCollection(true)\n        }\n\n      case \"assistants\":\n        return async () => {\n          setIsCreatingAssistant(true)\n        }\n\n      case \"tools\":\n        return async () => {\n          setIsCreatingTool(true)\n        }\n\n      case \"models\":\n        return async () => {\n          setIsCreatingModel(true)\n        }\n\n      default:\n        break\n    }\n  }\n\n  return (\n    <div className=\"flex w-full space-x-2\">\n      <Button className=\"flex h-[36px] grow\" onClick={getCreateFunction()}>\n        <IconPlus className=\"mr-1\" size={20} />\n        New{\" \"}\n        {contentType.charAt(0).toUpperCase() +\n          contentType.slice(1, contentType.length - 1)}\n      </Button>\n\n      {hasData && (\n        <Button className=\"size-[36px] p-1\" onClick={handleCreateFolder}>\n          <IconFolderPlus size={20} />\n        </Button>\n      )}\n\n      {isCreatingPrompt && (\n        <CreatePrompt\n          isOpen={isCreatingPrompt}\n          onOpenChange={setIsCreatingPrompt}\n        />\n      )}\n\n      {isCreatingPreset && (\n        <CreatePreset\n          isOpen={isCreatingPreset}\n          onOpenChange={setIsCreatingPreset}\n        />\n      )}\n\n      {isCreatingFile && (\n        <CreateFile isOpen={isCreatingFile} onOpenChange={setIsCreatingFile} />\n      )}\n\n      {isCreatingCollection && (\n        <CreateCollection\n          isOpen={isCreatingCollection}\n          onOpenChange={setIsCreatingCollection}\n        />\n      )}\n\n      {isCreatingAssistant && (\n        <CreateAssistant\n          isOpen={isCreatingAssistant}\n          onOpenChange={setIsCreatingAssistant}\n        />\n      )}\n\n      {isCreatingTool && (\n        <CreateTool isOpen={isCreatingTool} onOpenChange={setIsCreatingTool} />\n      )}\n\n      {isCreatingModel && (\n        <CreateModel\n          isOpen={isCreatingModel}\n          onOpenChange={setIsCreatingModel}\n        />\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/sidebar-data-list.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { updateAssistant } from \"@/db/assistants\"\nimport { updateChat } from \"@/db/chats\"\nimport { updateCollection } from \"@/db/collections\"\nimport { updateFile } from \"@/db/files\"\nimport { updateModel } from \"@/db/models\"\nimport { updatePreset } from \"@/db/presets\"\nimport { updatePrompt } from \"@/db/prompts\"\nimport { updateTool } from \"@/db/tools\"\nimport { cn } from \"@/lib/utils\"\nimport { Tables } from \"@/supabase/types\"\nimport { ContentType, DataItemType, DataListType } from \"@/types\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\nimport { Separator } from \"../ui/separator\"\nimport { AssistantItem } from \"./items/assistants/assistant-item\"\nimport { ChatItem } from \"./items/chat/chat-item\"\nimport { CollectionItem } from \"./items/collections/collection-item\"\nimport { FileItem } from \"./items/files/file-item\"\nimport { Folder } from \"./items/folders/folder-item\"\nimport { ModelItem } from \"./items/models/model-item\"\nimport { PresetItem } from \"./items/presets/preset-item\"\nimport { PromptItem } from \"./items/prompts/prompt-item\"\nimport { ToolItem } from \"./items/tools/tool-item\"\n\ninterface SidebarDataListProps {\n  contentType: ContentType\n  data: DataListType\n  folders: Tables<\"folders\">[]\n}\n\nexport const SidebarDataList: FC<SidebarDataListProps> = ({\n  contentType,\n  data,\n  folders\n}) => {\n  const {\n    setChats,\n    setPresets,\n    setPrompts,\n    setFiles,\n    setCollections,\n    setAssistants,\n    setTools,\n    setModels\n  } = useContext(ChatbotUIContext)\n\n  const divRef = useRef<HTMLDivElement>(null)\n\n  const [isOverflowing, setIsOverflowing] = useState(false)\n  const [isDragOver, setIsDragOver] = useState(false)\n\n  const getDataListComponent = (\n    contentType: ContentType,\n    item: DataItemType\n  ) => {\n    switch (contentType) {\n      case \"chats\":\n        return <ChatItem key={item.id} chat={item as Tables<\"chats\">} />\n\n      case \"presets\":\n        return <PresetItem key={item.id} preset={item as Tables<\"presets\">} />\n\n      case \"prompts\":\n        return <PromptItem key={item.id} prompt={item as Tables<\"prompts\">} />\n\n      case \"files\":\n        return <FileItem key={item.id} file={item as Tables<\"files\">} />\n\n      case \"collections\":\n        return (\n          <CollectionItem\n            key={item.id}\n            collection={item as Tables<\"collections\">}\n          />\n        )\n\n      case \"assistants\":\n        return (\n          <AssistantItem\n            key={item.id}\n            assistant={item as Tables<\"assistants\">}\n          />\n        )\n\n      case \"tools\":\n        return <ToolItem key={item.id} tool={item as Tables<\"tools\">} />\n\n      case \"models\":\n        return <ModelItem key={item.id} model={item as Tables<\"models\">} />\n\n      default:\n        return null\n    }\n  }\n\n  const getSortedData = (\n    data: any,\n    dateCategory: \"Today\" | \"Yesterday\" | \"Previous Week\" | \"Older\"\n  ) => {\n    const now = new Date()\n    const todayStart = new Date(now.setHours(0, 0, 0, 0))\n    const yesterdayStart = new Date(\n      new Date().setDate(todayStart.getDate() - 1)\n    )\n    const oneWeekAgoStart = new Date(\n      new Date().setDate(todayStart.getDate() - 7)\n    )\n\n    return data\n      .filter((item: any) => {\n        const itemDate = new Date(item.updated_at || item.created_at)\n        switch (dateCategory) {\n          case \"Today\":\n            return itemDate >= todayStart\n          case \"Yesterday\":\n            return itemDate >= yesterdayStart && itemDate < todayStart\n          case \"Previous Week\":\n            return itemDate >= oneWeekAgoStart && itemDate < yesterdayStart\n          case \"Older\":\n            return itemDate < oneWeekAgoStart\n          default:\n            return true\n        }\n      })\n      .sort(\n        (\n          a: { updated_at: string; created_at: string },\n          b: { updated_at: string; created_at: string }\n        ) =>\n          new Date(b.updated_at || b.created_at).getTime() -\n          new Date(a.updated_at || a.created_at).getTime()\n      )\n  }\n\n  const updateFunctions = {\n    chats: updateChat,\n    presets: updatePreset,\n    prompts: updatePrompt,\n    files: updateFile,\n    collections: updateCollection,\n    assistants: updateAssistant,\n    tools: updateTool,\n    models: updateModel\n  }\n\n  const stateUpdateFunctions = {\n    chats: setChats,\n    presets: setPresets,\n    prompts: setPrompts,\n    files: setFiles,\n    collections: setCollections,\n    assistants: setAssistants,\n    tools: setTools,\n    models: setModels\n  }\n\n  const updateFolder = async (itemId: string, folderId: string | null) => {\n    const item: any = data.find(item => item.id === itemId)\n\n    if (!item) return null\n\n    const updateFunction = updateFunctions[contentType]\n    const setStateFunction = stateUpdateFunctions[contentType]\n\n    if (!updateFunction || !setStateFunction) return\n\n    const updatedItem = await updateFunction(item.id, {\n      folder_id: folderId\n    })\n\n    setStateFunction((items: any) =>\n      items.map((item: any) =>\n        item.id === updatedItem.id ? updatedItem : item\n      )\n    )\n  }\n\n  const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {\n    e.preventDefault()\n    setIsDragOver(true)\n  }\n\n  const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {\n    e.preventDefault()\n    setIsDragOver(false)\n  }\n\n  const handleDragStart = (e: React.DragEvent<HTMLDivElement>, id: string) => {\n    e.dataTransfer.setData(\"text/plain\", id)\n  }\n\n  const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {\n    e.preventDefault()\n  }\n\n  const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {\n    e.preventDefault()\n\n    const target = e.target as Element\n\n    if (!target.closest(\"#folder\")) {\n      const itemId = e.dataTransfer.getData(\"text/plain\")\n      updateFolder(itemId, null)\n    }\n\n    setIsDragOver(false)\n  }\n\n  useEffect(() => {\n    if (divRef.current) {\n      setIsOverflowing(\n        divRef.current.scrollHeight > divRef.current.clientHeight\n      )\n    }\n  }, [data])\n\n  const dataWithFolders = data.filter(item => item.folder_id)\n  const dataWithoutFolders = data.filter(item => item.folder_id === null)\n\n  return (\n    <>\n      <div\n        ref={divRef}\n        className=\"mt-2 flex flex-col overflow-auto\"\n        onDrop={handleDrop}\n      >\n        {data.length === 0 && (\n          <div className=\"flex grow flex-col items-center justify-center\">\n            <div className=\" text-centertext-muted-foreground p-8 text-lg italic\">\n              No {contentType}.\n            </div>\n          </div>\n        )}\n\n        {(dataWithFolders.length > 0 || dataWithoutFolders.length > 0) && (\n          <div\n            className={`h-full ${\n              isOverflowing ? \"w-[calc(100%-8px)]\" : \"w-full\"\n            } space-y-2 pt-2 ${isOverflowing ? \"mr-2\" : \"\"}`}\n          >\n            {folders.map(folder => (\n              <Folder\n                key={folder.id}\n                folder={folder}\n                onUpdateFolder={updateFolder}\n                contentType={contentType}\n              >\n                {dataWithFolders\n                  .filter(item => item.folder_id === folder.id)\n                  .map(item => (\n                    <div\n                      key={item.id}\n                      draggable\n                      onDragStart={e => handleDragStart(e, item.id)}\n                    >\n                      {getDataListComponent(contentType, item)}\n                    </div>\n                  ))}\n              </Folder>\n            ))}\n\n            {folders.length > 0 && <Separator />}\n\n            {contentType === \"chats\" ? (\n              <>\n                {[\"Today\", \"Yesterday\", \"Previous Week\", \"Older\"].map(\n                  dateCategory => {\n                    const sortedData = getSortedData(\n                      dataWithoutFolders,\n                      dateCategory as\n                        | \"Today\"\n                        | \"Yesterday\"\n                        | \"Previous Week\"\n                        | \"Older\"\n                    )\n\n                    return (\n                      sortedData.length > 0 && (\n                        <div key={dateCategory} className=\"pb-2\">\n                          <div className=\"text-muted-foreground mb-1 text-sm font-bold\">\n                            {dateCategory}\n                          </div>\n\n                          <div\n                            className={cn(\n                              \"flex grow flex-col\",\n                              isDragOver && \"bg-accent\"\n                            )}\n                            onDrop={handleDrop}\n                            onDragEnter={handleDragEnter}\n                            onDragLeave={handleDragLeave}\n                            onDragOver={handleDragOver}\n                          >\n                            {sortedData.map((item: any) => (\n                              <div\n                                key={item.id}\n                                draggable\n                                onDragStart={e => handleDragStart(e, item.id)}\n                              >\n                                {getDataListComponent(contentType, item)}\n                              </div>\n                            ))}\n                          </div>\n                        </div>\n                      )\n                    )\n                  }\n                )}\n              </>\n            ) : (\n              <div\n                className={cn(\"flex grow flex-col\", isDragOver && \"bg-accent\")}\n                onDrop={handleDrop}\n                onDragEnter={handleDragEnter}\n                onDragLeave={handleDragLeave}\n                onDragOver={handleDragOver}\n              >\n                {dataWithoutFolders.map(item => {\n                  return (\n                    <div\n                      key={item.id}\n                      draggable\n                      onDragStart={e => handleDragStart(e, item.id)}\n                    >\n                      {getDataListComponent(contentType, item)}\n                    </div>\n                  )\n                })}\n              </div>\n            )}\n          </div>\n        )}\n      </div>\n\n      <div\n        className={cn(\"flex grow\", isDragOver && \"bg-accent\")}\n        onDrop={handleDrop}\n        onDragEnter={handleDragEnter}\n        onDragLeave={handleDragLeave}\n        onDragOver={handleDragOver}\n      />\n    </>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/sidebar-search.tsx",
    "content": "import { ContentType } from \"@/types\"\nimport { FC } from \"react\"\nimport { Input } from \"../ui/input\"\n\ninterface SidebarSearchProps {\n  contentType: ContentType\n  searchTerm: string\n  setSearchTerm: Function\n}\n\nexport const SidebarSearch: FC<SidebarSearchProps> = ({\n  contentType,\n  searchTerm,\n  setSearchTerm\n}) => {\n  return (\n    <Input\n      placeholder={`Search ${contentType}...`}\n      value={searchTerm}\n      onChange={e => setSearchTerm(e.target.value)}\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/sidebar-switch-item.tsx",
    "content": "import { ContentType } from \"@/types\"\nimport { FC } from \"react\"\nimport { TabsTrigger } from \"../ui/tabs\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\n\ninterface SidebarSwitchItemProps {\n  contentType: ContentType\n  icon: React.ReactNode\n  onContentTypeChange: (contentType: ContentType) => void\n}\n\nexport const SidebarSwitchItem: FC<SidebarSwitchItemProps> = ({\n  contentType,\n  icon,\n  onContentTypeChange\n}) => {\n  return (\n    <WithTooltip\n      display={\n        <div>{contentType[0].toUpperCase() + contentType.substring(1)}</div>\n      }\n      trigger={\n        <TabsTrigger\n          className=\"hover:opacity-50\"\n          value={contentType}\n          onClick={() => onContentTypeChange(contentType as ContentType)}\n        >\n          {icon}\n        </TabsTrigger>\n      }\n    />\n  )\n}\n"
  },
  {
    "path": "components/sidebar/sidebar-switcher.tsx",
    "content": "import { ContentType } from \"@/types\"\nimport {\n  IconAdjustmentsHorizontal,\n  IconBolt,\n  IconBooks,\n  IconFile,\n  IconMessage,\n  IconPencil,\n  IconRobotFace,\n  IconSparkles\n} from \"@tabler/icons-react\"\nimport { FC } from \"react\"\nimport { TabsList } from \"../ui/tabs\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\nimport { ProfileSettings } from \"../utility/profile-settings\"\nimport { SidebarSwitchItem } from \"./sidebar-switch-item\"\n\nexport const SIDEBAR_ICON_SIZE = 28\n\ninterface SidebarSwitcherProps {\n  onContentTypeChange: (contentType: ContentType) => void\n}\n\nexport const SidebarSwitcher: FC<SidebarSwitcherProps> = ({\n  onContentTypeChange\n}) => {\n  return (\n    <div className=\"flex flex-col justify-between border-r-2 pb-5\">\n      <TabsList className=\"bg-background grid h-[440px] grid-rows-7\">\n        <SidebarSwitchItem\n          icon={<IconMessage size={SIDEBAR_ICON_SIZE} />}\n          contentType=\"chats\"\n          onContentTypeChange={onContentTypeChange}\n        />\n\n        <SidebarSwitchItem\n          icon={<IconAdjustmentsHorizontal size={SIDEBAR_ICON_SIZE} />}\n          contentType=\"presets\"\n          onContentTypeChange={onContentTypeChange}\n        />\n\n        <SidebarSwitchItem\n          icon={<IconPencil size={SIDEBAR_ICON_SIZE} />}\n          contentType=\"prompts\"\n          onContentTypeChange={onContentTypeChange}\n        />\n\n        <SidebarSwitchItem\n          icon={<IconSparkles size={SIDEBAR_ICON_SIZE} />}\n          contentType=\"models\"\n          onContentTypeChange={onContentTypeChange}\n        />\n\n        <SidebarSwitchItem\n          icon={<IconFile size={SIDEBAR_ICON_SIZE} />}\n          contentType=\"files\"\n          onContentTypeChange={onContentTypeChange}\n        />\n\n        <SidebarSwitchItem\n          icon={<IconBooks size={SIDEBAR_ICON_SIZE} />}\n          contentType=\"collections\"\n          onContentTypeChange={onContentTypeChange}\n        />\n\n        <SidebarSwitchItem\n          icon={<IconRobotFace size={SIDEBAR_ICON_SIZE} />}\n          contentType=\"assistants\"\n          onContentTypeChange={onContentTypeChange}\n        />\n\n        <SidebarSwitchItem\n          icon={<IconBolt size={SIDEBAR_ICON_SIZE} />}\n          contentType=\"tools\"\n          onContentTypeChange={onContentTypeChange}\n        />\n      </TabsList>\n\n      <div className=\"flex flex-col items-center space-y-4\">\n        {/* TODO */}\n        {/* <WithTooltip display={<div>Import</div>} trigger={<Import />} /> */}\n\n        {/* TODO */}\n        {/* <Alerts /> */}\n\n        <WithTooltip\n          display={<div>Profile Settings</div>}\n          trigger={<ProfileSettings />}\n        />\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/sidebar/sidebar.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { Tables } from \"@/supabase/types\"\nimport { ContentType } from \"@/types\"\nimport { FC, useContext } from \"react\"\nimport { SIDEBAR_WIDTH } from \"../ui/dashboard\"\nimport { TabsContent } from \"../ui/tabs\"\nimport { WorkspaceSwitcher } from \"../utility/workspace-switcher\"\nimport { WorkspaceSettings } from \"../workspace/workspace-settings\"\nimport { SidebarContent } from \"./sidebar-content\"\n\ninterface SidebarProps {\n  contentType: ContentType\n  showSidebar: boolean\n}\n\nexport const Sidebar: FC<SidebarProps> = ({ contentType, showSidebar }) => {\n  const {\n    folders,\n    chats,\n    presets,\n    prompts,\n    files,\n    collections,\n    assistants,\n    tools,\n    models\n  } = useContext(ChatbotUIContext)\n\n  const chatFolders = folders.filter(folder => folder.type === \"chats\")\n  const presetFolders = folders.filter(folder => folder.type === \"presets\")\n  const promptFolders = folders.filter(folder => folder.type === \"prompts\")\n  const filesFolders = folders.filter(folder => folder.type === \"files\")\n  const collectionFolders = folders.filter(\n    folder => folder.type === \"collections\"\n  )\n  const assistantFolders = folders.filter(\n    folder => folder.type === \"assistants\"\n  )\n  const toolFolders = folders.filter(folder => folder.type === \"tools\")\n  const modelFolders = folders.filter(folder => folder.type === \"models\")\n\n  const renderSidebarContent = (\n    contentType: ContentType,\n    data: any[],\n    folders: Tables<\"folders\">[]\n  ) => {\n    return (\n      <SidebarContent contentType={contentType} data={data} folders={folders} />\n    )\n  }\n\n  return (\n    <TabsContent\n      className=\"m-0 w-full space-y-2\"\n      style={{\n        // Sidebar - SidebarSwitcher\n        minWidth: showSidebar ? `calc(${SIDEBAR_WIDTH}px - 60px)` : \"0px\",\n        maxWidth: showSidebar ? `calc(${SIDEBAR_WIDTH}px - 60px)` : \"0px\",\n        width: showSidebar ? `calc(${SIDEBAR_WIDTH}px - 60px)` : \"0px\"\n      }}\n      value={contentType}\n    >\n      <div className=\"flex h-full flex-col p-3\">\n        <div className=\"flex items-center border-b-2 pb-2\">\n          <WorkspaceSwitcher />\n\n          <WorkspaceSettings />\n        </div>\n\n        {(() => {\n          switch (contentType) {\n            case \"chats\":\n              return renderSidebarContent(\"chats\", chats, chatFolders)\n\n            case \"presets\":\n              return renderSidebarContent(\"presets\", presets, presetFolders)\n\n            case \"prompts\":\n              return renderSidebarContent(\"prompts\", prompts, promptFolders)\n\n            case \"files\":\n              return renderSidebarContent(\"files\", files, filesFolders)\n\n            case \"collections\":\n              return renderSidebarContent(\n                \"collections\",\n                collections,\n                collectionFolders\n              )\n\n            case \"assistants\":\n              return renderSidebarContent(\n                \"assistants\",\n                assistants,\n                assistantFolders\n              )\n\n            case \"tools\":\n              return renderSidebarContent(\"tools\", tools, toolFolders)\n\n            case \"models\":\n              return renderSidebarContent(\"models\", models, modelFolders)\n\n            default:\n              return null\n          }\n        })()}\n      </div>\n    </TabsContent>\n  )\n}\n"
  },
  {
    "path": "components/ui/accordion.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\"\nimport { ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Accordion = AccordionPrimitive.Root\n\nconst AccordionItem = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(({ className, ...props }, ref) => (\n  <AccordionPrimitive.Item\n    ref={ref}\n    className={cn(\"border-b\", className)}\n    {...props}\n  />\n))\nAccordionItem.displayName = \"AccordionItem\"\n\nconst AccordionTrigger = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <AccordionPrimitive.Header className=\"flex\">\n    <AccordionPrimitive.Trigger\n      ref={ref}\n      className={cn(\n        \"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronDown className=\"size-4 shrink-0 transition-transform duration-200\" />\n    </AccordionPrimitive.Trigger>\n  </AccordionPrimitive.Header>\n))\nAccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName\n\nconst AccordionContent = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <AccordionPrimitive.Content\n    ref={ref}\n    className=\"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm transition-all\"\n    {...props}\n  >\n    <div className={cn(\"pb-4 pt-0\", className)}>{children}</div>\n  </AccordionPrimitive.Content>\n))\n\nAccordionContent.displayName = AccordionPrimitive.Content.displayName\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent }\n"
  },
  {
    "path": "components/ui/advanced-settings.tsx",
    "content": "import {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger\n} from \"@/components/ui/collapsible\"\nimport { IconChevronDown, IconChevronRight } from \"@tabler/icons-react\"\nimport { FC, useState } from \"react\"\n\ninterface AdvancedSettingsProps {\n  children: React.ReactNode\n}\n\nexport const AdvancedSettings: FC<AdvancedSettingsProps> = ({ children }) => {\n  const [isOpen, setIsOpen] = useState(\n    false\n    // localStorage.getItem(\"advanced-settings-open\") === \"true\"\n  )\n\n  const handleOpenChange = (isOpen: boolean) => {\n    setIsOpen(isOpen)\n    // localStorage.setItem(\"advanced-settings-open\", String(isOpen))\n  }\n\n  return (\n    <Collapsible className=\"pt-2\" open={isOpen} onOpenChange={handleOpenChange}>\n      <CollapsibleTrigger className=\"hover:opacity-50\">\n        <div className=\"flex items-center font-bold\">\n          <div className=\"mr-1\">Advanced Settings</div>\n          {isOpen ? (\n            <IconChevronDown size={20} stroke={3} />\n          ) : (\n            <IconChevronRight size={20} stroke={3} />\n          )}\n        </div>\n      </CollapsibleTrigger>\n\n      <CollapsibleContent className=\"mt-4\">{children}</CollapsibleContent>\n    </Collapsible>\n  )\n}\n"
  },
  {
    "path": "components/ui/alert-dialog.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\n\nconst AlertDialog = AlertDialogPrimitive.Root\n\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger\n\nconst AlertDialogPortal = AlertDialogPrimitive.Portal\n\nconst AlertDialogOverlay = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Overlay\n    className={cn(\n      \"bg-background/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 backdrop-blur-sm\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName\n\nconst AlertDialogContent = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPortal>\n    <AlertDialogOverlay />\n    <AlertDialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg\",\n        className\n      )}\n      {...props}\n    />\n  </AlertDialogPortal>\n))\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName\n\nconst AlertDialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nAlertDialogHeader.displayName = \"AlertDialogHeader\"\n\nconst AlertDialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nAlertDialogFooter.displayName = \"AlertDialogFooter\"\n\nconst AlertDialogTitle = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold\", className)}\n    {...props}\n  />\n))\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName\n\nconst AlertDialogDescription = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-muted-foreground text-sm\", className)}\n    {...props}\n  />\n))\nAlertDialogDescription.displayName =\n  AlertDialogPrimitive.Description.displayName\n\nconst AlertDialogAction = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Action>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Action\n    ref={ref}\n    className={cn(buttonVariants(), className)}\n    {...props}\n  />\n))\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName\n\nconst AlertDialogCancel = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Cancel\n    ref={ref}\n    className={cn(\n      buttonVariants({ variant: \"outline\" }),\n      \"mt-2 sm:mt-0\",\n      className\n    )}\n    {...props}\n  />\n))\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName\n\nexport {\n  AlertDialog,\n  AlertDialogPortal,\n  AlertDialogOverlay,\n  AlertDialogTrigger,\n  AlertDialogContent,\n  AlertDialogHeader,\n  AlertDialogFooter,\n  AlertDialogTitle,\n  AlertDialogDescription,\n  AlertDialogAction,\n  AlertDialogCancel\n}\n"
  },
  {
    "path": "components/ui/alert.tsx",
    "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst alertVariants = cva(\n  \"[&>svg]:text-foreground relative w-full rounded-lg border p-4 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-background text-foreground\",\n        destructive:\n          \"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive\"\n      }\n    },\n    defaultVariants: {\n      variant: \"default\"\n    }\n  }\n)\n\nconst Alert = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>\n>(({ className, variant, ...props }, ref) => (\n  <div\n    ref={ref}\n    role=\"alert\"\n    className={cn(alertVariants({ variant }), className)}\n    {...props}\n  />\n))\nAlert.displayName = \"Alert\"\n\nconst AlertTitle = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n  <h5\n    ref={ref}\n    className={cn(\"mb-1 font-medium leading-none tracking-tight\", className)}\n    {...props}\n  />\n))\nAlertTitle.displayName = \"AlertTitle\"\n\nconst AlertDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"text-sm [&_p]:leading-relaxed\", className)}\n    {...props}\n  />\n))\nAlertDescription.displayName = \"AlertDescription\"\n\nexport { Alert, AlertTitle, AlertDescription }\n"
  },
  {
    "path": "components/ui/aspect-ratio.tsx",
    "content": "\"use client\"\n\nimport * as AspectRatioPrimitive from \"@radix-ui/react-aspect-ratio\"\n\nconst AspectRatio = AspectRatioPrimitive.Root\n\nexport { AspectRatio }\n"
  },
  {
    "path": "components/ui/avatar.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Avatar = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative flex size-10 shrink-0 overflow-hidden rounded-full\",\n      className\n    )}\n    {...props}\n  />\n))\nAvatar.displayName = AvatarPrimitive.Root.displayName\n\nconst AvatarImage = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Image>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Image\n    ref={ref}\n    className={cn(\"aspect-square size-full\", className)}\n    {...props}\n  />\n))\nAvatarImage.displayName = AvatarPrimitive.Image.displayName\n\nconst AvatarFallback = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Fallback>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Fallback\n    ref={ref}\n    className={cn(\n      \"bg-muted flex size-full items-center justify-center rounded-full\",\n      className\n    )}\n    {...props}\n  />\n))\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName\n\nexport { Avatar, AvatarImage, AvatarFallback }\n"
  },
  {
    "path": "components/ui/badge.tsx",
    "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n  \"focus:ring-ring inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"bg-primary text-primary-foreground hover:bg-primary/80 border-transparent\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent\",\n        destructive:\n          \"bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent\",\n        outline: \"text-foreground\"\n      }\n    },\n    defaultVariants: {\n      variant: \"default\"\n    }\n  }\n)\n\nexport interface BadgeProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof badgeVariants> {}\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n  return (\n    <div className={cn(badgeVariants({ variant }), className)} {...props} />\n  )\n}\n\nexport { Badge, badgeVariants }\n"
  },
  {
    "path": "components/ui/brand.tsx",
    "content": "\"use client\"\n\nimport Link from \"next/link\"\nimport { FC } from \"react\"\nimport { ChatbotUISVG } from \"../icons/chatbotui-svg\"\n\ninterface BrandProps {\n  theme?: \"dark\" | \"light\"\n}\n\nexport const Brand: FC<BrandProps> = ({ theme = \"dark\" }) => {\n  return (\n    <Link\n      className=\"flex cursor-pointer flex-col items-center hover:opacity-50\"\n      href=\"https://www.chatbotui.com\"\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n    >\n      <div className=\"mb-2\">\n        <ChatbotUISVG theme={theme === \"dark\" ? \"dark\" : \"light\"} scale={0.3} />\n      </div>\n\n      <div className=\"text-4xl font-bold tracking-wide\">Chatbot UI</div>\n    </Link>\n  )\n}\n"
  },
  {
    "path": "components/ui/button.tsx",
    "content": "import { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n  \"ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors hover:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n        outline:\n          \"border-input bg-background hover:bg-accent hover:text-accent-foreground border\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\"\n      },\n      size: {\n        default: \"h-10 px-4 py-2\",\n        sm: \"h-9 rounded-md px-3\",\n        lg: \"h-11 rounded-md px-8\",\n        icon: \"size-10\"\n      }\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\"\n    }\n  }\n)\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : \"button\"\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "components/ui/calendar.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport { DayPicker } from \"react-day-picker\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\n\nexport type CalendarProps = React.ComponentProps<typeof DayPicker>\n\nfunction Calendar({\n  className,\n  classNames,\n  showOutsideDays = true,\n  ...props\n}: CalendarProps) {\n  return (\n    <DayPicker\n      showOutsideDays={showOutsideDays}\n      className={cn(\"p-3\", className)}\n      classNames={{\n        months: \"flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0\",\n        month: \"space-y-4\",\n        caption: \"flex justify-center pt-1 relative items-center\",\n        caption_label: \"text-sm font-medium\",\n        nav: \"space-x-1 flex items-center\",\n        nav_button: cn(\n          buttonVariants({ variant: \"outline\" }),\n          \"size-7 bg-transparent p-0 opacity-50 hover:opacity-100\"\n        ),\n        nav_button_previous: \"absolute left-1\",\n        nav_button_next: \"absolute right-1\",\n        table: \"w-full border-collapse space-y-1\",\n        head_row: \"flex\",\n        head_cell:\n          \"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]\",\n        row: \"flex w-full mt-2\",\n        cell: \"h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20\",\n        day: cn(\n          buttonVariants({ variant: \"ghost\" }),\n          \"size-9 p-0 font-normal aria-selected:opacity-100\"\n        ),\n        day_range_end: \"day-range-end\",\n        day_selected:\n          \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground\",\n        day_today: \"bg-accent text-accent-foreground\",\n        day_outside:\n          \"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30\",\n        day_disabled: \"text-muted-foreground opacity-50\",\n        day_range_middle:\n          \"aria-selected:bg-accent aria-selected:text-accent-foreground\",\n        day_hidden: \"invisible\",\n        ...classNames\n      }}\n      components={{\n        IconLeft: ({ ...props }) => <ChevronLeft className=\"size-4\" />,\n        IconRight: ({ ...props }) => <ChevronRight className=\"size-4\" />\n      }}\n      {...props}\n    />\n  )\n}\nCalendar.displayName = \"Calendar\"\n\nexport { Calendar }\n"
  },
  {
    "path": "components/ui/card.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      \"bg-card text-card-foreground rounded-lg border shadow-sm\",\n      className\n    )}\n    {...props}\n  />\n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n    {...props}\n  />\n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n  <h3\n    ref={ref}\n    className={cn(\n      \"text-2xl font-semibold leading-none tracking-tight\",\n      className\n    )}\n    {...props}\n  />\n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n  <p\n    ref={ref}\n    className={cn(\"text-muted-foreground text-sm\", className)}\n    {...props}\n  />\n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex items-center p-6 pt-0\", className)}\n    {...props}\n  />\n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\n"
  },
  {
    "path": "components/ui/chat-settings-form.tsx",
    "content": "\"use client\"\n\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { CHAT_SETTING_LIMITS } from \"@/lib/chat-setting-limits\"\nimport { ChatSettings } from \"@/types\"\nimport { IconInfoCircle } from \"@tabler/icons-react\"\nimport { FC, useContext } from \"react\"\nimport { ModelSelect } from \"../models/model-select\"\nimport { AdvancedSettings } from \"./advanced-settings\"\nimport { Checkbox } from \"./checkbox\"\nimport { Label } from \"./label\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue\n} from \"./select\"\nimport { Slider } from \"./slider\"\nimport { TextareaAutosize } from \"./textarea-autosize\"\nimport { WithTooltip } from \"./with-tooltip\"\n\ninterface ChatSettingsFormProps {\n  chatSettings: ChatSettings\n  onChangeChatSettings: (value: ChatSettings) => void\n  useAdvancedDropdown?: boolean\n  showTooltip?: boolean\n}\n\nexport const ChatSettingsForm: FC<ChatSettingsFormProps> = ({\n  chatSettings,\n  onChangeChatSettings,\n  useAdvancedDropdown = true,\n  showTooltip = true\n}) => {\n  const { profile, models } = useContext(ChatbotUIContext)\n\n  if (!profile) return null\n\n  return (\n    <div className=\"space-y-3\">\n      <div className=\"space-y-1\">\n        <Label>Model</Label>\n\n        <ModelSelect\n          selectedModelId={chatSettings.model}\n          onSelectModel={model => {\n            onChangeChatSettings({ ...chatSettings, model })\n          }}\n        />\n      </div>\n\n      <div className=\"space-y-1\">\n        <Label>Prompt</Label>\n\n        <TextareaAutosize\n          className=\"bg-background border-input border-2\"\n          placeholder=\"You are a helpful AI assistant.\"\n          onValueChange={prompt => {\n            onChangeChatSettings({ ...chatSettings, prompt })\n          }}\n          value={chatSettings.prompt}\n          minRows={3}\n          maxRows={6}\n        />\n      </div>\n\n      {useAdvancedDropdown ? (\n        <AdvancedSettings>\n          <AdvancedContent\n            chatSettings={chatSettings}\n            onChangeChatSettings={onChangeChatSettings}\n            showTooltip={showTooltip}\n          />\n        </AdvancedSettings>\n      ) : (\n        <div>\n          <AdvancedContent\n            chatSettings={chatSettings}\n            onChangeChatSettings={onChangeChatSettings}\n            showTooltip={showTooltip}\n          />\n        </div>\n      )}\n    </div>\n  )\n}\n\ninterface AdvancedContentProps {\n  chatSettings: ChatSettings\n  onChangeChatSettings: (value: ChatSettings) => void\n  showTooltip: boolean\n}\n\nconst AdvancedContent: FC<AdvancedContentProps> = ({\n  chatSettings,\n  onChangeChatSettings,\n  showTooltip\n}) => {\n  const { profile, selectedWorkspace, availableOpenRouterModels, models } =\n    useContext(ChatbotUIContext)\n\n  const isCustomModel = models.some(\n    model => model.model_id === chatSettings.model\n  )\n\n  function findOpenRouterModel(modelId: string) {\n    return availableOpenRouterModels.find(model => model.modelId === modelId)\n  }\n\n  const MODEL_LIMITS = CHAT_SETTING_LIMITS[chatSettings.model] || {\n    MIN_TEMPERATURE: 0,\n    MAX_TEMPERATURE: 1,\n    MAX_CONTEXT_LENGTH:\n      findOpenRouterModel(chatSettings.model)?.maxContext || 4096\n  }\n\n  return (\n    <div className=\"mt-5\">\n      <div className=\"space-y-3\">\n        <Label className=\"flex items-center space-x-1\">\n          <div>Temperature:</div>\n\n          <div>{chatSettings.temperature}</div>\n        </Label>\n\n        <Slider\n          value={[chatSettings.temperature]}\n          onValueChange={temperature => {\n            onChangeChatSettings({\n              ...chatSettings,\n              temperature: temperature[0]\n            })\n          }}\n          min={MODEL_LIMITS.MIN_TEMPERATURE}\n          max={MODEL_LIMITS.MAX_TEMPERATURE}\n          step={0.01}\n        />\n      </div>\n\n      <div className=\"mt-6 space-y-3\">\n        <Label className=\"flex items-center space-x-1\">\n          <div>Context Length:</div>\n\n          <div>{chatSettings.contextLength}</div>\n        </Label>\n\n        <Slider\n          value={[chatSettings.contextLength]}\n          onValueChange={contextLength => {\n            onChangeChatSettings({\n              ...chatSettings,\n              contextLength: contextLength[0]\n            })\n          }}\n          min={0}\n          max={\n            isCustomModel\n              ? models.find(model => model.model_id === chatSettings.model)\n                  ?.context_length\n              : MODEL_LIMITS.MAX_CONTEXT_LENGTH\n          }\n          step={1}\n        />\n      </div>\n\n      <div className=\"mt-7 flex items-center space-x-2\">\n        <Checkbox\n          checked={chatSettings.includeProfileContext}\n          onCheckedChange={(value: boolean) =>\n            onChangeChatSettings({\n              ...chatSettings,\n              includeProfileContext: value\n            })\n          }\n        />\n\n        <Label>Chats Include Profile Context</Label>\n\n        {showTooltip && (\n          <WithTooltip\n            delayDuration={0}\n            display={\n              <div className=\"w-[400px] p-3\">\n                {profile?.profile_context || \"No profile context.\"}\n              </div>\n            }\n            trigger={\n              <IconInfoCircle className=\"cursor-hover:opacity-50\" size={16} />\n            }\n          />\n        )}\n      </div>\n\n      <div className=\"mt-4 flex items-center space-x-2\">\n        <Checkbox\n          checked={chatSettings.includeWorkspaceInstructions}\n          onCheckedChange={(value: boolean) =>\n            onChangeChatSettings({\n              ...chatSettings,\n              includeWorkspaceInstructions: value\n            })\n          }\n        />\n\n        <Label>Chats Include Workspace Instructions</Label>\n\n        {showTooltip && (\n          <WithTooltip\n            delayDuration={0}\n            display={\n              <div className=\"w-[400px] p-3\">\n                {selectedWorkspace?.instructions ||\n                  \"No workspace instructions.\"}\n              </div>\n            }\n            trigger={\n              <IconInfoCircle className=\"cursor-hover:opacity-50\" size={16} />\n            }\n          />\n        )}\n      </div>\n\n      <div className=\"mt-5\">\n        <Label>Embeddings Provider</Label>\n\n        <Select\n          value={chatSettings.embeddingsProvider}\n          onValueChange={(embeddingsProvider: \"openai\" | \"local\") => {\n            onChangeChatSettings({\n              ...chatSettings,\n              embeddingsProvider\n            })\n          }}\n        >\n          <SelectTrigger>\n            <SelectValue defaultValue=\"openai\" />\n          </SelectTrigger>\n\n          <SelectContent>\n            <SelectItem value=\"openai\">\n              {profile?.use_azure_openai ? \"Azure OpenAI\" : \"OpenAI\"}\n            </SelectItem>\n\n            {window.location.hostname === \"localhost\" && (\n              <SelectItem value=\"local\">Local</SelectItem>\n            )}\n          </SelectContent>\n        </Select>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/ui/checkbox.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Checkbox = React.forwardRef<\n  React.ElementRef<typeof CheckboxPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <CheckboxPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer size-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <CheckboxPrimitive.Indicator\n      className={cn(\"flex items-center justify-center text-current\")}\n    >\n      <Check className=\"size-4\" />\n    </CheckboxPrimitive.Indicator>\n  </CheckboxPrimitive.Root>\n))\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n"
  },
  {
    "path": "components/ui/collapsible.tsx",
    "content": "\"use client\"\n\nimport * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\"\n\nconst Collapsible = CollapsiblePrimitive.Root\n\nconst CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger\n\nconst CollapsibleContent = CollapsiblePrimitive.CollapsibleContent\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent }\n"
  },
  {
    "path": "components/ui/command.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DialogProps } from \"@radix-ui/react-dialog\"\nimport { Command as CommandPrimitive } from \"cmdk\"\nimport { Search } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Dialog, DialogContent } from \"@/components/ui/dialog\"\n\nconst Command = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive\n    ref={ref}\n    className={cn(\n      \"bg-popover text-popover-foreground flex size-full flex-col overflow-hidden rounded-md\",\n      className\n    )}\n    {...props}\n  />\n))\nCommand.displayName = CommandPrimitive.displayName\n\ninterface CommandDialogProps extends DialogProps {}\n\nconst CommandDialog = ({ children, ...props }: CommandDialogProps) => {\n  return (\n    <Dialog {...props}>\n      <DialogContent className=\"overflow-hidden p-0 shadow-lg\">\n        <Command className=\"[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:size-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:size-5\">\n          {children}\n        </Command>\n      </DialogContent>\n    </Dialog>\n  )\n}\n\nconst CommandInput = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Input>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>\n>(({ className, ...props }, ref) => (\n  <div className=\"flex items-center border-b px-3\" cmdk-input-wrapper=\"\">\n    <Search className=\"mr-2 size-4 shrink-0 opacity-50\" />\n    <CommandPrimitive.Input\n      ref={ref}\n      className={cn(\n        \"placeholder:text-muted-foreground flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  </div>\n))\n\nCommandInput.displayName = CommandPrimitive.Input.displayName\n\nconst CommandList = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive.List\n    ref={ref}\n    className={cn(\"max-h-[300px] overflow-y-auto overflow-x-hidden\", className)}\n    {...props}\n  />\n))\n\nCommandList.displayName = CommandPrimitive.List.displayName\n\nconst CommandEmpty = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Empty>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>\n>((props, ref) => (\n  <CommandPrimitive.Empty\n    ref={ref}\n    className=\"py-6 text-center text-sm\"\n    {...props}\n  />\n))\n\nCommandEmpty.displayName = CommandPrimitive.Empty.displayName\n\nconst CommandGroup = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Group>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive.Group\n    ref={ref}\n    className={cn(\n      \"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium\",\n      className\n    )}\n    {...props}\n  />\n))\n\nCommandGroup.displayName = CommandPrimitive.Group.displayName\n\nconst CommandSeparator = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive.Separator\n    ref={ref}\n    className={cn(\"bg-border -mx-1 h-px\", className)}\n    {...props}\n  />\n))\nCommandSeparator.displayName = CommandPrimitive.Separator.displayName\n\nconst CommandItem = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  />\n))\n\nCommandItem.displayName = CommandPrimitive.Item.displayName\n\nconst CommandShortcut = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\n        \"text-muted-foreground ml-auto text-xs tracking-widest\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\nCommandShortcut.displayName = \"CommandShortcut\"\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator\n}\n"
  },
  {
    "path": "components/ui/context-menu.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ContextMenuPrimitive from \"@radix-ui/react-context-menu\"\nimport { Check, ChevronRight, Circle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ContextMenu = ContextMenuPrimitive.Root\n\nconst ContextMenuTrigger = ContextMenuPrimitive.Trigger\n\nconst ContextMenuGroup = ContextMenuPrimitive.Group\n\nconst ContextMenuPortal = ContextMenuPrimitive.Portal\n\nconst ContextMenuSub = ContextMenuPrimitive.Sub\n\nconst ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup\n\nconst ContextMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {\n    inset?: boolean\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <ContextMenuPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto size-4\" />\n  </ContextMenuPrimitive.SubTrigger>\n))\nContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName\n\nconst ContextMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <ContextMenuPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-md\",\n      className\n    )}\n    {...props}\n  />\n))\nContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName\n\nconst ContextMenuContent = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <ContextMenuPrimitive.Portal>\n    <ContextMenuPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"bg-popover text-popover-foreground animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-md\",\n        className\n      )}\n      {...props}\n    />\n  </ContextMenuPrimitive.Portal>\n))\nContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName\n\nconst ContextMenuItem = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <ContextMenuPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName\n\nconst ContextMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <ContextMenuPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    checked={checked}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n      <ContextMenuPrimitive.ItemIndicator>\n        <Check className=\"size-4\" />\n      </ContextMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </ContextMenuPrimitive.CheckboxItem>\n))\nContextMenuCheckboxItem.displayName =\n  ContextMenuPrimitive.CheckboxItem.displayName\n\nconst ContextMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <ContextMenuPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n      <ContextMenuPrimitive.ItemIndicator>\n        <Circle className=\"size-2 fill-current\" />\n      </ContextMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </ContextMenuPrimitive.RadioItem>\n))\nContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName\n\nconst ContextMenuLabel = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <ContextMenuPrimitive.Label\n    ref={ref}\n    className={cn(\n      \"text-foreground px-2 py-1.5 text-sm font-semibold\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName\n\nconst ContextMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <ContextMenuPrimitive.Separator\n    ref={ref}\n    className={cn(\"bg-border -mx-1 my-1 h-px\", className)}\n    {...props}\n  />\n))\nContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName\n\nconst ContextMenuShortcut = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\n        \"text-muted-foreground ml-auto text-xs tracking-widest\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\nContextMenuShortcut.displayName = \"ContextMenuShortcut\"\n\nexport {\n  ContextMenu,\n  ContextMenuTrigger,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuCheckboxItem,\n  ContextMenuRadioItem,\n  ContextMenuLabel,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuGroup,\n  ContextMenuPortal,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuRadioGroup\n}\n"
  },
  {
    "path": "components/ui/dashboard.tsx",
    "content": "\"use client\"\n\nimport { Sidebar } from \"@/components/sidebar/sidebar\"\nimport { SidebarSwitcher } from \"@/components/sidebar/sidebar-switcher\"\nimport { Button } from \"@/components/ui/button\"\nimport { Tabs } from \"@/components/ui/tabs\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { cn } from \"@/lib/utils\"\nimport { ContentType } from \"@/types\"\nimport { IconChevronCompactRight } from \"@tabler/icons-react\"\nimport { usePathname, useRouter, useSearchParams } from \"next/navigation\"\nimport { FC, useState } from \"react\"\nimport { useSelectFileHandler } from \"../chat/chat-hooks/use-select-file-handler\"\nimport { CommandK } from \"../utility/command-k\"\n\nexport const SIDEBAR_WIDTH = 350\n\ninterface DashboardProps {\n  children: React.ReactNode\n}\n\nexport const Dashboard: FC<DashboardProps> = ({ children }) => {\n  useHotkey(\"s\", () => setShowSidebar(prevState => !prevState))\n\n  const pathname = usePathname()\n  const router = useRouter()\n  const searchParams = useSearchParams()\n  const tabValue = searchParams.get(\"tab\") || \"chats\"\n\n  const { handleSelectDeviceFile } = useSelectFileHandler()\n\n  const [contentType, setContentType] = useState<ContentType>(\n    tabValue as ContentType\n  )\n  const [showSidebar, setShowSidebar] = useState(\n    localStorage.getItem(\"showSidebar\") === \"true\"\n  )\n  const [isDragging, setIsDragging] = useState(false)\n\n  const onFileDrop = (event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault()\n\n    const files = event.dataTransfer.files\n    const file = files[0]\n\n    handleSelectDeviceFile(file)\n\n    setIsDragging(false)\n  }\n\n  const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault()\n    setIsDragging(true)\n  }\n\n  const handleDragLeave = (event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault()\n    setIsDragging(false)\n  }\n\n  const onDragOver = (event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault()\n  }\n\n  const handleToggleSidebar = () => {\n    setShowSidebar(prevState => !prevState)\n    localStorage.setItem(\"showSidebar\", String(!showSidebar))\n  }\n\n  return (\n    <div className=\"flex size-full\">\n      <CommandK />\n\n      <div\n        className={cn(\n          \"duration-200 dark:border-none \" + (showSidebar ? \"border-r-2\" : \"\")\n        )}\n        style={{\n          // Sidebar\n          minWidth: showSidebar ? `${SIDEBAR_WIDTH}px` : \"0px\",\n          maxWidth: showSidebar ? `${SIDEBAR_WIDTH}px` : \"0px\",\n          width: showSidebar ? `${SIDEBAR_WIDTH}px` : \"0px\"\n        }}\n      >\n        {showSidebar && (\n          <Tabs\n            className=\"flex h-full\"\n            value={contentType}\n            onValueChange={tabValue => {\n              setContentType(tabValue as ContentType)\n              router.replace(`${pathname}?tab=${tabValue}`)\n            }}\n          >\n            <SidebarSwitcher onContentTypeChange={setContentType} />\n\n            <Sidebar contentType={contentType} showSidebar={showSidebar} />\n          </Tabs>\n        )}\n      </div>\n\n      <div\n        className=\"bg-muted/50 relative flex w-screen min-w-[90%] grow flex-col sm:min-w-fit\"\n        onDrop={onFileDrop}\n        onDragOver={onDragOver}\n        onDragEnter={handleDragEnter}\n        onDragLeave={handleDragLeave}\n      >\n        {isDragging ? (\n          <div className=\"flex h-full items-center justify-center bg-black/50 text-2xl text-white\">\n            drop file here\n          </div>\n        ) : (\n          children\n        )}\n\n        <Button\n          className={cn(\n            \"absolute left-[4px] top-[50%] z-10 size-[32px] cursor-pointer\"\n          )}\n          style={{\n            // marginLeft: showSidebar ? `${SIDEBAR_WIDTH}px` : \"0px\",\n            transform: showSidebar ? \"rotate(180deg)\" : \"rotate(0deg)\"\n          }}\n          variant=\"ghost\"\n          size=\"icon\"\n          onClick={handleToggleSidebar}\n        >\n          <IconChevronCompactRight size={24} />\n        </Button>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/ui/dialog.tsx",
    "content": "\"use client\"\n\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Dialog = DialogPrimitive.Root\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = DialogPrimitive.Portal\n\nconst DialogClose = DialogPrimitive.Close\n\nconst DialogOverlay = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Overlay\n    ref={ref}\n    className={cn(\n      \"bg-background/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 backdrop-blur-sm\",\n      className\n    )}\n    {...props}\n  />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst DialogContent = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <DialogPortal>\n    <DialogOverlay />\n    <DialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid max-h-[calc(100%-60px)] w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      {/* <DialogPrimitive.Close className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </DialogPrimitive.Close> */}\n    </DialogPrimitive.Content>\n  </DialogPortal>\n))\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\nconst DialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-1.5 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogHeader.displayName = \"DialogHeader\"\n\nconst DialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogFooter.displayName = \"DialogFooter\"\n\nconst DialogTitle = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Title\n    ref={ref}\n    className={cn(\n      \"text-lg font-semibold leading-none tracking-tight\",\n      className\n    )}\n    {...props}\n  />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-muted-foreground text-sm\", className)}\n    {...props}\n  />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger\n}\n"
  },
  {
    "path": "components/ui/dropdown-menu.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { Check, ChevronRight, Circle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst DropdownMenu = DropdownMenuPrimitive.Root\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n    inset?: boolean\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto size-4\" />\n  </DropdownMenuPrimitive.SubTrigger>\n))\nDropdownMenuSubTrigger.displayName =\n  DropdownMenuPrimitive.SubTrigger.displayName\n\nconst DropdownMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuSubContent.displayName =\n  DropdownMenuPrimitive.SubContent.displayName\n\nconst DropdownMenuContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <DropdownMenuPrimitive.Portal>\n    <DropdownMenuPrimitive.Content\n      ref={ref}\n      sideOffset={sideOffset}\n      className={cn(\n        \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-md\",\n        className\n      )}\n      {...props}\n    />\n  </DropdownMenuPrimitive.Portal>\n))\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\n\nconst DropdownMenuItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <DropdownMenuPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    checked={checked}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Check className=\"size-4\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.CheckboxItem>\n))\nDropdownMenuCheckboxItem.displayName =\n  DropdownMenuPrimitive.CheckboxItem.displayName\n\nconst DropdownMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Circle className=\"size-2 fill-current\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.RadioItem>\n))\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName\n\nconst DropdownMenuLabel = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Label\n    ref={ref}\n    className={cn(\n      \"px-2 py-1.5 text-sm font-semibold\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\n\nconst DropdownMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.Separator\n    ref={ref}\n    className={cn(\"bg-muted -mx-1 my-1 h-px\", className)}\n    {...props}\n  />\n))\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\n\nconst DropdownMenuShortcut = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\"ml-auto text-xs tracking-widest opacity-60\", className)}\n      {...props}\n    />\n  )\n}\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\"\n\nexport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuGroup,\n  DropdownMenuPortal,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuRadioGroup\n}\n"
  },
  {
    "path": "components/ui/file-icon.tsx",
    "content": "import {\n  IconFile,\n  IconFileText,\n  IconFileTypeCsv,\n  IconFileTypeDocx,\n  IconFileTypePdf,\n  IconJson,\n  IconMarkdown,\n  IconPhoto\n} from \"@tabler/icons-react\"\nimport { FC } from \"react\"\n\ninterface FileIconProps {\n  type: string\n  size?: number\n}\n\nexport const FileIcon: FC<FileIconProps> = ({ type, size = 32 }) => {\n  if (type.includes(\"image\")) {\n    return <IconPhoto size={size} />\n  } else if (type.includes(\"pdf\")) {\n    return <IconFileTypePdf size={size} />\n  } else if (type.includes(\"csv\")) {\n    return <IconFileTypeCsv size={size} />\n  } else if (type.includes(\"docx\")) {\n    return <IconFileTypeDocx size={size} />\n  } else if (type.includes(\"plain\")) {\n    return <IconFileText size={size} />\n  } else if (type.includes(\"json\")) {\n    return <IconJson size={size} />\n  } else if (type.includes(\"markdown\")) {\n    return <IconMarkdown size={size} />\n  } else {\n    return <IconFile size={size} />\n  }\n}\n"
  },
  {
    "path": "components/ui/file-preview.tsx",
    "content": "import { cn } from \"@/lib/utils\"\nimport { Tables } from \"@/supabase/types\"\nimport { ChatFile, MessageImage } from \"@/types\"\nimport { IconFileFilled } from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { FC } from \"react\"\nimport { DrawingCanvas } from \"../utility/drawing-canvas\"\nimport { Dialog, DialogContent } from \"./dialog\"\n\ninterface FilePreviewProps {\n  type: \"image\" | \"file\" | \"file_item\"\n  item: ChatFile | MessageImage | Tables<\"file_items\">\n  isOpen: boolean\n  onOpenChange: (isOpen: boolean) => void\n}\n\nexport const FilePreview: FC<FilePreviewProps> = ({\n  type,\n  item,\n  isOpen,\n  onOpenChange\n}) => {\n  return (\n    <Dialog open={isOpen} onOpenChange={onOpenChange}>\n      <DialogContent\n        className={cn(\n          \"flex items-center justify-center outline-none\",\n          \"border-transparent bg-transparent\"\n        )}\n      >\n        {(() => {\n          if (type === \"image\") {\n            const imageItem = item as MessageImage\n\n            return imageItem.file ? (\n              <DrawingCanvas imageItem={imageItem} />\n            ) : (\n              <Image\n                className=\"rounded\"\n                src={imageItem.base64 || imageItem.url}\n                alt=\"File image\"\n                width={2000}\n                height={2000}\n                style={{\n                  maxHeight: \"67vh\",\n                  maxWidth: \"67vw\"\n                }}\n              />\n            )\n          } else if (type === \"file_item\") {\n            const fileItem = item as Tables<\"file_items\">\n            return (\n              <div className=\"bg-background text-primary h-[50vh] min-w-[700px] overflow-auto whitespace-pre-wrap rounded-xl p-4\">\n                <div>{fileItem.content}</div>\n              </div>\n            )\n          } else if (type === \"file\") {\n            return (\n              <div className=\"rounded bg-blue-500 p-2\">\n                <IconFileFilled />\n              </div>\n            )\n          }\n        })()}\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "components/ui/form.tsx",
    "content": "import * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport {\n  Controller,\n  ControllerProps,\n  FieldPath,\n  FieldValues,\n  FormProvider,\n  useFormContext\n} from \"react-hook-form\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/components/ui/label\"\n\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n> = {\n  name: TName\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\n  {} as FormFieldContextValue\n)\n\nconst FormField = <\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n>({\n  ...props\n}: ControllerProps<TFieldValues, TName>) => {\n  return (\n    <FormFieldContext.Provider value={{ name: props.name }}>\n      <Controller {...props} />\n    </FormFieldContext.Provider>\n  )\n}\n\nconst useFormField = () => {\n  const fieldContext = React.useContext(FormFieldContext)\n  const itemContext = React.useContext(FormItemContext)\n  const { getFieldState, formState } = useFormContext()\n\n  const fieldState = getFieldState(fieldContext.name, formState)\n\n  if (!fieldContext) {\n    throw new Error(\"useFormField should be used within <FormField>\")\n  }\n\n  const { id } = itemContext\n\n  return {\n    id,\n    name: fieldContext.name,\n    formItemId: `${id}-form-item`,\n    formDescriptionId: `${id}-form-item-description`,\n    formMessageId: `${id}-form-item-message`,\n    ...fieldState\n  }\n}\n\ntype FormItemContextValue = {\n  id: string\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue>(\n  {} as FormItemContextValue\n)\n\nconst FormItem = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n  const id = React.useId()\n\n  return (\n    <FormItemContext.Provider value={{ id }}>\n      <div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\n    </FormItemContext.Provider>\n  )\n})\nFormItem.displayName = \"FormItem\"\n\nconst FormLabel = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => {\n  const { error, formItemId } = useFormField()\n\n  return (\n    <Label\n      ref={ref}\n      className={cn(error && \"text-destructive\", className)}\n      htmlFor={formItemId}\n      {...props}\n    />\n  )\n})\nFormLabel.displayName = \"FormLabel\"\n\nconst FormControl = React.forwardRef<\n  React.ElementRef<typeof Slot>,\n  React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n  return (\n    <Slot\n      ref={ref}\n      id={formItemId}\n      aria-describedby={\n        !error\n          ? `${formDescriptionId}`\n          : `${formDescriptionId} ${formMessageId}`\n      }\n      aria-invalid={!!error}\n      {...props}\n    />\n  )\n})\nFormControl.displayName = \"FormControl\"\n\nconst FormDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n  const { formDescriptionId } = useFormField()\n\n  return (\n    <p\n      ref={ref}\n      id={formDescriptionId}\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  )\n})\nFormDescription.displayName = \"FormDescription\"\n\nconst FormMessage = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n  const { error, formMessageId } = useFormField()\n  const body = error ? String(error?.message) : children\n\n  if (!body) {\n    return null\n  }\n\n  return (\n    <p\n      ref={ref}\n      id={formMessageId}\n      className={cn(\"text-destructive text-sm font-medium\", className)}\n      {...props}\n    >\n      {body}\n    </p>\n  )\n})\nFormMessage.displayName = \"FormMessage\"\n\nexport {\n  useFormField,\n  Form,\n  FormItem,\n  FormLabel,\n  FormControl,\n  FormDescription,\n  FormMessage,\n  FormField\n}\n"
  },
  {
    "path": "components/ui/hover-card.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as HoverCardPrimitive from \"@radix-ui/react-hover-card\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst HoverCard = HoverCardPrimitive.Root\n\nconst HoverCardTrigger = HoverCardPrimitive.Trigger\n\nconst HoverCardContent = React.forwardRef<\n  React.ElementRef<typeof HoverCardPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = 4, ...props }, ref) => (\n  <HoverCardPrimitive.Content\n    ref={ref}\n    align={align}\n    sideOffset={sideOffset}\n    className={cn(\n      \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 rounded-md border p-4 shadow-md outline-none\",\n      className\n    )}\n    {...props}\n  />\n))\nHoverCardContent.displayName = HoverCardPrimitive.Content.displayName\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent }\n"
  },
  {
    "path": "components/ui/image-picker.tsx",
    "content": "import Image from \"next/image\"\nimport { ChangeEvent, FC, useState } from \"react\"\nimport { toast } from \"sonner\"\nimport { Input } from \"./input\"\n\ninterface ImagePickerProps {\n  src: string\n  image: File | null\n  onSrcChange: (src: string) => void\n  onImageChange: (image: File) => void\n  width?: number\n  height?: number\n}\n\nconst ImagePicker: FC<ImagePickerProps> = ({\n  src,\n  image,\n  onSrcChange,\n  onImageChange,\n  width = 200,\n  height = 200\n}) => {\n  const [previewSrc, setPreviewSrc] = useState<string>(src)\n  const [previewImage, setPreviewImage] = useState<File | null>(image)\n\n  const handleImageSelect = (e: ChangeEvent<HTMLInputElement>) => {\n    if (e.target.files) {\n      const file = e.target.files[0]\n\n      if (file.size > 6000000) {\n        toast.error(\"Image must be less than 6MB!\")\n        return\n      }\n\n      const url = URL.createObjectURL(file)\n\n      const img = new window.Image()\n      img.src = url\n\n      img.onload = () => {\n        const canvas = document.createElement(\"canvas\")\n        const ctx = canvas.getContext(\"2d\")\n\n        if (!ctx) {\n          toast.error(\"Unable to create canvas context.\")\n          return\n        }\n\n        const size = Math.min(img.width, img.height)\n        canvas.width = size\n        canvas.height = size\n\n        ctx.drawImage(\n          img,\n          (img.width - size) / 2,\n          (img.height - size) / 2,\n          size,\n          size,\n          0,\n          0,\n          size,\n          size\n        )\n\n        const squareUrl = canvas.toDataURL()\n\n        setPreviewSrc(squareUrl)\n        setPreviewImage(file)\n        onSrcChange(squareUrl)\n        onImageChange(file)\n      }\n    }\n  }\n\n  return (\n    <div>\n      {previewSrc && (\n        <Image\n          style={{ width: `${width}px`, height: `${width}px` }}\n          className=\"rounded\"\n          height={width}\n          width={width}\n          src={previewSrc}\n          alt={\"Image\"}\n        />\n      )}\n\n      <Input\n        className=\"mt-1 cursor-pointer hover:opacity-50\"\n        type=\"file\"\n        accept=\"image/png, image/jpeg, image/jpg\"\n        onChange={handleImageSelect}\n      />\n    </div>\n  )\n}\n\nexport default ImagePicker\n"
  },
  {
    "path": "components/ui/input.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n  extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n  ({ className, type, ...props }, ref) => {\n    return (\n      <input\n        type={type}\n        className={cn(\n          \"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:none flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n"
  },
  {
    "path": "components/ui/label.tsx",
    "content": "\"use client\"\n\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst labelVariants = cva(\n  \"text-sm font-semibold leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n)\n\nconst Label = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n    VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n  <LabelPrimitive.Root\n    ref={ref}\n    className={cn(labelVariants(), className)}\n    {...props}\n  />\n))\nLabel.displayName = LabelPrimitive.Root.displayName\n\nexport { Label }\n"
  },
  {
    "path": "components/ui/limit-display.tsx",
    "content": "import { FC } from \"react\"\n\ninterface LimitDisplayProps {\n  used: number\n  limit: number\n}\n\nexport const LimitDisplay: FC<LimitDisplayProps> = ({ used, limit }) => {\n  return (\n    <div className=\"text-xs italic\">\n      {used}/{limit}\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/ui/menubar.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as MenubarPrimitive from \"@radix-ui/react-menubar\"\nimport { Check, ChevronRight, Circle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst MenubarMenu = MenubarPrimitive.Menu\n\nconst MenubarGroup = MenubarPrimitive.Group\n\nconst MenubarPortal = MenubarPrimitive.Portal\n\nconst MenubarSub = MenubarPrimitive.Sub\n\nconst MenubarRadioGroup = MenubarPrimitive.RadioGroup\n\nconst Menubar = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"bg-background flex h-10 items-center space-x-1 rounded-md border p-1\",\n      className\n    )}\n    {...props}\n  />\n))\nMenubar.displayName = MenubarPrimitive.Root.displayName\n\nconst MenubarTrigger = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none\",\n      className\n    )}\n    {...props}\n  />\n))\nMenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName\n\nconst MenubarSubTrigger = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {\n    inset?: boolean\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <MenubarPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto size-4\" />\n  </MenubarPrimitive.SubTrigger>\n))\nMenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName\n\nconst MenubarSubContent = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1\",\n      className\n    )}\n    {...props}\n  />\n))\nMenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName\n\nconst MenubarContent = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>\n>(\n  (\n    { className, align = \"start\", alignOffset = -4, sideOffset = 8, ...props },\n    ref\n  ) => (\n    <MenubarPrimitive.Portal>\n      <MenubarPrimitive.Content\n        ref={ref}\n        align={align}\n        alignOffset={alignOffset}\n        sideOffset={sideOffset}\n        className={cn(\n          \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-48 overflow-hidden rounded-md border p-1 shadow-md\",\n          className\n        )}\n        {...props}\n      />\n    </MenubarPrimitive.Portal>\n  )\n)\nMenubarContent.displayName = MenubarPrimitive.Content.displayName\n\nconst MenubarItem = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <MenubarPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nMenubarItem.displayName = MenubarPrimitive.Item.displayName\n\nconst MenubarCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <MenubarPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    checked={checked}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n      <MenubarPrimitive.ItemIndicator>\n        <Check className=\"size-4\" />\n      </MenubarPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </MenubarPrimitive.CheckboxItem>\n))\nMenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName\n\nconst MenubarRadioItem = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <MenubarPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n      <MenubarPrimitive.ItemIndicator>\n        <Circle className=\"size-2 fill-current\" />\n      </MenubarPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </MenubarPrimitive.RadioItem>\n))\nMenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName\n\nconst MenubarLabel = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <MenubarPrimitive.Label\n    ref={ref}\n    className={cn(\n      \"px-2 py-1.5 text-sm font-semibold\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nMenubarLabel.displayName = MenubarPrimitive.Label.displayName\n\nconst MenubarSeparator = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.Separator\n    ref={ref}\n    className={cn(\"bg-muted -mx-1 my-1 h-px\", className)}\n    {...props}\n  />\n))\nMenubarSeparator.displayName = MenubarPrimitive.Separator.displayName\n\nconst MenubarShortcut = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\n        \"text-muted-foreground ml-auto text-xs tracking-widest\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\nMenubarShortcut.displayname = \"MenubarShortcut\"\n\nexport {\n  Menubar,\n  MenubarMenu,\n  MenubarTrigger,\n  MenubarContent,\n  MenubarItem,\n  MenubarSeparator,\n  MenubarLabel,\n  MenubarCheckboxItem,\n  MenubarRadioGroup,\n  MenubarRadioItem,\n  MenubarPortal,\n  MenubarSubContent,\n  MenubarSubTrigger,\n  MenubarGroup,\n  MenubarSub,\n  MenubarShortcut\n}\n"
  },
  {
    "path": "components/ui/navigation-menu.tsx",
    "content": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva } from \"class-variance-authority\"\nimport { ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst NavigationMenu = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n  <NavigationMenuPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative z-10 flex max-w-max flex-1 items-center justify-center\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <NavigationMenuViewport />\n  </NavigationMenuPrimitive.Root>\n))\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName\n\nconst NavigationMenuList = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.List\n    ref={ref}\n    className={cn(\n      \"group flex flex-1 list-none items-center justify-center space-x-1\",\n      className\n    )}\n    {...props}\n  />\n))\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item\n\nconst navigationMenuTriggerStyle = cva(\n  \"bg-background hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[active]:bg-accent/50 data-[state=open]:bg-accent/50 group inline-flex h-10 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors focus:outline-none disabled:pointer-events-none disabled:opacity-50\"\n)\n\nconst NavigationMenuTrigger = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <NavigationMenuPrimitive.Trigger\n    ref={ref}\n    className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n    {...props}\n  >\n    {children}{\" \"}\n    <ChevronDown\n      className=\"relative top-px ml-1 size-3 transition duration-200 group-data-[state=open]:rotate-180\"\n      aria-hidden=\"true\"\n    />\n  </NavigationMenuPrimitive.Trigger>\n))\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName\n\nconst NavigationMenuContent = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.Content\n    ref={ref}\n    className={cn(\n      \"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 left-0 top-0 w-full md:absolute md:w-auto \",\n      className\n    )}\n    {...props}\n  />\n))\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link\n\nconst NavigationMenuViewport = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>\n>(({ className, ...props }, ref) => (\n  <div className={cn(\"absolute left-0 top-full flex justify-center\")}>\n    <NavigationMenuPrimitive.Viewport\n      className={cn(\n        \"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow-lg md:w-[var(--radix-navigation-menu-viewport-width)]\",\n        className\n      )}\n      ref={ref}\n      {...props}\n    />\n  </div>\n))\nNavigationMenuViewport.displayName =\n  NavigationMenuPrimitive.Viewport.displayName\n\nconst NavigationMenuIndicator = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.Indicator\n    ref={ref}\n    className={cn(\n      \"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden\",\n      className\n    )}\n    {...props}\n  >\n    <div className=\"bg-border relative top-[60%] size-2 rotate-45 rounded-tl-sm shadow-md\" />\n  </NavigationMenuPrimitive.Indicator>\n))\nNavigationMenuIndicator.displayName =\n  NavigationMenuPrimitive.Indicator.displayName\n\nexport {\n  navigationMenuTriggerStyle,\n  NavigationMenu,\n  NavigationMenuList,\n  NavigationMenuItem,\n  NavigationMenuContent,\n  NavigationMenuTrigger,\n  NavigationMenuLink,\n  NavigationMenuIndicator,\n  NavigationMenuViewport\n}\n"
  },
  {
    "path": "components/ui/popover.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Popover = PopoverPrimitive.Root\n\nconst PopoverTrigger = PopoverPrimitive.Trigger\n\nconst PopoverContent = React.forwardRef<\n  React.ElementRef<typeof PopoverPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = 4, ...props }, ref) => (\n  <PopoverPrimitive.Portal>\n    <PopoverPrimitive.Content\n      ref={ref}\n      align={align}\n      sideOffset={sideOffset}\n      className={cn(\n        \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none\",\n        className\n      )}\n      {...props}\n    />\n  </PopoverPrimitive.Portal>\n))\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\nexport { Popover, PopoverTrigger, PopoverContent }\n"
  },
  {
    "path": "components/ui/progress.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Progress = React.forwardRef<\n  React.ElementRef<typeof ProgressPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>\n>(({ className, value, ...props }, ref) => (\n  <ProgressPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"bg-secondary relative h-4 w-full overflow-hidden rounded-full\",\n      className\n    )}\n    {...props}\n  >\n    <ProgressPrimitive.Indicator\n      className=\"bg-primary size-full flex-1 transition-all\"\n      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n    />\n  </ProgressPrimitive.Root>\n))\nProgress.displayName = ProgressPrimitive.Root.displayName\n\nexport { Progress }\n"
  },
  {
    "path": "components/ui/radio-group.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\"\nimport { Circle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst RadioGroup = React.forwardRef<\n  React.ElementRef<typeof RadioGroupPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => {\n  return (\n    <RadioGroupPrimitive.Root\n      className={cn(\"grid gap-2\", className)}\n      {...props}\n      ref={ref}\n    />\n  )\n})\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName\n\nconst RadioGroupItem = React.forwardRef<\n  React.ElementRef<typeof RadioGroupPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, ref) => {\n  return (\n    <RadioGroupPrimitive.Item\n      ref={ref}\n      className={cn(\n        \"border-primary text-primary ring-offset-background focus-visible:ring-ring aspect-square size-4 rounded-full border focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      <RadioGroupPrimitive.Indicator className=\"flex items-center justify-center\">\n        <Circle className=\"size-2.5 fill-current text-current\" />\n      </RadioGroupPrimitive.Indicator>\n    </RadioGroupPrimitive.Item>\n  )\n})\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName\n\nexport { RadioGroup, RadioGroupItem }\n"
  },
  {
    "path": "components/ui/screen-loader.tsx",
    "content": "import { IconLoader2 } from \"@tabler/icons-react\"\nimport { FC } from \"react\"\n\ninterface ScreenLoaderProps {}\n\nexport const ScreenLoader: FC<ScreenLoaderProps> = () => {\n  return (\n    <div className=\"flex size-full flex-col items-center justify-center\">\n      <IconLoader2 className=\"mt-4 size-12 animate-spin\" />\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/ui/scroll-area.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ScrollArea = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n  <ScrollAreaPrimitive.Root\n    ref={ref}\n    className={cn(\"relative overflow-hidden\", className)}\n    {...props}\n  >\n    <ScrollAreaPrimitive.Viewport className=\"size-full rounded-[inherit]\">\n      {children}\n    </ScrollAreaPrimitive.Viewport>\n    <ScrollBar />\n    <ScrollAreaPrimitive.Corner />\n  </ScrollAreaPrimitive.Root>\n))\nScrollArea.displayName = ScrollAreaPrimitive.Root.displayName\n\nconst ScrollBar = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>\n>(({ className, orientation = \"vertical\", ...props }, ref) => (\n  <ScrollAreaPrimitive.ScrollAreaScrollbar\n    ref={ref}\n    orientation={orientation}\n    className={cn(\n      \"flex touch-none select-none transition-colors\",\n      orientation === \"vertical\" &&\n        \"h-full w-2.5 border-l border-l-transparent p-px\",\n      orientation === \"horizontal\" &&\n        \"h-2.5 flex-col border-t border-t-transparent p-px\",\n      className\n    )}\n    {...props}\n  >\n    <ScrollAreaPrimitive.ScrollAreaThumb className=\"bg-border relative flex-1 rounded-full\" />\n  </ScrollAreaPrimitive.ScrollAreaScrollbar>\n))\nScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName\n\nexport { ScrollArea, ScrollBar }\n"
  },
  {
    "path": "components/ui/select.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Select = SelectPrimitive.Root\n\nconst SelectGroup = SelectPrimitive.Group\n\nconst SelectValue = SelectPrimitive.Value\n\nconst SelectTrigger = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <SelectPrimitive.Icon asChild>\n      <ChevronDown className=\"size-4 opacity-50\" />\n    </SelectPrimitive.Icon>\n  </SelectPrimitive.Trigger>\n))\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName\n\nconst SelectScrollUpButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollUpButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className\n    )}\n    {...props}\n  >\n    <ChevronUp className=\"size-4\" />\n  </SelectPrimitive.ScrollUpButton>\n))\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName\n\nconst SelectScrollDownButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollDownButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className\n    )}\n    {...props}\n  >\n    <ChevronDown className=\"size-4\" />\n  </SelectPrimitive.ScrollDownButton>\n))\nSelectScrollDownButton.displayName =\n  SelectPrimitive.ScrollDownButton.displayName\n\nconst SelectContent = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n  <SelectPrimitive.Portal>\n    <SelectPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border shadow-md\",\n        position === \"popper\" &&\n          \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n        className\n      )}\n      position={position}\n      {...props}\n    >\n      <SelectScrollUpButton />\n      <SelectPrimitive.Viewport\n        className={cn(\n          \"p-1\",\n          position === \"popper\" &&\n            \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n        )}\n      >\n        {children}\n      </SelectPrimitive.Viewport>\n      <SelectScrollDownButton />\n    </SelectPrimitive.Content>\n  </SelectPrimitive.Portal>\n))\nSelectContent.displayName = SelectPrimitive.Content.displayName\n\nconst SelectLabel = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Label\n    ref={ref}\n    className={cn(\"py-1.5 pl-8 pr-2 text-sm font-semibold\", className)}\n    {...props}\n  />\n))\nSelectLabel.displayName = SelectPrimitive.Label.displayName\n\nconst SelectItem = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n      <SelectPrimitive.ItemIndicator>\n        <Check className=\"size-4\" />\n      </SelectPrimitive.ItemIndicator>\n    </span>\n\n    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n  </SelectPrimitive.Item>\n))\nSelectItem.displayName = SelectPrimitive.Item.displayName\n\nconst SelectSeparator = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Separator\n    ref={ref}\n    className={cn(\"bg-muted -mx-1 my-1 h-px\", className)}\n    {...props}\n  />\n))\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName\n\nexport {\n  Select,\n  SelectGroup,\n  SelectValue,\n  SelectTrigger,\n  SelectContent,\n  SelectLabel,\n  SelectItem,\n  SelectSeparator,\n  SelectScrollUpButton,\n  SelectScrollDownButton\n}\n"
  },
  {
    "path": "components/ui/separator.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Separator = React.forwardRef<\n  React.ElementRef<typeof SeparatorPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>\n>(\n  (\n    { className, orientation = \"horizontal\", decorative = true, ...props },\n    ref\n  ) => (\n    <SeparatorPrimitive.Root\n      ref={ref}\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"bg-border shrink-0\",\n        orientation === \"horizontal\" ? \"h-px w-full\" : \"h-full w-px\",\n        className\n      )}\n      {...props}\n    />\n  )\n)\nSeparator.displayName = SeparatorPrimitive.Root.displayName\n\nexport { Separator }\n"
  },
  {
    "path": "components/ui/sheet.tsx",
    "content": "\"use client\"\n\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Sheet = SheetPrimitive.Root\n\nconst SheetTrigger = SheetPrimitive.Trigger\n\nconst SheetClose = SheetPrimitive.Close\n\nconst SheetPortal = SheetPrimitive.Portal\n\nconst SheetOverlay = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Overlay\n    className={cn(\n      \"bg-background/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 backdrop-blur-sm\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nSheetOverlay.displayName = SheetPrimitive.Overlay.displayName\n\nconst sheetVariants = cva(\n  \"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 gap-4 p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500\",\n  {\n    variants: {\n      side: {\n        top: \"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 border-b\",\n        bottom:\n          \"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 border-t\",\n        left: \"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm\",\n        right:\n          \"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0  h-full w-3/4 border-l sm:max-w-sm\"\n      }\n    },\n    defaultVariants: {\n      side: \"right\"\n    }\n  }\n)\n\ninterface SheetContentProps\n  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,\n    VariantProps<typeof sheetVariants> {}\n\nconst SheetContent = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Content>,\n  SheetContentProps\n>(({ side = \"right\", className, children, ...props }, ref) => (\n  <SheetPortal>\n    <SheetOverlay />\n    <SheetPrimitive.Content\n      ref={ref}\n      className={cn(sheetVariants({ side }), className)}\n      {...props}\n    >\n      {children}\n      {/* <SheetPrimitive.Close className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </SheetPrimitive.Close> */}\n    </SheetPrimitive.Content>\n  </SheetPortal>\n))\nSheetContent.displayName = SheetPrimitive.Content.displayName\n\nconst SheetHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nSheetHeader.displayName = \"SheetHeader\"\n\nconst SheetFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nSheetFooter.displayName = \"SheetFooter\"\n\nconst SheetTitle = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Title\n    ref={ref}\n    className={cn(\"text-foreground text-lg font-semibold\", className)}\n    {...props}\n  />\n))\nSheetTitle.displayName = SheetPrimitive.Title.displayName\n\nconst SheetDescription = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Description\n    ref={ref}\n    className={cn(\"text-muted-foreground text-sm\", className)}\n    {...props}\n  />\n))\nSheetDescription.displayName = SheetPrimitive.Description.displayName\n\nexport {\n  Sheet,\n  SheetClose,\n  SheetContent,\n  SheetDescription,\n  SheetFooter,\n  SheetHeader,\n  SheetOverlay,\n  SheetPortal,\n  SheetTitle,\n  SheetTrigger\n}\n"
  },
  {
    "path": "components/ui/skeleton.tsx",
    "content": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n  return (\n    <div\n      className={cn(\"bg-muted animate-pulse rounded-md\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton }\n"
  },
  {
    "path": "components/ui/slider.tsx",
    "content": "\"use client\"\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Slider = React.forwardRef<\n  React.ElementRef<typeof SliderPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <SliderPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative flex w-full touch-none select-none items-center\",\n      className\n    )}\n    {...props}\n  >\n    <SliderPrimitive.Track className=\"bg-secondary relative h-2 w-full grow overflow-hidden rounded-full\">\n      <SliderPrimitive.Range className=\"bg-primary absolute h-full\" />\n    </SliderPrimitive.Track>\n    <SliderPrimitive.Thumb className=\"border-primary bg-background ring-offset-background focus-visible:ring-ring block size-5 cursor-grab rounded-full border-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\" />\n  </SliderPrimitive.Root>\n))\nSlider.displayName = SliderPrimitive.Root.displayName\n\nexport { Slider }\n"
  },
  {
    "path": "components/ui/sonner.tsx",
    "content": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner } from \"sonner\"\n\ntype ToasterProps = React.ComponentProps<typeof Sonner>\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme()\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      toastOptions={{\n        classNames: {\n          toast:\n            \"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg\",\n          description: \"group-[.toast]:text-muted-foreground\",\n          actionButton:\n            \"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground\",\n          cancelButton:\n            \"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground\"\n        }\n      }}\n      {...props}\n    />\n  )\n}\n\nexport { Toaster }\n"
  },
  {
    "path": "components/ui/submit-button.tsx",
    "content": "\"use client\"\n\nimport React from \"react\"\nimport { useFormStatus } from \"react-dom\"\nimport { Button, ButtonProps } from \"./button\"\n\nconst SubmitButton = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  (props, ref) => {\n    const { pending } = useFormStatus()\n\n    return <Button disabled={pending} ref={ref} {...props} />\n  }\n)\n\nSubmitButton.displayName = \"SubmitButton\"\n\nexport { SubmitButton }\n"
  },
  {
    "path": "components/ui/switch.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Switch = React.forwardRef<\n  React.ElementRef<typeof SwitchPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n  <SwitchPrimitives.Root\n    className={cn(\n      \"focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  >\n    <SwitchPrimitives.Thumb\n      className={cn(\n        \"bg-background pointer-events-none block size-5 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\"\n      )}\n    />\n  </SwitchPrimitives.Root>\n))\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"
  },
  {
    "path": "components/ui/table.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Table = React.forwardRef<\n  HTMLTableElement,\n  React.HTMLAttributes<HTMLTableElement>\n>(({ className, ...props }, ref) => (\n  <div className=\"relative w-full overflow-auto\">\n    <table\n      ref={ref}\n      className={cn(\"w-full caption-bottom text-sm\", className)}\n      {...props}\n    />\n  </div>\n))\nTable.displayName = \"Table\"\n\nconst TableHeader = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n))\nTableHeader.displayName = \"TableHeader\"\n\nconst TableBody = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <tbody\n    ref={ref}\n    className={cn(\"[&_tr:last-child]:border-0\", className)}\n    {...props}\n  />\n))\nTableBody.displayName = \"TableBody\"\n\nconst TableFooter = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <tfoot\n    ref={ref}\n    className={cn(\n      \"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0\",\n      className\n    )}\n    {...props}\n  />\n))\nTableFooter.displayName = \"TableFooter\"\n\nconst TableRow = React.forwardRef<\n  HTMLTableRowElement,\n  React.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n  <tr\n    ref={ref}\n    className={cn(\n      \"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors\",\n      className\n    )}\n    {...props}\n  />\n))\nTableRow.displayName = \"TableRow\"\n\nconst TableHead = React.forwardRef<\n  HTMLTableCellElement,\n  React.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n  <th\n    ref={ref}\n    className={cn(\n      \"text-muted-foreground h-12 px-4 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0\",\n      className\n    )}\n    {...props}\n  />\n))\nTableHead.displayName = \"TableHead\"\n\nconst TableCell = React.forwardRef<\n  HTMLTableCellElement,\n  React.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n  <td\n    ref={ref}\n    className={cn(\"p-4 align-middle [&:has([role=checkbox])]:pr-0\", className)}\n    {...props}\n  />\n))\nTableCell.displayName = \"TableCell\"\n\nconst TableCaption = React.forwardRef<\n  HTMLTableCaptionElement,\n  React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n  <caption\n    ref={ref}\n    className={cn(\"text-muted-foreground mt-4 text-sm\", className)}\n    {...props}\n  />\n))\nTableCaption.displayName = \"TableCaption\"\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption\n}\n"
  },
  {
    "path": "components/ui/tabs.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Tabs = TabsPrimitive.Root\n\nconst TabsList = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.List\n    ref={ref}\n    className={cn(\n      \"bg-muted text-muted-foreground inline-flex h-10 items-center justify-center rounded-md p-1\",\n      className\n    )}\n    {...props}\n  />\n))\nTabsList.displayName = TabsPrimitive.List.displayName\n\nconst TabsTrigger = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm\",\n      className\n    )}\n    {...props}\n  />\n))\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName\n\nconst TabsContent = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Content\n    ref={ref}\n    className={cn(\n      \"ring-offset-background focus-visible:ring-ring mt-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2\",\n      className\n    )}\n    {...props}\n  />\n))\nTabsContent.displayName = TabsPrimitive.Content.displayName\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent }\n"
  },
  {
    "path": "components/ui/textarea-autosize.tsx",
    "content": "import { cn } from \"@/lib/utils\"\nimport { FC } from \"react\"\nimport ReactTextareaAutosize from \"react-textarea-autosize\"\n\ninterface TextareaAutosizeProps {\n  value: string\n  onValueChange: (value: string) => void\n\n  textareaRef?: React.RefObject<HTMLTextAreaElement>\n  className?: string\n\n  placeholder?: string\n  minRows?: number\n  maxRows?: number\n  maxLength?: number\n  onKeyDown?: (event: React.KeyboardEvent) => void\n  onPaste?: (event: React.ClipboardEvent) => void\n  onCompositionStart?: (event: React.CompositionEvent) => void\n  onCompositionEnd?: (event: React.CompositionEvent) => void\n}\n\nexport const TextareaAutosize: FC<TextareaAutosizeProps> = ({\n  value,\n  onValueChange,\n  textareaRef,\n  className,\n  placeholder = \"\",\n  minRows = 1,\n  maxRows = 6,\n  maxLength,\n  onKeyDown = () => {},\n  onPaste = () => {},\n  onCompositionStart = () => {},\n  onCompositionEnd = () => {}\n}) => {\n  return (\n    <ReactTextareaAutosize\n      ref={textareaRef}\n      className={cn(\n        \"bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex w-full resize-none rounded-md border-2 px-3 py-2 text-sm focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      minRows={minRows}\n      maxRows={minRows > maxRows ? minRows : maxRows}\n      placeholder={placeholder}\n      value={value}\n      maxLength={maxLength}\n      onChange={event => onValueChange(event.target.value)}\n      onKeyDown={onKeyDown}\n      onPaste={onPaste}\n      onCompositionStart={onCompositionStart}\n      onCompositionEnd={onCompositionEnd}\n    />\n  )\n}\n"
  },
  {
    "path": "components/ui/textarea.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface TextareaProps\n  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n  ({ className, ...props }, ref) => {\n    return (\n      <textarea\n        className={cn(\n          \"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nTextarea.displayName = \"Textarea\"\n\nexport { Textarea }\n"
  },
  {
    "path": "components/ui/toast.tsx",
    "content": "import * as React from \"react\"\nimport * as ToastPrimitives from \"@radix-ui/react-toast\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ToastProvider = ToastPrimitives.Provider\n\nconst ToastViewport = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Viewport>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Viewport\n    ref={ref}\n    className={cn(\n      \"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]\",\n      className\n    )}\n    {...props}\n  />\n))\nToastViewport.displayName = ToastPrimitives.Viewport.displayName\n\nconst toastVariants = cva(\n  \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-background text-foreground border\",\n        destructive:\n          \"destructive border-destructive bg-destructive text-destructive-foreground group\"\n      }\n    },\n    defaultVariants: {\n      variant: \"default\"\n    }\n  }\n)\n\nconst Toast = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n    VariantProps<typeof toastVariants>\n>(({ className, variant, ...props }, ref) => {\n  return (\n    <ToastPrimitives.Root\n      ref={ref}\n      className={cn(toastVariants({ variant }), className)}\n      {...props}\n    />\n  )\n})\nToast.displayName = ToastPrimitives.Root.displayName\n\nconst ToastAction = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Action>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Action\n    ref={ref}\n    className={cn(\n      \"ring-offset-background hover:bg-secondary focus:ring-ring group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n      className\n    )}\n    {...props}\n  />\n))\nToastAction.displayName = ToastPrimitives.Action.displayName\n\nconst ToastClose = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Close>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Close\n    ref={ref}\n    className={cn(\n      \"text-foreground/50 hover:text-foreground absolute right-2 top-2 rounded-md p-1 opacity-0 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600\",\n      className\n    )}\n    toast-close=\"\"\n    {...props}\n  >\n    <X className=\"size-4\" />\n  </ToastPrimitives.Close>\n))\nToastClose.displayName = ToastPrimitives.Close.displayName\n\nconst ToastTitle = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Title>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Title\n    ref={ref}\n    className={cn(\"text-sm font-semibold\", className)}\n    {...props}\n  />\n))\nToastTitle.displayName = ToastPrimitives.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Description>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Description\n    ref={ref}\n    className={cn(\"text-sm opacity-90\", className)}\n    {...props}\n  />\n))\nToastDescription.displayName = ToastPrimitives.Description.displayName\n\ntype ToastProps = React.ComponentPropsWithoutRef<typeof Toast>\n\ntype ToastActionElement = React.ReactElement<typeof ToastAction>\n\nexport {\n  type ToastProps,\n  type ToastActionElement,\n  ToastProvider,\n  ToastViewport,\n  Toast,\n  ToastTitle,\n  ToastDescription,\n  ToastClose,\n  ToastAction\n}\n"
  },
  {
    "path": "components/ui/toaster.tsx",
    "content": "\"use client\"\n\nimport {\n  Toast,\n  ToastClose,\n  ToastDescription,\n  ToastProvider,\n  ToastTitle,\n  ToastViewport\n} from \"@/components/ui/toast\"\nimport { useToast } from \"@/components/ui/use-toast\"\n\nexport function Toaster() {\n  const { toasts } = useToast()\n\n  return (\n    <ToastProvider>\n      {toasts.map(function ({ id, title, description, action, ...props }) {\n        return (\n          <Toast key={id} {...props}>\n            <div className=\"grid gap-1\">\n              {title && <ToastTitle>{title}</ToastTitle>}\n              {description && (\n                <ToastDescription>{description}</ToastDescription>\n              )}\n            </div>\n            {action}\n            <ToastClose />\n          </Toast>\n        )\n      })}\n      <ToastViewport />\n    </ToastProvider>\n  )\n}\n"
  },
  {
    "path": "components/ui/toggle-group.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ToggleGroupPrimitive from \"@radix-ui/react-toggle-group\"\nimport { VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { toggleVariants } from \"@/components/ui/toggle\"\n\nconst ToggleGroupContext = React.createContext<\n  VariantProps<typeof toggleVariants>\n>({\n  size: \"default\",\n  variant: \"default\"\n})\n\nconst ToggleGroup = React.forwardRef<\n  React.ElementRef<typeof ToggleGroupPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &\n    VariantProps<typeof toggleVariants>\n>(({ className, variant, size, children, ...props }, ref) => (\n  <ToggleGroupPrimitive.Root\n    ref={ref}\n    className={cn(\"flex items-center justify-center gap-1\", className)}\n    {...props}\n  >\n    <ToggleGroupContext.Provider value={{ variant, size }}>\n      {children}\n    </ToggleGroupContext.Provider>\n  </ToggleGroupPrimitive.Root>\n))\n\nToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName\n\nconst ToggleGroupItem = React.forwardRef<\n  React.ElementRef<typeof ToggleGroupPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &\n    VariantProps<typeof toggleVariants>\n>(({ className, children, variant, size, ...props }, ref) => {\n  const context = React.useContext(ToggleGroupContext)\n\n  return (\n    <ToggleGroupPrimitive.Item\n      ref={ref}\n      className={cn(\n        toggleVariants({\n          variant: context.variant || variant,\n          size: context.size || size\n        }),\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </ToggleGroupPrimitive.Item>\n  )\n})\n\nToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName\n\nexport { ToggleGroup, ToggleGroupItem }\n"
  },
  {
    "path": "components/ui/toggle.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst toggleVariants = cva(\n  \"ring-offset-background hover:bg-muted hover:text-muted-foreground focus-visible:ring-ring data-[state=on]:bg-accent data-[state=on]:text-accent-foreground inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        outline:\n          \"border-input hover:bg-accent hover:text-accent-foreground border bg-transparent\"\n      },\n      size: {\n        default: \"h-10 px-3\",\n        sm: \"h-9 px-2.5\",\n        lg: \"h-11 px-5\"\n      }\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\"\n    }\n  }\n)\n\nconst Toggle = React.forwardRef<\n  React.ElementRef<typeof TogglePrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &\n    VariantProps<typeof toggleVariants>\n>(({ className, variant, size, ...props }, ref) => (\n  <TogglePrimitive.Root\n    ref={ref}\n    className={cn(toggleVariants({ variant, size, className }))}\n    {...props}\n  />\n))\n\nToggle.displayName = TogglePrimitive.Root.displayName\n\nexport { Toggle, toggleVariants }\n"
  },
  {
    "path": "components/ui/tooltip.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst TooltipProvider = TooltipPrimitive.Provider\n\nconst Tooltip = TooltipPrimitive.Root\n\nconst TooltipTrigger = TooltipPrimitive.Trigger\n\nconst TooltipContent = React.forwardRef<\n  React.ElementRef<typeof TooltipPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <TooltipPrimitive.Content\n    ref={ref}\n    sideOffset={sideOffset}\n    className={cn(\n      \"bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md\",\n      className\n    )}\n    {...props}\n  />\n))\nTooltipContent.displayName = TooltipPrimitive.Content.displayName\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }\n"
  },
  {
    "path": "components/ui/use-toast.ts",
    "content": "// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport type { ToastActionElement, ToastProps } from \"@/components/ui/toast\"\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000000\n\ntype ToasterToast = ToastProps & {\n  id: string\n  title?: React.ReactNode\n  description?: React.ReactNode\n  action?: ToastActionElement\n}\n\nconst actionTypes = {\n  ADD_TOAST: \"ADD_TOAST\",\n  UPDATE_TOAST: \"UPDATE_TOAST\",\n  DISMISS_TOAST: \"DISMISS_TOAST\",\n  REMOVE_TOAST: \"REMOVE_TOAST\"\n} as const\n\nlet count = 0\n\nfunction genId() {\n  count = (count + 1) % Number.MAX_VALUE\n  return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n  | {\n      type: ActionType[\"ADD_TOAST\"]\n      toast: ToasterToast\n    }\n  | {\n      type: ActionType[\"UPDATE_TOAST\"]\n      toast: Partial<ToasterToast>\n    }\n  | {\n      type: ActionType[\"DISMISS_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n  | {\n      type: ActionType[\"REMOVE_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n\ninterface State {\n  toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()\n\nconst addToRemoveQueue = (toastId: string) => {\n  if (toastTimeouts.has(toastId)) {\n    return\n  }\n\n  const timeout = setTimeout(() => {\n    toastTimeouts.delete(toastId)\n    dispatch({\n      type: \"REMOVE_TOAST\",\n      toastId: toastId\n    })\n  }, TOAST_REMOVE_DELAY)\n\n  toastTimeouts.set(toastId, timeout)\n}\n\nexport const reducer = (state: State, action: Action): State => {\n  switch (action.type) {\n    case \"ADD_TOAST\":\n      return {\n        ...state,\n        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT)\n      }\n\n    case \"UPDATE_TOAST\":\n      return {\n        ...state,\n        toasts: state.toasts.map(t =>\n          t.id === action.toast.id ? { ...t, ...action.toast } : t\n        )\n      }\n\n    case \"DISMISS_TOAST\": {\n      const { toastId } = action\n\n      // ! Side effects ! - This could be extracted into a dismissToast() action,\n      // but I'll keep it here for simplicity\n      if (toastId) {\n        addToRemoveQueue(toastId)\n      } else {\n        state.toasts.forEach(toast => {\n          addToRemoveQueue(toast.id)\n        })\n      }\n\n      return {\n        ...state,\n        toasts: state.toasts.map(t =>\n          t.id === toastId || toastId === undefined\n            ? {\n                ...t,\n                open: false\n              }\n            : t\n        )\n      }\n    }\n    case \"REMOVE_TOAST\":\n      if (action.toastId === undefined) {\n        return {\n          ...state,\n          toasts: []\n        }\n      }\n      return {\n        ...state,\n        toasts: state.toasts.filter(t => t.id !== action.toastId)\n      }\n  }\n}\n\nconst listeners: Array<(state: State) => void> = []\n\nlet memoryState: State = { toasts: [] }\n\nfunction dispatch(action: Action) {\n  memoryState = reducer(memoryState, action)\n  listeners.forEach(listener => {\n    listener(memoryState)\n  })\n}\n\ntype Toast = Omit<ToasterToast, \"id\">\n\nfunction toast({ ...props }: Toast) {\n  const id = genId()\n\n  const update = (props: ToasterToast) =>\n    dispatch({\n      type: \"UPDATE_TOAST\",\n      toast: { ...props, id }\n    })\n  const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id })\n\n  dispatch({\n    type: \"ADD_TOAST\",\n    toast: {\n      ...props,\n      id,\n      open: true,\n      onOpenChange: open => {\n        if (!open) dismiss()\n      }\n    }\n  })\n\n  return {\n    id: id,\n    dismiss,\n    update\n  }\n}\n\nfunction useToast() {\n  const [state, setState] = React.useState<State>(memoryState)\n\n  React.useEffect(() => {\n    listeners.push(setState)\n    return () => {\n      const index = listeners.indexOf(setState)\n      if (index > -1) {\n        listeners.splice(index, 1)\n      }\n    }\n  }, [state])\n\n  return {\n    ...state,\n    toast,\n    dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId })\n  }\n}\n\nexport { useToast, toast }\n"
  },
  {
    "path": "components/ui/with-tooltip.tsx",
    "content": "import { FC } from \"react\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger\n} from \"./tooltip\"\n\ninterface WithTooltipProps {\n  display: React.ReactNode\n  trigger: React.ReactNode\n\n  delayDuration?: number\n  side?: \"left\" | \"right\" | \"top\" | \"bottom\"\n}\n\nexport const WithTooltip: FC<WithTooltipProps> = ({\n  display,\n  trigger,\n\n  delayDuration = 500,\n  side = \"right\"\n}) => {\n  return (\n    <TooltipProvider delayDuration={delayDuration}>\n      <Tooltip>\n        <TooltipTrigger>{trigger}</TooltipTrigger>\n\n        <TooltipContent side={side}>{display}</TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  )\n}\n"
  },
  {
    "path": "components/utility/alerts.tsx",
    "content": "import {\n  Popover,\n  PopoverContent,\n  PopoverTrigger\n} from \"@/components/ui/popover\"\nimport { IconBell } from \"@tabler/icons-react\"\nimport { FC } from \"react\"\nimport { SIDEBAR_ICON_SIZE } from \"../sidebar/sidebar-switcher\"\n\ninterface AlertsProps {}\n\nexport const Alerts: FC<AlertsProps> = () => {\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <div className=\"relative cursor-pointer hover:opacity-50\">\n          <IconBell size={SIDEBAR_ICON_SIZE} />\n          {1 > 0 && (\n            <span className=\"notification-indicator absolute right-[-4px] top-[-4px] flex size-4 items-center justify-center rounded-full bg-red-600 text-[10px] text-white\">\n              1\n            </span>\n          )}\n        </div>\n      </PopoverTrigger>\n      <PopoverContent className=\"mb-2 w-80\">\n        <div>placeholder</div>\n      </PopoverContent>\n    </Popover>\n  )\n}\n"
  },
  {
    "path": "components/utility/announcements.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger\n} from \"@/components/ui/popover\"\nimport { Announcement } from \"@/types/announcement\"\nimport { IconExternalLink, IconSpeakerphone } from \"@tabler/icons-react\"\nimport { FC, useEffect, useState } from \"react\"\nimport { SIDEBAR_ICON_SIZE } from \"../sidebar/sidebar-switcher\"\n\ninterface AnnouncementsProps {}\n\nexport const Announcements: FC<AnnouncementsProps> = () => {\n  const [announcements, setAnnouncements] = useState<Announcement[]>([])\n\n  useEffect(() => {\n    // Load announcements from local storage\n    const storedAnnouncements = localStorage.getItem(\"announcements\")\n    let parsedAnnouncements: Announcement[] = []\n\n    if (storedAnnouncements) {\n      parsedAnnouncements = JSON.parse(storedAnnouncements)\n    }\n\n    // Filter out announcements that are no longer in state\n    const validAnnouncements = announcements.filter((a: Announcement) =>\n      parsedAnnouncements.find(storedA => storedA.id === a.id)\n    )\n\n    // Add new announcements to the list\n    const newAnnouncements = announcements.filter(\n      (a: Announcement) =>\n        !parsedAnnouncements.find(storedA => storedA.id === a.id)\n    )\n\n    // Combine valid and new announcements\n    const combinedAnnouncements = [...validAnnouncements, ...newAnnouncements]\n\n    // Mark announcements as read if they are marked as read in local storage\n    const updatedAnnouncements = combinedAnnouncements.map(\n      (a: Announcement) => {\n        const storedAnnouncement = parsedAnnouncements.find(\n          (storedA: Announcement) => storedA.id === a.id\n        )\n        return storedAnnouncement?.read ? { ...a, read: true } : a\n      }\n    )\n\n    // Update state and local storage\n    setAnnouncements(updatedAnnouncements)\n    localStorage.setItem(\"announcements\", JSON.stringify(updatedAnnouncements))\n  }, [])\n\n  const unreadCount = announcements.filter(a => !a.read).length\n\n  const markAsRead = (id: string) => {\n    // Mark announcement as read in local storage and state\n    const updatedAnnouncements = announcements.map(a =>\n      a.id === id ? { ...a, read: true } : a\n    )\n    setAnnouncements(updatedAnnouncements)\n    localStorage.setItem(\"announcements\", JSON.stringify(updatedAnnouncements))\n  }\n\n  const markAllAsRead = () => {\n    // Mark all announcements as read in local storage and state\n    const updatedAnnouncements = announcements.map(a => ({ ...a, read: true }))\n    setAnnouncements(updatedAnnouncements)\n    localStorage.setItem(\"announcements\", JSON.stringify(updatedAnnouncements))\n  }\n\n  const markAllAsUnread = () => {\n    // Mark all announcements as unread in local storage and state\n    const updatedAnnouncements = announcements.map(a => ({ ...a, read: false }))\n    setAnnouncements(updatedAnnouncements)\n    localStorage.setItem(\"announcements\", JSON.stringify(updatedAnnouncements))\n  }\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <div className=\"relative cursor-pointer hover:opacity-50\">\n          <IconSpeakerphone size={SIDEBAR_ICON_SIZE} />\n          {unreadCount > 0 && (\n            <div className=\"notification-indicator absolute right-[-4px] top-[-4px] flex size-4 items-center justify-center rounded-full bg-red-500 text-[10px] text-white\">\n              {unreadCount}\n            </div>\n          )}\n        </div>\n      </PopoverTrigger>\n      <PopoverContent className=\"mb-2 w-80\" side=\"top\">\n        <div className=\"grid gap-4\">\n          <div>\n            <div className=\"mb-4 text-left text-xl font-bold leading-none\">\n              Updates\n            </div>\n\n            <div className=\"grid space-y-4\">\n              {announcements\n                .filter(a => !a.read)\n                .map((a: Announcement) => (\n                  <div key={a.id}>\n                    <div className=\"block select-none rounded-md border p-3\">\n                      <div className=\"flex items-center justify-between\">\n                        <div className=\"text-sm font-medium leading-none\">\n                          {a.title}\n                        </div>\n                        <div className=\"text-muted-foreground text-xs leading-snug\">\n                          {a.date}\n                        </div>\n                      </div>\n                      <div className=\"text-muted-foreground mt-3 text-sm leading-snug\">\n                        {a.content}\n                      </div>\n\n                      <div className=\"mt-3 space-x-2\">\n                        <Button\n                          className=\"h-[26px] text-xs\"\n                          size=\"sm\"\n                          onClick={() => markAsRead(a.id)}\n                        >\n                          Mark as Read\n                        </Button>\n\n                        {a.link && (\n                          <a href={a.link} target=\"_blank\" rel=\"noreferrer\">\n                            <Button className=\"h-[26px] text-xs\" size=\"sm\">\n                              Demo{\" \"}\n                              <IconExternalLink className=\"ml-1\" size={14} />\n                            </Button>\n                          </a>\n                        )}\n                      </div>\n                    </div>\n                  </div>\n                ))}\n            </div>\n\n            <div className=\"mt-1\">\n              {unreadCount > 0 ? (\n                <Button\n                  className=\"mt-2\"\n                  variant=\"outline\"\n                  onClick={markAllAsRead}\n                >\n                  Mark All as Read\n                </Button>\n              ) : (\n                <div className=\"text-muted-foreground text-sm leading-snug\">\n                  You are all caught up!\n                  {announcements.length > 0 && (\n                    <div\n                      className=\"mt-6 cursor-pointer underline\"\n                      onClick={() => markAllAsUnread()}\n                    >\n                      Show recent updates\n                    </div>\n                  )}\n                </div>\n              )}\n            </div>\n          </div>\n        </div>\n      </PopoverContent>\n    </Popover>\n  )\n}\n"
  },
  {
    "path": "components/utility/change-password.tsx",
    "content": "\"use client\"\n\nimport { supabase } from \"@/lib/supabase/browser-client\"\nimport { useRouter } from \"next/navigation\"\nimport { FC, useState } from \"react\"\nimport { Button } from \"../ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle\n} from \"../ui/dialog\"\nimport { Input } from \"../ui/input\"\nimport { toast } from \"sonner\"\n\ninterface ChangePasswordProps {}\n\nexport const ChangePassword: FC<ChangePasswordProps> = () => {\n  const router = useRouter()\n\n  const [newPassword, setNewPassword] = useState(\"\")\n  const [confirmPassword, setConfirmPassword] = useState(\"\")\n\n  const handleResetPassword = async () => {\n    if (!newPassword) return toast.info(\"Please enter your new password.\")\n\n    await supabase.auth.updateUser({ password: newPassword })\n\n    toast.success(\"Password changed successfully.\")\n\n    return router.push(\"/login\")\n  }\n\n  return (\n    <Dialog open={true}>\n      <DialogContent className=\"h-[240px] w-[400px] p-4\">\n        <DialogHeader>\n          <DialogTitle>Change Password</DialogTitle>\n        </DialogHeader>\n\n        <Input\n          id=\"password\"\n          placeholder=\"New Password\"\n          type=\"password\"\n          value={newPassword}\n          onChange={e => setNewPassword(e.target.value)}\n        />\n\n        <Input\n          id=\"confirmPassword\"\n          placeholder=\"Confirm New Password\"\n          type=\"password\"\n          value={confirmPassword}\n          onChange={e => setConfirmPassword(e.target.value)}\n        />\n\n        <DialogFooter>\n          <Button onClick={handleResetPassword}>Confirm Change</Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "components/utility/command-k.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { IconLoader2, IconSend } from \"@tabler/icons-react\"\nimport { FC, useContext, useState } from \"react\"\nimport { Dialog, DialogContent } from \"../ui/dialog\"\nimport { TextareaAutosize } from \"../ui/textarea-autosize\"\n\ninterface CommandKProps {}\n\nexport const CommandK: FC<CommandKProps> = ({}) => {\n  useHotkey(\"k\", () => setIsOpen(prevState => !prevState))\n\n  const { profile } = useContext(ChatbotUIContext)\n\n  const [isOpen, setIsOpen] = useState(false)\n  const [value, setValue] = useState(\"\")\n  const [loading, setLoading] = useState(false)\n  const [content, setContent] = useState(\"\")\n\n  const handleCommandK = async () => {\n    setLoading(true)\n\n    const response = await fetch(\"/api/command\", {\n      method: \"POST\",\n      body: JSON.stringify({\n        input: value\n      })\n    })\n\n    const data = await response.json()\n\n    setContent(data.content)\n    setLoading(false)\n    setValue(\"\")\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === \"Enter\" && !e.shiftKey) {\n      e.preventDefault()\n      handleCommandK()\n    }\n  }\n\n  if (!profile) return null\n\n  return (\n    isOpen && (\n      <Dialog open={isOpen} onOpenChange={setIsOpen}>\n        <DialogContent onKeyDown={handleKeyDown}>\n          {profile.openai_api_key ? (\n            <div className=\"space-y-2\">\n              <div>{content}</div>\n\n              <div>turn dark mode on.</div>\n              <div>find my sql chat</div>\n              <div>i need a new assistant</div>\n              <div>start a chat with my 2024 resolutions file</div>\n\n              <div className=\"border-input relative flex min-h-[50px] w-full items-center justify-center rounded-xl border-2\">\n                <TextareaAutosize\n                  className=\"ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring text-md flex w-full resize-none rounded-md border-none bg-transparent px-3 py-2 pr-14 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n                  placeholder=\"create a prompt for writing sql code\"\n                  value={value}\n                  onValueChange={setValue}\n                />\n                {loading ? (\n                  <IconLoader2\n                    className=\"absolute bottom-[8px] right-3 animate-spin cursor-pointer rounded p-1 hover:opacity-50\"\n                    size={30}\n                  />\n                ) : (\n                  <IconSend\n                    className=\"bg-primary text-secondary absolute bottom-[8px] right-3 cursor-pointer rounded p-1 hover:opacity-50\"\n                    onClick={handleCommandK}\n                    size={30}\n                  />\n                )}\n              </div>\n            </div>\n          ) : (\n            <div>Add your OpenAI API key in the settings to unlock CMD+K.</div>\n          )}\n        </DialogContent>\n      </Dialog>\n    )\n  )\n}\n"
  },
  {
    "path": "components/utility/drawing-canvas.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { MessageImage } from \"@/types\"\nimport { FC, MouseEvent, useContext, useEffect, useRef, useState } from \"react\"\n\ninterface DrawingCanvasProps {\n  imageItem: MessageImage\n}\n\nexport const DrawingCanvas: FC<DrawingCanvasProps> = ({ imageItem }) => {\n  const { setNewMessageImages } = useContext(ChatbotUIContext)\n\n  const canvasRef = useRef<HTMLCanvasElement>(null)\n  const [isDrawing, setIsDrawing] = useState(false)\n\n  useEffect(() => {\n    const canvas = canvasRef.current\n    const parentElement = canvas?.parentElement\n    if (canvas && parentElement) {\n      const context = canvas.getContext(\"2d\")\n      const image = new Image()\n\n      image.onload = () => {\n        const aspectRatio = image.width / image.height\n\n        let newWidth = parentElement.clientWidth\n        let newHeight = newWidth / aspectRatio\n\n        if (newHeight > parentElement.clientHeight) {\n          newHeight = parentElement.clientHeight\n          newWidth = newHeight * aspectRatio\n        }\n\n        canvas.width = newWidth\n        canvas.height = newHeight\n\n        context?.drawImage(image, 0, 0, newWidth, newHeight)\n      }\n\n      image.src = imageItem.url\n    }\n  }, [imageItem.url])\n\n  const startDrawing = ({ nativeEvent }: MouseEvent) => {\n    const { offsetX, offsetY } = nativeEvent\n    const context = canvasRef.current?.getContext(\"2d\")\n    if (context) {\n      context.strokeStyle = \"red\"\n      context.lineWidth = 2\n    }\n    context?.beginPath()\n    context?.moveTo(offsetX, offsetY)\n    setIsDrawing(true)\n  }\n\n  const draw = ({ nativeEvent }: MouseEvent) => {\n    if (!isDrawing) {\n      return\n    }\n    const { offsetX, offsetY } = nativeEvent\n    const context = canvasRef.current?.getContext(\"2d\")\n    context?.lineTo(offsetX, offsetY)\n    context?.stroke()\n  }\n\n  const finishDrawing = () => {\n    const canvas = canvasRef.current\n    const context = canvas?.getContext(\"2d\")\n    context?.closePath()\n    setIsDrawing(false)\n\n    if (canvas) {\n      const dataURL = canvas.toDataURL(\"image/png\")\n      fetch(dataURL)\n        .then(res => res.blob())\n        .then(blob => {\n          const newImageFile = new File([blob], \"drawing.png\", {\n            type: \"image/png\"\n          })\n\n          setNewMessageImages(prevImages => {\n            return prevImages.map(img => {\n              if (img.url === imageItem.url) {\n                return { ...img, base64: dataURL, file: newImageFile }\n              }\n              return img\n            })\n          })\n        })\n    }\n  }\n\n  return (\n    <canvas\n      ref={canvasRef}\n      className=\"cursor-crosshair rounded\"\n      width={2000}\n      height={2000}\n      onMouseDown={startDrawing}\n      onMouseUp={finishDrawing}\n      onMouseMove={draw}\n      onMouseLeave={finishDrawing}\n      style={{\n        maxHeight: \"67vh\",\n        maxWidth: \"67vw\"\n      }}\n    />\n  )\n}\n"
  },
  {
    "path": "components/utility/global-state.tsx",
    "content": "// TODO: Separate into multiple contexts, keeping simple for now\n\n\"use client\"\n\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { getProfileByUserId } from \"@/db/profile\"\nimport { getWorkspaceImageFromStorage } from \"@/db/storage/workspace-images\"\nimport { getWorkspacesByUserId } from \"@/db/workspaces\"\nimport { convertBlobToBase64 } from \"@/lib/blob-to-b64\"\nimport {\n  fetchHostedModels,\n  fetchOllamaModels,\n  fetchOpenRouterModels\n} from \"@/lib/models/fetch-models\"\nimport { supabase } from \"@/lib/supabase/browser-client\"\nimport { Tables } from \"@/supabase/types\"\nimport {\n  ChatFile,\n  ChatMessage,\n  ChatSettings,\n  LLM,\n  MessageImage,\n  OpenRouterLLM,\n  WorkspaceImage\n} from \"@/types\"\nimport { AssistantImage } from \"@/types/images/assistant-image\"\nimport { VALID_ENV_KEYS } from \"@/types/valid-keys\"\nimport { useRouter } from \"next/navigation\"\nimport { FC, useEffect, useState } from \"react\"\n\ninterface GlobalStateProps {\n  children: React.ReactNode\n}\n\nexport const GlobalState: FC<GlobalStateProps> = ({ children }) => {\n  const router = useRouter()\n\n  // PROFILE STORE\n  const [profile, setProfile] = useState<Tables<\"profiles\"> | null>(null)\n\n  // ITEMS STORE\n  const [assistants, setAssistants] = useState<Tables<\"assistants\">[]>([])\n  const [collections, setCollections] = useState<Tables<\"collections\">[]>([])\n  const [chats, setChats] = useState<Tables<\"chats\">[]>([])\n  const [files, setFiles] = useState<Tables<\"files\">[]>([])\n  const [folders, setFolders] = useState<Tables<\"folders\">[]>([])\n  const [models, setModels] = useState<Tables<\"models\">[]>([])\n  const [presets, setPresets] = useState<Tables<\"presets\">[]>([])\n  const [prompts, setPrompts] = useState<Tables<\"prompts\">[]>([])\n  const [tools, setTools] = useState<Tables<\"tools\">[]>([])\n  const [workspaces, setWorkspaces] = useState<Tables<\"workspaces\">[]>([])\n\n  // MODELS STORE\n  const [envKeyMap, setEnvKeyMap] = useState<Record<string, VALID_ENV_KEYS>>({})\n  const [availableHostedModels, setAvailableHostedModels] = useState<LLM[]>([])\n  const [availableLocalModels, setAvailableLocalModels] = useState<LLM[]>([])\n  const [availableOpenRouterModels, setAvailableOpenRouterModels] = useState<\n    OpenRouterLLM[]\n  >([])\n\n  // WORKSPACE STORE\n  const [selectedWorkspace, setSelectedWorkspace] =\n    useState<Tables<\"workspaces\"> | null>(null)\n  const [workspaceImages, setWorkspaceImages] = useState<WorkspaceImage[]>([])\n\n  // PRESET STORE\n  const [selectedPreset, setSelectedPreset] =\n    useState<Tables<\"presets\"> | null>(null)\n\n  // ASSISTANT STORE\n  const [selectedAssistant, setSelectedAssistant] =\n    useState<Tables<\"assistants\"> | null>(null)\n  const [assistantImages, setAssistantImages] = useState<AssistantImage[]>([])\n  const [openaiAssistants, setOpenaiAssistants] = useState<any[]>([])\n\n  // PASSIVE CHAT STORE\n  const [userInput, setUserInput] = useState<string>(\"\")\n  const [chatMessages, setChatMessages] = useState<ChatMessage[]>([])\n  const [chatSettings, setChatSettings] = useState<ChatSettings>({\n    model: \"gpt-4-turbo-preview\",\n    prompt: \"You are a helpful AI assistant.\",\n    temperature: 0.5,\n    contextLength: 4000,\n    includeProfileContext: true,\n    includeWorkspaceInstructions: true,\n    embeddingsProvider: \"openai\"\n  })\n  const [selectedChat, setSelectedChat] = useState<Tables<\"chats\"> | null>(null)\n  const [chatFileItems, setChatFileItems] = useState<Tables<\"file_items\">[]>([])\n\n  // ACTIVE CHAT STORE\n  const [isGenerating, setIsGenerating] = useState<boolean>(false)\n  const [firstTokenReceived, setFirstTokenReceived] = useState<boolean>(false)\n  const [abortController, setAbortController] =\n    useState<AbortController | null>(null)\n\n  // CHAT INPUT COMMAND STORE\n  const [isPromptPickerOpen, setIsPromptPickerOpen] = useState(false)\n  const [slashCommand, setSlashCommand] = useState(\"\")\n  const [isFilePickerOpen, setIsFilePickerOpen] = useState(false)\n  const [hashtagCommand, setHashtagCommand] = useState(\"\")\n  const [isToolPickerOpen, setIsToolPickerOpen] = useState(false)\n  const [toolCommand, setToolCommand] = useState(\"\")\n  const [focusPrompt, setFocusPrompt] = useState(false)\n  const [focusFile, setFocusFile] = useState(false)\n  const [focusTool, setFocusTool] = useState(false)\n  const [focusAssistant, setFocusAssistant] = useState(false)\n  const [atCommand, setAtCommand] = useState(\"\")\n  const [isAssistantPickerOpen, setIsAssistantPickerOpen] = useState(false)\n\n  // ATTACHMENTS STORE\n  const [chatFiles, setChatFiles] = useState<ChatFile[]>([])\n  const [chatImages, setChatImages] = useState<MessageImage[]>([])\n  const [newMessageFiles, setNewMessageFiles] = useState<ChatFile[]>([])\n  const [newMessageImages, setNewMessageImages] = useState<MessageImage[]>([])\n  const [showFilesDisplay, setShowFilesDisplay] = useState<boolean>(false)\n\n  // RETIEVAL STORE\n  const [useRetrieval, setUseRetrieval] = useState<boolean>(true)\n  const [sourceCount, setSourceCount] = useState<number>(4)\n\n  // TOOL STORE\n  const [selectedTools, setSelectedTools] = useState<Tables<\"tools\">[]>([])\n  const [toolInUse, setToolInUse] = useState<string>(\"none\")\n\n  useEffect(() => {\n    ;(async () => {\n      const profile = await fetchStartingData()\n\n      if (profile) {\n        const hostedModelRes = await fetchHostedModels(profile)\n        if (!hostedModelRes) return\n\n        setEnvKeyMap(hostedModelRes.envKeyMap)\n        setAvailableHostedModels(hostedModelRes.hostedModels)\n\n        if (\n          profile[\"openrouter_api_key\"] ||\n          hostedModelRes.envKeyMap[\"openrouter\"]\n        ) {\n          const openRouterModels = await fetchOpenRouterModels()\n          if (!openRouterModels) return\n          setAvailableOpenRouterModels(openRouterModels)\n        }\n      }\n\n      if (process.env.NEXT_PUBLIC_OLLAMA_URL) {\n        const localModels = await fetchOllamaModels()\n        if (!localModels) return\n        setAvailableLocalModels(localModels)\n      }\n    })()\n  }, [])\n\n  const fetchStartingData = async () => {\n    const session = (await supabase.auth.getSession()).data.session\n\n    if (session) {\n      const user = session.user\n\n      const profile = await getProfileByUserId(user.id)\n      setProfile(profile)\n\n      if (!profile.has_onboarded) {\n        return router.push(\"/setup\")\n      }\n\n      const workspaces = await getWorkspacesByUserId(user.id)\n      setWorkspaces(workspaces)\n\n      for (const workspace of workspaces) {\n        let workspaceImageUrl = \"\"\n\n        if (workspace.image_path) {\n          workspaceImageUrl =\n            (await getWorkspaceImageFromStorage(workspace.image_path)) || \"\"\n        }\n\n        if (workspaceImageUrl) {\n          const response = await fetch(workspaceImageUrl)\n          const blob = await response.blob()\n          const base64 = await convertBlobToBase64(blob)\n\n          setWorkspaceImages(prev => [\n            ...prev,\n            {\n              workspaceId: workspace.id,\n              path: workspace.image_path,\n              base64: base64,\n              url: workspaceImageUrl\n            }\n          ])\n        }\n      }\n\n      return profile\n    }\n  }\n\n  return (\n    <ChatbotUIContext.Provider\n      value={{\n        // PROFILE STORE\n        profile,\n        setProfile,\n\n        // ITEMS STORE\n        assistants,\n        setAssistants,\n        collections,\n        setCollections,\n        chats,\n        setChats,\n        files,\n        setFiles,\n        folders,\n        setFolders,\n        models,\n        setModels,\n        presets,\n        setPresets,\n        prompts,\n        setPrompts,\n        tools,\n        setTools,\n        workspaces,\n        setWorkspaces,\n\n        // MODELS STORE\n        envKeyMap,\n        setEnvKeyMap,\n        availableHostedModels,\n        setAvailableHostedModels,\n        availableLocalModels,\n        setAvailableLocalModels,\n        availableOpenRouterModels,\n        setAvailableOpenRouterModels,\n\n        // WORKSPACE STORE\n        selectedWorkspace,\n        setSelectedWorkspace,\n        workspaceImages,\n        setWorkspaceImages,\n\n        // PRESET STORE\n        selectedPreset,\n        setSelectedPreset,\n\n        // ASSISTANT STORE\n        selectedAssistant,\n        setSelectedAssistant,\n        assistantImages,\n        setAssistantImages,\n        openaiAssistants,\n        setOpenaiAssistants,\n\n        // PASSIVE CHAT STORE\n        userInput,\n        setUserInput,\n        chatMessages,\n        setChatMessages,\n        chatSettings,\n        setChatSettings,\n        selectedChat,\n        setSelectedChat,\n        chatFileItems,\n        setChatFileItems,\n\n        // ACTIVE CHAT STORE\n        isGenerating,\n        setIsGenerating,\n        firstTokenReceived,\n        setFirstTokenReceived,\n        abortController,\n        setAbortController,\n\n        // CHAT INPUT COMMAND STORE\n        isPromptPickerOpen,\n        setIsPromptPickerOpen,\n        slashCommand,\n        setSlashCommand,\n        isFilePickerOpen,\n        setIsFilePickerOpen,\n        hashtagCommand,\n        setHashtagCommand,\n        isToolPickerOpen,\n        setIsToolPickerOpen,\n        toolCommand,\n        setToolCommand,\n        focusPrompt,\n        setFocusPrompt,\n        focusFile,\n        setFocusFile,\n        focusTool,\n        setFocusTool,\n        focusAssistant,\n        setFocusAssistant,\n        atCommand,\n        setAtCommand,\n        isAssistantPickerOpen,\n        setIsAssistantPickerOpen,\n\n        // ATTACHMENT STORE\n        chatFiles,\n        setChatFiles,\n        chatImages,\n        setChatImages,\n        newMessageFiles,\n        setNewMessageFiles,\n        newMessageImages,\n        setNewMessageImages,\n        showFilesDisplay,\n        setShowFilesDisplay,\n\n        // RETRIEVAL STORE\n        useRetrieval,\n        setUseRetrieval,\n        sourceCount,\n        setSourceCount,\n\n        // TOOL STORE\n        selectedTools,\n        setSelectedTools,\n        toolInUse,\n        setToolInUse\n      }}\n    >\n      {children}\n    </ChatbotUIContext.Provider>\n  )\n}\n"
  },
  {
    "path": "components/utility/import.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { createAssistants } from \"@/db/assistants\"\nimport { createChats } from \"@/db/chats\"\nimport { createCollections } from \"@/db/collections\"\nimport { createFiles } from \"@/db/files\"\nimport { createPresets } from \"@/db/presets\"\nimport { createPrompts } from \"@/db/prompts\"\nimport { createTools } from \"@/db/tools\"\nimport { IconUpload, IconX } from \"@tabler/icons-react\"\nimport { FC, useContext, useRef, useState } from \"react\"\nimport { toast } from \"sonner\"\nimport { SIDEBAR_ICON_SIZE } from \"../sidebar/sidebar-switcher\"\nimport { Badge } from \"../ui/badge\"\nimport { Button } from \"../ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader\n} from \"../ui/dialog\"\nimport { Input } from \"../ui/input\"\n\ninterface ImportProps {}\n\nexport const Import: FC<ImportProps> = ({}) => {\n  const {\n    profile,\n    selectedWorkspace,\n    setChats,\n    setPresets,\n    setPrompts,\n    setFiles,\n    setCollections,\n    setAssistants,\n    setTools\n  } = useContext(ChatbotUIContext)\n\n  const inputRef = useRef<HTMLInputElement>(null)\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n  const [importList, setImportList] = useState<Array<Record<string, any>>>([])\n  const [importCounts, setImportCounts] = useState<{\n    chats: number\n    presets: number\n    prompts: number\n    files: number\n    collections: number\n    assistants: number\n    tools: number\n  }>({\n    chats: 0,\n    presets: 0,\n    prompts: 0,\n    files: 0,\n    collections: 0,\n    assistants: 0,\n    tools: 0\n  })\n\n  const stateUpdateFunctions = {\n    chats: setChats,\n    presets: setPresets,\n    prompts: setPrompts,\n    files: setFiles,\n    collections: setCollections,\n    assistants: setAssistants,\n    tools: setTools\n  }\n\n  const handleSelectFiles = async (e: any) => {\n    const filePromises = Array.from(e.target.files).map(file => {\n      return new Promise((resolve, reject) => {\n        const reader = new FileReader()\n        reader.onload = event => {\n          try {\n            const data = JSON.parse(event.target?.result as string)\n            resolve(Array.isArray(data) ? data : [data])\n          } catch (error) {\n            reject(error)\n          }\n        }\n        reader.readAsText(file as Blob)\n      })\n    })\n\n    try {\n      const results = await Promise.all(filePromises)\n      const flatResults = results.flat()\n      let uniqueResults: Array<Record<string, any>> = []\n      setImportList(prevState => {\n        const newState = [...prevState, ...flatResults]\n        uniqueResults = Array.from(\n          new Set(newState.map(item => JSON.stringify(item)))\n        ).map(item => JSON.parse(item))\n        return uniqueResults\n      })\n\n      setImportCounts(prevCounts => {\n        const countTypes = [\n          \"chats\",\n          \"presets\",\n          \"prompts\",\n          \"files\",\n          \"collections\",\n          \"assistants\"\n        ]\n        const newCounts: any = { ...prevCounts }\n        countTypes.forEach(type => {\n          newCounts[type] = uniqueResults.filter(\n            item => item.contentType === type\n          ).length\n        })\n        return newCounts\n      })\n    } catch (error) {\n      console.error(error)\n    }\n  }\n\n  const handleRemoveItem = (item: any) => {\n    setImportList(prev => prev.filter(prevItem => prevItem !== item))\n\n    setImportCounts(prev => {\n      const newCounts: any = { ...prev }\n      newCounts[item.contentType] -= 1\n      return newCounts\n    })\n  }\n\n  const handleCancel = () => {\n    setImportList([])\n    setImportCounts({\n      chats: 0,\n      presets: 0,\n      prompts: 0,\n      files: 0,\n      collections: 0,\n      assistants: 0,\n      tools: 0\n    })\n    setIsOpen(false)\n  }\n\n  const handleSaveData = async () => {\n    if (!profile) return\n    if (!selectedWorkspace) return\n\n    const saveData: any = {\n      chats: [],\n      presets: [],\n      prompts: [],\n      files: [],\n      collections: [],\n      assistants: [],\n      tools: []\n    }\n\n    importList.forEach(item => {\n      const { contentType, ...itemWithoutContentType } = item\n      itemWithoutContentType.user_id = profile.user_id\n      itemWithoutContentType.workspace_id = selectedWorkspace.id\n      saveData[contentType].push(itemWithoutContentType)\n    })\n\n    const createdItems = {\n      chats: await createChats(saveData.chats),\n      presets: await createPresets(saveData.presets, selectedWorkspace.id),\n      prompts: await createPrompts(saveData.prompts, selectedWorkspace.id),\n      files: await createFiles(saveData.files, selectedWorkspace.id),\n      collections: await createCollections(\n        saveData.collections,\n        selectedWorkspace.id\n      ),\n      assistants: await createAssistants(\n        saveData.assistants,\n        selectedWorkspace.id\n      ),\n      tools: await createTools(saveData.tools, selectedWorkspace.id)\n    }\n\n    Object.keys(createdItems).forEach(key => {\n      const typedKey = key as keyof typeof stateUpdateFunctions\n      stateUpdateFunctions[typedKey]((prevItems: any) => [\n        ...prevItems,\n        ...createdItems[typedKey]\n      ])\n    })\n\n    toast.success(\"Data imported successfully!\")\n\n    setImportList([])\n    setImportCounts({\n      chats: 0,\n      presets: 0,\n      prompts: 0,\n      files: 0,\n      collections: 0,\n      assistants: 0,\n      tools: 0\n    })\n    setIsOpen(false)\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\" && !e.shiftKey) {\n      e.preventDefault()\n      buttonRef.current?.click()\n    }\n  }\n\n  return (\n    <>\n      <IconUpload\n        className=\"cursor-pointer hover:opacity-50\"\n        size={SIDEBAR_ICON_SIZE}\n        onClick={() => setIsOpen(true)}\n      />\n\n      {isOpen && (\n        <Dialog open={isOpen} onOpenChange={setIsOpen}>\n          <DialogContent\n            className=\"max-w-[600px] space-y-4\"\n            onKeyDown={handleKeyDown}\n          >\n            <DialogHeader>\n              <div className=\"text-2xl font-bold\">Import Data</div>\n\n              <DialogDescription>\n                Import data from a JSON file(s).\n              </DialogDescription>\n            </DialogHeader>\n\n            <div className=\"max-w-[560px] space-y-4\">\n              <div className=\"space-y-1\">\n                {importList.map((item, index) => (\n                  <div key={index} className=\"flex space-x-2\">\n                    <Button className=\"shrink-0\" variant=\"ghost\" size=\"icon\">\n                      <IconX\n                        className=\"cursor-pointer hover:opacity-50\"\n                        onClick={() => handleRemoveItem(item)}\n                      />\n                    </Button>\n\n                    <div className=\"flex items-center space-x-2 truncate\">\n                      <Badge>\n                        {item.contentType.slice(0, -1).toUpperCase()}\n                      </Badge>\n\n                      <div className=\"truncate\">{item.name}</div>\n                    </div>\n                  </div>\n                ))}\n              </div>\n\n              {Object.entries(importCounts).map(([key, value]) => {\n                if (value > 0) {\n                  return <div key={key}>{`${key}: ${value}`}</div>\n                }\n                return null\n              })}\n\n              <Input\n                className=\"mt-4\"\n                ref={inputRef}\n                type=\"file\"\n                onChange={handleSelectFiles}\n                accept=\".json\"\n                multiple\n              />\n            </div>\n\n            <DialogFooter>\n              <Button variant=\"ghost\" onClick={handleCancel}>\n                Cancel\n              </Button>\n\n              <Button\n                ref={buttonRef}\n                onClick={handleSaveData}\n                disabled={importList.length === 0}\n              >\n                Save Data\n              </Button>\n            </DialogFooter>\n          </DialogContent>\n        </Dialog>\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "components/utility/profile-settings.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport {\n  PROFILE_CONTEXT_MAX,\n  PROFILE_DISPLAY_NAME_MAX,\n  PROFILE_USERNAME_MAX,\n  PROFILE_USERNAME_MIN\n} from \"@/db/limits\"\nimport { updateProfile } from \"@/db/profile\"\nimport { uploadProfileImage } from \"@/db/storage/profile-images\"\nimport { exportLocalStorageAsJSON } from \"@/lib/export-old-data\"\nimport { fetchOpenRouterModels } from \"@/lib/models/fetch-models\"\nimport { LLM_LIST_MAP } from \"@/lib/models/llm/llm-list\"\nimport { supabase } from \"@/lib/supabase/browser-client\"\nimport { cn } from \"@/lib/utils\"\nimport { OpenRouterLLM } from \"@/types\"\nimport {\n  IconCircleCheckFilled,\n  IconCircleXFilled,\n  IconFileDownload,\n  IconLoader2,\n  IconLogout,\n  IconUser\n} from \"@tabler/icons-react\"\nimport Image from \"next/image\"\nimport { useRouter } from \"next/navigation\"\nimport { FC, useCallback, useContext, useRef, useState } from \"react\"\nimport { toast } from \"sonner\"\nimport { SIDEBAR_ICON_SIZE } from \"../sidebar/sidebar-switcher\"\nimport { Button } from \"../ui/button\"\nimport ImagePicker from \"../ui/image-picker\"\nimport { Input } from \"../ui/input\"\nimport { Label } from \"../ui/label\"\nimport { LimitDisplay } from \"../ui/limit-display\"\nimport {\n  Sheet,\n  SheetContent,\n  SheetHeader,\n  SheetTitle,\n  SheetTrigger\n} from \"../ui/sheet\"\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../ui/tabs\"\nimport { TextareaAutosize } from \"../ui/textarea-autosize\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\nimport { ThemeSwitcher } from \"./theme-switcher\"\n\ninterface ProfileSettingsProps {}\n\nexport const ProfileSettings: FC<ProfileSettingsProps> = ({}) => {\n  const {\n    profile,\n    setProfile,\n    envKeyMap,\n    setAvailableHostedModels,\n    setAvailableOpenRouterModels,\n    availableOpenRouterModels\n  } = useContext(ChatbotUIContext)\n\n  const router = useRouter()\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n\n  const [displayName, setDisplayName] = useState(profile?.display_name || \"\")\n  const [username, setUsername] = useState(profile?.username || \"\")\n  const [usernameAvailable, setUsernameAvailable] = useState(true)\n  const [loadingUsername, setLoadingUsername] = useState(false)\n  const [profileImageSrc, setProfileImageSrc] = useState(\n    profile?.image_url || \"\"\n  )\n  const [profileImageFile, setProfileImageFile] = useState<File | null>(null)\n  const [profileInstructions, setProfileInstructions] = useState(\n    profile?.profile_context || \"\"\n  )\n\n  const [useAzureOpenai, setUseAzureOpenai] = useState(\n    profile?.use_azure_openai\n  )\n  const [openaiAPIKey, setOpenaiAPIKey] = useState(\n    profile?.openai_api_key || \"\"\n  )\n  const [openaiOrgID, setOpenaiOrgID] = useState(\n    profile?.openai_organization_id || \"\"\n  )\n  const [azureOpenaiAPIKey, setAzureOpenaiAPIKey] = useState(\n    profile?.azure_openai_api_key || \"\"\n  )\n  const [azureOpenaiEndpoint, setAzureOpenaiEndpoint] = useState(\n    profile?.azure_openai_endpoint || \"\"\n  )\n  const [azureOpenai35TurboID, setAzureOpenai35TurboID] = useState(\n    profile?.azure_openai_35_turbo_id || \"\"\n  )\n  const [azureOpenai45TurboID, setAzureOpenai45TurboID] = useState(\n    profile?.azure_openai_45_turbo_id || \"\"\n  )\n  const [azureOpenai45VisionID, setAzureOpenai45VisionID] = useState(\n    profile?.azure_openai_45_vision_id || \"\"\n  )\n  const [azureEmbeddingsID, setAzureEmbeddingsID] = useState(\n    profile?.azure_openai_embeddings_id || \"\"\n  )\n  const [anthropicAPIKey, setAnthropicAPIKey] = useState(\n    profile?.anthropic_api_key || \"\"\n  )\n  const [googleGeminiAPIKey, setGoogleGeminiAPIKey] = useState(\n    profile?.google_gemini_api_key || \"\"\n  )\n  const [mistralAPIKey, setMistralAPIKey] = useState(\n    profile?.mistral_api_key || \"\"\n  )\n  const [groqAPIKey, setGroqAPIKey] = useState(profile?.groq_api_key || \"\")\n  const [perplexityAPIKey, setPerplexityAPIKey] = useState(\n    profile?.perplexity_api_key || \"\"\n  )\n\n  const [openrouterAPIKey, setOpenrouterAPIKey] = useState(\n    profile?.openrouter_api_key || \"\"\n  )\n\n  const handleSignOut = async () => {\n    await supabase.auth.signOut()\n    router.push(\"/login\")\n    router.refresh()\n    return\n  }\n\n  const handleSave = async () => {\n    if (!profile) return\n    let profileImageUrl = profile.image_url\n    let profileImagePath = \"\"\n\n    if (profileImageFile) {\n      const { path, url } = await uploadProfileImage(profile, profileImageFile)\n      profileImageUrl = url ?? profileImageUrl\n      profileImagePath = path\n    }\n\n    const updatedProfile = await updateProfile(profile.id, {\n      ...profile,\n      display_name: displayName,\n      username,\n      profile_context: profileInstructions,\n      image_url: profileImageUrl,\n      image_path: profileImagePath,\n      openai_api_key: openaiAPIKey,\n      openai_organization_id: openaiOrgID,\n      anthropic_api_key: anthropicAPIKey,\n      google_gemini_api_key: googleGeminiAPIKey,\n      mistral_api_key: mistralAPIKey,\n      groq_api_key: groqAPIKey,\n      perplexity_api_key: perplexityAPIKey,\n      use_azure_openai: useAzureOpenai,\n      azure_openai_api_key: azureOpenaiAPIKey,\n      azure_openai_endpoint: azureOpenaiEndpoint,\n      azure_openai_35_turbo_id: azureOpenai35TurboID,\n      azure_openai_45_turbo_id: azureOpenai45TurboID,\n      azure_openai_45_vision_id: azureOpenai45VisionID,\n      azure_openai_embeddings_id: azureEmbeddingsID,\n      openrouter_api_key: openrouterAPIKey\n    })\n\n    setProfile(updatedProfile)\n\n    toast.success(\"Profile updated!\")\n\n    const providers = [\n      \"openai\",\n      \"google\",\n      \"azure\",\n      \"anthropic\",\n      \"mistral\",\n      \"groq\",\n      \"perplexity\",\n      \"openrouter\"\n    ]\n\n    providers.forEach(async provider => {\n      let providerKey: keyof typeof profile\n\n      if (provider === \"google\") {\n        providerKey = \"google_gemini_api_key\"\n      } else if (provider === \"azure\") {\n        providerKey = \"azure_openai_api_key\"\n      } else {\n        providerKey = `${provider}_api_key` as keyof typeof profile\n      }\n\n      const models = LLM_LIST_MAP[provider]\n      const envKeyActive = envKeyMap[provider]\n\n      if (!envKeyActive) {\n        const hasApiKey = !!updatedProfile[providerKey]\n\n        if (provider === \"openrouter\") {\n          if (hasApiKey && availableOpenRouterModels.length === 0) {\n            const openrouterModels: OpenRouterLLM[] =\n              await fetchOpenRouterModels()\n            setAvailableOpenRouterModels(prev => {\n              const newModels = openrouterModels.filter(\n                model =>\n                  !prev.some(prevModel => prevModel.modelId === model.modelId)\n              )\n              return [...prev, ...newModels]\n            })\n          } else {\n            setAvailableOpenRouterModels([])\n          }\n        } else {\n          if (hasApiKey && Array.isArray(models)) {\n            setAvailableHostedModels(prev => {\n              const newModels = models.filter(\n                model =>\n                  !prev.some(prevModel => prevModel.modelId === model.modelId)\n              )\n              return [...prev, ...newModels]\n            })\n          } else if (!hasApiKey && Array.isArray(models)) {\n            setAvailableHostedModels(prev =>\n              prev.filter(model => !models.includes(model))\n            )\n          }\n        }\n      }\n    })\n\n    setIsOpen(false)\n  }\n\n  const debounce = (func: (...args: any[]) => void, wait: number) => {\n    let timeout: NodeJS.Timeout | null\n\n    return (...args: any[]) => {\n      const later = () => {\n        if (timeout) clearTimeout(timeout)\n        func(...args)\n      }\n\n      if (timeout) clearTimeout(timeout)\n      timeout = setTimeout(later, wait)\n    }\n  }\n\n  const checkUsernameAvailability = useCallback(\n    debounce(async (username: string) => {\n      if (!username) return\n\n      if (username.length < PROFILE_USERNAME_MIN) {\n        setUsernameAvailable(false)\n        return\n      }\n\n      if (username.length > PROFILE_USERNAME_MAX) {\n        setUsernameAvailable(false)\n        return\n      }\n\n      const usernameRegex = /^[a-zA-Z0-9_]+$/\n      if (!usernameRegex.test(username)) {\n        setUsernameAvailable(false)\n        toast.error(\n          \"Username must be letters, numbers, or underscores only - no other characters or spacing allowed.\"\n        )\n        return\n      }\n\n      setLoadingUsername(true)\n\n      const response = await fetch(`/api/username/available`, {\n        method: \"POST\",\n        body: JSON.stringify({ username })\n      })\n\n      const data = await response.json()\n      const isAvailable = data.isAvailable\n\n      setUsernameAvailable(isAvailable)\n\n      if (username === profile?.username) {\n        setUsernameAvailable(true)\n      }\n\n      setLoadingUsername(false)\n    }, 500),\n    []\n  )\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\") {\n      buttonRef.current?.click()\n    }\n  }\n\n  if (!profile) return null\n\n  return (\n    <Sheet open={isOpen} onOpenChange={setIsOpen}>\n      <SheetTrigger asChild>\n        {profile.image_url ? (\n          <Image\n            className=\"mt-2 size-[34px] cursor-pointer rounded hover:opacity-50\"\n            src={profile.image_url + \"?\" + new Date().getTime()}\n            height={34}\n            width={34}\n            alt={\"Image\"}\n          />\n        ) : (\n          <Button size=\"icon\" variant=\"ghost\">\n            <IconUser size={SIDEBAR_ICON_SIZE} />\n          </Button>\n        )}\n      </SheetTrigger>\n\n      <SheetContent\n        className=\"flex flex-col justify-between\"\n        side=\"left\"\n        onKeyDown={handleKeyDown}\n      >\n        <div className=\"grow overflow-auto\">\n          <SheetHeader>\n            <SheetTitle className=\"flex items-center justify-between space-x-2\">\n              <div>User Settings</div>\n\n              <Button\n                tabIndex={-1}\n                className=\"text-xs\"\n                size=\"sm\"\n                onClick={handleSignOut}\n              >\n                <IconLogout className=\"mr-1\" size={20} />\n                Logout\n              </Button>\n            </SheetTitle>\n          </SheetHeader>\n\n          <Tabs defaultValue=\"profile\">\n            <TabsList className=\"mt-4 grid w-full grid-cols-2\">\n              <TabsTrigger value=\"profile\">Profile</TabsTrigger>\n              <TabsTrigger value=\"keys\">API Keys</TabsTrigger>\n            </TabsList>\n\n            <TabsContent className=\"mt-4 space-y-4\" value=\"profile\">\n              <div className=\"space-y-1\">\n                <div className=\"flex items-center space-x-2\">\n                  <Label>Username</Label>\n\n                  <div className=\"text-xs\">\n                    {username !== profile.username ? (\n                      usernameAvailable ? (\n                        <div className=\"text-green-500\">AVAILABLE</div>\n                      ) : (\n                        <div className=\"text-red-500\">UNAVAILABLE</div>\n                      )\n                    ) : null}\n                  </div>\n                </div>\n\n                <div className=\"relative\">\n                  <Input\n                    className=\"pr-10\"\n                    placeholder=\"Username...\"\n                    value={username}\n                    onChange={e => {\n                      setUsername(e.target.value)\n                      checkUsernameAvailability(e.target.value)\n                    }}\n                    minLength={PROFILE_USERNAME_MIN}\n                    maxLength={PROFILE_USERNAME_MAX}\n                  />\n\n                  {username !== profile.username ? (\n                    <div className=\"absolute inset-y-0 right-0 flex items-center pr-3\">\n                      {loadingUsername ? (\n                        <IconLoader2 className=\"animate-spin\" />\n                      ) : usernameAvailable ? (\n                        <IconCircleCheckFilled className=\"text-green-500\" />\n                      ) : (\n                        <IconCircleXFilled className=\"text-red-500\" />\n                      )}\n                    </div>\n                  ) : null}\n                </div>\n\n                <LimitDisplay\n                  used={username.length}\n                  limit={PROFILE_USERNAME_MAX}\n                />\n              </div>\n\n              <div className=\"space-y-1\">\n                <Label>Profile Image</Label>\n\n                <ImagePicker\n                  src={profileImageSrc}\n                  image={profileImageFile}\n                  height={50}\n                  width={50}\n                  onSrcChange={setProfileImageSrc}\n                  onImageChange={setProfileImageFile}\n                />\n              </div>\n\n              <div className=\"space-y-1\">\n                <Label>Chat Display Name</Label>\n\n                <Input\n                  placeholder=\"Chat display name...\"\n                  value={displayName}\n                  onChange={e => setDisplayName(e.target.value)}\n                  maxLength={PROFILE_DISPLAY_NAME_MAX}\n                />\n              </div>\n\n              <div className=\"space-y-1\">\n                <Label className=\"text-sm\">\n                  What would you like the AI to know about you to provide better\n                  responses?\n                </Label>\n\n                <TextareaAutosize\n                  value={profileInstructions}\n                  onValueChange={setProfileInstructions}\n                  placeholder=\"Profile context... (optional)\"\n                  minRows={6}\n                  maxRows={10}\n                />\n\n                <LimitDisplay\n                  used={profileInstructions.length}\n                  limit={PROFILE_CONTEXT_MAX}\n                />\n              </div>\n            </TabsContent>\n\n            <TabsContent className=\"mt-4 space-y-4\" value=\"keys\">\n              <div className=\"mt-5 space-y-2\">\n                <Label className=\"flex items-center\">\n                  {useAzureOpenai\n                    ? envKeyMap[\"azure\"]\n                      ? \"\"\n                      : \"Azure OpenAI API Key\"\n                    : envKeyMap[\"openai\"]\n                      ? \"\"\n                      : \"OpenAI API Key\"}\n\n                  <Button\n                    className={cn(\n                      \"h-[18px] w-[150px] text-[11px]\",\n                      (useAzureOpenai && !envKeyMap[\"azure\"]) ||\n                        (!useAzureOpenai && !envKeyMap[\"openai\"])\n                        ? \"ml-3\"\n                        : \"mb-3\"\n                    )}\n                    onClick={() => setUseAzureOpenai(!useAzureOpenai)}\n                  >\n                    {useAzureOpenai\n                      ? \"Switch To Standard OpenAI\"\n                      : \"Switch To Azure OpenAI\"}\n                  </Button>\n                </Label>\n\n                {useAzureOpenai ? (\n                  <>\n                    {envKeyMap[\"azure\"] ? (\n                      <Label>Azure OpenAI API key set by admin.</Label>\n                    ) : (\n                      <Input\n                        placeholder=\"Azure OpenAI API Key\"\n                        type=\"password\"\n                        value={azureOpenaiAPIKey}\n                        onChange={e => setAzureOpenaiAPIKey(e.target.value)}\n                      />\n                    )}\n                  </>\n                ) : (\n                  <>\n                    {envKeyMap[\"openai\"] ? (\n                      <Label>OpenAI API key set by admin.</Label>\n                    ) : (\n                      <Input\n                        placeholder=\"OpenAI API Key\"\n                        type=\"password\"\n                        value={openaiAPIKey}\n                        onChange={e => setOpenaiAPIKey(e.target.value)}\n                      />\n                    )}\n                  </>\n                )}\n              </div>\n\n              <div className=\"ml-8 space-y-3\">\n                {useAzureOpenai ? (\n                  <>\n                    {\n                      <div className=\"space-y-1\">\n                        {envKeyMap[\"azure_openai_endpoint\"] ? (\n                          <Label className=\"text-xs\">\n                            Azure endpoint set by admin.\n                          </Label>\n                        ) : (\n                          <>\n                            <Label>Azure Endpoint</Label>\n\n                            <Input\n                              placeholder=\"https://your-endpoint.openai.azure.com\"\n                              value={azureOpenaiEndpoint}\n                              onChange={e =>\n                                setAzureOpenaiEndpoint(e.target.value)\n                              }\n                            />\n                          </>\n                        )}\n                      </div>\n                    }\n\n                    {\n                      <div className=\"space-y-1\">\n                        {envKeyMap[\"azure_gpt_35_turbo_name\"] ? (\n                          <Label className=\"text-xs\">\n                            Azure GPT-3.5 Turbo deployment name set by admin.\n                          </Label>\n                        ) : (\n                          <>\n                            <Label>Azure GPT-3.5 Turbo Deployment Name</Label>\n\n                            <Input\n                              placeholder=\"Azure GPT-3.5 Turbo Deployment Name\"\n                              value={azureOpenai35TurboID}\n                              onChange={e =>\n                                setAzureOpenai35TurboID(e.target.value)\n                              }\n                            />\n                          </>\n                        )}\n                      </div>\n                    }\n\n                    {\n                      <div className=\"space-y-1\">\n                        {envKeyMap[\"azure_gpt_45_turbo_name\"] ? (\n                          <Label className=\"text-xs\">\n                            Azure GPT-4.5 Turbo deployment name set by admin.\n                          </Label>\n                        ) : (\n                          <>\n                            <Label>Azure GPT-4.5 Turbo Deployment Name</Label>\n\n                            <Input\n                              placeholder=\"Azure GPT-4.5 Turbo Deployment Name\"\n                              value={azureOpenai45TurboID}\n                              onChange={e =>\n                                setAzureOpenai45TurboID(e.target.value)\n                              }\n                            />\n                          </>\n                        )}\n                      </div>\n                    }\n\n                    {\n                      <div className=\"space-y-1\">\n                        {envKeyMap[\"azure_gpt_45_vision_name\"] ? (\n                          <Label className=\"text-xs\">\n                            Azure GPT-4.5 Vision deployment name set by admin.\n                          </Label>\n                        ) : (\n                          <>\n                            <Label>Azure GPT-4.5 Vision Deployment Name</Label>\n\n                            <Input\n                              placeholder=\"Azure GPT-4.5 Vision Deployment Name\"\n                              value={azureOpenai45VisionID}\n                              onChange={e =>\n                                setAzureOpenai45VisionID(e.target.value)\n                              }\n                            />\n                          </>\n                        )}\n                      </div>\n                    }\n\n                    {\n                      <div className=\"space-y-1\">\n                        {envKeyMap[\"azure_embeddings_name\"] ? (\n                          <Label className=\"text-xs\">\n                            Azure Embeddings deployment name set by admin.\n                          </Label>\n                        ) : (\n                          <>\n                            <Label>Azure Embeddings Deployment Name</Label>\n\n                            <Input\n                              placeholder=\"Azure Embeddings Deployment Name\"\n                              value={azureEmbeddingsID}\n                              onChange={e =>\n                                setAzureEmbeddingsID(e.target.value)\n                              }\n                            />\n                          </>\n                        )}\n                      </div>\n                    }\n                  </>\n                ) : (\n                  <>\n                    <div className=\"space-y-1\">\n                      {envKeyMap[\"openai_organization_id\"] ? (\n                        <Label className=\"text-xs\">\n                          OpenAI Organization ID set by admin.\n                        </Label>\n                      ) : (\n                        <>\n                          <Label>OpenAI Organization ID</Label>\n\n                          <Input\n                            placeholder=\"OpenAI Organization ID (optional)\"\n                            disabled={\n                              !!process.env.NEXT_PUBLIC_OPENAI_ORGANIZATION_ID\n                            }\n                            type=\"password\"\n                            value={openaiOrgID}\n                            onChange={e => setOpenaiOrgID(e.target.value)}\n                          />\n                        </>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n\n              <div className=\"space-y-1\">\n                {envKeyMap[\"anthropic\"] ? (\n                  <Label>Anthropic API key set by admin.</Label>\n                ) : (\n                  <>\n                    <Label>Anthropic API Key</Label>\n                    <Input\n                      placeholder=\"Anthropic API Key\"\n                      type=\"password\"\n                      value={anthropicAPIKey}\n                      onChange={e => setAnthropicAPIKey(e.target.value)}\n                    />\n                  </>\n                )}\n              </div>\n\n              <div className=\"space-y-1\">\n                {envKeyMap[\"google\"] ? (\n                  <Label>Google Gemini API key set by admin.</Label>\n                ) : (\n                  <>\n                    <Label>Google Gemini API Key</Label>\n                    <Input\n                      placeholder=\"Google Gemini API Key\"\n                      type=\"password\"\n                      value={googleGeminiAPIKey}\n                      onChange={e => setGoogleGeminiAPIKey(e.target.value)}\n                    />\n                  </>\n                )}\n              </div>\n\n              <div className=\"space-y-1\">\n                {envKeyMap[\"mistral\"] ? (\n                  <Label>Mistral API key set by admin.</Label>\n                ) : (\n                  <>\n                    <Label>Mistral API Key</Label>\n                    <Input\n                      placeholder=\"Mistral API Key\"\n                      type=\"password\"\n                      value={mistralAPIKey}\n                      onChange={e => setMistralAPIKey(e.target.value)}\n                    />\n                  </>\n                )}\n              </div>\n\n              <div className=\"space-y-1\">\n                {envKeyMap[\"groq\"] ? (\n                  <Label>Groq API key set by admin.</Label>\n                ) : (\n                  <>\n                    <Label>Groq API Key</Label>\n                    <Input\n                      placeholder=\"Groq API Key\"\n                      type=\"password\"\n                      value={groqAPIKey}\n                      onChange={e => setGroqAPIKey(e.target.value)}\n                    />\n                  </>\n                )}\n              </div>\n\n              <div className=\"space-y-1\">\n                {envKeyMap[\"perplexity\"] ? (\n                  <Label>Perplexity API key set by admin.</Label>\n                ) : (\n                  <>\n                    <Label>Perplexity API Key</Label>\n                    <Input\n                      placeholder=\"Perplexity API Key\"\n                      type=\"password\"\n                      value={perplexityAPIKey}\n                      onChange={e => setPerplexityAPIKey(e.target.value)}\n                    />\n                  </>\n                )}\n              </div>\n\n              <div className=\"space-y-1\">\n                {envKeyMap[\"openrouter\"] ? (\n                  <Label>OpenRouter API key set by admin.</Label>\n                ) : (\n                  <>\n                    <Label>OpenRouter API Key</Label>\n                    <Input\n                      placeholder=\"OpenRouter API Key\"\n                      type=\"password\"\n                      value={openrouterAPIKey}\n                      onChange={e => setOpenrouterAPIKey(e.target.value)}\n                    />\n                  </>\n                )}\n              </div>\n            </TabsContent>\n          </Tabs>\n        </div>\n\n        <div className=\"mt-6 flex items-center\">\n          <div className=\"flex items-center space-x-1\">\n            <ThemeSwitcher />\n\n            <WithTooltip\n              display={\n                <div>\n                  Download Chatbot UI 1.0 data as JSON. Import coming soon!\n                </div>\n              }\n              trigger={\n                <IconFileDownload\n                  className=\"cursor-pointer hover:opacity-50\"\n                  size={32}\n                  onClick={exportLocalStorageAsJSON}\n                />\n              }\n            />\n          </div>\n\n          <div className=\"ml-auto space-x-2\">\n            <Button variant=\"ghost\" onClick={() => setIsOpen(false)}>\n              Cancel\n            </Button>\n\n            <Button ref={buttonRef} onClick={handleSave}>\n              Save\n            </Button>\n          </div>\n        </div>\n      </SheetContent>\n    </Sheet>\n  )\n}\n"
  },
  {
    "path": "components/utility/providers.tsx",
    "content": "\"use client\"\n\nimport { TooltipProvider } from \"@/components/ui/tooltip\"\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\"\nimport { ThemeProviderProps } from \"next-themes/dist/types\"\nimport { FC } from \"react\"\n\nexport const Providers: FC<ThemeProviderProps> = ({ children, ...props }) => {\n  return (\n    <NextThemesProvider {...props}>\n      <TooltipProvider>{children}</TooltipProvider>\n    </NextThemesProvider>\n  )\n}\n"
  },
  {
    "path": "components/utility/theme-switcher.tsx",
    "content": "import { IconMoon, IconSun } from \"@tabler/icons-react\"\nimport { useTheme } from \"next-themes\"\nimport { FC } from \"react\"\nimport { SIDEBAR_ICON_SIZE } from \"../sidebar/sidebar-switcher\"\nimport { Button } from \"../ui/button\"\n\ninterface ThemeSwitcherProps {}\n\nexport const ThemeSwitcher: FC<ThemeSwitcherProps> = () => {\n  const { setTheme, theme } = useTheme()\n\n  const handleChange = (theme: \"dark\" | \"light\") => {\n    localStorage.setItem(\"theme\", theme)\n\n    setTheme(theme)\n  }\n\n  return (\n    <Button\n      className=\"flex cursor-pointer space-x-2\"\n      variant=\"ghost\"\n      size=\"icon\"\n      onClick={() => handleChange(theme === \"light\" ? \"dark\" : \"light\")}\n    >\n      {theme === \"dark\" ? (\n        <IconMoon size={SIDEBAR_ICON_SIZE} />\n      ) : (\n        <IconSun size={SIDEBAR_ICON_SIZE} />\n      )}\n    </Button>\n  )\n}\n"
  },
  {
    "path": "components/utility/translations-provider.tsx",
    "content": "\"use client\"\n\nimport initTranslations from \"@/lib/i18n\"\nimport { createInstance } from \"i18next\"\nimport { I18nextProvider } from \"react-i18next\"\n\nexport default function TranslationsProvider({\n  children,\n  locale,\n  namespaces,\n  resources\n}: any) {\n  const i18n = createInstance()\n\n  initTranslations(locale, namespaces, i18n, resources)\n\n  return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>\n}\n"
  },
  {
    "path": "components/utility/workspace-switcher.tsx",
    "content": "\"use client\"\n\nimport { useChatHandler } from \"@/components/chat/chat-hooks/use-chat-handler\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger\n} from \"@/components/ui/popover\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { createWorkspace } from \"@/db/workspaces\"\nimport useHotkey from \"@/lib/hooks/use-hotkey\"\nimport { IconBuilding, IconHome, IconPlus } from \"@tabler/icons-react\"\nimport { ChevronsUpDown } from \"lucide-react\"\nimport Image from \"next/image\"\nimport { useRouter } from \"next/navigation\"\nimport { FC, useContext, useEffect, useState } from \"react\"\nimport { Button } from \"../ui/button\"\nimport { Input } from \"../ui/input\"\n\ninterface WorkspaceSwitcherProps {}\n\nexport const WorkspaceSwitcher: FC<WorkspaceSwitcherProps> = ({}) => {\n  useHotkey(\";\", () => setOpen(prevState => !prevState))\n\n  const {\n    workspaces,\n    workspaceImages,\n    selectedWorkspace,\n    setSelectedWorkspace,\n    setWorkspaces\n  } = useContext(ChatbotUIContext)\n\n  const { handleNewChat } = useChatHandler()\n\n  const router = useRouter()\n\n  const [open, setOpen] = useState(false)\n  const [value, setValue] = useState(\"\")\n  const [search, setSearch] = useState(\"\")\n\n  useEffect(() => {\n    if (!selectedWorkspace) return\n\n    setValue(selectedWorkspace.id)\n  }, [selectedWorkspace])\n\n  const handleCreateWorkspace = async () => {\n    if (!selectedWorkspace) return\n\n    const createdWorkspace = await createWorkspace({\n      user_id: selectedWorkspace.user_id,\n      default_context_length: selectedWorkspace.default_context_length,\n      default_model: selectedWorkspace.default_model,\n      default_prompt: selectedWorkspace.default_prompt,\n      default_temperature: selectedWorkspace.default_temperature,\n      description: \"\",\n      embeddings_provider: \"openai\",\n      include_profile_context: selectedWorkspace.include_profile_context,\n      include_workspace_instructions:\n        selectedWorkspace.include_workspace_instructions,\n      instructions: selectedWorkspace.instructions,\n      is_home: false,\n      name: \"New Workspace\"\n    })\n\n    setWorkspaces([...workspaces, createdWorkspace])\n    setSelectedWorkspace(createdWorkspace)\n    setOpen(false)\n\n    return router.push(`/${createdWorkspace.id}/chat`)\n  }\n\n  const getWorkspaceName = (workspaceId: string) => {\n    const workspace = workspaces.find(workspace => workspace.id === workspaceId)\n\n    if (!workspace) return\n\n    return workspace.name\n  }\n\n  const handleSelect = (workspaceId: string) => {\n    const workspace = workspaces.find(workspace => workspace.id === workspaceId)\n\n    if (!workspace) return\n\n    setSelectedWorkspace(workspace)\n    setOpen(false)\n\n    return router.push(`/${workspace.id}/chat`)\n  }\n\n  const workspaceImage = workspaceImages.find(\n    image => image.workspaceId === selectedWorkspace?.id\n  )\n  const imageSrc = workspaceImage\n    ? workspaceImage.url\n    : selectedWorkspace?.is_home\n      ? \"\"\n      : \"\"\n\n  const IconComponent = selectedWorkspace?.is_home ? IconHome : IconBuilding\n\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <PopoverTrigger\n        className=\"border-input flex h-[36px]\n        w-full cursor-pointer items-center justify-between rounded-md border px-2 py-1 hover:opacity-50\"\n      >\n        <div className=\"flex items-center truncate\">\n          {selectedWorkspace && (\n            <div className=\"flex items-center\">\n              {workspaceImage ? (\n                <Image\n                  style={{ width: \"22px\", height: \"22px\" }}\n                  className=\"mr-2 rounded\"\n                  src={imageSrc}\n                  width={22}\n                  height={22}\n                  alt={selectedWorkspace.name}\n                />\n              ) : (\n                <IconComponent className=\"mb-0.5 mr-2\" size={22} />\n              )}\n            </div>\n          )}\n\n          {getWorkspaceName(value) || \"Select workspace...\"}\n        </div>\n\n        <ChevronsUpDown className=\"ml-2 size-4 shrink-0 opacity-50\" />\n      </PopoverTrigger>\n\n      <PopoverContent className=\"p-2\">\n        <div className=\"space-y-2\">\n          <Button\n            className=\"flex w-full items-center space-x-2\"\n            size=\"sm\"\n            onClick={handleCreateWorkspace}\n          >\n            <IconPlus />\n            <div className=\"ml-2\">New Workspace</div>\n          </Button>\n\n          <Input\n            placeholder=\"Search workspaces...\"\n            autoFocus\n            value={search}\n            onChange={e => setSearch(e.target.value)}\n          />\n\n          <div className=\"flex flex-col space-y-1\">\n            {workspaces\n              .filter(workspace => workspace.is_home)\n              .map(workspace => {\n                const image = workspaceImages.find(\n                  image => image.workspaceId === workspace.id\n                )\n\n                return (\n                  <Button\n                    key={workspace.id}\n                    className=\"flex items-center justify-start\"\n                    variant=\"ghost\"\n                    onClick={() => handleSelect(workspace.id)}\n                  >\n                    {image ? (\n                      <Image\n                        style={{ width: \"28px\", height: \"28px\" }}\n                        className=\"mr-3 rounded\"\n                        src={image.url || \"\"}\n                        width={28}\n                        height={28}\n                        alt={workspace.name}\n                      />\n                    ) : (\n                      <IconHome className=\"mr-3\" size={28} />\n                    )}\n\n                    <div className=\"text-lg font-semibold\">\n                      {workspace.name}\n                    </div>\n                  </Button>\n                )\n              })}\n\n            {workspaces\n              .filter(\n                workspace =>\n                  !workspace.is_home &&\n                  workspace.name.toLowerCase().includes(search.toLowerCase())\n              )\n              .sort((a, b) => a.name.localeCompare(b.name))\n              .map(workspace => {\n                const image = workspaceImages.find(\n                  image => image.workspaceId === workspace.id\n                )\n\n                return (\n                  <Button\n                    key={workspace.id}\n                    className=\"flex items-center justify-start\"\n                    variant=\"ghost\"\n                    onClick={() => handleSelect(workspace.id)}\n                  >\n                    {image ? (\n                      <Image\n                        style={{ width: \"28px\", height: \"28px\" }}\n                        className=\"mr-3 rounded\"\n                        src={image.url || \"\"}\n                        width={28}\n                        height={28}\n                        alt={workspace.name}\n                      />\n                    ) : (\n                      <IconBuilding className=\"mr-3\" size={28} />\n                    )}\n\n                    <div className=\"text-lg font-semibold\">\n                      {workspace.name}\n                    </div>\n                  </Button>\n                )\n              })}\n          </div>\n        </div>\n      </PopoverContent>\n    </Popover>\n  )\n}\n"
  },
  {
    "path": "components/workspace/assign-workspaces.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { Tables } from \"@/supabase/types\"\nimport { IconChevronDown, IconCircleCheckFilled } from \"@tabler/icons-react\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\nimport { Button } from \"../ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuTrigger\n} from \"../ui/dropdown-menu\"\nimport { Input } from \"../ui/input\"\nimport { toast } from \"sonner\"\n\ninterface AssignWorkspaces {\n  selectedWorkspaces: Tables<\"workspaces\">[]\n  onSelectWorkspace: (workspace: Tables<\"workspaces\">) => void\n}\n\nexport const AssignWorkspaces: FC<AssignWorkspaces> = ({\n  selectedWorkspaces,\n  onSelectWorkspace\n}) => {\n  const { workspaces } = useContext(ChatbotUIContext)\n\n  const inputRef = useRef<HTMLInputElement>(null)\n  const triggerRef = useRef<HTMLButtonElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n  const [search, setSearch] = useState(\"\")\n\n  useEffect(() => {\n    if (isOpen) {\n      setTimeout(() => {\n        inputRef.current?.focus()\n      }, 100) // FIX: hacky\n    }\n  }, [isOpen])\n\n  const handleWorkspaceSelect = (workspace: Tables<\"workspaces\">) => {\n    onSelectWorkspace(workspace)\n  }\n\n  if (!workspaces) return null\n\n  return (\n    <DropdownMenu\n      open={isOpen}\n      onOpenChange={isOpen => {\n        setIsOpen(isOpen)\n        setSearch(\"\")\n      }}\n    >\n      <DropdownMenuTrigger\n        className=\"bg-background w-full justify-start border-2 px-3 py-5\"\n        asChild\n      >\n        <Button\n          ref={triggerRef}\n          className=\"flex items-center justify-between\"\n          variant=\"ghost\"\n        >\n          <div className=\"flex items-center\">\n            <div className=\"ml-2 flex items-center\">\n              {selectedWorkspaces.length} workspaces selected\n            </div>\n          </div>\n\n          <IconChevronDown />\n        </Button>\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent\n        style={{ width: triggerRef.current?.offsetWidth }}\n        className=\"space-y-2 overflow-auto p-2\"\n        align=\"start\"\n      >\n        <Input\n          ref={inputRef}\n          placeholder=\"Search workspaces...\"\n          value={search}\n          onChange={e => setSearch(e.target.value)}\n          onKeyDown={e => e.stopPropagation()}\n        />\n\n        {selectedWorkspaces\n          .filter(workspace =>\n            workspace.name.toLowerCase().includes(search.toLowerCase())\n          )\n          .map(workspace => (\n            <WorkspaceItem\n              key={workspace.id}\n              selectedWorkspaces={selectedWorkspaces}\n              workspace={workspace}\n              selected={selectedWorkspaces.some(\n                selectedWorkspace => selectedWorkspace.id === workspace.id\n              )}\n              onSelect={handleWorkspaceSelect}\n            />\n          ))}\n\n        {workspaces\n          .filter(\n            workspace =>\n              !selectedWorkspaces.some(\n                selectedWorkspace => selectedWorkspace.id === workspace.id\n              ) && workspace.name.toLowerCase().includes(search.toLowerCase())\n          )\n          .map(workspace => (\n            <WorkspaceItem\n              key={workspace.id}\n              selectedWorkspaces={selectedWorkspaces}\n              workspace={workspace}\n              selected={selectedWorkspaces.some(\n                selectedWorkspace => selectedWorkspace.id === workspace.id\n              )}\n              onSelect={handleWorkspaceSelect}\n            />\n          ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\ninterface WorkspaceItemProps {\n  selectedWorkspaces: Tables<\"workspaces\">[]\n  workspace: Tables<\"workspaces\">\n  selected: boolean\n  onSelect: (workspace: Tables<\"workspaces\">) => void\n}\n\nconst WorkspaceItem: FC<WorkspaceItemProps> = ({\n  selectedWorkspaces,\n  workspace,\n  selected,\n  onSelect\n}) => {\n  const handleSelect = () => {\n    if (selected && selectedWorkspaces.length === 1) {\n      toast.info(\"You must select at least one workspace\")\n      return\n    }\n\n    onSelect(workspace)\n  }\n\n  return (\n    <div\n      className=\"flex cursor-pointer items-center justify-between py-0.5 hover:opacity-50\"\n      onClick={handleSelect}\n    >\n      <div className=\"flex grow items-center truncate\">\n        <div className=\"truncate\">{workspace.name}</div>\n      </div>\n\n      {selected && (\n        <IconCircleCheckFilled size={20} className=\"min-w-[30px] flex-none\" />\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "components/workspace/delete-workspace.tsx",
    "content": "import { useChatHandler } from \"@/components/chat/chat-hooks/use-chat-handler\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from \"@/components/ui/dialog\"\nimport { ChatbotUIContext } from \"@/context/context\"\nimport { deleteWorkspace } from \"@/db/workspaces\"\nimport { Tables } from \"@/supabase/types\"\nimport { FC, useContext, useRef, useState } from \"react\"\nimport { Input } from \"../ui/input\"\nimport { useRouter } from \"next/navigation\"\n\ninterface DeleteWorkspaceProps {\n  workspace: Tables<\"workspaces\">\n  onDelete: () => void\n}\n\nexport const DeleteWorkspace: FC<DeleteWorkspaceProps> = ({\n  workspace,\n  onDelete\n}) => {\n  const { setWorkspaces, setSelectedWorkspace } = useContext(ChatbotUIContext)\n  const { handleNewChat } = useChatHandler()\n  const router = useRouter()\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [showWorkspaceDialog, setShowWorkspaceDialog] = useState(false)\n\n  const [name, setName] = useState(\"\")\n\n  const handleDeleteWorkspace = async () => {\n    await deleteWorkspace(workspace.id)\n\n    setWorkspaces(prevWorkspaces => {\n      const filteredWorkspaces = prevWorkspaces.filter(\n        w => w.id !== workspace.id\n      )\n\n      const defaultWorkspace = filteredWorkspaces[0]\n\n      setSelectedWorkspace(defaultWorkspace)\n      router.push(`/${defaultWorkspace.id}/chat`)\n\n      return filteredWorkspaces\n    })\n\n    setShowWorkspaceDialog(false)\n    onDelete()\n\n    handleNewChat()\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\") {\n      buttonRef.current?.click()\n    }\n  }\n\n  return (\n    <Dialog open={showWorkspaceDialog} onOpenChange={setShowWorkspaceDialog}>\n      <DialogTrigger asChild>\n        <Button variant=\"destructive\">Delete</Button>\n      </DialogTrigger>\n\n      <DialogContent onKeyDown={handleKeyDown}>\n        <DialogHeader>\n          <DialogTitle>Delete {workspace.name}</DialogTitle>\n\n          <DialogDescription className=\"space-y-1\">\n            WARNING: Deleting a workspace will delete all of its data.\n          </DialogDescription>\n        </DialogHeader>\n\n        <Input\n          className=\"mt-4\"\n          placeholder=\"Type the name of this workspace to confirm\"\n          value={name}\n          onChange={e => setName(e.target.value)}\n        />\n\n        <DialogFooter>\n          <Button variant=\"ghost\" onClick={() => setShowWorkspaceDialog(false)}>\n            Cancel\n          </Button>\n\n          <Button\n            ref={buttonRef}\n            variant=\"destructive\"\n            onClick={handleDeleteWorkspace}\n            disabled={name !== workspace.name}\n          >\n            Delete\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "components/workspace/workspace-settings.tsx",
    "content": "import { ChatbotUIContext } from \"@/context/context\"\nimport { WORKSPACE_INSTRUCTIONS_MAX } from \"@/db/limits\"\nimport {\n  getWorkspaceImageFromStorage,\n  uploadWorkspaceImage\n} from \"@/db/storage/workspace-images\"\nimport { updateWorkspace } from \"@/db/workspaces\"\nimport { convertBlobToBase64 } from \"@/lib/blob-to-b64\"\nimport { LLMID } from \"@/types\"\nimport { IconHome, IconSettings } from \"@tabler/icons-react\"\nimport { FC, useContext, useEffect, useRef, useState } from \"react\"\nimport { toast } from \"sonner\"\nimport { Button } from \"../ui/button\"\nimport { ChatSettingsForm } from \"../ui/chat-settings-form\"\nimport ImagePicker from \"../ui/image-picker\"\nimport { Input } from \"../ui/input\"\nimport { Label } from \"../ui/label\"\nimport { LimitDisplay } from \"../ui/limit-display\"\nimport {\n  Sheet,\n  SheetContent,\n  SheetHeader,\n  SheetTitle,\n  SheetTrigger\n} from \"../ui/sheet\"\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../ui/tabs\"\nimport { TextareaAutosize } from \"../ui/textarea-autosize\"\nimport { WithTooltip } from \"../ui/with-tooltip\"\nimport { DeleteWorkspace } from \"./delete-workspace\"\n\ninterface WorkspaceSettingsProps {}\n\nexport const WorkspaceSettings: FC<WorkspaceSettingsProps> = ({}) => {\n  const {\n    profile,\n    selectedWorkspace,\n    setSelectedWorkspace,\n    setWorkspaces,\n    setChatSettings,\n    workspaceImages,\n    setWorkspaceImages\n  } = useContext(ChatbotUIContext)\n\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  const [isOpen, setIsOpen] = useState(false)\n\n  const [name, setName] = useState(selectedWorkspace?.name || \"\")\n  const [imageLink, setImageLink] = useState(\"\")\n  const [selectedImage, setSelectedImage] = useState<File | null>(null)\n  const [description, setDescription] = useState(\n    selectedWorkspace?.description || \"\"\n  )\n  const [instructions, setInstructions] = useState(\n    selectedWorkspace?.instructions || \"\"\n  )\n\n  const [defaultChatSettings, setDefaultChatSettings] = useState({\n    model: selectedWorkspace?.default_model,\n    prompt: selectedWorkspace?.default_prompt,\n    temperature: selectedWorkspace?.default_temperature,\n    contextLength: selectedWorkspace?.default_context_length,\n    includeProfileContext: selectedWorkspace?.include_profile_context,\n    includeWorkspaceInstructions:\n      selectedWorkspace?.include_workspace_instructions,\n    embeddingsProvider: selectedWorkspace?.embeddings_provider\n  })\n\n  useEffect(() => {\n    const workspaceImage =\n      workspaceImages.find(\n        image => image.path === selectedWorkspace?.image_path\n      )?.base64 || \"\"\n\n    setImageLink(workspaceImage)\n  }, [workspaceImages])\n\n  const handleSave = async () => {\n    if (!selectedWorkspace) return\n\n    let imagePath = \"\"\n\n    if (selectedImage) {\n      imagePath = await uploadWorkspaceImage(selectedWorkspace, selectedImage)\n\n      const url = (await getWorkspaceImageFromStorage(imagePath)) || \"\"\n\n      if (url) {\n        const response = await fetch(url)\n        const blob = await response.blob()\n        const base64 = await convertBlobToBase64(blob)\n\n        setWorkspaceImages(prev => [\n          ...prev,\n          {\n            workspaceId: selectedWorkspace.id,\n            path: imagePath,\n            base64,\n            url\n          }\n        ])\n      }\n    }\n\n    const updatedWorkspace = await updateWorkspace(selectedWorkspace.id, {\n      ...selectedWorkspace,\n      name,\n      description,\n      image_path: imagePath,\n      instructions,\n      default_model: defaultChatSettings.model,\n      default_prompt: defaultChatSettings.prompt,\n      default_temperature: defaultChatSettings.temperature,\n      default_context_length: defaultChatSettings.contextLength,\n      embeddings_provider: defaultChatSettings.embeddingsProvider,\n      include_profile_context: defaultChatSettings.includeProfileContext,\n      include_workspace_instructions:\n        defaultChatSettings.includeWorkspaceInstructions\n    })\n\n    if (\n      defaultChatSettings.model &&\n      defaultChatSettings.prompt &&\n      defaultChatSettings.temperature &&\n      defaultChatSettings.contextLength &&\n      defaultChatSettings.includeProfileContext &&\n      defaultChatSettings.includeWorkspaceInstructions &&\n      defaultChatSettings.embeddingsProvider\n    ) {\n      setChatSettings({\n        model: defaultChatSettings.model as LLMID,\n        prompt: defaultChatSettings.prompt,\n        temperature: defaultChatSettings.temperature,\n        contextLength: defaultChatSettings.contextLength,\n        includeProfileContext: defaultChatSettings.includeProfileContext,\n        includeWorkspaceInstructions:\n          defaultChatSettings.includeWorkspaceInstructions,\n        embeddingsProvider: defaultChatSettings.embeddingsProvider as\n          | \"openai\"\n          | \"local\"\n      })\n    }\n\n    setIsOpen(false)\n    setSelectedWorkspace(updatedWorkspace)\n    setWorkspaces(workspaces => {\n      return workspaces.map(workspace => {\n        if (workspace.id === selectedWorkspace.id) {\n          return updatedWorkspace\n        }\n\n        return workspace\n      })\n    })\n\n    toast.success(\"Workspace updated!\")\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    if (e.key === \"Enter\" && !e.shiftKey) {\n      buttonRef.current?.click()\n    }\n  }\n\n  if (!selectedWorkspace || !profile) return null\n\n  return (\n    <Sheet open={isOpen} onOpenChange={setIsOpen}>\n      <SheetTrigger asChild>\n        <WithTooltip\n          display={<div>Workspace Settings</div>}\n          trigger={\n            <IconSettings\n              className=\"ml-3 cursor-pointer pr-[5px] hover:opacity-50\"\n              size={32}\n              onClick={() => setIsOpen(true)}\n            />\n          }\n        />\n      </SheetTrigger>\n\n      <SheetContent\n        className=\"flex flex-col justify-between\"\n        side=\"left\"\n        onKeyDown={handleKeyDown}\n      >\n        <div className=\"grow overflow-auto\">\n          <SheetHeader>\n            <SheetTitle className=\"flex items-center justify-between\">\n              Workspace Settings\n              {selectedWorkspace?.is_home && <IconHome />}\n            </SheetTitle>\n\n            {selectedWorkspace?.is_home && (\n              <div className=\"text-sm font-light\">\n                This is your home workspace for personal use.\n              </div>\n            )}\n          </SheetHeader>\n\n          <Tabs defaultValue=\"main\">\n            <TabsList className=\"mt-4 grid w-full grid-cols-2\">\n              <TabsTrigger value=\"main\">Main</TabsTrigger>\n              <TabsTrigger value=\"defaults\">Defaults</TabsTrigger>\n            </TabsList>\n\n            <TabsContent className=\"mt-4 space-y-4\" value=\"main\">\n              <>\n                <div className=\"space-y-1\">\n                  <Label>Workspace Name</Label>\n\n                  <Input\n                    placeholder=\"Name...\"\n                    value={name}\n                    onChange={e => setName(e.target.value)}\n                  />\n                </div>\n\n                {/* <div className=\"space-y-1\">\n                  <Label>Description</Label>\n\n                  <Input\n                    placeholder=\"Description... (optional)\"\n                    value={description}\n                    onChange={e => setDescription(e.target.value)}\n                  />\n                </div> */}\n\n                <div className=\"space-y-1\">\n                  <Label>Workspace Image</Label>\n\n                  <ImagePicker\n                    src={imageLink}\n                    image={selectedImage}\n                    onSrcChange={setImageLink}\n                    onImageChange={setSelectedImage}\n                    width={50}\n                    height={50}\n                  />\n                </div>\n              </>\n\n              <div className=\"space-y-1\">\n                <Label>\n                  How would you like the AI to respond in this workspace?\n                </Label>\n\n                <TextareaAutosize\n                  placeholder=\"Instructions... (optional)\"\n                  value={instructions}\n                  onValueChange={setInstructions}\n                  minRows={5}\n                  maxRows={10}\n                  maxLength={1500}\n                />\n\n                <LimitDisplay\n                  used={instructions.length}\n                  limit={WORKSPACE_INSTRUCTIONS_MAX}\n                />\n              </div>\n            </TabsContent>\n\n            <TabsContent className=\"mt-5\" value=\"defaults\">\n              <div className=\"mb-4 text-sm\">\n                These are the settings your workspace begins with when selected.\n              </div>\n\n              <ChatSettingsForm\n                chatSettings={defaultChatSettings as any}\n                onChangeChatSettings={setDefaultChatSettings}\n              />\n            </TabsContent>\n          </Tabs>\n        </div>\n\n        <div className=\"mt-6 flex justify-between\">\n          <div>\n            {!selectedWorkspace.is_home && (\n              <DeleteWorkspace\n                workspace={selectedWorkspace}\n                onDelete={() => setIsOpen(false)}\n              />\n            )}\n          </div>\n\n          <div className=\"space-x-2\">\n            <Button variant=\"ghost\" onClick={() => setIsOpen(false)}>\n              Cancel\n            </Button>\n\n            <Button ref={buttonRef} onClick={handleSave}>\n              Save\n            </Button>\n          </div>\n        </div>\n      </SheetContent>\n    </Sheet>\n  )\n}\n"
  },
  {
    "path": "components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"tailwind.config.js\",\n    \"css\": \"app/globals.css\",\n    \"baseColor\": \"gray\",\n    \"cssVariables\": true\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\"\n  }\n}\n"
  },
  {
    "path": "context/context.tsx",
    "content": "import { Tables } from \"@/supabase/types\"\nimport {\n  ChatFile,\n  ChatMessage,\n  ChatSettings,\n  LLM,\n  MessageImage,\n  OpenRouterLLM,\n  WorkspaceImage\n} from \"@/types\"\nimport { AssistantImage } from \"@/types/images/assistant-image\"\nimport { VALID_ENV_KEYS } from \"@/types/valid-keys\"\nimport { Dispatch, SetStateAction, createContext } from \"react\"\n\ninterface ChatbotUIContext {\n  // PROFILE STORE\n  profile: Tables<\"profiles\"> | null\n  setProfile: Dispatch<SetStateAction<Tables<\"profiles\"> | null>>\n\n  // ITEMS STORE\n  assistants: Tables<\"assistants\">[]\n  setAssistants: Dispatch<SetStateAction<Tables<\"assistants\">[]>>\n  collections: Tables<\"collections\">[]\n  setCollections: Dispatch<SetStateAction<Tables<\"collections\">[]>>\n  chats: Tables<\"chats\">[]\n  setChats: Dispatch<SetStateAction<Tables<\"chats\">[]>>\n  files: Tables<\"files\">[]\n  setFiles: Dispatch<SetStateAction<Tables<\"files\">[]>>\n  folders: Tables<\"folders\">[]\n  setFolders: Dispatch<SetStateAction<Tables<\"folders\">[]>>\n  models: Tables<\"models\">[]\n  setModels: Dispatch<SetStateAction<Tables<\"models\">[]>>\n  presets: Tables<\"presets\">[]\n  setPresets: Dispatch<SetStateAction<Tables<\"presets\">[]>>\n  prompts: Tables<\"prompts\">[]\n  setPrompts: Dispatch<SetStateAction<Tables<\"prompts\">[]>>\n  tools: Tables<\"tools\">[]\n  setTools: Dispatch<SetStateAction<Tables<\"tools\">[]>>\n  workspaces: Tables<\"workspaces\">[]\n  setWorkspaces: Dispatch<SetStateAction<Tables<\"workspaces\">[]>>\n\n  // MODELS STORE\n  envKeyMap: Record<string, VALID_ENV_KEYS>\n  setEnvKeyMap: Dispatch<SetStateAction<Record<string, VALID_ENV_KEYS>>>\n  availableHostedModels: LLM[]\n  setAvailableHostedModels: Dispatch<SetStateAction<LLM[]>>\n  availableLocalModels: LLM[]\n  setAvailableLocalModels: Dispatch<SetStateAction<LLM[]>>\n  availableOpenRouterModels: OpenRouterLLM[]\n  setAvailableOpenRouterModels: Dispatch<SetStateAction<OpenRouterLLM[]>>\n\n  // WORKSPACE STORE\n  selectedWorkspace: Tables<\"workspaces\"> | null\n  setSelectedWorkspace: Dispatch<SetStateAction<Tables<\"workspaces\"> | null>>\n  workspaceImages: WorkspaceImage[]\n  setWorkspaceImages: Dispatch<SetStateAction<WorkspaceImage[]>>\n\n  // PRESET STORE\n  selectedPreset: Tables<\"presets\"> | null\n  setSelectedPreset: Dispatch<SetStateAction<Tables<\"presets\"> | null>>\n\n  // ASSISTANT STORE\n  selectedAssistant: Tables<\"assistants\"> | null\n  setSelectedAssistant: Dispatch<SetStateAction<Tables<\"assistants\"> | null>>\n  assistantImages: AssistantImage[]\n  setAssistantImages: Dispatch<SetStateAction<AssistantImage[]>>\n  openaiAssistants: any[]\n  setOpenaiAssistants: Dispatch<SetStateAction<any[]>>\n\n  // PASSIVE CHAT STORE\n  userInput: string\n  setUserInput: Dispatch<SetStateAction<string>>\n  chatMessages: ChatMessage[]\n  setChatMessages: Dispatch<SetStateAction<ChatMessage[]>>\n  chatSettings: ChatSettings | null\n  setChatSettings: Dispatch<SetStateAction<ChatSettings>>\n  selectedChat: Tables<\"chats\"> | null\n  setSelectedChat: Dispatch<SetStateAction<Tables<\"chats\"> | null>>\n  chatFileItems: Tables<\"file_items\">[]\n  setChatFileItems: Dispatch<SetStateAction<Tables<\"file_items\">[]>>\n\n  // ACTIVE CHAT STORE\n  abortController: AbortController | null\n  setAbortController: Dispatch<SetStateAction<AbortController | null>>\n  firstTokenReceived: boolean\n  setFirstTokenReceived: Dispatch<SetStateAction<boolean>>\n  isGenerating: boolean\n  setIsGenerating: Dispatch<SetStateAction<boolean>>\n\n  // CHAT INPUT COMMAND STORE\n  isPromptPickerOpen: boolean\n  setIsPromptPickerOpen: Dispatch<SetStateAction<boolean>>\n  slashCommand: string\n  setSlashCommand: Dispatch<SetStateAction<string>>\n  isFilePickerOpen: boolean\n  setIsFilePickerOpen: Dispatch<SetStateAction<boolean>>\n  hashtagCommand: string\n  setHashtagCommand: Dispatch<SetStateAction<string>>\n  isToolPickerOpen: boolean\n  setIsToolPickerOpen: Dispatch<SetStateAction<boolean>>\n  toolCommand: string\n  setToolCommand: Dispatch<SetStateAction<string>>\n  focusPrompt: boolean\n  setFocusPrompt: Dispatch<SetStateAction<boolean>>\n  focusFile: boolean\n  setFocusFile: Dispatch<SetStateAction<boolean>>\n  focusTool: boolean\n  setFocusTool: Dispatch<SetStateAction<boolean>>\n  focusAssistant: boolean\n  setFocusAssistant: Dispatch<SetStateAction<boolean>>\n  atCommand: string\n  setAtCommand: Dispatch<SetStateAction<string>>\n  isAssistantPickerOpen: boolean\n  setIsAssistantPickerOpen: Dispatch<SetStateAction<boolean>>\n\n  // ATTACHMENTS STORE\n  chatFiles: ChatFile[]\n  setChatFiles: Dispatch<SetStateAction<ChatFile[]>>\n  chatImages: MessageImage[]\n  setChatImages: Dispatch<SetStateAction<MessageImage[]>>\n  newMessageFiles: ChatFile[]\n  setNewMessageFiles: Dispatch<SetStateAction<ChatFile[]>>\n  newMessageImages: MessageImage[]\n  setNewMessageImages: Dispatch<SetStateAction<MessageImage[]>>\n  showFilesDisplay: boolean\n  setShowFilesDisplay: Dispatch<SetStateAction<boolean>>\n\n  // RETRIEVAL STORE\n  useRetrieval: boolean\n  setUseRetrieval: Dispatch<SetStateAction<boolean>>\n  sourceCount: number\n  setSourceCount: Dispatch<SetStateAction<number>>\n\n  // TOOL STORE\n  selectedTools: Tables<\"tools\">[]\n  setSelectedTools: Dispatch<SetStateAction<Tables<\"tools\">[]>>\n  toolInUse: string\n  setToolInUse: Dispatch<SetStateAction<string>>\n}\n\nexport const ChatbotUIContext = createContext<ChatbotUIContext>({\n  // PROFILE STORE\n  profile: null,\n  setProfile: () => {},\n\n  // ITEMS STORE\n  assistants: [],\n  setAssistants: () => {},\n  collections: [],\n  setCollections: () => {},\n  chats: [],\n  setChats: () => {},\n  files: [],\n  setFiles: () => {},\n  folders: [],\n  setFolders: () => {},\n  models: [],\n  setModels: () => {},\n  presets: [],\n  setPresets: () => {},\n  prompts: [],\n  setPrompts: () => {},\n  tools: [],\n  setTools: () => {},\n  workspaces: [],\n  setWorkspaces: () => {},\n\n  // MODELS STORE\n  envKeyMap: {},\n  setEnvKeyMap: () => {},\n  availableHostedModels: [],\n  setAvailableHostedModels: () => {},\n  availableLocalModels: [],\n  setAvailableLocalModels: () => {},\n  availableOpenRouterModels: [],\n  setAvailableOpenRouterModels: () => {},\n\n  // WORKSPACE STORE\n  selectedWorkspace: null,\n  setSelectedWorkspace: () => {},\n  workspaceImages: [],\n  setWorkspaceImages: () => {},\n\n  // PRESET STORE\n  selectedPreset: null,\n  setSelectedPreset: () => {},\n\n  // ASSISTANT STORE\n  selectedAssistant: null,\n  setSelectedAssistant: () => {},\n  assistantImages: [],\n  setAssistantImages: () => {},\n  openaiAssistants: [],\n  setOpenaiAssistants: () => {},\n\n  // PASSIVE CHAT STORE\n  userInput: \"\",\n  setUserInput: () => {},\n  selectedChat: null,\n  setSelectedChat: () => {},\n  chatMessages: [],\n  setChatMessages: () => {},\n  chatSettings: null,\n  setChatSettings: () => {},\n  chatFileItems: [],\n  setChatFileItems: () => {},\n\n  // ACTIVE CHAT STORE\n  isGenerating: false,\n  setIsGenerating: () => {},\n  firstTokenReceived: false,\n  setFirstTokenReceived: () => {},\n  abortController: null,\n  setAbortController: () => {},\n\n  // CHAT INPUT COMMAND STORE\n  isPromptPickerOpen: false,\n  setIsPromptPickerOpen: () => {},\n  slashCommand: \"\",\n  setSlashCommand: () => {},\n  isFilePickerOpen: false,\n  setIsFilePickerOpen: () => {},\n  hashtagCommand: \"\",\n  setHashtagCommand: () => {},\n  isToolPickerOpen: false,\n  setIsToolPickerOpen: () => {},\n  toolCommand: \"\",\n  setToolCommand: () => {},\n  focusPrompt: false,\n  setFocusPrompt: () => {},\n  focusFile: false,\n  setFocusFile: () => {},\n  focusTool: false,\n  setFocusTool: () => {},\n  focusAssistant: false,\n  setFocusAssistant: () => {},\n  atCommand: \"\",\n  setAtCommand: () => {},\n  isAssistantPickerOpen: false,\n  setIsAssistantPickerOpen: () => {},\n\n  // ATTACHMENTS STORE\n  chatFiles: [],\n  setChatFiles: () => {},\n  chatImages: [],\n  setChatImages: () => {},\n  newMessageFiles: [],\n  setNewMessageFiles: () => {},\n  newMessageImages: [],\n  setNewMessageImages: () => {},\n  showFilesDisplay: false,\n  setShowFilesDisplay: () => {},\n\n  // RETRIEVAL STORE\n  useRetrieval: false,\n  setUseRetrieval: () => {},\n  sourceCount: 4,\n  setSourceCount: () => {},\n\n  // TOOL STORE\n  selectedTools: [],\n  setSelectedTools: () => {},\n  toolInUse: \"none\",\n  setToolInUse: () => {}\n})\n"
  },
  {
    "path": "db/assistant-collections.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert } from \"@/supabase/types\"\n\nexport const getAssistantCollectionsByAssistantId = async (\n  assistantId: string\n) => {\n  const { data: assistantCollections, error } = await supabase\n    .from(\"assistants\")\n    .select(\n      `\n        id, \n        name, \n        collections (*)\n      `\n    )\n    .eq(\"id\", assistantId)\n    .single()\n\n  if (!assistantCollections) {\n    throw new Error(error.message)\n  }\n\n  return assistantCollections\n}\n\nexport const createAssistantCollection = async (\n  assistantCollection: TablesInsert<\"assistant_collections\">\n) => {\n  const { data: createdAssistantCollection, error } = await supabase\n    .from(\"assistant_collections\")\n    .insert(assistantCollection)\n    .select(\"*\")\n\n  if (!createdAssistantCollection) {\n    throw new Error(error.message)\n  }\n\n  return createdAssistantCollection\n}\n\nexport const createAssistantCollections = async (\n  assistantCollections: TablesInsert<\"assistant_collections\">[]\n) => {\n  const { data: createdAssistantCollections, error } = await supabase\n    .from(\"assistant_collections\")\n    .insert(assistantCollections)\n    .select(\"*\")\n\n  if (!createdAssistantCollections) {\n    throw new Error(error.message)\n  }\n\n  return createdAssistantCollections\n}\n\nexport const deleteAssistantCollection = async (\n  assistantId: string,\n  collectionId: string\n) => {\n  const { error } = await supabase\n    .from(\"assistant_collections\")\n    .delete()\n    .eq(\"assistant_id\", assistantId)\n    .eq(\"collection_id\", collectionId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/assistant-files.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert } from \"@/supabase/types\"\n\nexport const getAssistantFilesByAssistantId = async (assistantId: string) => {\n  const { data: assistantFiles, error } = await supabase\n    .from(\"assistants\")\n    .select(\n      `\n        id, \n        name, \n        files (*)\n      `\n    )\n    .eq(\"id\", assistantId)\n    .single()\n\n  if (!assistantFiles) {\n    throw new Error(error.message)\n  }\n\n  return assistantFiles\n}\n\nexport const createAssistantFile = async (\n  assistantFile: TablesInsert<\"assistant_files\">\n) => {\n  const { data: createdAssistantFile, error } = await supabase\n    .from(\"assistant_files\")\n    .insert(assistantFile)\n    .select(\"*\")\n\n  if (!createdAssistantFile) {\n    throw new Error(error.message)\n  }\n\n  return createdAssistantFile\n}\n\nexport const createAssistantFiles = async (\n  assistantFiles: TablesInsert<\"assistant_files\">[]\n) => {\n  const { data: createdAssistantFiles, error } = await supabase\n    .from(\"assistant_files\")\n    .insert(assistantFiles)\n    .select(\"*\")\n\n  if (!createdAssistantFiles) {\n    throw new Error(error.message)\n  }\n\n  return createdAssistantFiles\n}\n\nexport const deleteAssistantFile = async (\n  assistantId: string,\n  fileId: string\n) => {\n  const { error } = await supabase\n    .from(\"assistant_files\")\n    .delete()\n    .eq(\"assistant_id\", assistantId)\n    .eq(\"file_id\", fileId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/assistant-tools.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert } from \"@/supabase/types\"\n\nexport const getAssistantToolsByAssistantId = async (assistantId: string) => {\n  const { data: assistantTools, error } = await supabase\n    .from(\"assistants\")\n    .select(\n      `\n        id, \n        name, \n        tools (*)\n      `\n    )\n    .eq(\"id\", assistantId)\n    .single()\n\n  if (!assistantTools) {\n    throw new Error(error.message)\n  }\n\n  return assistantTools\n}\n\nexport const createAssistantTool = async (\n  assistantTool: TablesInsert<\"assistant_tools\">\n) => {\n  const { data: createdAssistantTool, error } = await supabase\n    .from(\"assistant_tools\")\n    .insert(assistantTool)\n    .select(\"*\")\n\n  if (!createdAssistantTool) {\n    throw new Error(error.message)\n  }\n\n  return createdAssistantTool\n}\n\nexport const createAssistantTools = async (\n  assistantTools: TablesInsert<\"assistant_tools\">[]\n) => {\n  const { data: createdAssistantTools, error } = await supabase\n    .from(\"assistant_tools\")\n    .insert(assistantTools)\n    .select(\"*\")\n\n  if (!createdAssistantTools) {\n    throw new Error(error.message)\n  }\n\n  return createdAssistantTools\n}\n\nexport const deleteAssistantTool = async (\n  assistantId: string,\n  toolId: string\n) => {\n  const { error } = await supabase\n    .from(\"assistant_tools\")\n    .delete()\n    .eq(\"assistant_id\", assistantId)\n    .eq(\"tool_id\", toolId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/assistants.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getAssistantById = async (assistantId: string) => {\n  const { data: assistant, error } = await supabase\n    .from(\"assistants\")\n    .select(\"*\")\n    .eq(\"id\", assistantId)\n    .single()\n\n  if (!assistant) {\n    throw new Error(error.message)\n  }\n\n  return assistant\n}\n\nexport const getAssistantWorkspacesByWorkspaceId = async (\n  workspaceId: string\n) => {\n  const { data: workspace, error } = await supabase\n    .from(\"workspaces\")\n    .select(\n      `\n      id,\n      name,\n      assistants (*)\n    `\n    )\n    .eq(\"id\", workspaceId)\n    .single()\n\n  if (!workspace) {\n    throw new Error(error.message)\n  }\n\n  return workspace\n}\n\nexport const getAssistantWorkspacesByAssistantId = async (\n  assistantId: string\n) => {\n  const { data: assistant, error } = await supabase\n    .from(\"assistants\")\n    .select(\n      `\n      id, \n      name, \n      workspaces (*)\n    `\n    )\n    .eq(\"id\", assistantId)\n    .single()\n\n  if (!assistant) {\n    throw new Error(error.message)\n  }\n\n  return assistant\n}\n\nexport const createAssistant = async (\n  assistant: TablesInsert<\"assistants\">,\n  workspace_id: string\n) => {\n  const { data: createdAssistant, error } = await supabase\n    .from(\"assistants\")\n    .insert([assistant])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createAssistantWorkspace({\n    user_id: createdAssistant.user_id,\n    assistant_id: createdAssistant.id,\n    workspace_id\n  })\n\n  return createdAssistant\n}\n\nexport const createAssistants = async (\n  assistants: TablesInsert<\"assistants\">[],\n  workspace_id: string\n) => {\n  const { data: createdAssistants, error } = await supabase\n    .from(\"assistants\")\n    .insert(assistants)\n    .select(\"*\")\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createAssistantWorkspaces(\n    createdAssistants.map(assistant => ({\n      user_id: assistant.user_id,\n      assistant_id: assistant.id,\n      workspace_id\n    }))\n  )\n\n  return createdAssistants\n}\n\nexport const createAssistantWorkspace = async (item: {\n  user_id: string\n  assistant_id: string\n  workspace_id: string\n}) => {\n  const { data: createdAssistantWorkspace, error } = await supabase\n    .from(\"assistant_workspaces\")\n    .insert([item])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdAssistantWorkspace\n}\n\nexport const createAssistantWorkspaces = async (\n  items: { user_id: string; assistant_id: string; workspace_id: string }[]\n) => {\n  const { data: createdAssistantWorkspaces, error } = await supabase\n    .from(\"assistant_workspaces\")\n    .insert(items)\n    .select(\"*\")\n\n  if (error) throw new Error(error.message)\n\n  return createdAssistantWorkspaces\n}\n\nexport const updateAssistant = async (\n  assistantId: string,\n  assistant: TablesUpdate<\"assistants\">\n) => {\n  const { data: updatedAssistant, error } = await supabase\n    .from(\"assistants\")\n    .update(assistant)\n    .eq(\"id\", assistantId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedAssistant\n}\n\nexport const deleteAssistant = async (assistantId: string) => {\n  const { error } = await supabase\n    .from(\"assistants\")\n    .delete()\n    .eq(\"id\", assistantId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n\nexport const deleteAssistantWorkspace = async (\n  assistantId: string,\n  workspaceId: string\n) => {\n  const { error } = await supabase\n    .from(\"assistant_workspaces\")\n    .delete()\n    .eq(\"assistant_id\", assistantId)\n    .eq(\"workspace_id\", workspaceId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/chat-files.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert } from \"@/supabase/types\"\n\nexport const getChatFilesByChatId = async (chatId: string) => {\n  const { data: chatFiles, error } = await supabase\n    .from(\"chats\")\n    .select(\n      `\n      id, \n      name, \n      files (*)\n    `\n    )\n    .eq(\"id\", chatId)\n    .single()\n\n  if (!chatFiles) {\n    throw new Error(error.message)\n  }\n\n  return chatFiles\n}\n\nexport const createChatFile = async (chatFile: TablesInsert<\"chat_files\">) => {\n  const { data: createdChatFile, error } = await supabase\n    .from(\"chat_files\")\n    .insert(chatFile)\n    .select(\"*\")\n\n  if (!createdChatFile) {\n    throw new Error(error.message)\n  }\n\n  return createdChatFile\n}\n\nexport const createChatFiles = async (\n  chatFiles: TablesInsert<\"chat_files\">[]\n) => {\n  const { data: createdChatFiles, error } = await supabase\n    .from(\"chat_files\")\n    .insert(chatFiles)\n    .select(\"*\")\n\n  if (!createdChatFiles) {\n    throw new Error(error.message)\n  }\n\n  return createdChatFiles\n}\n"
  },
  {
    "path": "db/chats.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getChatById = async (chatId: string) => {\n  const { data: chat } = await supabase\n    .from(\"chats\")\n    .select(\"*\")\n    .eq(\"id\", chatId)\n    .maybeSingle()\n\n  return chat\n}\n\nexport const getChatsByWorkspaceId = async (workspaceId: string) => {\n  const { data: chats, error } = await supabase\n    .from(\"chats\")\n    .select(\"*\")\n    .eq(\"workspace_id\", workspaceId)\n    .order(\"created_at\", { ascending: false })\n\n  if (!chats) {\n    throw new Error(error.message)\n  }\n\n  return chats\n}\n\nexport const createChat = async (chat: TablesInsert<\"chats\">) => {\n  const { data: createdChat, error } = await supabase\n    .from(\"chats\")\n    .insert([chat])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdChat\n}\n\nexport const createChats = async (chats: TablesInsert<\"chats\">[]) => {\n  const { data: createdChats, error } = await supabase\n    .from(\"chats\")\n    .insert(chats)\n    .select(\"*\")\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdChats\n}\n\nexport const updateChat = async (\n  chatId: string,\n  chat: TablesUpdate<\"chats\">\n) => {\n  const { data: updatedChat, error } = await supabase\n    .from(\"chats\")\n    .update(chat)\n    .eq(\"id\", chatId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedChat\n}\n\nexport const deleteChat = async (chatId: string) => {\n  const { error } = await supabase.from(\"chats\").delete().eq(\"id\", chatId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n"
  },
  {
    "path": "db/collection-files.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert } from \"@/supabase/types\"\n\nexport const getCollectionFilesByCollectionId = async (\n  collectionId: string\n) => {\n  const { data: collectionFiles, error } = await supabase\n    .from(\"collections\")\n    .select(\n      `\n        id, \n        name, \n        files ( id, name, type )\n      `\n    )\n    .eq(\"id\", collectionId)\n    .single()\n\n  if (!collectionFiles) {\n    throw new Error(error.message)\n  }\n\n  return collectionFiles\n}\n\nexport const createCollectionFile = async (\n  collectionFile: TablesInsert<\"collection_files\">\n) => {\n  const { data: createdCollectionFile, error } = await supabase\n    .from(\"collection_files\")\n    .insert(collectionFile)\n    .select(\"*\")\n\n  if (!createdCollectionFile) {\n    throw new Error(error.message)\n  }\n\n  return createdCollectionFile\n}\n\nexport const createCollectionFiles = async (\n  collectionFiles: TablesInsert<\"collection_files\">[]\n) => {\n  const { data: createdCollectionFiles, error } = await supabase\n    .from(\"collection_files\")\n    .insert(collectionFiles)\n    .select(\"*\")\n\n  if (!createdCollectionFiles) {\n    throw new Error(error.message)\n  }\n\n  return createdCollectionFiles\n}\n\nexport const deleteCollectionFile = async (\n  collectionId: string,\n  fileId: string\n) => {\n  const { error } = await supabase\n    .from(\"collection_files\")\n    .delete()\n    .eq(\"collection_id\", collectionId)\n    .eq(\"file_id\", fileId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/collections.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getCollectionById = async (collectionId: string) => {\n  const { data: collection, error } = await supabase\n    .from(\"collections\")\n    .select(\"*\")\n    .eq(\"id\", collectionId)\n    .single()\n\n  if (!collection) {\n    throw new Error(error.message)\n  }\n\n  return collection\n}\n\nexport const getCollectionWorkspacesByWorkspaceId = async (\n  workspaceId: string\n) => {\n  const { data: workspace, error } = await supabase\n    .from(\"workspaces\")\n    .select(\n      `\n      id,\n      name,\n      collections (*)\n    `\n    )\n    .eq(\"id\", workspaceId)\n    .single()\n\n  if (!workspace) {\n    throw new Error(error.message)\n  }\n\n  return workspace\n}\n\nexport const getCollectionWorkspacesByCollectionId = async (\n  collectionId: string\n) => {\n  const { data: collection, error } = await supabase\n    .from(\"collections\")\n    .select(\n      `\n      id, \n      name, \n      workspaces (*)\n    `\n    )\n    .eq(\"id\", collectionId)\n    .single()\n\n  if (!collection) {\n    throw new Error(error.message)\n  }\n\n  return collection\n}\n\nexport const createCollection = async (\n  collection: TablesInsert<\"collections\">,\n  workspace_id: string\n) => {\n  const { data: createdCollection, error } = await supabase\n    .from(\"collections\")\n    .insert([collection])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createCollectionWorkspace({\n    user_id: createdCollection.user_id,\n    collection_id: createdCollection.id,\n    workspace_id\n  })\n\n  return createdCollection\n}\n\nexport const createCollections = async (\n  collections: TablesInsert<\"collections\">[],\n  workspace_id: string\n) => {\n  const { data: createdCollections, error } = await supabase\n    .from(\"collections\")\n    .insert(collections)\n    .select(\"*\")\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createCollectionWorkspaces(\n    createdCollections.map(collection => ({\n      user_id: collection.user_id,\n      collection_id: collection.id,\n      workspace_id\n    }))\n  )\n\n  return createdCollections\n}\n\nexport const createCollectionWorkspace = async (item: {\n  user_id: string\n  collection_id: string\n  workspace_id: string\n}) => {\n  const { data: createdCollectionWorkspace, error } = await supabase\n    .from(\"collection_workspaces\")\n    .insert([item])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdCollectionWorkspace\n}\n\nexport const createCollectionWorkspaces = async (\n  items: { user_id: string; collection_id: string; workspace_id: string }[]\n) => {\n  const { data: createdCollectionWorkspaces, error } = await supabase\n    .from(\"collection_workspaces\")\n    .insert(items)\n    .select(\"*\")\n\n  if (error) throw new Error(error.message)\n\n  return createdCollectionWorkspaces\n}\n\nexport const updateCollection = async (\n  collectionId: string,\n  collection: TablesUpdate<\"collections\">\n) => {\n  const { data: updatedCollection, error } = await supabase\n    .from(\"collections\")\n    .update(collection)\n    .eq(\"id\", collectionId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedCollection\n}\n\nexport const deleteCollection = async (collectionId: string) => {\n  const { error } = await supabase\n    .from(\"collections\")\n    .delete()\n    .eq(\"id\", collectionId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n\nexport const deleteCollectionWorkspace = async (\n  collectionId: string,\n  workspaceId: string\n) => {\n  const { error } = await supabase\n    .from(\"collection_workspaces\")\n    .delete()\n    .eq(\"collection_id\", collectionId)\n    .eq(\"workspace_id\", workspaceId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/files.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\nimport mammoth from \"mammoth\"\nimport { toast } from \"sonner\"\nimport { uploadFile } from \"./storage/files\"\n\nexport const getFileById = async (fileId: string) => {\n  const { data: file, error } = await supabase\n    .from(\"files\")\n    .select(\"*\")\n    .eq(\"id\", fileId)\n    .single()\n\n  if (!file) {\n    throw new Error(error.message)\n  }\n\n  return file\n}\n\nexport const getFileWorkspacesByWorkspaceId = async (workspaceId: string) => {\n  const { data: workspace, error } = await supabase\n    .from(\"workspaces\")\n    .select(\n      `\n      id,\n      name,\n      files (*)\n    `\n    )\n    .eq(\"id\", workspaceId)\n    .single()\n\n  if (!workspace) {\n    throw new Error(error.message)\n  }\n\n  return workspace\n}\n\nexport const getFileWorkspacesByFileId = async (fileId: string) => {\n  const { data: file, error } = await supabase\n    .from(\"files\")\n    .select(\n      `\n      id, \n      name, \n      workspaces (*)\n    `\n    )\n    .eq(\"id\", fileId)\n    .single()\n\n  if (!file) {\n    throw new Error(error.message)\n  }\n\n  return file\n}\n\nexport const createFileBasedOnExtension = async (\n  file: File,\n  fileRecord: TablesInsert<\"files\">,\n  workspace_id: string,\n  embeddingsProvider: \"openai\" | \"local\"\n) => {\n  const fileExtension = file.name.split(\".\").pop()\n\n  if (fileExtension === \"docx\") {\n    const arrayBuffer = await file.arrayBuffer()\n    const result = await mammoth.extractRawText({\n      arrayBuffer\n    })\n\n    return createDocXFile(\n      result.value,\n      file,\n      fileRecord,\n      workspace_id,\n      embeddingsProvider\n    )\n  } else {\n    return createFile(file, fileRecord, workspace_id, embeddingsProvider)\n  }\n}\n\n// For non-docx files\nexport const createFile = async (\n  file: File,\n  fileRecord: TablesInsert<\"files\">,\n  workspace_id: string,\n  embeddingsProvider: \"openai\" | \"local\"\n) => {\n  let validFilename = fileRecord.name.replace(/[^a-z0-9.]/gi, \"_\").toLowerCase()\n  const extension = file.name.split(\".\").pop()\n  const extensionIndex = validFilename.lastIndexOf(\".\")\n  const baseName = validFilename.substring(0, (extensionIndex < 0) ? undefined : extensionIndex)\n  const maxBaseNameLength = 100 - (extension?.length || 0) - 1\n  if (baseName.length > maxBaseNameLength) {\n    fileRecord.name = baseName.substring(0, maxBaseNameLength) + \".\" + extension\n  } else {\n    fileRecord.name = baseName + \".\" + extension\n  }\n  const { data: createdFile, error } = await supabase\n    .from(\"files\")\n    .insert([fileRecord])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createFileWorkspace({\n    user_id: createdFile.user_id,\n    file_id: createdFile.id,\n    workspace_id\n  })\n\n  const filePath = await uploadFile(file, {\n    name: createdFile.name,\n    user_id: createdFile.user_id,\n    file_id: createdFile.name\n  })\n\n  await updateFile(createdFile.id, {\n    file_path: filePath\n  })\n\n  const formData = new FormData()\n  formData.append(\"file_id\", createdFile.id)\n  formData.append(\"embeddingsProvider\", embeddingsProvider)\n\n  const response = await fetch(\"/api/retrieval/process\", {\n    method: \"POST\",\n    body: formData\n  })\n\n  if (!response.ok) {\n    const jsonText = await response.text()\n    const json = JSON.parse(jsonText)\n    console.error(\n      `Error processing file:${createdFile.id}, status:${response.status}, response:${json.message}`\n    )\n    toast.error(\"Failed to process file. Reason:\" + json.message, {\n      duration: 10000\n    })\n    await deleteFile(createdFile.id)\n  }\n\n  const fetchedFile = await getFileById(createdFile.id)\n\n  return fetchedFile\n}\n\n// // Handle docx files\nexport const createDocXFile = async (\n  text: string,\n  file: File,\n  fileRecord: TablesInsert<\"files\">,\n  workspace_id: string,\n  embeddingsProvider: \"openai\" | \"local\"\n) => {\n  const { data: createdFile, error } = await supabase\n    .from(\"files\")\n    .insert([fileRecord])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createFileWorkspace({\n    user_id: createdFile.user_id,\n    file_id: createdFile.id,\n    workspace_id\n  })\n\n  const filePath = await uploadFile(file, {\n    name: createdFile.name,\n    user_id: createdFile.user_id,\n    file_id: createdFile.name\n  })\n\n  await updateFile(createdFile.id, {\n    file_path: filePath\n  })\n\n  const response = await fetch(\"/api/retrieval/process/docx\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\"\n    },\n    body: JSON.stringify({\n      text: text,\n      fileId: createdFile.id,\n      embeddingsProvider,\n      fileExtension: \"docx\"\n    })\n  })\n\n  if (!response.ok) {\n    const jsonText = await response.text()\n    const json = JSON.parse(jsonText)\n    console.error(\n      `Error processing file:${createdFile.id}, status:${response.status}, response:${json.message}`\n    )\n    toast.error(\"Failed to process file. Reason:\" + json.message, {\n      duration: 10000\n    })\n    await deleteFile(createdFile.id)\n  }\n\n  const fetchedFile = await getFileById(createdFile.id)\n\n  return fetchedFile\n}\n\nexport const createFiles = async (\n  files: TablesInsert<\"files\">[],\n  workspace_id: string\n) => {\n  const { data: createdFiles, error } = await supabase\n    .from(\"files\")\n    .insert(files)\n    .select(\"*\")\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createFileWorkspaces(\n    createdFiles.map(file => ({\n      user_id: file.user_id,\n      file_id: file.id,\n      workspace_id\n    }))\n  )\n\n  return createdFiles\n}\n\nexport const createFileWorkspace = async (item: {\n  user_id: string\n  file_id: string\n  workspace_id: string\n}) => {\n  const { data: createdFileWorkspace, error } = await supabase\n    .from(\"file_workspaces\")\n    .insert([item])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdFileWorkspace\n}\n\nexport const createFileWorkspaces = async (\n  items: { user_id: string; file_id: string; workspace_id: string }[]\n) => {\n  const { data: createdFileWorkspaces, error } = await supabase\n    .from(\"file_workspaces\")\n    .insert(items)\n    .select(\"*\")\n\n  if (error) throw new Error(error.message)\n\n  return createdFileWorkspaces\n}\n\nexport const updateFile = async (\n  fileId: string,\n  file: TablesUpdate<\"files\">\n) => {\n  const { data: updatedFile, error } = await supabase\n    .from(\"files\")\n    .update(file)\n    .eq(\"id\", fileId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedFile\n}\n\nexport const deleteFile = async (fileId: string) => {\n  const { error } = await supabase.from(\"files\").delete().eq(\"id\", fileId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n\nexport const deleteFileWorkspace = async (\n  fileId: string,\n  workspaceId: string\n) => {\n  const { error } = await supabase\n    .from(\"file_workspaces\")\n    .delete()\n    .eq(\"file_id\", fileId)\n    .eq(\"workspace_id\", workspaceId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/folders.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getFoldersByWorkspaceId = async (workspaceId: string) => {\n  const { data: folders, error } = await supabase\n    .from(\"folders\")\n    .select(\"*\")\n    .eq(\"workspace_id\", workspaceId)\n\n  if (!folders) {\n    throw new Error(error.message)\n  }\n\n  return folders\n}\n\nexport const createFolder = async (folder: TablesInsert<\"folders\">) => {\n  const { data: createdFolder, error } = await supabase\n    .from(\"folders\")\n    .insert([folder])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdFolder\n}\n\nexport const updateFolder = async (\n  folderId: string,\n  folder: TablesUpdate<\"folders\">\n) => {\n  const { data: updatedFolder, error } = await supabase\n    .from(\"folders\")\n    .update(folder)\n    .eq(\"id\", folderId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedFolder\n}\n\nexport const deleteFolder = async (folderId: string) => {\n  const { error } = await supabase.from(\"folders\").delete().eq(\"id\", folderId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n"
  },
  {
    "path": "db/index.ts",
    "content": "import \"./assistants\"\nimport \"./chats\"\nimport \"./file-items\"\nimport \"./files\"\nimport \"./folders\"\nimport \"./messages\"\nimport \"./presets\"\nimport \"./profile\"\nimport \"./prompts\"\nimport \"./workspaces\"\n"
  },
  {
    "path": "db/limits.ts",
    "content": "// Profiles\nexport const PROFILE_BIO_MAX = 500\nexport const PROFILE_DISPLAY_NAME_MAX = 100\nexport const PROFILE_CONTEXT_MAX = 1500\nexport const PROFILE_USERNAME_MIN = 3\nexport const PROFILE_USERNAME_MAX = 25\n\n// Workspaces\nexport const WORKSPACE_NAME_MAX = 100\nexport const WORKSPACE_DESCRIPTION_MAX = 500\nexport const WORKSPACE_INSTRUCTIONS_MAX = 1500\n\n// Chats\n\n// Presets\nexport const PRESET_NAME_MAX = 100\nexport const PRESET_DESCRIPTION_MAX = 500\nexport const PRESET_PROMPT_MAX = 100000\n\n// Prompts\nexport const PROMPT_NAME_MAX = 100\nexport const PROMPT_CONTENT_MAX = 100000\n\n// Files\nexport const FILE_NAME_MAX = 100\nexport const FILE_DESCRIPTION_MAX = 500\n\n// Collections\nexport const COLLECTION_NAME_MAX = 100\nexport const COLLECTION_DESCRIPTION_MAX = 500\n\n// Assistant\nexport const ASSISTANT_NAME_MAX = 100\nexport const ASSISTANT_DESCRIPTION_MAX = 500\nexport const ASSISTANT_PROMPT_MAX = 100000\n\n// Tools\nexport const TOOL_NAME_MAX = 100\nexport const TOOL_DESCRIPTION_MAX = 500\n\n// Models\nexport const MODEL_NAME_MAX = 100\nexport const MODEL_DESCRIPTION_MAX = 500\n"
  },
  {
    "path": "db/message-file-items.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert } from \"@/supabase/types\"\n\nexport const getMessageFileItemsByMessageId = async (messageId: string) => {\n  const { data: messageFileItems, error } = await supabase\n    .from(\"messages\")\n    .select(\n      `\n      id,\n      file_items (*)\n    `\n    )\n    .eq(\"id\", messageId)\n    .single()\n\n  if (!messageFileItems) {\n    throw new Error(error.message)\n  }\n\n  return messageFileItems\n}\n\nexport const createMessageFileItems = async (\n  messageFileItems: TablesInsert<\"message_file_items\">[]\n) => {\n  const { data: createdMessageFileItems, error } = await supabase\n    .from(\"message_file_items\")\n    .insert(messageFileItems)\n    .select(\"*\")\n\n  if (!createdMessageFileItems) {\n    throw new Error(error.message)\n  }\n\n  return createdMessageFileItems\n}\n"
  },
  {
    "path": "db/messages.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getMessageById = async (messageId: string) => {\n  const { data: message } = await supabase\n    .from(\"messages\")\n    .select(\"*\")\n    .eq(\"id\", messageId)\n    .single()\n\n  if (!message) {\n    throw new Error(\"Message not found\")\n  }\n\n  return message\n}\n\nexport const getMessagesByChatId = async (chatId: string) => {\n  const { data: messages } = await supabase\n    .from(\"messages\")\n    .select(\"*\")\n    .eq(\"chat_id\", chatId)\n\n  if (!messages) {\n    throw new Error(\"Messages not found\")\n  }\n\n  return messages\n}\n\nexport const createMessage = async (message: TablesInsert<\"messages\">) => {\n  const { data: createdMessage, error } = await supabase\n    .from(\"messages\")\n    .insert([message])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdMessage\n}\n\nexport const createMessages = async (messages: TablesInsert<\"messages\">[]) => {\n  const { data: createdMessages, error } = await supabase\n    .from(\"messages\")\n    .insert(messages)\n    .select(\"*\")\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdMessages\n}\n\nexport const updateMessage = async (\n  messageId: string,\n  message: TablesUpdate<\"messages\">\n) => {\n  const { data: updatedMessage, error } = await supabase\n    .from(\"messages\")\n    .update(message)\n    .eq(\"id\", messageId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedMessage\n}\n\nexport const deleteMessage = async (messageId: string) => {\n  const { error } = await supabase.from(\"messages\").delete().eq(\"id\", messageId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n\nexport async function deleteMessagesIncludingAndAfter(\n  userId: string,\n  chatId: string,\n  sequenceNumber: number\n) {\n  const { error } = await supabase.rpc(\"delete_messages_including_and_after\", {\n    p_user_id: userId,\n    p_chat_id: chatId,\n    p_sequence_number: sequenceNumber\n  })\n\n  if (error) {\n    return {\n      error: \"Failed to delete messages.\"\n    }\n  }\n\n  return true\n}\n"
  },
  {
    "path": "db/models.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getModelById = async (modelId: string) => {\n  const { data: model, error } = await supabase\n    .from(\"models\")\n    .select(\"*\")\n    .eq(\"id\", modelId)\n    .single()\n\n  if (!model) {\n    throw new Error(error.message)\n  }\n\n  return model\n}\n\nexport const getModelWorkspacesByWorkspaceId = async (workspaceId: string) => {\n  const { data: workspace, error } = await supabase\n    .from(\"workspaces\")\n    .select(\n      `\n      id,\n      name,\n      models (*)\n    `\n    )\n    .eq(\"id\", workspaceId)\n    .single()\n\n  if (!workspace) {\n    throw new Error(error.message)\n  }\n\n  return workspace\n}\n\nexport const getModelWorkspacesByModelId = async (modelId: string) => {\n  const { data: model, error } = await supabase\n    .from(\"models\")\n    .select(\n      `\n      id, \n      name, \n      workspaces (*)\n    `\n    )\n    .eq(\"id\", modelId)\n    .single()\n\n  if (!model) {\n    throw new Error(error.message)\n  }\n\n  return model\n}\n\nexport const createModel = async (\n  model: TablesInsert<\"models\">,\n  workspace_id: string\n) => {\n  const { data: createdModel, error } = await supabase\n    .from(\"models\")\n    .insert([model])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createModelWorkspace({\n    user_id: model.user_id,\n    model_id: createdModel.id,\n    workspace_id: workspace_id\n  })\n\n  return createdModel\n}\n\nexport const createModels = async (\n  models: TablesInsert<\"models\">[],\n  workspace_id: string\n) => {\n  const { data: createdModels, error } = await supabase\n    .from(\"models\")\n    .insert(models)\n    .select(\"*\")\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createModelWorkspaces(\n    createdModels.map(model => ({\n      user_id: model.user_id,\n      model_id: model.id,\n      workspace_id\n    }))\n  )\n\n  return createdModels\n}\n\nexport const createModelWorkspace = async (item: {\n  user_id: string\n  model_id: string\n  workspace_id: string\n}) => {\n  const { data: createdModelWorkspace, error } = await supabase\n    .from(\"model_workspaces\")\n    .insert([item])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdModelWorkspace\n}\n\nexport const createModelWorkspaces = async (\n  items: { user_id: string; model_id: string; workspace_id: string }[]\n) => {\n  const { data: createdModelWorkspaces, error } = await supabase\n    .from(\"model_workspaces\")\n    .insert(items)\n    .select(\"*\")\n\n  if (error) throw new Error(error.message)\n\n  return createdModelWorkspaces\n}\n\nexport const updateModel = async (\n  modelId: string,\n  model: TablesUpdate<\"models\">\n) => {\n  const { data: updatedModel, error } = await supabase\n    .from(\"models\")\n    .update(model)\n    .eq(\"id\", modelId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedModel\n}\n\nexport const deleteModel = async (modelId: string) => {\n  const { error } = await supabase.from(\"models\").delete().eq(\"id\", modelId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n\nexport const deleteModelWorkspace = async (\n  modelId: string,\n  workspaceId: string\n) => {\n  const { error } = await supabase\n    .from(\"model_workspaces\")\n    .delete()\n    .eq(\"model_id\", modelId)\n    .eq(\"workspace_id\", workspaceId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/presets.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getPresetById = async (presetId: string) => {\n  const { data: preset, error } = await supabase\n    .from(\"presets\")\n    .select(\"*\")\n    .eq(\"id\", presetId)\n    .single()\n\n  if (!preset) {\n    throw new Error(error.message)\n  }\n\n  return preset\n}\n\nexport const getPresetWorkspacesByWorkspaceId = async (workspaceId: string) => {\n  const { data: workspace, error } = await supabase\n    .from(\"workspaces\")\n    .select(\n      `\n      id,\n      name,\n      presets (*)\n    `\n    )\n    .eq(\"id\", workspaceId)\n    .single()\n\n  if (!workspace) {\n    throw new Error(error.message)\n  }\n\n  return workspace\n}\n\nexport const getPresetWorkspacesByPresetId = async (presetId: string) => {\n  const { data: preset, error } = await supabase\n    .from(\"presets\")\n    .select(\n      `\n      id, \n      name, \n      workspaces (*)\n    `\n    )\n    .eq(\"id\", presetId)\n    .single()\n\n  if (!preset) {\n    throw new Error(error.message)\n  }\n\n  return preset\n}\n\nexport const createPreset = async (\n  preset: TablesInsert<\"presets\">,\n  workspace_id: string\n) => {\n  const { data: createdPreset, error } = await supabase\n    .from(\"presets\")\n    .insert([preset])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createPresetWorkspace({\n    user_id: preset.user_id,\n    preset_id: createdPreset.id,\n    workspace_id: workspace_id\n  })\n\n  return createdPreset\n}\n\nexport const createPresets = async (\n  presets: TablesInsert<\"presets\">[],\n  workspace_id: string\n) => {\n  const { data: createdPresets, error } = await supabase\n    .from(\"presets\")\n    .insert(presets)\n    .select(\"*\")\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createPresetWorkspaces(\n    createdPresets.map(preset => ({\n      user_id: preset.user_id,\n      preset_id: preset.id,\n      workspace_id\n    }))\n  )\n\n  return createdPresets\n}\n\nexport const createPresetWorkspace = async (item: {\n  user_id: string\n  preset_id: string\n  workspace_id: string\n}) => {\n  const { data: createdPresetWorkspace, error } = await supabase\n    .from(\"preset_workspaces\")\n    .insert([item])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdPresetWorkspace\n}\n\nexport const createPresetWorkspaces = async (\n  items: { user_id: string; preset_id: string; workspace_id: string }[]\n) => {\n  const { data: createdPresetWorkspaces, error } = await supabase\n    .from(\"preset_workspaces\")\n    .insert(items)\n    .select(\"*\")\n\n  if (error) throw new Error(error.message)\n\n  return createdPresetWorkspaces\n}\n\nexport const updatePreset = async (\n  presetId: string,\n  preset: TablesUpdate<\"presets\">\n) => {\n  const { data: updatedPreset, error } = await supabase\n    .from(\"presets\")\n    .update(preset)\n    .eq(\"id\", presetId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedPreset\n}\n\nexport const deletePreset = async (presetId: string) => {\n  const { error } = await supabase.from(\"presets\").delete().eq(\"id\", presetId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n\nexport const deletePresetWorkspace = async (\n  presetId: string,\n  workspaceId: string\n) => {\n  const { error } = await supabase\n    .from(\"preset_workspaces\")\n    .delete()\n    .eq(\"preset_id\", presetId)\n    .eq(\"workspace_id\", workspaceId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/profile.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getProfileByUserId = async (userId: string) => {\n  const { data: profile, error } = await supabase\n    .from(\"profiles\")\n    .select(\"*\")\n    .eq(\"user_id\", userId)\n    .single()\n\n  if (!profile) {\n    throw new Error(error.message)\n  }\n\n  return profile\n}\n\nexport const getProfilesByUserId = async (userId: string) => {\n  const { data: profiles, error } = await supabase\n    .from(\"profiles\")\n    .select(\"*\")\n    .eq(\"user_id\", userId)\n\n  if (!profiles) {\n    throw new Error(error.message)\n  }\n\n  return profiles\n}\n\nexport const createProfile = async (profile: TablesInsert<\"profiles\">) => {\n  const { data: createdProfile, error } = await supabase\n    .from(\"profiles\")\n    .insert([profile])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdProfile\n}\n\nexport const updateProfile = async (\n  profileId: string,\n  profile: TablesUpdate<\"profiles\">\n) => {\n  const { data: updatedProfile, error } = await supabase\n    .from(\"profiles\")\n    .update(profile)\n    .eq(\"id\", profileId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedProfile\n}\n\nexport const deleteProfile = async (profileId: string) => {\n  const { error } = await supabase.from(\"profiles\").delete().eq(\"id\", profileId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n"
  },
  {
    "path": "db/prompts.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getPromptById = async (promptId: string) => {\n  const { data: prompt, error } = await supabase\n    .from(\"prompts\")\n    .select(\"*\")\n    .eq(\"id\", promptId)\n    .single()\n\n  if (!prompt) {\n    throw new Error(error.message)\n  }\n\n  return prompt\n}\n\nexport const getPromptWorkspacesByWorkspaceId = async (workspaceId: string) => {\n  const { data: workspace, error } = await supabase\n    .from(\"workspaces\")\n    .select(\n      `\n      id,\n      name,\n      prompts (*)\n    `\n    )\n    .eq(\"id\", workspaceId)\n    .single()\n\n  if (!workspace) {\n    throw new Error(error.message)\n  }\n\n  return workspace\n}\n\nexport const getPromptWorkspacesByPromptId = async (promptId: string) => {\n  const { data: prompt, error } = await supabase\n    .from(\"prompts\")\n    .select(\n      `\n      id, \n      name, \n      workspaces (*)\n    `\n    )\n    .eq(\"id\", promptId)\n    .single()\n\n  if (!prompt) {\n    throw new Error(error.message)\n  }\n\n  return prompt\n}\n\nexport const createPrompt = async (\n  prompt: TablesInsert<\"prompts\">,\n  workspace_id: string\n) => {\n  const { data: createdPrompt, error } = await supabase\n    .from(\"prompts\")\n    .insert([prompt])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createPromptWorkspace({\n    user_id: createdPrompt.user_id,\n    prompt_id: createdPrompt.id,\n    workspace_id\n  })\n\n  return createdPrompt\n}\n\nexport const createPrompts = async (\n  prompts: TablesInsert<\"prompts\">[],\n  workspace_id: string\n) => {\n  const { data: createdPrompts, error } = await supabase\n    .from(\"prompts\")\n    .insert(prompts)\n    .select(\"*\")\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createPromptWorkspaces(\n    createdPrompts.map(prompt => ({\n      user_id: prompt.user_id,\n      prompt_id: prompt.id,\n      workspace_id\n    }))\n  )\n\n  return createdPrompts\n}\n\nexport const createPromptWorkspace = async (item: {\n  user_id: string\n  prompt_id: string\n  workspace_id: string\n}) => {\n  const { data: createdPromptWorkspace, error } = await supabase\n    .from(\"prompt_workspaces\")\n    .insert([item])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdPromptWorkspace\n}\n\nexport const createPromptWorkspaces = async (\n  items: { user_id: string; prompt_id: string; workspace_id: string }[]\n) => {\n  const { data: createdPromptWorkspaces, error } = await supabase\n    .from(\"prompt_workspaces\")\n    .insert(items)\n    .select(\"*\")\n\n  if (error) throw new Error(error.message)\n\n  return createdPromptWorkspaces\n}\n\nexport const updatePrompt = async (\n  promptId: string,\n  prompt: TablesUpdate<\"prompts\">\n) => {\n  const { data: updatedPrompt, error } = await supabase\n    .from(\"prompts\")\n    .update(prompt)\n    .eq(\"id\", promptId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedPrompt\n}\n\nexport const deletePrompt = async (promptId: string) => {\n  const { error } = await supabase.from(\"prompts\").delete().eq(\"id\", promptId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n\nexport const deletePromptWorkspace = async (\n  promptId: string,\n  workspaceId: string\n) => {\n  const { error } = await supabase\n    .from(\"prompt_workspaces\")\n    .delete()\n    .eq(\"prompt_id\", promptId)\n    .eq(\"workspace_id\", workspaceId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/storage/assistant-images.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { Tables } from \"@/supabase/types\"\n\nexport const uploadAssistantImage = async (\n  assistant: Tables<\"assistants\">,\n  image: File\n) => {\n  const bucket = \"assistant_images\"\n\n  const imageSizeLimit = 6000000 // 6MB\n\n  if (image.size > imageSizeLimit) {\n    throw new Error(`Image must be less than ${imageSizeLimit / 1000000}MB`)\n  }\n\n  const currentPath = assistant.image_path\n  let filePath = `${assistant.user_id}/${assistant.id}/${Date.now()}`\n\n  if (currentPath.length > 0) {\n    const { error: deleteError } = await supabase.storage\n      .from(bucket)\n      .remove([currentPath])\n\n    if (deleteError) {\n      throw new Error(\"Error deleting old image\")\n    }\n  }\n\n  const { error } = await supabase.storage\n    .from(bucket)\n    .upload(filePath, image, {\n      upsert: true\n    })\n\n  if (error) {\n    throw new Error(\"Error uploading image\")\n  }\n\n  return filePath\n}\n\nexport const getAssistantImageFromStorage = async (filePath: string) => {\n  try {\n    const { data, error } = await supabase.storage\n      .from(\"assistant_images\")\n      .createSignedUrl(filePath, 60 * 60 * 24) // 24hrs\n\n    if (error) {\n      throw new Error(\"Error downloading assistant image\")\n    }\n\n    return data.signedUrl\n  } catch (error) {\n    console.error(error)\n  }\n}\n"
  },
  {
    "path": "db/storage/files.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { toast } from \"sonner\"\n\nexport const uploadFile = async (\n  file: File,\n  payload: {\n    name: string\n    user_id: string\n    file_id: string\n  }\n) => {\n  const SIZE_LIMIT = parseInt(\n    process.env.NEXT_PUBLIC_USER_FILE_SIZE_LIMIT || \"10000000\"\n  )\n\n  if (file.size > SIZE_LIMIT) {\n    throw new Error(\n      `File must be less than ${Math.floor(SIZE_LIMIT / 1000000)}MB`\n    )\n  }\n\n  const filePath = `${payload.user_id}/${Buffer.from(payload.file_id).toString(\"base64\")}`\n\n  const { error } = await supabase.storage\n    .from(\"files\")\n    .upload(filePath, file, {\n      upsert: true\n    })\n\n  if (error) {\n    throw new Error(\"Error uploading file\")\n  }\n\n  return filePath\n}\n\nexport const deleteFileFromStorage = async (filePath: string) => {\n  const { error } = await supabase.storage.from(\"files\").remove([filePath])\n\n  if (error) {\n    toast.error(\"Failed to remove file!\")\n    return\n  }\n}\n\nexport const getFileFromStorage = async (filePath: string) => {\n  const { data, error } = await supabase.storage\n    .from(\"files\")\n    .createSignedUrl(filePath, 60 * 60 * 24) // 24hrs\n\n  if (error) {\n    console.error(`Error uploading file with path: ${filePath}`, error)\n    throw new Error(\"Error downloading file\")\n  }\n\n  return data.signedUrl\n}\n"
  },
  {
    "path": "db/storage/message-images.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\n\nexport const uploadMessageImage = async (path: string, image: File) => {\n  const bucket = \"message_images\"\n\n  const imageSizeLimit = 6000000 // 6MB\n\n  if (image.size > imageSizeLimit) {\n    throw new Error(`Image must be less than ${imageSizeLimit / 1000000}MB`)\n  }\n\n  const { error } = await supabase.storage.from(bucket).upload(path, image, {\n    upsert: true\n  })\n\n  if (error) {\n    throw new Error(\"Error uploading image\")\n  }\n\n  return path\n}\n\nexport const getMessageImageFromStorage = async (filePath: string) => {\n  const { data, error } = await supabase.storage\n    .from(\"message_images\")\n    .createSignedUrl(filePath, 60 * 60 * 24) // 24hrs\n\n  if (error) {\n    throw new Error(\"Error downloading message image\")\n  }\n\n  return data.signedUrl\n}\n"
  },
  {
    "path": "db/storage/profile-images.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { Tables } from \"@/supabase/types\"\n\nexport const uploadProfileImage = async (\n  profile: Tables<\"profiles\">,\n  image: File\n) => {\n  const bucket = \"profile_images\"\n\n  const imageSizeLimit = 2000000 // 2MB\n\n  if (image.size > imageSizeLimit) {\n    throw new Error(`Image must be less than ${imageSizeLimit / 1000000}MB`)\n  }\n\n  const currentPath = profile.image_path\n  let filePath = `${profile.user_id}/${Date.now()}`\n\n  if (currentPath.length > 0) {\n    const { error: deleteError } = await supabase.storage\n      .from(bucket)\n      .remove([currentPath])\n\n    if (deleteError) {\n      throw new Error(\"Error deleting old image\")\n    }\n  }\n\n  const { error } = await supabase.storage\n    .from(bucket)\n    .upload(filePath, image, {\n      upsert: true\n    })\n\n  if (error) {\n    throw new Error(\"Error uploading image\")\n  }\n\n  const { data: getPublicUrlData } = supabase.storage\n    .from(bucket)\n    .getPublicUrl(filePath)\n\n  return {\n    path: filePath,\n    url: getPublicUrlData.publicUrl\n  }\n}\n"
  },
  {
    "path": "db/storage/workspace-images.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { Tables } from \"@/supabase/types\"\n\nexport const uploadWorkspaceImage = async (\n  workspace: Tables<\"workspaces\">,\n  image: File\n) => {\n  const bucket = \"workspace_images\"\n\n  const imageSizeLimit = 6000000 // 6MB\n\n  if (image.size > imageSizeLimit) {\n    throw new Error(`Image must be less than ${imageSizeLimit / 1000000}MB`)\n  }\n\n  const currentPath = workspace.image_path\n  let filePath = `${workspace.user_id}/${workspace.id}/${Date.now()}`\n\n  if (currentPath.length > 0) {\n    const { error: deleteError } = await supabase.storage\n      .from(bucket)\n      .remove([currentPath])\n\n    if (deleteError) {\n      throw new Error(\"Error deleting old image\")\n    }\n  }\n\n  const { error } = await supabase.storage\n    .from(bucket)\n    .upload(filePath, image, {\n      upsert: true\n    })\n\n  if (error) {\n    throw new Error(\"Error uploading image\")\n  }\n\n  return filePath\n}\n\nexport const getWorkspaceImageFromStorage = async (filePath: string) => {\n  try {\n    const { data, error } = await supabase.storage\n      .from(\"workspace_images\")\n      .createSignedUrl(filePath, 60 * 60 * 24) // 24hrs\n\n    if (error) {\n      throw new Error(\"Error downloading workspace image\")\n    }\n\n    return data.signedUrl\n  } catch (error) {\n    console.error(error)\n  }\n}\n"
  },
  {
    "path": "db/tools.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getToolById = async (toolId: string) => {\n  const { data: tool, error } = await supabase\n    .from(\"tools\")\n    .select(\"*\")\n    .eq(\"id\", toolId)\n    .single()\n\n  if (!tool) {\n    throw new Error(error.message)\n  }\n\n  return tool\n}\n\nexport const getToolWorkspacesByWorkspaceId = async (workspaceId: string) => {\n  const { data: workspace, error } = await supabase\n    .from(\"workspaces\")\n    .select(\n      `\n      id,\n      name,\n      tools (*)\n    `\n    )\n    .eq(\"id\", workspaceId)\n    .single()\n\n  if (!workspace) {\n    throw new Error(error.message)\n  }\n\n  return workspace\n}\n\nexport const getToolWorkspacesByToolId = async (toolId: string) => {\n  const { data: tool, error } = await supabase\n    .from(\"tools\")\n    .select(\n      `\n      id, \n      name, \n      workspaces (*)\n    `\n    )\n    .eq(\"id\", toolId)\n    .single()\n\n  if (!tool) {\n    throw new Error(error.message)\n  }\n\n  return tool\n}\n\nexport const createTool = async (\n  tool: TablesInsert<\"tools\">,\n  workspace_id: string\n) => {\n  const { data: createdTool, error } = await supabase\n    .from(\"tools\")\n    .insert([tool])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createToolWorkspace({\n    user_id: createdTool.user_id,\n    tool_id: createdTool.id,\n    workspace_id\n  })\n\n  return createdTool\n}\n\nexport const createTools = async (\n  tools: TablesInsert<\"tools\">[],\n  workspace_id: string\n) => {\n  const { data: createdTools, error } = await supabase\n    .from(\"tools\")\n    .insert(tools)\n    .select(\"*\")\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  await createToolWorkspaces(\n    createdTools.map(tool => ({\n      user_id: tool.user_id,\n      tool_id: tool.id,\n      workspace_id\n    }))\n  )\n\n  return createdTools\n}\n\nexport const createToolWorkspace = async (item: {\n  user_id: string\n  tool_id: string\n  workspace_id: string\n}) => {\n  const { data: createdToolWorkspace, error } = await supabase\n    .from(\"tool_workspaces\")\n    .insert([item])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdToolWorkspace\n}\n\nexport const createToolWorkspaces = async (\n  items: { user_id: string; tool_id: string; workspace_id: string }[]\n) => {\n  const { data: createdToolWorkspaces, error } = await supabase\n    .from(\"tool_workspaces\")\n    .insert(items)\n    .select(\"*\")\n\n  if (error) throw new Error(error.message)\n\n  return createdToolWorkspaces\n}\n\nexport const updateTool = async (\n  toolId: string,\n  tool: TablesUpdate<\"tools\">\n) => {\n  const { data: updatedTool, error } = await supabase\n    .from(\"tools\")\n    .update(tool)\n    .eq(\"id\", toolId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedTool\n}\n\nexport const deleteTool = async (toolId: string) => {\n  const { error } = await supabase.from(\"tools\").delete().eq(\"id\", toolId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n\nexport const deleteToolWorkspace = async (\n  toolId: string,\n  workspaceId: string\n) => {\n  const { error } = await supabase\n    .from(\"tool_workspaces\")\n    .delete()\n    .eq(\"tool_id\", toolId)\n    .eq(\"workspace_id\", workspaceId)\n\n  if (error) throw new Error(error.message)\n\n  return true\n}\n"
  },
  {
    "path": "db/workspaces.ts",
    "content": "import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert, TablesUpdate } from \"@/supabase/types\"\n\nexport const getHomeWorkspaceByUserId = async (userId: string) => {\n  const { data: homeWorkspace, error } = await supabase\n    .from(\"workspaces\")\n    .select(\"*\")\n    .eq(\"user_id\", userId)\n    .eq(\"is_home\", true)\n    .single()\n\n  if (!homeWorkspace) {\n    throw new Error(error.message)\n  }\n\n  return homeWorkspace.id\n}\n\nexport const getWorkspaceById = async (workspaceId: string) => {\n  const { data: workspace, error } = await supabase\n    .from(\"workspaces\")\n    .select(\"*\")\n    .eq(\"id\", workspaceId)\n    .single()\n\n  if (!workspace) {\n    throw new Error(error.message)\n  }\n\n  return workspace\n}\n\nexport const getWorkspacesByUserId = async (userId: string) => {\n  const { data: workspaces, error } = await supabase\n    .from(\"workspaces\")\n    .select(\"*\")\n    .eq(\"user_id\", userId)\n    .order(\"created_at\", { ascending: false })\n\n  if (!workspaces) {\n    throw new Error(error.message)\n  }\n\n  return workspaces\n}\n\nexport const createWorkspace = async (\n  workspace: TablesInsert<\"workspaces\">\n) => {\n  const { data: createdWorkspace, error } = await supabase\n    .from(\"workspaces\")\n    .insert([workspace])\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return createdWorkspace\n}\n\nexport const updateWorkspace = async (\n  workspaceId: string,\n  workspace: TablesUpdate<\"workspaces\">\n) => {\n  const { data: updatedWorkspace, error } = await supabase\n    .from(\"workspaces\")\n    .update(workspace)\n    .eq(\"id\", workspaceId)\n    .select(\"*\")\n    .single()\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return updatedWorkspace\n}\n\nexport const deleteWorkspace = async (workspaceId: string) => {\n  const { error } = await supabase\n    .from(\"workspaces\")\n    .delete()\n    .eq(\"id\", workspaceId)\n\n  if (error) {\n    throw new Error(error.message)\n  }\n\n  return true\n}\n"
  },
  {
    "path": "i18nConfig.js",
    "content": "const i18nConfig = {\n  defaultLocale: \"en\",\n  locales: [\n    \"ar\",\n    \"bn\",\n    \"de\",\n    \"en\",\n    \"es\",\n    \"fr\",\n    \"he\",\n    \"id\",\n    \"it\",\n    \"ja\",\n    \"ko\",\n    \"pt\",\n    \"ru\",\n    \"si\",\n    \"sv\",\n    \"te\",\n    \"vi\",\n    \"zh\"\n  ]\n}\n\nmodule.exports = i18nConfig\n"
  },
  {
    "path": "jest.config.ts",
    "content": "import type { Config } from \"jest\"\nimport nextJest from \"next/jest.js\"\n\nconst createJestConfig = nextJest({\n  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment\n  dir: \"./\"\n})\n\n// Add any custom config to be passed to Jest\nconst config: Config = {\n  coverageProvider: \"v8\",\n  testEnvironment: \"jsdom\"\n  // Add more setup options before each test is run\n  // setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],\n}\n\n// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async\nexport default createJestConfig(config)\n"
  },
  {
    "path": "lib/blob-to-b64.ts",
    "content": "export const convertBlobToBase64 = async (blob: Blob): Promise<string> => {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader()\n    reader.onloadend = () => resolve(reader.result as string)\n    reader.onerror = reject\n    reader.readAsDataURL(blob)\n  })\n}\n"
  },
  {
    "path": "lib/build-prompt.ts",
    "content": "import { Tables } from \"@/supabase/types\"\nimport { ChatPayload, MessageImage } from \"@/types\"\nimport { encode } from \"gpt-tokenizer\"\nimport { getBase64FromDataURL, getMediaTypeFromDataURL } from \"@/lib/utils\"\n\nconst buildBasePrompt = (\n  prompt: string,\n  profileContext: string,\n  workspaceInstructions: string,\n  assistant: Tables<\"assistants\"> | null\n) => {\n  let fullPrompt = \"\"\n\n  if (assistant) {\n    fullPrompt += `<INJECT ROLE>\\nYou are not an AI. You are ${assistant.name}.\\n</INJECT ROLE>\\n\\n`\n  }\n\n  fullPrompt += `Today is ${new Date().toLocaleDateString()}.\\n\\n`\n\n  if (profileContext) {\n    fullPrompt += `User Info:\\n${profileContext}\\n\\n`\n  }\n\n  if (workspaceInstructions) {\n    fullPrompt += `System Instructions:\\n${workspaceInstructions}\\n\\n`\n  }\n\n  fullPrompt += `User Instructions:\\n${prompt}`\n\n  return fullPrompt\n}\n\nexport async function buildFinalMessages(\n  payload: ChatPayload,\n  profile: Tables<\"profiles\">,\n  chatImages: MessageImage[]\n) {\n  const {\n    chatSettings,\n    workspaceInstructions,\n    chatMessages,\n    assistant,\n    messageFileItems,\n    chatFileItems\n  } = payload\n\n  const BUILT_PROMPT = buildBasePrompt(\n    chatSettings.prompt,\n    chatSettings.includeProfileContext ? profile.profile_context || \"\" : \"\",\n    chatSettings.includeWorkspaceInstructions ? workspaceInstructions : \"\",\n    assistant\n  )\n\n  const CHUNK_SIZE = chatSettings.contextLength\n  const PROMPT_TOKENS = encode(chatSettings.prompt).length\n\n  let remainingTokens = CHUNK_SIZE - PROMPT_TOKENS\n\n  let usedTokens = 0\n  usedTokens += PROMPT_TOKENS\n\n  const processedChatMessages = chatMessages.map((chatMessage, index) => {\n    const nextChatMessage = chatMessages[index + 1]\n\n    if (nextChatMessage === undefined) {\n      return chatMessage\n    }\n\n    const nextChatMessageFileItems = nextChatMessage.fileItems\n\n    if (nextChatMessageFileItems.length > 0) {\n      const findFileItems = nextChatMessageFileItems\n        .map(fileItemId =>\n          chatFileItems.find(chatFileItem => chatFileItem.id === fileItemId)\n        )\n        .filter(item => item !== undefined) as Tables<\"file_items\">[]\n\n      const retrievalText = buildRetrievalText(findFileItems)\n\n      return {\n        message: {\n          ...chatMessage.message,\n          content:\n            `${chatMessage.message.content}\\n\\n${retrievalText}` as string\n        },\n        fileItems: []\n      }\n    }\n\n    return chatMessage\n  })\n\n  let finalMessages = []\n\n  for (let i = processedChatMessages.length - 1; i >= 0; i--) {\n    const message = processedChatMessages[i].message\n    const messageTokens = encode(message.content).length\n\n    if (messageTokens <= remainingTokens) {\n      remainingTokens -= messageTokens\n      usedTokens += messageTokens\n      finalMessages.unshift(message)\n    } else {\n      break\n    }\n  }\n\n  let tempSystemMessage: Tables<\"messages\"> = {\n    chat_id: \"\",\n    assistant_id: null,\n    content: BUILT_PROMPT,\n    created_at: \"\",\n    id: processedChatMessages.length + \"\",\n    image_paths: [],\n    model: payload.chatSettings.model,\n    role: \"system\",\n    sequence_number: processedChatMessages.length,\n    updated_at: \"\",\n    user_id: \"\"\n  }\n\n  finalMessages.unshift(tempSystemMessage)\n\n  finalMessages = finalMessages.map(message => {\n    let content\n\n    if (message.image_paths.length > 0) {\n      content = [\n        {\n          type: \"text\",\n          text: message.content\n        },\n        ...message.image_paths.map(path => {\n          let formedUrl = \"\"\n\n          if (path.startsWith(\"data\")) {\n            formedUrl = path\n          } else {\n            const chatImage = chatImages.find(image => image.path === path)\n\n            if (chatImage) {\n              formedUrl = chatImage.base64\n            }\n          }\n\n          return {\n            type: \"image_url\",\n            image_url: {\n              url: formedUrl\n            }\n          }\n        })\n      ]\n    } else {\n      content = message.content\n    }\n\n    return {\n      role: message.role,\n      content\n    }\n  })\n\n  if (messageFileItems.length > 0) {\n    const retrievalText = buildRetrievalText(messageFileItems)\n\n    finalMessages[finalMessages.length - 1] = {\n      ...finalMessages[finalMessages.length - 1],\n      content: `${\n        finalMessages[finalMessages.length - 1].content\n      }\\n\\n${retrievalText}`\n    }\n  }\n\n  return finalMessages\n}\n\nfunction buildRetrievalText(fileItems: Tables<\"file_items\">[]) {\n  const retrievalText = fileItems\n    .map(item => `<BEGIN SOURCE>\\n${item.content}\\n</END SOURCE>`)\n    .join(\"\\n\\n\")\n\n  return `You may use the following sources if needed to answer the user's question. If you don't know the answer, say \"I don't know.\"\\n\\n${retrievalText}`\n}\n\nfunction adaptSingleMessageForGoogleGemini(message: any) {\n\n  let adaptedParts = []\n\n  let rawParts = []\n  if(!Array.isArray(message.content)) {\n    rawParts.push({type: 'text', text: message.content})\n  } else {\n    rawParts = message.content\n  }\n\n  for(let i = 0; i < rawParts.length; i++) {\n    let rawPart = rawParts[i]\n\n    if(rawPart.type == 'text') {\n      adaptedParts.push({text: rawPart.text})\n    } else if(rawPart.type === 'image_url') {\n      adaptedParts.push({\n        inlineData: {\n          data: getBase64FromDataURL(rawPart.image_url.url),\n          mimeType: getMediaTypeFromDataURL(rawPart.image_url.url),\n        }\n      })\n    }\n  }\n\n  let role = 'user'\n  if([\"user\", \"system\"].includes(message.role)) {\n    role = 'user'\n  } else if(message.role === 'assistant') {\n    role = 'model'\n  }\n\n  return {\n    role: role,\n    parts: adaptedParts\n  }\n}\n\nfunction adaptMessagesForGeminiVision(\n  messages: any[]\n) {\n  // Gemini Pro Vision cannot process multiple messages\n  // Reformat, using all texts and last visual only\n\n  const basePrompt = messages[0].parts[0].text\n  const baseRole = messages[0].role\n  const lastMessage = messages[messages.length-1]\n  const visualMessageParts = lastMessage.parts;\n  let visualQueryMessages = [{\n    role: \"user\",\n    parts: [\n      `${baseRole}:\\n${basePrompt}\\n\\nuser:\\n${visualMessageParts[0].text}\\n\\n`,\n      visualMessageParts.slice(1)\n    ]\n  }]\n  return visualQueryMessages\n}\n\nexport async function adaptMessagesForGoogleGemini(\n  payload: ChatPayload,\n  messages:  any[]\n) {\n  let geminiMessages = []\n  for (let i = 0; i < messages.length; i++) {\n    let adaptedMessage = adaptSingleMessageForGoogleGemini(messages[i])\n    geminiMessages.push(adaptedMessage)\n  }\n\n  if(payload.chatSettings.model === \"gemini-pro-vision\") {\n    geminiMessages = adaptMessagesForGeminiVision(geminiMessages)\n  }\n  return geminiMessages\n}\n\n"
  },
  {
    "path": "lib/chat-setting-limits.ts",
    "content": "import { LLMID } from \"@/types\"\n\ntype ChatSettingLimits = {\n  MIN_TEMPERATURE: number\n  MAX_TEMPERATURE: number\n  MAX_TOKEN_OUTPUT_LENGTH: number\n  MAX_CONTEXT_LENGTH: number\n}\n\nexport const CHAT_SETTING_LIMITS: Record<LLMID, ChatSettingLimits> = {\n  // ANTHROPIC MODELS\n  \"claude-2.1\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 200000\n  },\n  \"claude-instant-1.2\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 100000\n  },\n  \"claude-3-haiku-20240307\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 200000\n  },\n  \"claude-3-sonnet-20240229\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 200000\n  },\n  \"claude-3-opus-20240229\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 200000\n  },\n  \"claude-3-5-sonnet-20240620\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 200000\n  },\n\n  // GOOGLE MODELS\n  \n  \"gemini-1.5-flash\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 8192,\n    MAX_CONTEXT_LENGTH: 1040384\n  },\n  \"gemini-1.5-pro-latest\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 8192,\n    MAX_CONTEXT_LENGTH: 1040384\n  },\n  \"gemini-pro\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 2048,\n    MAX_CONTEXT_LENGTH: 30720\n  },\n  \"gemini-pro-vision\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 12288\n  },\n\n  // MISTRAL MODELS\n  \"mistral-tiny\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 2000,\n    MAX_CONTEXT_LENGTH: 8000\n  },\n  \"mistral-small-latest\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 2000,\n    MAX_CONTEXT_LENGTH: 32000\n  },\n  \"mistral-medium-latest\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 2000,\n    MAX_CONTEXT_LENGTH: 32000\n  },\n  \"mistral-large-latest\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 2000,\n    MAX_CONTEXT_LENGTH: 32000\n  },\n\n  // GROQ MODELS\n  \"llama3-8b-8192\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 8192,\n    MAX_CONTEXT_LENGTH: 8192\n  },\n  \"llama3-70b-8192\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 8192,\n    MAX_CONTEXT_LENGTH: 8192\n  },\n  \"mixtral-8x7b-32768\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 32768\n  },\n  \"gemma-7b-it\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 2.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 8192,\n    MAX_CONTEXT_LENGTH: 8192\n  },\n\n  // OPENAI MODELS\n  \"gpt-3.5-turbo\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 2.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 4096\n    // MAX_CONTEXT_LENGTH: 16385 (TODO: Change this back to 16385 when OpenAI bumps the model)\n  },\n  \"gpt-4-turbo-preview\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 2.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 128000\n  },\n  \"gpt-4-vision-preview\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 2.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 128000\n  },\n  \"gpt-4\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 2.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 8192\n  },\n  \"gpt-4o\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 2.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 128000\n  },\n\n  // PERPLEXITY MODELS\n  \"pplx-7b-online\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.99,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 4096\n  },\n  \"pplx-70b-online\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.99,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 4096\n  },\n  \"pplx-7b-chat\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 8192\n  },\n  \"pplx-70b-chat\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 4096\n  },\n  \"mixtral-8x7b-instruct\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 16384,\n    MAX_CONTEXT_LENGTH: 16384\n  },\n  \"mistral-7b-instruct\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 16384,\n    MAX_CONTEXT_LENGTH: 16384\n  },\n  \"llama-2-70b-chat\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 2.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 4096\n  },\n  \"codellama-34b-instruct\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 4096,\n    MAX_CONTEXT_LENGTH: 16384\n  },\n  \"codellama-70b-instruct\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 16384,\n    MAX_CONTEXT_LENGTH: 16384\n  },\n  \"sonar-small-chat\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 16384,\n    MAX_CONTEXT_LENGTH: 16384\n  },\n  \"sonar-small-online\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 12000,\n    MAX_CONTEXT_LENGTH: 12000\n  },\n  \"sonar-medium-chat\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 16384,\n    MAX_CONTEXT_LENGTH: 16384\n  },\n  \"sonar-medium-online\": {\n    MIN_TEMPERATURE: 0.0,\n    MAX_TEMPERATURE: 1.0,\n    MAX_TOKEN_OUTPUT_LENGTH: 12000,\n    MAX_CONTEXT_LENGTH: 12000\n  }\n}\n"
  },
  {
    "path": "lib/consume-stream.ts",
    "content": "export async function consumeReadableStream(\n  stream: ReadableStream<Uint8Array>,\n  callback: (chunk: string) => void,\n  signal: AbortSignal\n): Promise<void> {\n  const reader = stream.getReader()\n  const decoder = new TextDecoder()\n\n  signal.addEventListener(\"abort\", () => reader.cancel(), { once: true })\n\n  try {\n    while (true) {\n      const { done, value } = await reader.read()\n\n      if (done) {\n        break\n      }\n\n      if (value) {\n        callback(decoder.decode(value, { stream: true }))\n      }\n    }\n  } catch (error) {\n    if (signal.aborted) {\n      console.error(\"Stream reading was aborted:\", error)\n    } else {\n      console.error(\"Error consuming stream:\", error)\n    }\n  } finally {\n    reader.releaseLock()\n  }\n}\n"
  },
  {
    "path": "lib/envs.ts",
    "content": "import { EnvKey } from \"@/types/key-type\"\n\n// returns true if the key is found in the environment variables\nexport function isUsingEnvironmentKey(type: EnvKey) {\n  return Boolean(process.env[type])\n}\n"
  },
  {
    "path": "lib/export-old-data.ts",
    "content": "export function exportLocalStorageAsJSON() {\n  const data: { [key: string]: string | null } = {}\n  for (let i = 0; i < localStorage.length; i++) {\n    const key = localStorage.key(i)\n    if (key !== null) {\n      data[key] = localStorage.getItem(key)\n    }\n  }\n\n  const json = JSON.stringify(data)\n  const blob = new Blob([json], { type: \"application/json\" })\n  const url = URL.createObjectURL(blob)\n\n  const a = document.createElement(\"a\")\n  a.href = url\n  a.download = \"chatbot-ui-data.json\"\n  document.body.appendChild(a)\n  a.click()\n  document.body.removeChild(a)\n  URL.revokeObjectURL(url)\n}\n"
  },
  {
    "path": "lib/generate-local-embedding.ts",
    "content": "import { pipeline } from \"@xenova/transformers\"\n\nexport async function generateLocalEmbedding(content: string) {\n  const generateEmbedding = await pipeline(\n    \"feature-extraction\",\n    \"Xenova/all-MiniLM-L6-v2\"\n  )\n\n  const output = await generateEmbedding(content, {\n    pooling: \"mean\",\n    normalize: true\n  })\n\n  const embedding = Array.from(output.data)\n\n  return embedding\n}\n"
  },
  {
    "path": "lib/hooks/use-copy-to-clipboard.tsx",
    "content": "import { useState } from \"react\"\n\nexport interface useCopyToClipboardProps {\n  timeout?: number\n}\n\nexport function useCopyToClipboard({\n  timeout = 2000\n}: useCopyToClipboardProps) {\n  const [isCopied, setIsCopied] = useState<Boolean>(false)\n\n  const copyToClipboard = (value: string) => {\n    if (typeof window === \"undefined\" || !navigator.clipboard?.writeText) {\n      return\n    }\n\n    if (!value) {\n      return\n    }\n\n    navigator.clipboard.writeText(value).then(() => {\n      setIsCopied(true)\n\n      setTimeout(() => {\n        setIsCopied(false)\n      }, timeout)\n    })\n  }\n\n  return { isCopied, copyToClipboard }\n}\n"
  },
  {
    "path": "lib/hooks/use-hotkey.tsx",
    "content": "import { useEffect } from \"react\"\n\nconst useHotkey = (key: string, callback: () => void): void => {\n  useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent): void => {\n      if (event.metaKey && event.shiftKey && event.key === key) {\n        event.preventDefault()\n        callback()\n      }\n    }\n\n    window.addEventListener(\"keydown\", handleKeyDown)\n\n    return () => {\n      window.removeEventListener(\"keydown\", handleKeyDown)\n    }\n  }, [key, callback])\n}\n\nexport default useHotkey\n"
  },
  {
    "path": "lib/i18n.ts",
    "content": "import i18nConfig from \"@/i18nConfig\"\nimport { createInstance } from \"i18next\"\nimport resourcesToBackend from \"i18next-resources-to-backend\"\nimport { initReactI18next } from \"react-i18next/initReactI18next\"\n\nexport default async function initTranslations(\n  locale: any,\n  namespaces: any,\n  i18nInstance?: any,\n  resources?: any\n) {\n  i18nInstance = i18nInstance || createInstance()\n\n  i18nInstance.use(initReactI18next)\n\n  if (!resources) {\n    i18nInstance.use(\n      resourcesToBackend(\n        (language: string, namespace: string) =>\n          import(`/public/locales/${language}/${namespace}.json`)\n      )\n    )\n  }\n\n  await i18nInstance.init({\n    lng: locale,\n    resources,\n    fallbackLng: i18nConfig.defaultLocale,\n    supportedLngs: i18nConfig.locales,\n    defaultNS: namespaces[0],\n    fallbackNS: namespaces[0],\n    ns: namespaces,\n    preload: resources ? [] : i18nConfig.locales\n  })\n\n  return {\n    i18n: i18nInstance,\n    resources: i18nInstance.services.resourceStore.data,\n    t: i18nInstance.t\n  }\n}\n"
  },
  {
    "path": "lib/models/fetch-models.ts",
    "content": "import { Tables } from \"@/supabase/types\"\nimport { LLM, LLMID, OpenRouterLLM } from \"@/types\"\nimport { toast } from \"sonner\"\nimport { LLM_LIST_MAP } from \"./llm/llm-list\"\n\nexport const fetchHostedModels = async (profile: Tables<\"profiles\">) => {\n  try {\n    const providers = [\"google\", \"anthropic\", \"mistral\", \"groq\", \"perplexity\"]\n\n    if (profile.use_azure_openai) {\n      providers.push(\"azure\")\n    } else {\n      providers.push(\"openai\")\n    }\n\n    const response = await fetch(\"/api/keys\")\n\n    if (!response.ok) {\n      throw new Error(`Server is not responding.`)\n    }\n\n    const data = await response.json()\n\n    let modelsToAdd: LLM[] = []\n\n    for (const provider of providers) {\n      let providerKey: keyof typeof profile\n\n      if (provider === \"google\") {\n        providerKey = \"google_gemini_api_key\"\n      } else if (provider === \"azure\") {\n        providerKey = \"azure_openai_api_key\"\n      } else {\n        providerKey = `${provider}_api_key` as keyof typeof profile\n      }\n\n      if (profile?.[providerKey] || data.isUsingEnvKeyMap[provider]) {\n        const models = LLM_LIST_MAP[provider]\n\n        if (Array.isArray(models)) {\n          modelsToAdd.push(...models)\n        }\n      }\n    }\n\n    return {\n      envKeyMap: data.isUsingEnvKeyMap,\n      hostedModels: modelsToAdd\n    }\n  } catch (error) {\n    console.warn(\"Error fetching hosted models: \" + error)\n  }\n}\n\nexport const fetchOllamaModels = async () => {\n  try {\n    const response = await fetch(\n      process.env.NEXT_PUBLIC_OLLAMA_URL + \"/api/tags\"\n    )\n\n    if (!response.ok) {\n      throw new Error(`Ollama server is not responding.`)\n    }\n\n    const data = await response.json()\n\n    const localModels: LLM[] = data.models.map((model: any) => ({\n      modelId: model.name as LLMID,\n      modelName: model.name,\n      provider: \"ollama\",\n      hostedId: model.name,\n      platformLink: \"https://ollama.ai/library\",\n      imageInput: false\n    }))\n\n    return localModels\n  } catch (error) {\n    console.warn(\"Error fetching Ollama models: \" + error)\n  }\n}\n\nexport const fetchOpenRouterModels = async () => {\n  try {\n    const response = await fetch(\"https://openrouter.ai/api/v1/models\")\n\n    if (!response.ok) {\n      throw new Error(`OpenRouter server is not responding.`)\n    }\n\n    const { data } = await response.json()\n\n    const openRouterModels = data.map(\n      (model: {\n        id: string\n        name: string\n        context_length: number\n      }): OpenRouterLLM => ({\n        modelId: model.id as LLMID,\n        modelName: model.id,\n        provider: \"openrouter\",\n        hostedId: model.name,\n        platformLink: \"https://openrouter.dev\",\n        imageInput: false,\n        maxContext: model.context_length\n      })\n    )\n\n    return openRouterModels\n  } catch (error) {\n    console.error(\"Error fetching Open Router models: \" + error)\n    toast.error(\"Error fetching Open Router models: \" + error)\n  }\n}\n"
  },
  {
    "path": "lib/models/llm/anthropic-llm-list.ts",
    "content": "import { LLM } from \"@/types\"\n\nconst ANTHROPIC_PLATFORM_LINK =\n  \"https://docs.anthropic.com/claude/reference/getting-started-with-the-api\"\n\n// Anthropic Models (UPDATED 06/20/24) -----------------------------\n\n// Claude 2 (UPDATED 12/21/23)\nconst CLAUDE_2: LLM = {\n  modelId: \"claude-2.1\",\n  modelName: \"Claude 2\",\n  provider: \"anthropic\",\n  hostedId: \"claude-2.1\",\n  platformLink: ANTHROPIC_PLATFORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 8,\n    outputCost: 24\n  }\n}\n\n// Claude Instant (UPDATED 12/21/23)\nconst CLAUDE_INSTANT: LLM = {\n  modelId: \"claude-instant-1.2\",\n  modelName: \"Claude Instant\",\n  provider: \"anthropic\",\n  hostedId: \"claude-instant-1.2\",\n  platformLink: ANTHROPIC_PLATFORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 0.8,\n    outputCost: 2.4\n  }\n}\n\n// Claude 3 Haiku (UPDATED 03/13/24)\nconst CLAUDE_3_HAIKU: LLM = {\n  modelId: \"claude-3-haiku-20240307\",\n  modelName: \"Claude 3 Haiku\",\n  provider: \"anthropic\",\n  hostedId: \"claude-3-haiku-20240307\",\n  platformLink: ANTHROPIC_PLATFORM_LINK,\n  imageInput: true,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 0.25,\n    outputCost: 1.25\n  }\n}\n\n// Claude 3 Sonnet (UPDATED 03/04/24)\nconst CLAUDE_3_SONNET: LLM = {\n  modelId: \"claude-3-sonnet-20240229\",\n  modelName: \"Claude 3 Sonnet\",\n  provider: \"anthropic\",\n  hostedId: \"claude-3-sonnet-20240229\",\n  platformLink: ANTHROPIC_PLATFORM_LINK,\n  imageInput: true,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 3,\n    outputCost: 15\n  }\n}\n\n// Claude 3 Opus (UPDATED 03/04/24)\nconst CLAUDE_3_OPUS: LLM = {\n  modelId: \"claude-3-opus-20240229\",\n  modelName: \"Claude 3 Opus\",\n  provider: \"anthropic\",\n  hostedId: \"claude-3-opus-20240229\",\n  platformLink: ANTHROPIC_PLATFORM_LINK,\n  imageInput: true,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 15,\n    outputCost: 75\n  }\n}\n\n// Claude 3.5 Sonnet (UPDATED 06/20/24)\nconst CLAUDE_3_5_SONNET: LLM = {\n  modelId: \"claude-3-5-sonnet-20240620\",\n  modelName: \"Claude 3.5 Sonnet\",\n  provider: \"anthropic\",\n  hostedId: \"claude-3-5-sonnet-20240620\",\n  platformLink: ANTHROPIC_PLATFORM_LINK,\n  imageInput: true,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 3,\n    outputCost: 15\n  }\n}\n\nexport const ANTHROPIC_LLM_LIST: LLM[] = [\n  CLAUDE_2,\n  CLAUDE_INSTANT,\n  CLAUDE_3_HAIKU,\n  CLAUDE_3_SONNET,\n  CLAUDE_3_OPUS,\n  CLAUDE_3_5_SONNET\n]\n"
  },
  {
    "path": "lib/models/llm/google-llm-list.ts",
    "content": "import { LLM } from \"@/types\"\n\nconst GOOGLE_PLATORM_LINK = \"https://ai.google.dev/\"\n\n// Google Models (UPDATED 12/22/23) -----------------------------\n\n// Gemini 1.5 Flash\nconst GEMINI_1_5_FLASH: LLM = {\n  modelId: \"gemini-1.5-flash\",\n  modelName: \"Gemini 1.5 Flash\",\n  provider: \"google\",\n  hostedId: \"gemini-1.5-flash\",\n  platformLink: GOOGLE_PLATORM_LINK,\n  imageInput: true\n}\n\n// Gemini 1.5 Pro (UPDATED 05/28/24)\nconst GEMINI_1_5_PRO: LLM = {\n  modelId: \"gemini-1.5-pro-latest\",\n  modelName: \"Gemini 1.5 Pro\",\n  provider: \"google\",\n  hostedId: \"gemini-1.5-pro-latest\",\n  platformLink: GOOGLE_PLATORM_LINK,\n  imageInput: true\n}\n\n// Gemini Pro (UPDATED 12/22/23)\nconst GEMINI_PRO: LLM = {\n  modelId: \"gemini-pro\",\n  modelName: \"Gemini Pro\",\n  provider: \"google\",\n  hostedId: \"gemini-pro\",\n  platformLink: GOOGLE_PLATORM_LINK,\n  imageInput: false\n}\n\n// Gemini Pro Vision (UPDATED 12/22/23)\nconst GEMINI_PRO_VISION: LLM = {\n  modelId: \"gemini-pro-vision\",\n  modelName: \"Gemini Pro Vision\",\n  provider: \"google\",\n  hostedId: \"gemini-pro-vision\",\n  platformLink: GOOGLE_PLATORM_LINK,\n  imageInput: true\n}\n\nexport const GOOGLE_LLM_LIST: LLM[] = [GEMINI_PRO, GEMINI_PRO_VISION, GEMINI_1_5_PRO, GEMINI_1_5_FLASH]\n"
  },
  {
    "path": "lib/models/llm/groq-llm-list.ts",
    "content": "import { LLM } from \"@/types\"\n\nconst GROQ_PLATORM_LINK = \"https://groq.com/\"\n\nconst LLaMA3_8B: LLM = {\n  modelId: \"llama3-8b-8192\",\n  modelName: \"LLaMA3-8b-chat\",\n  provider: \"groq\",\n  hostedId: \"llama3-8b-8192\",\n  platformLink: GROQ_PLATORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 0.05,\n    outputCost: 0.1\n  }\n}\n\nconst LLaMA3_70B: LLM = {\n  modelId: \"llama3-70b-8192\",\n  modelName: \"LLaMA3-70b-chat\",\n  provider: \"groq\",\n  hostedId: \"llama3-70b-4096\",\n  platformLink: GROQ_PLATORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 0.59,\n    outputCost: 0.79\n  }\n}\n\nconst MIXTRAL_8X7B: LLM = {\n  modelId: \"mixtral-8x7b-32768\",\n  modelName: \"Mixtral-8x7b-Instruct-v0.1\",\n  provider: \"groq\",\n  hostedId: \"mixtral-8x7b-32768\",\n  platformLink: GROQ_PLATORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 0.27,\n    outputCost: 0.27\n  }\n}\n\nconst GEMMA_7B_IT: LLM = {\n  modelId: \"gemma-7b-it\",\n  modelName: \"Gemma-7b-It\",\n  provider: \"groq\",\n  hostedId: \"gemma-7b-it\",\n  platformLink: GROQ_PLATORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 0.15,\n    outputCost: 0.15\n  }\n}\n\nexport const GROQ_LLM_LIST: LLM[] = [\n  LLaMA3_8B,\n  LLaMA3_70B,\n  MIXTRAL_8X7B,\n  GEMMA_7B_IT\n]\n"
  },
  {
    "path": "lib/models/llm/llm-list.ts",
    "content": "import { LLM } from \"@/types\"\nimport { ANTHROPIC_LLM_LIST } from \"./anthropic-llm-list\"\nimport { GOOGLE_LLM_LIST } from \"./google-llm-list\"\nimport { MISTRAL_LLM_LIST } from \"./mistral-llm-list\"\nimport { GROQ_LLM_LIST } from \"./groq-llm-list\"\nimport { OPENAI_LLM_LIST } from \"./openai-llm-list\"\nimport { PERPLEXITY_LLM_LIST } from \"./perplexity-llm-list\"\n\nexport const LLM_LIST: LLM[] = [\n  ...OPENAI_LLM_LIST,\n  ...GOOGLE_LLM_LIST,\n  ...MISTRAL_LLM_LIST,\n  ...GROQ_LLM_LIST,\n  ...PERPLEXITY_LLM_LIST,\n  ...ANTHROPIC_LLM_LIST\n]\n\nexport const LLM_LIST_MAP: Record<string, LLM[]> = {\n  openai: OPENAI_LLM_LIST,\n  azure: OPENAI_LLM_LIST,\n  google: GOOGLE_LLM_LIST,\n  mistral: MISTRAL_LLM_LIST,\n  groq: GROQ_LLM_LIST,\n  perplexity: PERPLEXITY_LLM_LIST,\n  anthropic: ANTHROPIC_LLM_LIST\n}\n"
  },
  {
    "path": "lib/models/llm/mistral-llm-list.ts",
    "content": "import { LLM } from \"@/types\"\n\nconst MISTRAL_PLATORM_LINK = \"https://docs.mistral.ai/\"\n\n// Mistral Models (UPDATED 12/21/23) -----------------------------\n\n// Mistral 7B (UPDATED 12/21/23)\nconst MISTRAL_7B: LLM = {\n  modelId: \"mistral-tiny\",\n  modelName: \"Mistral Tiny\",\n  provider: \"mistral\",\n  hostedId: \"mistral-tiny\",\n  platformLink: MISTRAL_PLATORM_LINK,\n  imageInput: false\n}\n\n// Mixtral (UPDATED 12/21/23)\nconst MIXTRAL: LLM = {\n  modelId: \"mistral-small-latest\",\n  modelName: \"Mistral Small\",\n  provider: \"mistral\",\n  hostedId: \"mistral-small-latest\",\n  platformLink: MISTRAL_PLATORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 2,\n    outputCost: 6\n  }\n}\n\n// Mistral Medium (UPDATED 12/21/23)\nconst MISTRAL_MEDIUM: LLM = {\n  modelId: \"mistral-medium-latest\",\n  modelName: \"Mistral Medium\",\n  provider: \"mistral\",\n  hostedId: \"mistral-medium-latest\",\n  platformLink: MISTRAL_PLATORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 2.7,\n    outputCost: 8.1\n  }\n}\n\n// Mistral Large (UPDATED 03/05/24)\nconst MISTRAL_LARGE: LLM = {\n  modelId: \"mistral-large-latest\",\n  modelName: \"Mistral Large\",\n  provider: \"mistral\",\n  hostedId: \"mistral-large-latest\",\n  platformLink: MISTRAL_PLATORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 8,\n    outputCost: 24\n  }\n}\n\nexport const MISTRAL_LLM_LIST: LLM[] = [\n  MISTRAL_7B,\n  MIXTRAL,\n  MISTRAL_MEDIUM,\n  MISTRAL_LARGE\n]\n"
  },
  {
    "path": "lib/models/llm/openai-llm-list.ts",
    "content": "import { LLM } from \"@/types\"\n\nconst OPENAI_PLATORM_LINK = \"https://platform.openai.com/docs/overview\"\n\n// OpenAI Models (UPDATED 1/25/24) -----------------------------\nconst GPT4o: LLM = {\n  modelId: \"gpt-4o\",\n  modelName: \"GPT-4o\",\n  provider: \"openai\",\n  hostedId: \"gpt-4o\",\n  platformLink: OPENAI_PLATORM_LINK,\n  imageInput: true,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 5,\n    outputCost: 15\n  }\n}\n\n// GPT-4 Turbo (UPDATED 1/25/24)\nconst GPT4Turbo: LLM = {\n  modelId: \"gpt-4-turbo-preview\",\n  modelName: \"GPT-4 Turbo\",\n  provider: \"openai\",\n  hostedId: \"gpt-4-turbo-preview\",\n  platformLink: OPENAI_PLATORM_LINK,\n  imageInput: true,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 10,\n    outputCost: 30\n  }\n}\n\n// GPT-4 Vision (UPDATED 12/18/23)\nconst GPT4Vision: LLM = {\n  modelId: \"gpt-4-vision-preview\",\n  modelName: \"GPT-4 Vision\",\n  provider: \"openai\",\n  hostedId: \"gpt-4-vision-preview\",\n  platformLink: OPENAI_PLATORM_LINK,\n  imageInput: true,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 10\n  }\n}\n\n// GPT-4 (UPDATED 1/29/24)\nconst GPT4: LLM = {\n  modelId: \"gpt-4\",\n  modelName: \"GPT-4\",\n  provider: \"openai\",\n  hostedId: \"gpt-4\",\n  platformLink: OPENAI_PLATORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 30,\n    outputCost: 60\n  }\n}\n\n// GPT-3.5 Turbo (UPDATED 1/25/24)\nconst GPT3_5Turbo: LLM = {\n  modelId: \"gpt-3.5-turbo\",\n  modelName: \"GPT-3.5 Turbo\",\n  provider: \"openai\",\n  hostedId: \"gpt-3.5-turbo\",\n  platformLink: OPENAI_PLATORM_LINK,\n  imageInput: false,\n  pricing: {\n    currency: \"USD\",\n    unit: \"1M tokens\",\n    inputCost: 0.5,\n    outputCost: 1.5\n  }\n}\n\nexport const OPENAI_LLM_LIST: LLM[] = [\n  GPT4o,\n  GPT4Turbo,\n  GPT4Vision,\n  GPT4,\n  GPT3_5Turbo\n]\n"
  },
  {
    "path": "lib/models/llm/perplexity-llm-list.ts",
    "content": "import { LLM } from \"@/types\"\n\nconst PERPLEXITY_PLATORM_LINK =\n  \"https://docs.perplexity.ai/docs/getting-started\"\n\n// Perplexity Models (UPDATED 2/25/24) -----------------------------\n// Model Deprecation Notice\n// Please note that on March 15, the pplx-70b-chat, pplx-70b-online, llama-2-70b-chat, and codellama-34b-instruct models will no longer be available through the Perplexity API.\n\n// Mixtral 8x7B Instruct (UPDATED 1/31/24)\nconst MIXTRAL_8X7B_INSTRUCT: LLM = {\n  modelId: \"mixtral-8x7b-instruct\",\n  modelName: \"Mixtral 8x7B Instruct\",\n  provider: \"perplexity\",\n  hostedId: \"mixtral-8x7b-instruct\",\n  platformLink: PERPLEXITY_PLATORM_LINK,\n  imageInput: false\n}\n\n// Mistral 7B Instruct (UPDATED 1/31/24)\nconst MISTRAL_7B_INSTRUCT: LLM = {\n  modelId: \"mistral-7b-instruct\",\n  modelName: \"Mistral 7B Instruct\",\n  provider: \"perplexity\",\n  hostedId: \"mistral-7b-instruct\",\n  platformLink: PERPLEXITY_PLATORM_LINK,\n  imageInput: false\n}\n\n// CodeLlama 70B Instruct (UPDATED 1/31/24)\nconst CODELLAMA_70B_INSTRUCT: LLM = {\n  modelId: \"codellama-70b-instruct\",\n  modelName: \"CodeLlama 70B Instruct\",\n  provider: \"perplexity\",\n  hostedId: \"codellama-70b-instruct\",\n  platformLink: PERPLEXITY_PLATORM_LINK,\n  imageInput: false\n}\n\n// Sonar Small Chat (UPDATED 2/25/24)\nconst PERPLEXITY_SONAR_SMALL_CHAT_7B: LLM = {\n  modelId: \"sonar-small-chat\",\n  modelName: \"Sonar Small Chat\",\n  provider: \"perplexity\",\n  hostedId: \"sonar-small-chat\",\n  platformLink: PERPLEXITY_PLATORM_LINK,\n  imageInput: false\n}\n\n// Sonar Small Online (UPDATED 2/25/24)\nconst PERPLEXITY_SONAR_SMALL_ONLINE_7B: LLM = {\n  modelId: \"sonar-small-online\",\n  modelName: \"Sonar Small Online\",\n  provider: \"perplexity\",\n  hostedId: \"sonar-small-online\",\n  platformLink: PERPLEXITY_PLATORM_LINK,\n  imageInput: false\n}\n\n// Sonar Medium Chat (UPDATED 2/25/24)\nconst PERPLEXITY_SONAR_MEDIUM_CHAT_8x7B: LLM = {\n  modelId: \"sonar-medium-chat\",\n  modelName: \"Sonar Medium Chat\",\n  provider: \"perplexity\",\n  hostedId: \"sonar-medium-chat\",\n  platformLink: PERPLEXITY_PLATORM_LINK,\n  imageInput: false\n}\n\n// Sonar Medium Online (UPDATED 2/25/24)\nconst PERPLEXITY_SONAR_MEDIUM_ONLINE_8x7B: LLM = {\n  modelId: \"sonar-medium-online\",\n  modelName: \"Sonar Medium Online\",\n  provider: \"perplexity\",\n  hostedId: \"sonar-medium-online\",\n  platformLink: PERPLEXITY_PLATORM_LINK,\n  imageInput: false\n}\n\nexport const PERPLEXITY_LLM_LIST: LLM[] = [\n  MIXTRAL_8X7B_INSTRUCT,\n  MISTRAL_7B_INSTRUCT,\n  CODELLAMA_70B_INSTRUCT,\n  PERPLEXITY_SONAR_SMALL_CHAT_7B,\n  PERPLEXITY_SONAR_SMALL_ONLINE_7B,\n  PERPLEXITY_SONAR_MEDIUM_CHAT_8x7B,\n  PERPLEXITY_SONAR_MEDIUM_ONLINE_8x7B\n]\n"
  },
  {
    "path": "lib/openapi-conversion.ts",
    "content": "import $RefParser from \"@apidevtools/json-schema-ref-parser\"\n\ninterface OpenAPIData {\n  info: {\n    title: string\n    description: string\n    server: string\n  }\n  routes: {\n    path: string\n    method: string\n    operationId: string\n    requestInBody?: boolean\n  }[]\n  functions: any\n}\n\nexport const validateOpenAPI = async (openapiSpec: any) => {\n  if (!openapiSpec.info) {\n    throw new Error(\"('info'): field required\")\n  }\n\n  if (!openapiSpec.info.title) {\n    throw new Error(\"('info', 'title'): field required\")\n  }\n\n  if (!openapiSpec.info.version) {\n    throw new Error(\"('info', 'version'): field required\")\n  }\n\n  if (\n    !openapiSpec.servers ||\n    !openapiSpec.servers.length ||\n    !openapiSpec.servers[0].url\n  ) {\n    throw new Error(\"Could not find a valid URL in `servers`\")\n  }\n\n  if (!openapiSpec.paths || Object.keys(openapiSpec.paths).length === 0) {\n    throw new Error(\"No paths found in the OpenAPI spec\")\n  }\n\n  Object.keys(openapiSpec.paths).forEach(path => {\n    if (!path.startsWith(\"/\")) {\n      throw new Error(`Path ${path} does not start with a slash; skipping`)\n    }\n  })\n\n  if (\n    Object.values(openapiSpec.paths).some((methods: any) =>\n      Object.values(methods).some((spec: any) => !spec.operationId)\n    )\n  ) {\n    throw new Error(\"Some methods are missing operationId\")\n  }\n\n  if (\n    Object.values(openapiSpec.paths).some((methods: any) =>\n      Object.values(methods).some(\n        (spec: any) => spec.requestBody && !spec.requestBody.content\n      )\n    )\n  ) {\n    throw new Error(\n      \"Some methods with a requestBody are missing requestBody.content\"\n    )\n  }\n\n  if (\n    Object.values(openapiSpec.paths).some((methods: any) =>\n      Object.values(methods).some((spec: any) => {\n        if (spec.requestBody?.content?.[\"application/json\"]?.schema) {\n          if (\n            !spec.requestBody.content[\"application/json\"].schema.properties ||\n            Object.keys(spec.requestBody.content[\"application/json\"].schema)\n              .length === 0\n          ) {\n            throw new Error(\n              `In context=('paths', '${Object.keys(methods)[0]}', '${\n                Object.keys(spec)[0]\n              }', 'requestBody', 'content', 'application/json', 'schema'), object schema missing properties`\n            )\n          }\n        }\n      })\n    )\n  ) {\n    throw new Error(\"Some object schemas are missing properties\")\n  }\n}\n\nexport const openapiToFunctions = async (\n  openapiSpec: any\n): Promise<OpenAPIData> => {\n  const functions: any[] = [] // Define a proper type for function objects\n  const routes: {\n    path: string\n    method: string\n    operationId: string\n    requestInBody?: boolean // Add a flag to indicate if the request should be in the body\n  }[] = []\n\n  for (const [path, methods] of Object.entries(openapiSpec.paths)) {\n    if (typeof methods !== \"object\" || methods === null) {\n      continue\n    }\n\n    for (const [method, specWithRef] of Object.entries(\n      methods as Record<string, any>\n    )) {\n      const spec: any = await $RefParser.dereference(specWithRef)\n      const functionName = spec.operationId\n      const desc = spec.description || spec.summary || \"\"\n\n      const schema: { type: string; properties: any; required?: string[] } = {\n        type: \"object\",\n        properties: {}\n      }\n\n      const reqBody = spec.requestBody?.content?.[\"application/json\"]?.schema\n      if (reqBody) {\n        schema.properties.requestBody = reqBody\n      }\n\n      const params = spec.parameters || []\n      if (params.length > 0) {\n        const paramProperties = params.reduce((acc: any, param: any) => {\n          if (param.schema) {\n            acc[param.name] = param.schema\n          }\n          return acc\n        }, {})\n\n        schema.properties.parameters = {\n          type: \"object\",\n          properties: paramProperties\n        }\n      }\n\n      functions.push({\n        type: \"function\",\n        function: {\n          name: functionName,\n          description: desc,\n          parameters: schema\n        }\n      })\n\n      // Determine if the request should be in the body based on the presence of requestBody\n      const requestInBody = !!spec.requestBody\n\n      routes.push({\n        path,\n        method,\n        operationId: functionName,\n        requestInBody // Include this flag in the route information\n      })\n    }\n  }\n\n  return {\n    info: {\n      title: openapiSpec.info.title,\n      description: openapiSpec.info.description,\n      server: openapiSpec.servers[0].url\n    },\n    routes,\n    functions\n  }\n}\n"
  },
  {
    "path": "lib/retrieval/processing/csv.ts",
    "content": "import { FileItemChunk } from \"@/types\"\nimport { encode } from \"gpt-tokenizer\"\nimport { CSVLoader } from \"langchain/document_loaders/fs/csv\"\nimport { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\"\nimport { CHUNK_OVERLAP, CHUNK_SIZE } from \".\"\n\nexport const processCSV = async (csv: Blob): Promise<FileItemChunk[]> => {\n  const loader = new CSVLoader(csv)\n  const docs = await loader.load()\n  let completeText = docs.map(doc => doc.pageContent).join(\"\\n\\n\")\n\n  const splitter = new RecursiveCharacterTextSplitter({\n    chunkSize: CHUNK_SIZE,\n    chunkOverlap: CHUNK_OVERLAP,\n    separators: [\"\\n\\n\"]\n  })\n  const splitDocs = await splitter.createDocuments([completeText])\n\n  let chunks: FileItemChunk[] = []\n\n  for (let i = 0; i < splitDocs.length; i++) {\n    const doc = splitDocs[i]\n\n    chunks.push({\n      content: doc.pageContent,\n      tokens: encode(doc.pageContent).length\n    })\n  }\n\n  return chunks\n}\n"
  },
  {
    "path": "lib/retrieval/processing/docx.ts",
    "content": "import { FileItemChunk } from \"@/types\"\nimport { encode } from \"gpt-tokenizer\"\nimport { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\"\nimport { CHUNK_OVERLAP, CHUNK_SIZE } from \".\"\n\nexport const processDocX = async (text: string): Promise<FileItemChunk[]> => {\n  const splitter = new RecursiveCharacterTextSplitter({\n    chunkSize: CHUNK_SIZE,\n    chunkOverlap: CHUNK_OVERLAP\n  })\n  const splitDocs = await splitter.createDocuments([text])\n\n  let chunks: FileItemChunk[] = []\n\n  for (let i = 0; i < splitDocs.length; i++) {\n    const doc = splitDocs[i]\n\n    chunks.push({\n      content: doc.pageContent,\n      tokens: encode(doc.pageContent).length\n    })\n  }\n\n  return chunks\n}\n"
  },
  {
    "path": "lib/retrieval/processing/index.ts",
    "content": "export * from \"./csv\"\nexport * from \"./docx\"\nexport * from \"./json\"\nexport * from \"./md\"\nexport * from \"./pdf\"\nexport * from \"./txt\"\n\nexport const CHUNK_SIZE = 4000\nexport const CHUNK_OVERLAP = 200\n"
  },
  {
    "path": "lib/retrieval/processing/json.ts",
    "content": "import { FileItemChunk } from \"@/types\"\nimport { encode } from \"gpt-tokenizer\"\nimport { JSONLoader } from \"langchain/document_loaders/fs/json\"\nimport { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\"\nimport { CHUNK_OVERLAP, CHUNK_SIZE } from \".\"\n\nexport const processJSON = async (json: Blob): Promise<FileItemChunk[]> => {\n  const loader = new JSONLoader(json)\n  const docs = await loader.load()\n  let completeText = docs.map(doc => doc.pageContent).join(\" \")\n\n  const splitter = new RecursiveCharacterTextSplitter({\n    chunkSize: CHUNK_SIZE,\n    chunkOverlap: CHUNK_OVERLAP\n  })\n  const splitDocs = await splitter.createDocuments([completeText])\n\n  let chunks: FileItemChunk[] = []\n\n  splitDocs.forEach(doc => {\n    const docTokens = encode(doc.pageContent).length\n  })\n\n  for (let i = 0; i < splitDocs.length; i++) {\n    const doc = splitDocs[i]\n\n    chunks.push({\n      content: doc.pageContent,\n      tokens: encode(doc.pageContent).length\n    })\n  }\n\n  return chunks\n}\n"
  },
  {
    "path": "lib/retrieval/processing/md.ts",
    "content": "import { FileItemChunk } from \"@/types\"\nimport { encode } from \"gpt-tokenizer\"\nimport { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\"\nimport { CHUNK_OVERLAP, CHUNK_SIZE } from \".\"\n\nexport const processMarkdown = async (\n  markdown: Blob\n): Promise<FileItemChunk[]> => {\n  const fileBuffer = Buffer.from(await markdown.arrayBuffer())\n  const textDecoder = new TextDecoder(\"utf-8\")\n  const textContent = textDecoder.decode(fileBuffer)\n\n  const splitter = RecursiveCharacterTextSplitter.fromLanguage(\"markdown\", {\n    chunkSize: CHUNK_SIZE,\n    chunkOverlap: CHUNK_OVERLAP\n  })\n\n  const splitDocs = await splitter.createDocuments([textContent])\n\n  let chunks: FileItemChunk[] = []\n\n  for (let i = 0; i < splitDocs.length; i++) {\n    const doc = splitDocs[i]\n\n    chunks.push({\n      content: doc.pageContent,\n      tokens: encode(doc.pageContent).length\n    })\n  }\n\n  return chunks\n}\n"
  },
  {
    "path": "lib/retrieval/processing/pdf.ts",
    "content": "import { FileItemChunk } from \"@/types\"\nimport { encode } from \"gpt-tokenizer\"\nimport { PDFLoader } from \"langchain/document_loaders/fs/pdf\"\nimport { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\"\nimport { CHUNK_OVERLAP, CHUNK_SIZE } from \".\"\n\nexport const processPdf = async (pdf: Blob): Promise<FileItemChunk[]> => {\n  const loader = new PDFLoader(pdf)\n  const docs = await loader.load()\n  let completeText = docs.map(doc => doc.pageContent).join(\" \")\n\n  const splitter = new RecursiveCharacterTextSplitter({\n    chunkSize: CHUNK_SIZE,\n    chunkOverlap: CHUNK_OVERLAP\n  })\n  const splitDocs = await splitter.createDocuments([completeText])\n\n  let chunks: FileItemChunk[] = []\n\n  for (let i = 0; i < splitDocs.length; i++) {\n    const doc = splitDocs[i]\n\n    chunks.push({\n      content: doc.pageContent,\n      tokens: encode(doc.pageContent).length\n    })\n  }\n\n  return chunks\n}\n"
  },
  {
    "path": "lib/retrieval/processing/txt.ts",
    "content": "import { FileItemChunk } from \"@/types\"\nimport { encode } from \"gpt-tokenizer\"\nimport { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\"\nimport { CHUNK_OVERLAP, CHUNK_SIZE } from \".\"\n\nexport const processTxt = async (txt: Blob): Promise<FileItemChunk[]> => {\n  const fileBuffer = Buffer.from(await txt.arrayBuffer())\n  const textDecoder = new TextDecoder(\"utf-8\")\n  const textContent = textDecoder.decode(fileBuffer)\n\n  const splitter = new RecursiveCharacterTextSplitter({\n    chunkSize: CHUNK_SIZE,\n    chunkOverlap: CHUNK_OVERLAP\n  })\n  const splitDocs = await splitter.createDocuments([textContent])\n\n  let chunks: FileItemChunk[] = []\n\n  for (let i = 0; i < splitDocs.length; i++) {\n    const doc = splitDocs[i]\n\n    chunks.push({\n      content: doc.pageContent,\n      tokens: encode(doc.pageContent).length\n    })\n  }\n\n  return chunks\n}\n"
  },
  {
    "path": "lib/server/server-chat-helpers.ts",
    "content": "import { Database, Tables } from \"@/supabase/types\"\nimport { VALID_ENV_KEYS } from \"@/types/valid-keys\"\nimport { createServerClient } from \"@supabase/ssr\"\nimport { cookies } from \"next/headers\"\n\nexport async function getServerProfile() {\n  const cookieStore = cookies()\n  const supabase = createServerClient<Database>(\n    process.env.NEXT_PUBLIC_SUPABASE_URL!,\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n    {\n      cookies: {\n        get(name: string) {\n          return cookieStore.get(name)?.value\n        }\n      }\n    }\n  )\n\n  const user = (await supabase.auth.getUser()).data.user\n  if (!user) {\n    throw new Error(\"User not found\")\n  }\n\n  const { data: profile } = await supabase\n    .from(\"profiles\")\n    .select(\"*\")\n    .eq(\"user_id\", user.id)\n    .single()\n\n  if (!profile) {\n    throw new Error(\"Profile not found\")\n  }\n\n  const profileWithKeys = addApiKeysToProfile(profile)\n\n  return profileWithKeys\n}\n\nfunction addApiKeysToProfile(profile: Tables<\"profiles\">) {\n  const apiKeys = {\n    [VALID_ENV_KEYS.OPENAI_API_KEY]: \"openai_api_key\",\n    [VALID_ENV_KEYS.ANTHROPIC_API_KEY]: \"anthropic_api_key\",\n    [VALID_ENV_KEYS.GOOGLE_GEMINI_API_KEY]: \"google_gemini_api_key\",\n    [VALID_ENV_KEYS.MISTRAL_API_KEY]: \"mistral_api_key\",\n    [VALID_ENV_KEYS.GROQ_API_KEY]: \"groq_api_key\",\n    [VALID_ENV_KEYS.PERPLEXITY_API_KEY]: \"perplexity_api_key\",\n    [VALID_ENV_KEYS.AZURE_OPENAI_API_KEY]: \"azure_openai_api_key\",\n    [VALID_ENV_KEYS.OPENROUTER_API_KEY]: \"openrouter_api_key\",\n\n    [VALID_ENV_KEYS.OPENAI_ORGANIZATION_ID]: \"openai_organization_id\",\n\n    [VALID_ENV_KEYS.AZURE_OPENAI_ENDPOINT]: \"azure_openai_endpoint\",\n    [VALID_ENV_KEYS.AZURE_GPT_35_TURBO_NAME]: \"azure_openai_35_turbo_id\",\n    [VALID_ENV_KEYS.AZURE_GPT_45_VISION_NAME]: \"azure_openai_45_vision_id\",\n    [VALID_ENV_KEYS.AZURE_GPT_45_TURBO_NAME]: \"azure_openai_45_turbo_id\",\n    [VALID_ENV_KEYS.AZURE_EMBEDDINGS_NAME]: \"azure_openai_embeddings_id\"\n  }\n\n  for (const [envKey, profileKey] of Object.entries(apiKeys)) {\n    if (process.env[envKey]) {\n      ;(profile as any)[profileKey] = process.env[envKey]\n    }\n  }\n\n  return profile\n}\n\nexport function checkApiKey(apiKey: string | null, keyName: string) {\n  if (apiKey === null || apiKey === \"\") {\n    throw new Error(`${keyName} API Key not found`)\n  }\n}\n"
  },
  {
    "path": "lib/server/server-utils.ts",
    "content": "export function createResponse(data: object, status: number): Response {\n  return new Response(JSON.stringify(data), {\n    status,\n    headers: {\n      \"Content-Type\": \"application/json\"\n    }\n  })\n}\n"
  },
  {
    "path": "lib/supabase/browser-client.ts",
    "content": "import { Database } from \"@/supabase/types\"\nimport { createBrowserClient } from \"@supabase/ssr\"\n\nexport const supabase = createBrowserClient<Database>(\n  process.env.NEXT_PUBLIC_SUPABASE_URL!,\n  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!\n)\n"
  },
  {
    "path": "lib/supabase/client.ts",
    "content": "import { createBrowserClient } from \"@supabase/ssr\"\n\nexport const createClient = () =>\n  createBrowserClient(\n    process.env.NEXT_PUBLIC_SUPABASE_URL!,\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!\n  )\n"
  },
  {
    "path": "lib/supabase/middleware.ts",
    "content": "import { createServerClient, type CookieOptions } from \"@supabase/ssr\"\nimport { NextResponse, type NextRequest } from \"next/server\"\n\nexport const createClient = (request: NextRequest) => {\n  // Create an unmodified response\n  let response = NextResponse.next({\n    request: {\n      headers: request.headers\n    }\n  })\n\n  const supabase = createServerClient(\n    process.env.NEXT_PUBLIC_SUPABASE_URL!,\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n    {\n      cookies: {\n        get(name: string) {\n          return request.cookies.get(name)?.value\n        },\n        set(name: string, value: string, options: CookieOptions) {\n          // If the cookie is updated, update the cookies for the request and response\n          request.cookies.set({\n            name,\n            value,\n            ...options\n          })\n          response = NextResponse.next({\n            request: {\n              headers: request.headers\n            }\n          })\n          response.cookies.set({\n            name,\n            value,\n            ...options\n          })\n        },\n        remove(name: string, options: CookieOptions) {\n          // If the cookie is removed, update the cookies for the request and response\n          request.cookies.set({\n            name,\n            value: \"\",\n            ...options\n          })\n          response = NextResponse.next({\n            request: {\n              headers: request.headers\n            }\n          })\n          response.cookies.set({\n            name,\n            value: \"\",\n            ...options\n          })\n        }\n      }\n    }\n  )\n\n  return { supabase, response }\n}\n"
  },
  {
    "path": "lib/supabase/server.ts",
    "content": "import { createServerClient, type CookieOptions } from \"@supabase/ssr\"\nimport { cookies } from \"next/headers\"\n\nexport const createClient = (cookieStore: ReturnType<typeof cookies>) => {\n  return createServerClient(\n    process.env.NEXT_PUBLIC_SUPABASE_URL!,\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n    {\n      cookies: {\n        get(name: string) {\n          return cookieStore.get(name)?.value\n        },\n        set(name: string, value: string, options: CookieOptions) {\n          try {\n            cookieStore.set({ name, value, ...options })\n          } catch (error) {\n            // The `set` method was called from a Server Component.\n            // This can be ignored if you have middleware refreshing\n            // user sessions.\n          }\n        },\n        remove(name: string, options: CookieOptions) {\n          try {\n            cookieStore.set({ name, value: \"\", ...options })\n          } catch (error) {\n            // The `delete` method was called from a Server Component.\n            // This can be ignored if you have middleware refreshing\n            // user sessions.\n          }\n        }\n      }\n    }\n  )\n}\n"
  },
  {
    "path": "lib/utils.ts",
    "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n\nexport function formatDate(input: string | number | Date): string {\n  const date = new Date(input)\n  return date.toLocaleDateString(\"en-US\", {\n    month: \"long\",\n    day: \"numeric\",\n    year: \"numeric\"\n  })\n}\n\nexport function getMediaTypeFromDataURL(dataURL: string): string | null {\n  const matches = dataURL.match(/^data:([A-Za-z-+\\/]+);base64/)\n  return matches ? matches[1] : null\n}\n\nexport function getBase64FromDataURL(dataURL: string): string | null {\n  const matches = dataURL.match(/^data:[A-Za-z-+\\/]+;base64,(.*)$/)\n  return matches ? matches[1] : null\n}\n"
  },
  {
    "path": "license",
    "content": "MIT License\n\nCopyright (c) 2024 Mckay Wrigley\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "middleware.ts",
    "content": "import { createClient } from \"@/lib/supabase/middleware\"\nimport { i18nRouter } from \"next-i18n-router\"\nimport { NextResponse, type NextRequest } from \"next/server\"\nimport i18nConfig from \"./i18nConfig\"\n\nexport async function middleware(request: NextRequest) {\n  const i18nResult = i18nRouter(request, i18nConfig)\n  if (i18nResult) return i18nResult\n\n  try {\n    const { supabase, response } = createClient(request)\n\n    const session = await supabase.auth.getSession()\n\n    const redirectToChat = session && request.nextUrl.pathname === \"/\"\n\n    if (redirectToChat) {\n      const { data: homeWorkspace, error } = await supabase\n        .from(\"workspaces\")\n        .select(\"*\")\n        .eq(\"user_id\", session.data.session?.user.id)\n        .eq(\"is_home\", true)\n        .single()\n\n      if (!homeWorkspace) {\n        throw new Error(error?.message)\n      }\n\n      return NextResponse.redirect(\n        new URL(`/${homeWorkspace.id}/chat`, request.url)\n      )\n    }\n\n    return response\n  } catch (e) {\n    return NextResponse.next({\n      request: {\n        headers: request.headers\n      }\n    })\n  }\n}\n\nexport const config = {\n  matcher: \"/((?!api|static|.*\\\\..*|_next|auth).*)\"\n}\n"
  },
  {
    "path": "next.config.js",
    "content": "const withBundleAnalyzer = require(\"@next/bundle-analyzer\")({\n  enabled: process.env.ANALYZE === \"true\"\n})\n\nconst withPWA = require(\"next-pwa\")({\n  dest: \"public\"\n})\n\nmodule.exports = withBundleAnalyzer(\n  withPWA({\n    reactStrictMode: true,\n    images: {\n      remotePatterns: [\n        {\n          protocol: \"http\",\n          hostname: \"localhost\"\n        },\n        {\n          protocol: \"http\",\n          hostname: \"127.0.0.1\"\n        },\n        {\n          protocol: \"https\",\n          hostname: \"**\"\n        }\n      ]\n    },\n    experimental: {\n      serverComponentsExternalPackages: [\"sharp\", \"onnxruntime-node\"]\n    }\n  })\n)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"chatbot-ui\",\n  \"version\": \"2.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"chat\": \"supabase start && npm run db-types && npm run dev\",\n    \"restart\": \"supabase stop && npm run chat\",\n    \"update\": \"git pull origin main && npm run db-migrate && npm run db-types\",\n    \"prepare\": \"husky install\",\n    \"clean\": \"npm run lint:fix && npm run format:write\",\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\",\n    \"lint:fix\": \"next lint --fix\",\n    \"analyze\": \"ANALYZE=true npm run build\",\n    \"preview\": \"next build && next start\",\n    \"type-check\": \"tsc --noEmit\",\n    \"format:write\": \"prettier --write \\\"{app,lib,db,components,context,types}/**/*.{ts,tsx}\\\" --cache\",\n    \"format:check\": \"prettier --check \\\"{app,lib,db,components,context,types}**/*.{ts,tsx}\\\" --cache\",\n    \"db-reset\": \"supabase db reset && npm run db-types\",\n    \"db-migrate\": \"supabase migration up && npm run db-types\",\n    \"db-types\": \"supabase gen types typescript --local > supabase/types.ts\",\n    \"db-pull\": \"supabase db remote commit\",\n    \"db-push\": \"supabase db push\",\n    \"test\": \"jest\"\n  },\n  \"dependencies\": {\n    \"@anthropic-ai/sdk\": \"^0.18.0\",\n    \"@apidevtools/json-schema-ref-parser\": \"^11.1.0\",\n    \"@azure/openai\": \"^1.0.0-beta.8\",\n    \"@google/generative-ai\": \"^0.11.4\",\n    \"@hookform/resolvers\": \"^3.3.2\",\n    \"@mistralai/mistralai\": \"^0.0.8\",\n    \"@radix-ui/react-accordion\": \"^1.1.2\",\n    \"@radix-ui/react-alert-dialog\": \"^1.0.5\",\n    \"@radix-ui/react-aspect-ratio\": \"^1.0.3\",\n    \"@radix-ui/react-avatar\": \"^1.0.4\",\n    \"@radix-ui/react-checkbox\": \"^1.0.4\",\n    \"@radix-ui/react-collapsible\": \"^1.0.3\",\n    \"@radix-ui/react-context-menu\": \"^2.1.5\",\n    \"@radix-ui/react-dialog\": \"^1.0.5\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.0.6\",\n    \"@radix-ui/react-hover-card\": \"^1.0.7\",\n    \"@radix-ui/react-label\": \"^2.0.2\",\n    \"@radix-ui/react-menubar\": \"^1.0.4\",\n    \"@radix-ui/react-navigation-menu\": \"^1.1.4\",\n    \"@radix-ui/react-popover\": \"^1.0.7\",\n    \"@radix-ui/react-progress\": \"^1.0.3\",\n    \"@radix-ui/react-radio-group\": \"^1.1.3\",\n    \"@radix-ui/react-scroll-area\": \"^1.0.5\",\n    \"@radix-ui/react-select\": \"^2.0.0\",\n    \"@radix-ui/react-separator\": \"^1.0.3\",\n    \"@radix-ui/react-slider\": \"^1.1.2\",\n    \"@radix-ui/react-slot\": \"^1.0.2\",\n    \"@radix-ui/react-switch\": \"^1.0.3\",\n    \"@radix-ui/react-tabs\": \"^1.0.4\",\n    \"@radix-ui/react-toast\": \"^1.1.5\",\n    \"@radix-ui/react-toggle\": \"^1.0.3\",\n    \"@radix-ui/react-toggle-group\": \"^1.0.4\",\n    \"@radix-ui/react-tooltip\": \"^1.0.7\",\n    \"@supabase/ssr\": \"^0.0.10\",\n    \"@supabase/supabase-js\": \"^2.38.4\",\n    \"@tabler/icons-react\": \"^2.40.0\",\n    \"@vercel/analytics\": \"^1.1.1\",\n    \"@vercel/edge-config\": \"^0.4.1\",\n    \"@xenova/transformers\": \"^2.13.4\",\n    \"ai\": \"^2.2.31\",\n    \"class-variance-authority\": \"^0.7.0\",\n    \"cmdk\": \"^0.2.0\",\n    \"d3-dsv\": \"^2.0.0\",\n    \"date-fns\": \"^2.30.0\",\n    \"endent\": \"^2.1.0\",\n    \"gpt-tokenizer\": \"^2.1.2\",\n    \"i18next\": \"^23.7.16\",\n    \"i18next-resources-to-backend\": \"^1.2.0\",\n    \"langchain\": \"^0.0.213\",\n    \"lucide-react\": \"^0.292.0\",\n    \"mammoth\": \"^1.6.0\",\n    \"next\": \"^14.1.0\",\n    \"next-i18n-router\": \"^5.2.0\",\n    \"next-pwa\": \"5.6.0\",\n    \"next-themes\": \"^0.2.1\",\n    \"openai\": \"^4.23.0\",\n    \"pdf-parse\": \"^1.1.1\",\n    \"react\": \"^18\",\n    \"react-day-picker\": \"^8.9.1\",\n    \"react-dom\": \"^18\",\n    \"react-hook-form\": \"^7.48.2\",\n    \"react-i18next\": \"^14.0.0\",\n    \"react-markdown\": \"^9.0.1\",\n    \"react-syntax-highlighter\": \"^15.5.0\",\n    \"react-textarea-autosize\": \"^8.5.3\",\n    \"remark-gfm\": \"^4.0.0\",\n    \"remark-math\": \"^6.0.0\",\n    \"sonner\": \"^1.3.1\",\n    \"uuid\": \"^9.0.1\",\n    \"zod\": \"^3.22.4\"\n  },\n  \"devDependencies\": {\n    \"@next/bundle-analyzer\": \"^14.0.2\",\n    \"@tailwindcss/typography\": \"^0.5.10\",\n    \"@testing-library/jest-dom\": \"^6.2.0\",\n    \"@testing-library/react\": \"^14.1.2\",\n    \"@types/jest\": \"^29.5.11\",\n    \"@types/node\": \"^20\",\n    \"@types/pdf-parse\": \"^1.1.4\",\n    \"@types/react\": \"^18\",\n    \"@types/react-dom\": \"^18\",\n    \"@types/react-syntax-highlighter\": \"^15.5.11\",\n    \"@types/uuid\": \"^9.0.7\",\n    \"autoprefixer\": \"^10.4.16\",\n    \"clsx\": \"^2.0.0\",\n    \"eslint\": \"^8\",\n    \"eslint-config-next\": \"^14.0.2\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-plugin-tailwindcss\": \"^3.13.0\",\n    \"husky\": \"^8.0.3\",\n    \"jest\": \"^29.7.0\",\n    \"jest-environment-jsdom\": \"^29.7.0\",\n    \"postcss\": \"^8.4.31\",\n    \"prettier\": \"^3.1.0\",\n    \"tailwind-merge\": \"^2.0.0\",\n    \"tailwindcss\": \"^3.3.5\",\n    \"tailwindcss-animate\": \"^1.0.7\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "prettier.config.cjs",
    "content": "/** @type {import('prettier').Config} */\nmodule.exports = {\n  endOfLine: 'lf',\n  semi: false,\n  useTabs: false,\n  singleQuote: false,\n  arrowParens: 'avoid',\n  tabWidth: 2,\n  trailingComma: 'none',\n  importOrder: [\n    '^.+\\\\.scss$',\n    '^.+\\\\.css$',\n    '^(react/(.*)$)|^(react$)',\n    '^(next/(.*)$)|^(next$)',\n    '<THIRD_PARTY_MODULES>',\n    '',\n    '^types$',\n    '^@/types/(.*)$',\n    '^@/config/(.*)$',\n    '^@/lib/(.*)$',\n    '^@/hooks/(.*)$',\n    '^@/components/ui/(.*)$',\n    '^@/components/(.*)$',\n    '^@/registry/(.*)$',\n    '^@/styles/(.*)$',\n    '^@/app/(.*)$',\n    '',\n    '^[./]'\n  ],\n  importOrderSeparation: false,\n  importOrderSortSpecifiers: true,\n  importOrderBuiltinModulesToTop: true,\n  importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'],\n  importOrderMergeDuplicateImports: true,\n  importOrderCombineTypeAndValueImports: true\n}\n"
  },
  {
    "path": "public/locales/de/translation.json",
    "content": "{\n  \"Ask anything. Type \\\"/\\\" for prompts, \\\"@\\\" for files, and \\\"#\\\" for tools.\": \"Ask anything. Type \\\"/\\\" for prompts, \\\"@\\\" for files, and \\\"#\\\" for tools.\",\n  \"Quick Settings\": \"Quick Settings\"\n}\n"
  },
  {
    "path": "public/locales/en/translation.json",
    "content": "{\n  \"Ask anything. Type \\\"/\\\" for prompts, \\\"@\\\" for files, and \\\"#\\\" for tools.\": \"Ask anything. Type \\\"/\\\" for prompts, \\\"@\\\" for files, and \\\"#\\\" for tools.\"\n}\n"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n  \"short_name\": \"Chatbot UI\",\n  \"name\": \"Chatbot UI\",\n  \"icons\": [\n    {\n      \"src\": \"/icon-192x192.png\",\n      \"type\": \"image/ico\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"/icon-256x256.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"256x256\"\n    },\n    {\n      \"src\": \"/icon-512x512.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\":\"maskable\"\n    }\n  ],\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#000000\"\n}\n"
  },
  {
    "path": "public/worker-development.js",
    "content": "/******/ (() => { // webpackBootstrap\nvar __webpack_exports__ = {};\nself.__WB_DISABLE_DEV_LOGS = true;\n/******/ })()\n;"
  },
  {
    "path": "supabase/.gitignore",
    "content": "# Supabase\n.branches\n.temp\n"
  },
  {
    "path": "supabase/config.toml",
    "content": "# A string used to distinguish different Supabase projects on the same host. Defaults to the\n# working directory name when running `supabase init`.\nproject_id = \"chatbotui\"\n\n[api]\n# Port to use for the API URL.\nport = 54321\n# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API\n# endpoints. public and storage are always included.\nschemas = [\"public\", \"storage\", \"graphql_public\"]\n# Extra schemas to add to the search_path of every request. public is always included.\nextra_search_path = [\"public\", \"extensions\"]\n# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size\n# for accidental or malicious requests.\nmax_rows = 1000\n\n[db]\n# Port to use for the local database URL.\nport = 54322\n# Port used by db diff command to initialise the shadow database.\nshadow_port = 54320\n# The database major version to use. This has to be the same as your remote database's. Run `SHOW\n# server_version;` on the remote database to check.\nmajor_version = 15\n\n[db.pooler]\nenabled = false\nport = 54329\npool_mode = \"transaction\"\ndefault_pool_size = 20\nmax_client_conn = 100\n\n[studio]\nenabled = true\n# Port to use for Supabase Studio.\nport = 54323\n# External URL of the API server that frontend connects to.\napi_url = \"http://localhost\"\n\n# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they\n# are monitored, and you can view the emails that would have been sent from the web interface.\n[inbucket]\nenabled = true\n# Port to use for the email testing server web interface.\nport = 54324\n# Uncomment to expose additional ports for testing user applications that send emails.\n# smtp_port = 54325\n# pop3_port = 54326\n\n[storage]\n# The maximum file size allowed (e.g. \"5MB\", \"500KB\").\nfile_size_limit = \"50MiB\"\n\n[auth]\n# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used\n# in emails.\nsite_url = \"http://localhost:3000\"\n# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.\nadditional_redirect_urls = [\"https://localhost:3000\"]\n# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).\njwt_expiry = 604800\n# If disabled, the refresh token will never expire.\nenable_refresh_token_rotation = true\n# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.\n# Requires enable_refresh_token_rotation = true.\nrefresh_token_reuse_interval = 10\n# Allow/disallow new user signups to your project.\nenable_signup = true\n\n[auth.email]\n# Allow/disallow new user signups via email to your project.\nenable_signup = true\n# If enabled, a user will be required to confirm any email change on both the old, and new email\n# addresses. If disabled, only the new email is required to confirm.\ndouble_confirm_changes = true\n# If enabled, users need to confirm their email address before signing in.\nenable_confirmations = false\n\n# Uncomment to customize email template\n# [auth.email.template.invite]\n# subject = \"You have been invited\"\n# content_path = \"./supabase/templates/invite.html\"\n\n[auth.sms]\n# Allow/disallow new user signups via SMS to your project.\nenable_signup = true\n# If enabled, users need to confirm their phone number before signing in.\nenable_confirmations = false\n\n# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.\n[auth.sms.twilio]\nenabled = false\naccount_sid = \"\"\nmessage_service_sid = \"\"\n# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:\nauth_token = \"env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)\"\n\n# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,\n# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`,\n# `twitter`, `slack`, `spotify`, `workos`, `zoom`.\n[auth.external.apple]\nenabled = false\nclient_id = \"\"\n# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:\nsecret = \"env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)\"\n# Overrides the default auth redirectUrl.\nredirect_uri = \"\"\n# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,\n# or any other third-party OIDC providers.\nurl = \"\"\n\n[analytics]\nenabled = false\nport = 54327\nvector_port = 54328\n# Configure one of the supported backends: `postgres`, `bigquery`\nbackend = \"postgres\"\n"
  },
  {
    "path": "supabase/migrations/20240108234540_setup.sql",
    "content": "-- Enable HTTP extension\ncreate extension http with schema extensions;\n\n-- Enable vector extension\ncreate extension vector with schema extensions;\n\n-- Function to update modified column\nCREATE OR REPLACE FUNCTION update_updated_at_column()\nRETURNS TRIGGER AS $$\nBEGIN\n    NEW.updated_at = now(); \n    RETURN NEW; \nEND;\n$$ language 'plpgsql';\n\n-- Function to delete a message and all following messages\nCREATE OR REPLACE FUNCTION delete_message_including_and_after(\n    p_user_id UUID, \n    p_chat_id UUID, \n    p_sequence_number INT\n)\nRETURNS VOID AS $$\nBEGIN\n    DELETE FROM messages \n    WHERE user_id = p_user_id AND chat_id = p_chat_id AND sequence_number >= p_sequence_number;\nEND;\n$$ LANGUAGE plpgsql;\n\n-- Function to create duplicate messages for a new chat\nCREATE OR REPLACE FUNCTION create_duplicate_messages_for_new_chat(old_chat_id UUID, new_chat_id UUID, new_user_id UUID)\nRETURNS VOID AS $$\nBEGIN\n    INSERT INTO messages (user_id, chat_id, content, role, model, sequence_number, tokens, created_at, updated_at)\n    SELECT new_user_id, new_chat_id, content, role, model, sequence_number, tokens, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP\n    FROM messages\n    WHERE chat_id = old_chat_id;\nEND;\n$$ LANGUAGE plpgsql;\n\n-- Policy to allow users to read their own files\nCREATE POLICY \"Allow users to read their own files\"\nON storage.objects FOR SELECT\nTO authenticated\nUSING (auth.uid()::text = owner_id::text);\n\n-- Function to delete a storage object\nCREATE OR REPLACE FUNCTION delete_storage_object(bucket TEXT, object TEXT, OUT status INT, OUT content TEXT)\nRETURNS RECORD\nLANGUAGE 'plpgsql'\nSECURITY DEFINER\nAS $$\nDECLARE\n  project_url TEXT := 'http://supabase_kong_chatbotui:8000';\n  service_role_key TEXT := 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU'; -- full access needed for http request to storage\n  url TEXT := project_url || '/storage/v1/object/' || bucket || '/' || object;\nBEGIN\n  SELECT\n      INTO status, content\n           result.status::INT, result.content::TEXT\n      FROM extensions.http((\n    'DELETE',\n    url,\n    ARRAY[extensions.http_header('authorization','Bearer ' || service_role_key)],\n    NULL,\n    NULL)::extensions.http_request) AS result;\nEND;\n$$;\n\n-- Function to delete a storage object from a bucket\nCREATE OR REPLACE FUNCTION delete_storage_object_from_bucket(bucket_name TEXT, object_path TEXT, OUT status INT, OUT content TEXT)\nRETURNS RECORD\nLANGUAGE 'plpgsql'\nSECURITY DEFINER\nAS $$\nBEGIN\n  SELECT\n      INTO status, content\n           result.status, result.content\n      FROM public.delete_storage_object(bucket_name, object_path) AS result;\nEND;\n$$;"
  },
  {
    "path": "supabase/migrations/20240108234541_add_profiles.sql",
    "content": "--------------- PROFILES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS profiles (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- RELATIONSHIPS\n    user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- REQUIRED\n    bio TEXT NOT NULL CHECK (char_length(bio) <= 500),\n    has_onboarded BOOLEAN NOT NULL DEFAULT FALSE,\n    image_url TEXT NOT NULL CHECK (char_length(image_url) <= 1000), -- public file url in profile_images bucket\n    image_path TEXT NOT NULL CHECK (char_length(image_path) <= 1000), -- file path in profile_images bucket\n    profile_context TEXT NOT NULL CHECK (char_length(profile_context) <= 1500),\n    display_name TEXT NOT NULL CHECK (char_length(display_name) <= 100),\n    use_azure_openai BOOLEAN NOT NULL,\n    username TEXT NOT NULL UNIQUE CHECK (char_length(username) >= 3 AND char_length(username) <= 25),\n\n    -- OPTIONAL\n    anthropic_api_key TEXT CHECK (char_length(anthropic_api_key) <= 1000),\n    azure_openai_35_turbo_id TEXT CHECK (char_length(azure_openai_35_turbo_id) <= 1000),\n    azure_openai_45_turbo_id TEXT CHECK (char_length(azure_openai_45_turbo_id) <= 1000),\n    azure_openai_45_vision_id TEXT CHECK (char_length(azure_openai_45_vision_id) <= 1000),\n    azure_openai_api_key TEXT CHECK (char_length(azure_openai_api_key) <= 1000),\n    azure_openai_endpoint TEXT CHECK (char_length(azure_openai_endpoint) <= 1000),\n    google_gemini_api_key TEXT CHECK (char_length(google_gemini_api_key) <= 1000),\n    mistral_api_key TEXT CHECK (char_length(mistral_api_key) <= 1000),\n    openai_api_key TEXT CHECK (char_length(openai_api_key) <= 1000),\n    openai_organization_id TEXT CHECK (char_length(openai_organization_id) <= 1000),\n    perplexity_api_key TEXT CHECK (char_length(perplexity_api_key) <= 1000)\n);\n\n-- INDEXES --\n\nCREATE INDEX idx_profiles_user_id ON profiles (user_id);\n\n-- RLS --\n\nALTER TABLE profiles ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own profiles\"\n    ON profiles\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- FUNCTIONS --\n\nCREATE OR REPLACE FUNCTION delete_old_profile_image()\nRETURNS TRIGGER\nLANGUAGE 'plpgsql'\nSECURITY DEFINER\nAS $$\nDECLARE\n  status INT;\n  content TEXT;\nBEGIN\n  IF TG_OP = 'DELETE' THEN\n    SELECT\n      INTO status, content\n      result.status, result.content\n      FROM public.delete_storage_object_from_bucket('profile_images', OLD.image_path) AS result;\n    IF status <> 200 THEN\n      RAISE WARNING 'Could not delete profile image: % %', status, content;\n    END IF;\n  END IF;\n  IF TG_OP = 'DELETE' THEN\n    RETURN OLD;\n  END IF;\n  RETURN NEW;\nEND;\n$$;\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_profiles_updated_at\nBEFORE UPDATE ON profiles\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();\n\nCREATE OR REPLACE FUNCTION create_profile_and_workspace() \nRETURNS TRIGGER\nsecurity definer set search_path = public\nAS $$\nDECLARE\n    random_username TEXT;\nBEGIN\n    -- Generate a random username\n    random_username := 'user' || substr(replace(gen_random_uuid()::text, '-', ''), 1, 16);\n\n    -- Create a profile for the new user\n    INSERT INTO public.profiles(user_id, anthropic_api_key, azure_openai_35_turbo_id, azure_openai_45_turbo_id, azure_openai_45_vision_id, azure_openai_api_key, azure_openai_endpoint, google_gemini_api_key, has_onboarded, image_url, image_path, mistral_api_key, display_name, bio, openai_api_key, openai_organization_id, perplexity_api_key, profile_context, use_azure_openai, username)\n    VALUES(\n        NEW.id,\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        FALSE,\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        FALSE,\n        random_username\n    );\n\n    -- Create the home workspace for the new user\n    INSERT INTO public.workspaces(user_id, is_home, name, default_context_length, default_model, default_prompt, default_temperature, description, embeddings_provider, include_profile_context, include_workspace_instructions, instructions)\n    VALUES(\n        NEW.id,\n        TRUE,\n        'Home',\n        4096,\n        'gpt-4-1106-preview',\n        'You are a friendly, helpful AI assistant.',\n        0.5,\n        'My home workspace.',\n        'openai',\n        TRUE,\n        TRUE,\n        ''\n    );\n\n    RETURN NEW;\nEND;\n$$ language 'plpgsql';\n\nCREATE TRIGGER create_profile_and_workspace_trigger\nAFTER INSERT ON auth.users\nFOR EACH ROW\nEXECUTE PROCEDURE public.create_profile_and_workspace();\n\nCREATE TRIGGER delete_old_profile_image\nAFTER DELETE ON profiles\nFOR EACH ROW\nEXECUTE PROCEDURE delete_old_profile_image();\n\n-- STORAGE --\n\nINSERT INTO storage.buckets (id, name, public) VALUES ('profile_images', 'profile_images', true);\n\nCREATE POLICY \"Allow public read access on profile images\"\n    ON storage.objects FOR SELECT\n    USING (bucket_id = 'profile_images');\n\nCREATE POLICY \"Allow authenticated insert access to own profile images\"\n    ON storage.objects FOR INSERT TO authenticated\n    WITH CHECK (bucket_id = 'profile_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow authenticated update access to own profile images\"\n    ON storage.objects FOR UPDATE TO authenticated\n    USING (bucket_id = 'profile_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow authenticated delete access to own profile images\"\n    ON storage.objects FOR DELETE TO authenticated\n    USING (bucket_id = 'profile_images' AND (storage.foldername(name))[1] = auth.uid()::text);"
  },
  {
    "path": "supabase/migrations/20240108234542_add_workspaces.sql",
    "content": "--------------- WORKSPACES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS workspaces (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- SHARING\n    sharing TEXT NOT NULL DEFAULT 'private',\n\n    -- REQUIRED\n    default_context_length INTEGER NOT NULL,\n    default_model TEXT NOT NULL CHECK (char_length(default_model) <= 1000),\n    default_prompt TEXT NOT NULL CHECK (char_length(default_prompt) <= 100000),\n    default_temperature REAL NOT NULL,\n    description TEXT NOT NULL CHECK (char_length(description) <= 500),\n    embeddings_provider TEXT NOT NULL CHECK (char_length(embeddings_provider) <= 1000),\n    include_profile_context BOOLEAN NOT NULL,\n    include_workspace_instructions BOOLEAN NOT NULL,\n    instructions TEXT NOT NULL CHECK (char_length(instructions) <= 1500),\n    is_home BOOLEAN NOT NULL DEFAULT FALSE,\n    name TEXT NOT NULL CHECK (char_length(name) <= 100)\n);\n\n-- INDEXES --\n\nCREATE INDEX idx_workspaces_user_id ON workspaces (user_id);\n\n-- RLS --\n\nALTER TABLE workspaces ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own workspaces\"\n    ON workspaces\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private workspaces\"\n    ON workspaces\n    FOR SELECT\n    USING (sharing <> 'private');\n\n-- FUNCTIONS --\n\nCREATE OR REPLACE FUNCTION prevent_home_field_update()\nRETURNS TRIGGER AS $$\nBEGIN\n  IF NEW.is_home IS DISTINCT FROM OLD.is_home THEN\n    RAISE EXCEPTION 'Updating the home field of workspace is not allowed.';\n  END IF;\n  \n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_workspaces_updated_at\nBEFORE UPDATE ON workspaces\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();\n\nCREATE OR REPLACE FUNCTION prevent_home_workspace_deletion()\nRETURNS TRIGGER AS $$\nBEGIN\n  IF OLD.is_home THEN\n    RAISE EXCEPTION 'Home workspace deletion is not allowed.';\n  END IF;\n  \n  RETURN OLD;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER prevent_update_of_home_field\nBEFORE UPDATE ON workspaces\nFOR EACH ROW\nEXECUTE PROCEDURE prevent_home_field_update();\n\n-- INDEXES --\n\nCREATE UNIQUE INDEX idx_unique_home_workspace_per_user \nON workspaces(user_id) \nWHERE is_home;"
  },
  {
    "path": "supabase/migrations/20240108234543_add_folders.sql",
    "content": "--------------- FOLDERS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS folders (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- REQUIRED\n    name TEXT NOT NULL,\n    description TEXT NOT NULL,\n    type TEXT NOT NULL\n);\n\n-- INDEXES --\n\nCREATE INDEX folders_user_id_idx ON folders(user_id);\nCREATE INDEX folders_workspace_id_idx ON folders(workspace_id);\n\n-- RLS --\n\nALTER TABLE folders ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own folders\"\n    ON folders\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_folders_updated_at\nBEFORE UPDATE ON folders\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();"
  },
  {
    "path": "supabase/migrations/20240108234544_add_files.sql",
    "content": "--------------- FILES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS files (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- OPTIONAL RELATIONSHIPS\n    folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- SHARING\n    sharing TEXT NOT NULL DEFAULT 'private',\n\n    -- REQUIRED\n    description TEXT NOT NULL CHECK (char_length(description) <= 500),\n    file_path TEXT NOT NULL CHECK (char_length(file_path) <= 1000),\n    name TEXT NOT NULL CHECK (char_length(name) <= 100),\n    size INT NOT NULL,\n    tokens INT NOT NULL,\n    type TEXT NOT NULL CHECK (char_length(type) <= 100)\n);\n\n-- INDEXES --\n\nCREATE INDEX files_user_id_idx ON files(user_id);\n\n-- RLS --\n\nALTER TABLE files ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own files\"\n    ON files\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private files\"\n    ON files\n    FOR SELECT\n    USING (sharing <> 'private');\n\n-- FUNCTIONS --\n\nCREATE OR REPLACE FUNCTION delete_old_file()\nRETURNS TRIGGER\nLANGUAGE 'plpgsql'\nSECURITY DEFINER\nAS $$\nDECLARE\n  status INT;\n  content TEXT;\nBEGIN\n  IF TG_OP = 'DELETE' THEN\n    SELECT\n      INTO status, content\n      result.status, result.content\n      FROM public.delete_storage_object_from_bucket('files', OLD.file_path) AS result;\n    IF status <> 200 THEN\n      RAISE WARNING 'Could not delete file: % %', status, content;\n    END IF;\n  END IF;\n  IF TG_OP = 'DELETE' THEN\n    RETURN OLD;\n  END IF;\n  RETURN NEW;\nEND;\n$$;\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_files_updated_at\nBEFORE UPDATE ON files\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();\n\nCREATE TRIGGER delete_old_file\nBEFORE DELETE ON files\nFOR EACH ROW\nEXECUTE PROCEDURE delete_old_file();\n\n-- STORAGE --\n\nINSERT INTO storage.buckets (id, name, public) VALUES ('files', 'files', false);\n\nCREATE OR REPLACE FUNCTION public.non_private_file_exists(p_name text)\nRETURNS boolean\nLANGUAGE sql\nSECURITY DEFINER\nAS $$\n    SELECT EXISTS (\n        SELECT 1\n        FROM files\n        WHERE (id::text = (storage.foldername(p_name))[2]) AND sharing <> 'private'\n    );\n$$;\n\nCREATE POLICY \"Allow public read access on non-private files\"\n    ON storage.objects FOR SELECT TO public\n    USING (bucket_id = 'files' AND public.non_private_file_exists(name));\n\nCREATE POLICY \"Allow authenticated insert access to own file\"\n    ON storage.objects FOR INSERT TO authenticated\n    WITH CHECK (bucket_id = 'files' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow authenticated update access to own file\"\n    ON storage.objects FOR UPDATE TO authenticated\n    USING (bucket_id = 'files' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow authenticated delete access to own file\"\n    ON storage.objects FOR DELETE TO authenticated\n    USING (bucket_id = 'files' AND (storage.foldername(name))[1] = auth.uid()::text);\n\n--------------- FILE WORKSPACES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS file_workspaces (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    file_id UUID NOT NULL REFERENCES files(id) ON DELETE CASCADE,\n    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(file_id, workspace_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX file_workspaces_user_id_idx ON file_workspaces(user_id);\nCREATE INDEX file_workspaces_file_id_idx ON file_workspaces(file_id);\nCREATE INDEX file_workspaces_workspace_id_idx ON file_workspaces(workspace_id);\n\n-- RLS --\n\nALTER TABLE file_workspaces ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own file_workspaces\"\n    ON file_workspaces\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_file_workspaces_updated_at\nBEFORE UPDATE ON file_workspaces \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();"
  },
  {
    "path": "supabase/migrations/20240108234545_add_file_items.sql",
    "content": "--------------- FILE ITEMS ---------------\n\ncreate table file_items (\n  -- ID\n  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n  -- RELATIONSHIPS\n  file_id UUID NOT NULL REFERENCES files(id) ON DELETE CASCADE,\n  user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n  -- METADATA\n  created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  updated_at TIMESTAMPTZ,\n\n  -- SHARING\n  sharing TEXT NOT NULL DEFAULT 'private',\n\n  -- REQUIRED\n  content TEXT NOT NULL,\n  local_embedding vector(384), -- 384 works for local w/ Xenova/all-MiniLM-L6-v2\n  openai_embedding vector(1536), -- 1536 for OpenAI\n  tokens INT NOT NULL\n);\n\n-- INDEXES --\n\nCREATE INDEX file_items_file_id_idx ON file_items(file_id);\n\nCREATE INDEX file_items_embedding_idx ON file_items\n  USING hnsw (openai_embedding vector_cosine_ops);\n\nCREATE INDEX file_items_local_embedding_idx ON file_items\n  USING hnsw (local_embedding vector_cosine_ops);\n\n-- RLS\n\nALTER TABLE file_items ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own file items\"\n    ON file_items\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private file items\"\n    ON file_items\n    FOR SELECT\n    USING (file_id IN (\n        SELECT id FROM files WHERE sharing <> 'private'\n    ));\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_profiles_updated_at\nBEFORE UPDATE ON file_items\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();\n\n-- FUNCTIONS --\n\ncreate function match_file_items_local (\n  query_embedding vector(384),\n  match_count int DEFAULT null,\n  file_ids UUID[] DEFAULT null\n) returns table (\n  id UUID,\n  file_id UUID,\n  content TEXT,\n  tokens INT,\n  similarity float\n)\nlanguage plpgsql\nas $$\n#variable_conflict use_column\nbegin\n  return query\n  select\n    id,\n    file_id,\n    content,\n    tokens,\n    1 - (file_items.local_embedding <=> query_embedding) as similarity\n  from file_items\n  where (file_id = ANY(file_ids))\n  order by file_items.local_embedding <=> query_embedding\n  limit match_count;\nend;\n$$;\n\ncreate function match_file_items_openai (\n  query_embedding vector(1536),\n  match_count int DEFAULT null,\n  file_ids UUID[] DEFAULT null\n) returns table (\n  id UUID,\n  file_id UUID,\n  content TEXT,\n  tokens INT,\n  similarity float\n)\nlanguage plpgsql\nas $$\n#variable_conflict use_column\nbegin\n  return query\n  select\n    id,\n    file_id,\n    content,\n    tokens,\n    1 - (file_items.openai_embedding <=> query_embedding) as similarity\n  from file_items\n  where (file_id = ANY(file_ids))\n  order by file_items.openai_embedding <=> query_embedding\n  limit match_count;\nend;\n$$;"
  },
  {
    "path": "supabase/migrations/20240108234546_add_presets.sql",
    "content": "--------------- PRESETS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS presets (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- OPTIONAL RELATIONSHIPS\n    folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- SHARING\n    sharing TEXT NOT NULL DEFAULT 'private',\n\n    -- REQUIRED\n    context_length INT NOT NULL,\n    description TEXT NOT NULL CHECK (char_length(description) <= 500),\n    embeddings_provider TEXT NOT NULL CHECK (char_length(embeddings_provider) <= 1000),\n    include_profile_context BOOLEAN NOT NULL,\n    include_workspace_instructions BOOLEAN NOT NULL,\n    model TEXT NOT NULL CHECK (char_length(model) <= 1000),\n    name TEXT NOT NULL CHECK (char_length(name) <= 100),\n    prompt TEXT NOT NULL CHECK (char_length(prompt) <= 100000),\n    temperature REAL NOT NULL\n);\n\n-- INDEXES --\n\nCREATE INDEX presets_user_id_idx ON presets(user_id);\n\n-- RLS --\n\nALTER TABLE presets ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own presets\"\n    ON presets\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private presets\"\n    ON presets\n    FOR SELECT\n    USING (sharing <> 'private');\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_presets_updated_at\nBEFORE UPDATE ON presets \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n\n--------------- PRESET WORKSPACES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS preset_workspaces (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    preset_id UUID NOT NULL REFERENCES presets(id) ON DELETE CASCADE,\n    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(preset_id, workspace_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX preset_workspaces_user_id_idx ON preset_workspaces(user_id);\nCREATE INDEX preset_workspaces_preset_id_idx ON preset_workspaces(preset_id);\nCREATE INDEX preset_workspaces_workspace_id_idx ON preset_workspaces(workspace_id);\n\n-- RLS --\n\nALTER TABLE preset_workspaces ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own preset_workspaces\"\n    ON preset_workspaces\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_preset_workspaces_updated_at\nBEFORE UPDATE ON preset_workspaces \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n"
  },
  {
    "path": "supabase/migrations/20240108234547_add_assistants.sql",
    "content": "--------------- ASSISTANTS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS assistants (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- OPTIONAL RELATIONSHIPS\n    folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n     --SHARING\n    sharing TEXT NOT NULL DEFAULT 'private',\n\n    -- REQUIRED\n    context_length INT NOT NULL,\n    description TEXT NOT NULL CHECK (char_length(description) <= 500),\n    embeddings_provider TEXT NOT NULL CHECK (char_length(embeddings_provider) <= 1000),\n    include_profile_context BOOLEAN NOT NULL,\n    include_workspace_instructions BOOLEAN NOT NULL,\n    model TEXT NOT NULL CHECK (char_length(model) <= 1000),\n    name TEXT NOT NULL CHECK (char_length(name) <= 100),\n    image_path TEXT NOT NULL CHECK (char_length(image_path) <= 1000), -- file path in assistant_images bucket\n    prompt TEXT NOT NULL CHECK (char_length(prompt) <= 100000),\n    temperature REAL NOT NULL\n);\n\n-- INDEXES --\n\nCREATE INDEX assistants_user_id_idx ON assistants(user_id);\n\n-- RLS --\n\nALTER TABLE assistants ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own assistants\"\n    ON assistants\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private assistants\"\n    ON assistants\n    FOR SELECT\n    USING (sharing <> 'private');\n\n-- FUNCTIONS --\n\nCREATE OR REPLACE FUNCTION delete_old_assistant_image()\nRETURNS TRIGGER\nLANGUAGE 'plpgsql'\nSECURITY DEFINER\nAS $$\nDECLARE\n  status INT;\n  content TEXT;\nBEGIN\n  IF TG_OP = 'DELETE' THEN\n    SELECT\n      INTO status, content\n      result.status, result.content\n      FROM public.delete_storage_object_from_bucket('assistant_images', OLD.image_path) AS result;\n    IF status <> 200 THEN\n      RAISE WARNING 'Could not delete assistant image: % %', status, content;\n    END IF;\n  END IF;\n  IF TG_OP = 'DELETE' THEN\n    RETURN OLD;\n  END IF;\n  RETURN NEW;\nEND;\n$$;\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_assistants_updated_at\nBEFORE UPDATE ON assistants\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();\n\nCREATE TRIGGER delete_old_assistant_image\nAFTER DELETE ON assistants\nFOR EACH ROW\nEXECUTE PROCEDURE delete_old_assistant_image();\n\n-- STORAGE --\n\nINSERT INTO storage.buckets (id, name, public) VALUES ('assistant_images', 'assistant_images', false);\n\nCREATE OR REPLACE FUNCTION public.non_private_assistant_exists(p_name text)\nRETURNS boolean\nLANGUAGE sql\nSECURITY DEFINER\nAS $$\n    SELECT EXISTS (\n        SELECT 1\n        FROM assistants\n        WHERE (id::text = (storage.filename(p_name))) AND sharing <> 'private'\n    );\n$$;\n\nCREATE POLICY \"Allow public read access on non-private assistant images\"\n    ON storage.objects FOR SELECT TO public\n    USING (bucket_id = 'assistant_images' AND public.non_private_assistant_exists(name));\n\nCREATE POLICY \"Allow insert access to own assistant images\"\n    ON storage.objects FOR INSERT TO authenticated\n    WITH CHECK (bucket_id = 'assistant_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow update access to own assistant images\"\n    ON storage.objects FOR UPDATE TO authenticated\n    USING (bucket_id = 'assistant_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow delete access to own assistant images\"\n    ON storage.objects FOR DELETE TO authenticated\n    USING (bucket_id = 'assistant_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\n--------------- ASSISTANT WORKSPACES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS assistant_workspaces (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    assistant_id UUID NOT NULL REFERENCES assistants(id) ON DELETE CASCADE,\n    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(assistant_id, workspace_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX assistant_workspaces_user_id_idx ON assistant_workspaces(user_id);\nCREATE INDEX assistant_workspaces_assistant_id_idx ON assistant_workspaces(assistant_id);\nCREATE INDEX assistant_workspaces_workspace_id_idx ON assistant_workspaces(workspace_id);\n\n-- RLS --\n\nALTER TABLE assistant_workspaces ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own assistant_workspaces\"\n    ON assistant_workspaces\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_assistant_workspaces_updated_at\nBEFORE UPDATE ON assistant_workspaces \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n"
  },
  {
    "path": "supabase/migrations/20240108234548_add_chats.sql",
    "content": "--------------- CHATS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS chats (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,\n    \n    -- OPTIONAL RELATIONSHIPS\n    assistant_id UUID REFERENCES assistants(id) ON DELETE CASCADE,\n    folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- SHARING\n    sharing TEXT NOT NULL DEFAULT 'private',\n\n    -- REQUIRED\n    context_length INT NOT NULL,\n    embeddings_provider TEXT NOT NULL CHECK (char_length(embeddings_provider) <= 1000),\n    include_profile_context BOOLEAN NOT NULL,\n    include_workspace_instructions BOOLEAN NOT NULL,\n    model TEXT NOT NULL CHECK (char_length(model) <= 1000),\n    name TEXT NOT NULL CHECK (char_length(name) <= 200),\n    prompt TEXT NOT NULL CHECK (char_length(prompt) <= 100000),\n    temperature REAL NOT NULL\n);\n\n-- INDEXES --\n\nCREATE INDEX idx_chats_user_id ON chats (user_id);\nCREATE INDEX idx_chats_workspace_id ON chats (workspace_id);\n\n-- RLS --\n\nALTER TABLE chats ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own chats\"\n    ON chats\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private chats\"\n    ON chats\n    FOR SELECT\n    USING (sharing <> 'private');\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_chats_updated_at\nBEFORE UPDATE ON chats \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n\n--------------- CHAT FILES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS chat_files (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    chat_id UUID NOT NULL REFERENCES chats(id) ON DELETE CASCADE,\n    file_id UUID NOT NULL REFERENCES files(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(chat_id, file_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX idx_chat_files_chat_id ON chat_files (chat_id);\n\n-- RLS --\n\nALTER TABLE chat_files ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own chat_files\"\n    ON chat_files\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_chat_files_updated_at\nBEFORE UPDATE ON chat_files \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();"
  },
  {
    "path": "supabase/migrations/20240108234549_add_messages.sql",
    "content": "--------------- MESSAGES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS messages (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- RELATIONSHIPS\n    chat_id UUID NOT NULL REFERENCES chats(id) ON DELETE CASCADE,\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- REQUIRED\n    content TEXT NOT NULL CHECK (char_length(content) <= 1000000),\n    image_paths TEXT[] NOT NULL, -- file paths in message_images bucket\n    model TEXT NOT NULL CHECK (char_length(model) <= 1000),\n    role TEXT NOT NULL CHECK (char_length(role) <= 1000),\n    sequence_number INT NOT NULL,\n\n    -- CONSTRAINTS\n    CONSTRAINT check_image_paths_length CHECK (array_length(image_paths, 1) <= 16)\n);\n\n-- INDEXES --\n\nCREATE INDEX idx_messages_chat_id ON messages (chat_id);\n\n-- RLS --\n\nALTER TABLE messages ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own messages\"\n    ON messages\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to messages for non-private chats\"\n    ON messages\n    FOR SELECT\n    USING (chat_id IN (\n        SELECT id FROM chats WHERE sharing <> 'private'\n    ));\n\n-- FUNCTIONS --\n\nCREATE OR REPLACE FUNCTION delete_old_message_images()\nRETURNS TRIGGER\nLANGUAGE 'plpgsql'\nSECURITY DEFINER\nAS $$\nDECLARE\n  status INT;\n  content TEXT;\n  image_path TEXT;\nBEGIN\n  IF TG_OP = 'DELETE' THEN\n    FOREACH image_path IN ARRAY OLD.image_paths\n    LOOP\n      SELECT\n        INTO status, content\n        result.status, result.content\n        FROM public.delete_storage_object_from_bucket('message_images', image_path) AS result;\n      IF status <> 200 THEN\n        RAISE WARNING 'Could not delete message image: % %', status, content;\n      END IF;\n    END LOOP;\n  END IF;\n  IF TG_OP = 'DELETE' THEN\n    RETURN OLD;\n  END IF;\n  RETURN NEW;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION delete_messages_including_and_after(\n    p_user_id UUID, \n    p_chat_id UUID, \n    p_sequence_number INT\n)\nRETURNS VOID AS $$\nBEGIN\n    DELETE FROM messages \n    WHERE user_id = p_user_id AND chat_id = p_chat_id AND sequence_number >= p_sequence_number;\nEND;\n$$ LANGUAGE plpgsql;\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_messages_updated_at\nBEFORE UPDATE ON messages\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();\n\nCREATE TRIGGER delete_old_message_images\nAFTER DELETE ON messages\nFOR EACH ROW\nEXECUTE PROCEDURE delete_old_message_images();\n\n-- STORAGE --\n\n-- MESSAGE IMAGES\n\nINSERT INTO storage.buckets (id, name, public) VALUES ('message_images', 'message_images', false);\n\nCREATE POLICY \"Allow read access to own message images\"\n    ON storage.objects FOR SELECT\n    USING (\n        bucket_id = 'message_images' AND \n        (\n            (storage.foldername(name))[1] = auth.uid()::text OR\n            (\n                EXISTS (\n                    SELECT 1 FROM chats \n                    WHERE id = (\n                        SELECT chat_id FROM messages WHERE id = (storage.foldername(name))[2]::uuid\n                    ) AND sharing <> 'private'\n                )\n            )\n        )\n    );\n\nCREATE POLICY \"Allow insert access to own message images\"\n    ON storage.objects FOR INSERT\n    WITH CHECK (bucket_id = 'message_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow update access to own message images\"\n    ON storage.objects FOR UPDATE\n    USING (bucket_id = 'message_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow delete access to own message images\"\n    ON storage.objects FOR DELETE\n    USING (bucket_id = 'message_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\n--------------- MESSAGE FILE ITEMS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS message_file_items (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n    file_item_id UUID NOT NULL REFERENCES file_items(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(message_id, file_item_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX idx_message_file_items_message_id ON message_file_items (message_id);\n\n-- RLS --\n\nALTER TABLE message_file_items ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own message_file_items\"\n    ON message_file_items\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_message_file_items_updated_at\nBEFORE UPDATE ON message_file_items \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();"
  },
  {
    "path": "supabase/migrations/20240108234550_add_prompts.sql",
    "content": "--------------- PROMPTS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS prompts (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- OPTIONAL RELATIONSHIPS\n    folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- SHARING\n    sharing TEXT NOT NULL DEFAULT 'private',\n\n    -- REQUIRED\n    content TEXT NOT NULL CHECK (char_length(content) <= 100000),\n    name TEXT NOT NULL CHECK (char_length(name) <= 100)\n);\n\n-- INDEXES --\n\nCREATE INDEX prompts_user_id_idx ON prompts(user_id);\n\n-- RLS --\n\nALTER TABLE prompts ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own prompts\"\n    ON prompts\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private prompts\"\n    ON prompts\n    FOR SELECT\n    USING (sharing <> 'private');\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_prompts_updated_at\nBEFORE UPDATE ON prompts\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();\n\n--------------- PROMPT WORKSPACES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS prompt_workspaces (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    prompt_id UUID NOT NULL REFERENCES prompts(id) ON DELETE CASCADE,\n    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(prompt_id, workspace_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX prompt_workspaces_user_id_idx ON prompt_workspaces(user_id);\nCREATE INDEX prompt_workspaces_prompt_id_idx ON prompt_workspaces(prompt_id);\nCREATE INDEX prompt_workspaces_workspace_id_idx ON prompt_workspaces(workspace_id);\n\n-- RLS --\n\nALTER TABLE prompt_workspaces ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own prompt_workspaces\"\n    ON prompt_workspaces\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_prompt_workspaces_updated_at\nBEFORE UPDATE ON prompt_workspaces \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n"
  },
  {
    "path": "supabase/migrations/20240108234551_add_collections.sql",
    "content": "--------------- COLLECTIONS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS collections (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- OPTIONAL RELATIONSHIPS\n    folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- SHARING\n    sharing TEXT NOT NULL DEFAULT 'private',\n\n    -- REQUIRED\n    description TEXT NOT NULL CHECK (char_length(description) <= 500),\n    name TEXT NOT NULL CHECK (char_length(name) <= 100)\n);\n\n-- INDEXES --\n\nCREATE INDEX collections_user_id_idx ON collections(user_id);\n\n-- RLS --\n\nALTER TABLE collections ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own collections\"\n    ON collections\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private collections\"\n    ON collections\n    FOR SELECT\n    USING (sharing <> 'private');\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_collections_updated_at\nBEFORE UPDATE ON collections\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();\n\n--------------- COLLECTION WORKSPACES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS collection_workspaces (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    collection_id UUID NOT NULL REFERENCES collections(id) ON DELETE CASCADE,\n    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(collection_id, workspace_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX collection_workspaces_user_id_idx ON collection_workspaces(user_id);\nCREATE INDEX collection_workspaces_collection_id_idx ON collection_workspaces(collection_id);\nCREATE INDEX collection_workspaces_workspace_id_idx ON collection_workspaces(workspace_id);\n\n-- RLS --\n\nALTER TABLE collection_workspaces ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own collection_workspaces\"\n    ON collection_workspaces\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_collection_workspaces_updated_at\nBEFORE UPDATE ON collection_workspaces \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n\n--------------- COLLECTION FILES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS collection_files (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    collection_id UUID NOT NULL REFERENCES collections(id) ON DELETE CASCADE,\n    file_id UUID NOT NULL REFERENCES files(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(collection_id, file_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX idx_collection_files_collection_id ON collection_files (collection_id);\nCREATE INDEX idx_collection_files_file_id ON collection_files (file_id);\n\n-- RLS --\n\nALTER TABLE collection_files ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own collection_files\"\n    ON collection_files\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to collection files for non-private collections\"\n    ON collection_files\n    FOR SELECT\n    USING (collection_id IN (\n        SELECT id FROM collections WHERE sharing <> 'private'\n    ));\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_collection_files_updated_at\nBEFORE UPDATE ON collection_files \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n\n--------------- REFERS BACK TO FILES ---------------\n\nCREATE POLICY \"Allow view access to files for non-private collections\"\n    ON files\n    FOR SELECT\n    USING (id IN (\n        SELECT file_id FROM collection_files WHERE collection_id IN (\n            SELECT id FROM collections WHERE sharing <> 'private'\n        )\n    ));"
  },
  {
    "path": "supabase/migrations/20240115135033_add_openrouter.sql",
    "content": "ALTER TABLE profiles\nADD COLUMN openrouter_api_key TEXT CHECK (char_length(openrouter_api_key) <= 1000);\n"
  },
  {
    "path": "supabase/migrations/20240115171510_add_assistant_files.sql",
    "content": "--------------- ASSISTANT FILES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS assistant_files (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    assistant_id UUID NOT NULL REFERENCES assistants(id) ON DELETE CASCADE,\n    file_id UUID NOT NULL REFERENCES files(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(assistant_id, file_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX assistant_files_user_id_idx ON assistant_files(user_id);\nCREATE INDEX assistant_files_assistant_id_idx ON assistant_files(assistant_id);\nCREATE INDEX assistant_files_file_id_idx ON assistant_files(file_id);\n\n-- RLS --\n\nALTER TABLE assistant_files ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own assistant_files\"\n    ON assistant_files\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_assistant_files_updated_at\nBEFORE UPDATE ON assistant_files \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n\n--------------- ASSISTANT COLLECTIONS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS assistant_collections (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    assistant_id UUID NOT NULL REFERENCES assistants(id) ON DELETE CASCADE,\n    collection_id UUID NOT NULL REFERENCES collections(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(assistant_id, collection_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX assistant_collections_user_id_idx ON assistant_collections(user_id);\nCREATE INDEX assistant_collections_assistant_id_idx ON assistant_collections(assistant_id);\nCREATE INDEX assistant_collections_collection_id_idx ON assistant_collections(collection_id);\n\n-- RLS --\n\nALTER TABLE assistant_collections ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own assistant_collections\"\n    ON assistant_collections\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_assistant_collections_updated_at\nBEFORE UPDATE ON assistant_collections \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();"
  },
  {
    "path": "supabase/migrations/20240115171524_add_tools.sql",
    "content": "--------------- TOOLS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS tools (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- OPTIONAL RELATIONSHIPS\n    folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n     --SHARING\n    sharing TEXT NOT NULL DEFAULT 'private',\n\n    -- REQUIRED\n    description TEXT NOT NULL CHECK (char_length(description) <= 500),\n    name TEXT NOT NULL CHECK (char_length(name) <= 100),\n    schema JSONB NOT NULL,\n    url TEXT NOT NULL CHECK (char_length(url) <= 1000)\n);\n\n-- INDEXES --\n\nCREATE INDEX tools_user_id_idx ON tools(user_id);\n\n-- RLS --\n\nALTER TABLE tools ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own tools\"\n    ON tools\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private tools\"\n    ON tools\n    FOR SELECT\n    USING (sharing <> 'private');\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_tools_updated_at\nBEFORE UPDATE ON tools\nFOR EACH ROW\nEXECUTE PROCEDURE update_updated_at_column();\n\n--------------- TOOL WORKSPACES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS tool_workspaces (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    tool_id UUID NOT NULL REFERENCES tools(id) ON DELETE CASCADE,\n    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(tool_id, workspace_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX tool_workspaces_user_id_idx ON tool_workspaces(user_id);\nCREATE INDEX tool_workspaces_tool_id_idx ON tool_workspaces(tool_id);\nCREATE INDEX tool_workspaces_workspace_id_idx ON tool_workspaces(workspace_id);\n\n-- RLS --\n\nALTER TABLE tool_workspaces ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own tool_workspaces\"\n    ON tool_workspaces\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_tool_workspaces_updated_at\nBEFORE UPDATE ON tool_workspaces \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();"
  },
  {
    "path": "supabase/migrations/20240115172125_add_assistant_tools.sql",
    "content": "--------------- ASSISTANT TOOLS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS assistant_tools (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    assistant_id UUID NOT NULL REFERENCES assistants(id) ON DELETE CASCADE,\n    tool_id UUID NOT NULL REFERENCES tools(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(assistant_id, tool_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX assistant_tools_user_id_idx ON assistant_tools(user_id);\nCREATE INDEX assistant_tools_assistant_id_idx ON assistant_tools(assistant_id);\nCREATE INDEX assistant_tools_tool_id_idx ON assistant_tools(tool_id);\n\n-- RLS --\n\nALTER TABLE assistant_tools ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own assistant_tools\"\n    ON assistant_tools\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_assistant_tools_updated_at\nBEFORE UPDATE ON assistant_tools \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();"
  },
  {
    "path": "supabase/migrations/20240118224049_add_azure_embeddings.sql",
    "content": "ALTER TABLE profiles\nADD COLUMN azure_openai_embeddings_id TEXT CHECK (char_length(azure_openai_embeddings_id) <= 1000);\n"
  },
  {
    "path": "supabase/migrations/20240124234424_tool_improvements.sql",
    "content": "ALTER TABLE tools\nADD COLUMN custom_headers JSONB NOT NULL DEFAULT '{}',\nADD COLUMN request_in_body BOOLEAN NOT NULL DEFAULT TRUE,\nALTER COLUMN schema SET DEFAULT '{}';\n"
  },
  {
    "path": "supabase/migrations/20240125192042_upgrade_openai_models.sql",
    "content": "-- WORKSPACES\n\nUPDATE workspaces\nSET default_model = 'gpt-4-turbo-preview'\nWHERE default_model = 'gpt-4-1106-preview';\n\nUPDATE workspaces\nSET default_model = 'gpt-3.5-turbo'\nWHERE default_model = 'gpt-3.5-turbo-1106';\n\n-- PRESETS\n\nUPDATE presets\nSET model = 'gpt-4-turbo-preview'\nWHERE model = 'gpt-4-1106-preview';\n\nUPDATE presets\nSET model = 'gpt-3.5-turbo'\nWHERE model = 'gpt-3.5-turbo-1106';\n\n-- ASSISTANTS\n\nUPDATE assistants\nSET model = 'gpt-4-turbo-preview'\nWHERE model = 'gpt-4-1106-preview';\n\nUPDATE assistants\nSET model = 'gpt-3.5-turbo'\nWHERE model = 'gpt-3.5-turbo-1106';\n\n-- CHATS\n\nUPDATE chats\nSET model = 'gpt-4-turbo-preview'\nWHERE model = 'gpt-4-1106-preview';\n\nUPDATE chats\nSET model = 'gpt-3.5-turbo'\nWHERE model = 'gpt-3.5-turbo-1106';\n\n-- MESSAGES\n\nUPDATE messages\nSET model = 'gpt-4-turbo-preview'\nWHERE model = 'gpt-4-1106-preview';\n\nUPDATE messages\nSET model = 'gpt-3.5-turbo'\nWHERE model = 'gpt-3.5-turbo-1106';\n\n-- PROFILES\n\nCREATE OR REPLACE FUNCTION create_profile_and_workspace() \nRETURNS TRIGGER\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path = public\nAS $$\nDECLARE\n    random_username TEXT;\nBEGIN\n    -- Generate a random username\n    random_username := 'user' || substr(replace(gen_random_uuid()::text, '-', ''), 1, 16);\n\n    -- Create a profile for the new user\n    INSERT INTO public.profiles(user_id, anthropic_api_key, azure_openai_35_turbo_id, azure_openai_45_turbo_id, azure_openai_45_vision_id, azure_openai_api_key, azure_openai_endpoint, google_gemini_api_key, has_onboarded, image_url, image_path, mistral_api_key, display_name, bio, openai_api_key, openai_organization_id, perplexity_api_key, profile_context, use_azure_openai, username)\n    VALUES(\n        NEW.id,\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        FALSE,\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        FALSE,\n        random_username\n    );\n\n    INSERT INTO public.workspaces(user_id, is_home, name, default_context_length, default_model, default_prompt, default_temperature, description, embeddings_provider, include_profile_context, include_workspace_instructions, instructions)\n    VALUES(\n        NEW.id,\n        TRUE,\n        'Home',\n        4096,\n        'gpt-4-turbo-preview', -- Updated default model\n        'You are a friendly, helpful AI assistant.',\n        0.5,\n        'My home workspace.',\n        'openai',\n        TRUE,\n        TRUE,\n        ''\n    );\n\n    RETURN NEW;\nEND;\n$$;\n"
  },
  {
    "path": "supabase/migrations/20240125194719_add_custom_models.sql",
    "content": "--------------- MODELS ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS models (\n    -- ID\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n\n    -- RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n\n    -- OPTIONAL RELATIONSHIPS\n    folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ,\n\n    -- SHARING\n    sharing TEXT NOT NULL DEFAULT 'private',\n\n    -- REQUIRED\n    api_key TEXT NOT NULL CHECK (char_length(api_key) <= 1000),\n    base_url TEXT NOT NULL CHECK (char_length(base_url) <= 1000),\n    description TEXT NOT NULL CHECK (char_length(description) <= 500),\n    model_id TEXT NOT NULL CHECK (char_length(model_id) <= 1000),\n    name TEXT NOT NULL CHECK (char_length(name) <= 100)\n);\n\n-- INDEXES --\n\nCREATE INDEX models_user_id_idx ON models(user_id);\n\n-- RLS --\n\nALTER TABLE models ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own models\"\n    ON models\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\nCREATE POLICY \"Allow view access to non-private models\"\n    ON models\n    FOR SELECT\n    USING (sharing <> 'private');\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_models_updated_at\nBEFORE UPDATE ON models \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n\n--------------- MODEL WORKSPACES ---------------\n\n-- TABLE --\n\nCREATE TABLE IF NOT EXISTS model_workspaces (\n    -- REQUIRED RELATIONSHIPS\n    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,\n    model_id UUID NOT NULL REFERENCES models(id) ON DELETE CASCADE,\n    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,\n\n    PRIMARY KEY(model_id, workspace_id),\n\n    -- METADATA\n    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMPTZ\n);\n\n-- INDEXES --\n\nCREATE INDEX model_workspaces_user_id_idx ON model_workspaces(user_id);\nCREATE INDEX model_workspaces_model_id_idx ON model_workspaces(model_id);\nCREATE INDEX model_workspaces_workspace_id_idx ON model_workspaces(workspace_id);\n\n-- RLS --\n\nALTER TABLE model_workspaces ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY \"Allow full access to own model_workspaces\"\n    ON model_workspaces\n    USING (user_id = auth.uid())\n    WITH CHECK (user_id = auth.uid());\n\n-- TRIGGERS --\n\nCREATE TRIGGER update_model_workspaces_updated_at\nBEFORE UPDATE ON model_workspaces \nFOR EACH ROW \nEXECUTE PROCEDURE update_updated_at_column();\n"
  },
  {
    "path": "supabase/migrations/20240129232644_add_workspace_images.sql",
    "content": "-- ALTER TABLE --\n\nALTER TABLE workspaces\nADD COLUMN image_path TEXT DEFAULT '' NOT NULL CHECK (char_length(image_path) <= 1000);\n\n-- STORAGE --\n\nINSERT INTO storage.buckets (id, name, public) VALUES ('workspace_images', 'workspace_images', false);\n\n-- FUNCTIONS --\n\nCREATE OR REPLACE FUNCTION delete_old_workspace_image()\nRETURNS TRIGGER\nLANGUAGE 'plpgsql'\nSECURITY DEFINER\nAS $$\nDECLARE\n  status INT;\n  content TEXT;\nBEGIN\n  IF TG_OP = 'DELETE' THEN\n    SELECT\n      INTO status, content\n      result.status, result.content\n      FROM public.delete_storage_object_from_bucket('workspace_images', OLD.image_path) AS result;\n    IF status <> 200 THEN\n      RAISE WARNING 'Could not delete workspace image: % %', status, content;\n    END IF;\n  END IF;\n  IF TG_OP = 'DELETE' THEN\n    RETURN OLD;\n  END IF;\n  RETURN NEW;\nEND;\n$$;\n\n-- TRIGGERS --\n\nCREATE TRIGGER delete_old_workspace_image\nAFTER DELETE ON workspaces\nFOR EACH ROW\nEXECUTE PROCEDURE delete_old_workspace_image();\n\n-- POLICIES --\n\nCREATE OR REPLACE FUNCTION public.non_private_workspace_exists(p_name text)\nRETURNS boolean\nLANGUAGE sql\nSECURITY DEFINER\nAS $$\n    SELECT EXISTS (\n        SELECT 1\n        FROM workspaces\n        WHERE (id::text = (storage.filename(p_name))) AND sharing <> 'private'\n    );\n$$;\n\nCREATE POLICY \"Allow public read access on non-private workspace images\"\n    ON storage.objects FOR SELECT TO public\n    USING (bucket_id = 'workspace_images' AND public.non_private_workspace_exists(name));\n\nCREATE POLICY \"Allow insert access to own workspace images\"\n    ON storage.objects FOR INSERT TO authenticated\n    WITH CHECK (bucket_id = 'workspace_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow update access to own workspace images\"\n    ON storage.objects FOR UPDATE TO authenticated\n    USING (bucket_id = 'workspace_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n\nCREATE POLICY \"Allow delete access to own workspace images\"\n    ON storage.objects FOR DELETE TO authenticated\n    USING (bucket_id = 'workspace_images' AND (storage.foldername(name))[1] = auth.uid()::text);\n"
  },
  {
    "path": "supabase/migrations/20240212063532_add_at_assistants.sql",
    "content": "ALTER TABLE messages ADD COLUMN assistant_id UUID REFERENCES assistants(id) ON DELETE CASCADE DEFAULT NULL;\n"
  },
  {
    "path": "supabase/migrations/20240213040255_remove_request_in_body_from_tools.sql",
    "content": "ALTER TABLE tools\nDROP COLUMN request_in_body;\n"
  },
  {
    "path": "supabase/migrations/20240213085646_add_context_length_to_custom_models.sql",
    "content": "ALTER TABLE models ADD COLUMN context_length INT NOT NULL DEFAULT 4096;"
  },
  {
    "path": "supabase/migrations/20240302004845_add_groq.sql",
    "content": "ALTER TABLE profiles ADD COLUMN groq_api_key TEXT CHECK (char_length(groq_api_key) <= 1000);\n"
  },
  {
    "path": "supabase/seed.sql",
    "content": "INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user) VALUES\n('00000000-0000-0000-0000-000000000000', 'e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', 'authenticated', 'authenticated', 'test@test.com', crypt('password', gen_salt('bf')), '2023-02-18 23:31:13.017218+00', NULL, '', '2023-02-18 23:31:12.757017+00', '', NULL, '', '', NULL, '2023-02-18 23:31:13.01781+00', '{\"provider\": \"email\", \"providers\": [\"email\"]}', '{}', NULL, '2023-02-18 23:31:12.752281+00', '2023-02-18 23:31:13.019418+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, 'f');\n\n-- Start data for workspaces \nINSERT INTO workspaces (user_id, name, description, default_context_length, default_model, default_prompt, default_temperature, include_profile_context, include_workspace_instructions, instructions, is_home, sharing, embeddings_provider) VALUES \n('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', 'Workspace 1', 'This is for testing.', 4000, 'gpt-4-turbo-preview', 'You are an assistant.', 0.5, true, true, 'These are the instructions.', false, 'private', 'openai');\n\n-- Get workspace ids\nDO $$\nDECLARE\n  workspace1_id UUID;\nBEGIN\n  SELECT id INTO workspace1_id FROM workspaces WHERE name='Home';\n\n  -- start data for folders\n  INSERT INTO folders (user_id, workspace_id, name, description, type) VALUES\n  ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', workspace1_id, 'Chat Folder 1', 'This is a folder for chats', 'chats');\n\n  -- start data for files\n  INSERT INTO files (user_id, name, description, file_path, size, tokens, type) VALUES\n  ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', 'File 1', 'This is a file for testing', 'https://example.com/file1', 1000000, 250, 'pdf');\n\n  -- start data for file_workspaces\n  DECLARE\n    file1_id UUID;\n  BEGIN\n    SELECT id INTO file1_id FROM files WHERE name='File 1';\n\n    INSERT INTO file_workspaces (user_id, file_id, workspace_id) VALUES\n    ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', file1_id, workspace1_id);\n  END;\n\n  -- DECLARE\n  --   file1_id UUID;\n  -- BEGIN\n  --   SELECT id INTO file1_id FROM chats WHERE name='Chat 1';\n\n  --   -- start data for file items\n  --   INSERT INTO file_items (user_id, file_id, content, embedding) VALUES\n  --   ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', file1_id, 'File Item 1', []);\n  -- END;\n\n  -- start data for presets\n  INSERT INTO presets (user_id, created_at, updated_at, sharing, include_profile_context, include_workspace_instructions, context_length, model, name, prompt, temperature, description, embeddings_provider) VALUES\n  ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'private', TRUE, TRUE, 4000, 'gpt-4-turbo-preview', 'Preset 1', 'Prompt 1', 0.5, 'Description for Preset 1', 'openai');\n\n  -- Get preset id\n  DECLARE\n    preset1_id UUID;\n  BEGIN\n    SELECT id INTO preset1_id FROM presets WHERE name='Preset 1';\n\n    -- start data for preset_workspaces\n    INSERT INTO preset_workspaces (user_id, preset_id, workspace_id) VALUES\n    ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', preset1_id, workspace1_id);\n  END;\n\n  -- Start data for assistants \n  INSERT INTO assistants (user_id, name, description, model, image_path, sharing, context_length, include_profile_context, include_workspace_instructions, prompt, temperature, embeddings_provider) VALUES \n  ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', 'Albert Einstein', 'This is an Albert Einstein assistant.', 'gpt-4-turbo-preview', '', 'private', 4000, TRUE, TRUE, 'You are Albert Einstein.', 0.5, 'openai');\n\n  -- Get assistant id\n  DECLARE\n    assistant1_id UUID;\n  BEGIN\n    SELECT id INTO assistant1_id FROM assistants WHERE name='Albert Einstein';\n\n    -- start data for assistant_workspaces\n    INSERT INTO assistant_workspaces (user_id, assistant_id, workspace_id) VALUES\n    ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', assistant1_id, workspace1_id);\n  END;\n\n  -- Start data for chats \n  INSERT INTO chats (user_id, workspace_id, name, model, prompt, temperature, context_length, include_profile_context, include_workspace_instructions, embeddings_provider) VALUES \n  ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', workspace1_id, 'Chat 1', 'gpt-4-turbo-preview', 'You are an assistant.', 0.5, 4000, TRUE, TRUE, 'openai');\n\n  DECLARE\n    folder1_id UUID;\n  BEGIN\n    SELECT id INTO folder1_id FROM folders WHERE name='Chat Folder 1';\n\n    INSERT INTO chats (user_id, workspace_id, name, model, prompt, temperature, context_length, include_profile_context, include_workspace_instructions, folder_id, embeddings_provider) VALUES \n    ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', workspace1_id, 'Chat 4', 'gpt-4-turbo-preview', 'You are an assistant.', 0.5, 4000, TRUE, TRUE, folder1_id, 'openai');\n  END;\n\n  -- Start data for messages \n  -- Get chat ids\n    DECLARE\n      chat1_id UUID;\n    BEGIN\n      SELECT id INTO chat1_id FROM chats WHERE name='Chat 1';\n\n      INSERT INTO messages (user_id, chat_id, content, role, model, sequence_number, image_paths) VALUES\n      -- Chat 1\n      ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', chat1_id, 'Hello! This is a long message with **markdown**. It contains multiple sentences and paragraphs. Let me add more content to this message. I am a user interacting with an AI assistant. I can ask the assistant to perform various tasks, such as generating text, answering questions, and even writing code. The assistant uses a powerful language model to understand my requests and generate appropriate responses. This is a very interesting and exciting technology!', 'user', 'gpt-4-turbo-preview', 0, '{}'),\n      ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', chat1_id, 'How are you? This is another long message with *italic markdown*. It also contains multiple sentences and paragraphs. Let me add more content to this message. As an AI assistant, I can understand and respond to a wide range of requests. I can generate text, answer questions, and even write code. I use a powerful language model to understand your requests and generate appropriate responses. This is a very interesting and exciting technology!', 'assistant', 'gpt-4-turbo-preview', 1, '{}'),\n      ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', chat1_id, 'I am fine, thank you! This is a third long message with [link markdown](http://example.com). It contains even more sentences and paragraphs. Let me add even more content to this message. As a user, I can interact with the AI assistant in a variety of ways. I can ask it to generate text, answer questions, and even write code. The assistant uses a powerful language model to understand my requests and generate appropriate responses. This is a very interesting and exciting technology!', 'user', 'gpt-4-turbo-preview', 2, '{}'),\n      ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', chat1_id, 'Great to hear that! This is a fourth long message with `code markdown`. It contains a lot of sentences and paragraphs. Let me add even more content to this message. As an AI assistant, I can understand and respond to a wide range of requests. I can generate text, answer questions, and even write code. I use a powerful language model to understand your requests and generate appropriate responses. This is a very interesting and exciting technology!', 'assistant', 'gpt-4-turbo-preview', 3, '{}'),\n      ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', chat1_id, 'What can you do? This is a fifth long message with > blockquote markdown. It contains a ton of sentences and paragraphs. Let me add even more content to this message. As a user, I can interact with the AI assistant in a variety of ways. I can ask it to generate text, answer questions, and even write code. The assistant uses a powerful language model to understand my requests and generate appropriate responses. This is a very interesting and exciting technology!', 'user', 'gpt-4-turbo-preview', 4, '{}'),\n      ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', chat1_id, 'I can assist you with various tasks. This is a sixth long message with - list markdown. It contains an enormous amount of sentences and paragraphs. Let me add even more content to this message. As an AI assistant, I can understand and respond to a wide range of requests. I can generate text, answer questions, and even write code. I use a powerful language model to understand your requests and generate appropriate responses. This is a very interesting and exciting technology!', 'assistant', 'gpt-4-turbo-preview', 5, '{}'),\n      ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', chat1_id, 'Can you assist me with my homework? This is a seventh long message with 1. numbered list markdown. It contains a plethora of sentences and paragraphs. Let me add even more content to this message. As a user, I can interact with the AI assistant in a variety of ways. I can ask it to generate text, answer questions, and even write code. The assistant uses a powerful language model to understand my requests and generate appropriate responses. This is a very interesting and exciting technology!', 'user', 'gpt-4-turbo-preview', 6, '{}'),\n      ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', chat1_id, 'Sure, I would be happy to help. What do you need assistance with? This is an eighth long message with --- horizontal rule markdown. It contains a multitude of sentences and paragraphs. Let me add even more content to this message. As an AI assistant, I can understand and respond to a wide range of requests. I can generate text, answer questions, and even write code. I use a powerful language model to understand your requests and generate appropriate responses. This is a very interesting and exciting technology!', 'assistant', 'gpt-4-turbo-preview', 7, '{}'),\n      ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', chat1_id, 'I need help with my math homework. This is a ninth long message with # heading markdown. It contains a vast number of sentences and paragraphs. Let me add even more content to this message. As a user, I can interact with the AI assistant in a variety of ways. I can ask it to generate text, answer questions, and even write code. The assistant uses a powerful language model to understand my requests and generate appropriate responses. This is a very interesting and exciting technology!', 'user', 'gpt-4-turbo-preview', 8, '{}');\n    END;\n\n  -- Start data for prompts \n  INSERT INTO prompts (user_id, folder_id, created_at, updated_at, sharing, content, name) VALUES \n  ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', null, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'private', 'I want you to act as a storyteller. You will come up with entertaining stories that are engaging, imaginative and captivating for the audience. It can be fairy tales, educational stories or any other type of stories which has the potential to capture people''s attention and imagination. Depending on the target audience, you may choose specific themes or topics for your storytelling session e.g., if it’s children then you can talk about animals; If it’s adults then history-based tales might engage them better etc. My first request is ''I need an interesting story on perseverance.''', 'Storyteller');\n\n  -- Start data for prompt_workspaces\n  DECLARE\n    prompt1_id UUID;\n  BEGIN\n    SELECT id INTO prompt1_id FROM prompts WHERE name='Storyteller';\n\n    INSERT INTO prompt_workspaces (user_id, prompt_id, workspace_id) VALUES\n    ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', prompt1_id, workspace1_id);\n  END;\n\n  -- Start data for collections  \n  INSERT INTO collections (user_id, name, description, created_at, updated_at, sharing) VALUES\n  ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', 'Collection 1', 'This is a description for Collection 1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'private');\n\n  -- Start data for collection_workspaces\n  DECLARE\n    collection1_id UUID;\n  BEGIN\n    SELECT id INTO collection1_id FROM collections WHERE name='Collection 1';\n\n    INSERT INTO collection_workspaces (user_id, collection_id, workspace_id) VALUES\n    ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', collection1_id, workspace1_id);\n  END;\n\n  -- Start data for tools\n  INSERT INTO tools (user_id, description, name, schema, url, created_at, updated_at, sharing) VALUES\n  ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', 'This is a description for Tool 1', 'Tool 1', '{}', 'http://example.com', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'private');\n\n  -- Start data for tool_workspaces\n  DECLARE\n    tool1_id UUID;\n    workspace1_id UUID;\n  BEGIN\n    SELECT id INTO tool1_id FROM tools WHERE name='Tool 1';\n    SELECT id INTO workspace1_id FROM workspaces WHERE name='Home';\n\n    INSERT INTO tool_workspaces (user_id, tool_id, workspace_id) VALUES\n    ('e9fc7e46-a8a5-4fd4-8ba7-af485013e6fa', tool1_id, workspace1_id);\n  END;\n\nEND $$;"
  },
  {
    "path": "supabase/types.ts",
    "content": "export type Json =\n  | string\n  | number\n  | boolean\n  | null\n  | { [key: string]: Json | undefined }\n  | Json[]\n\nexport type Database = {\n  graphql_public: {\n    Tables: {\n      [_ in never]: never\n    }\n    Views: {\n      [_ in never]: never\n    }\n    Functions: {\n      graphql: {\n        Args: {\n          operationName?: string\n          query?: string\n          variables?: Json\n          extensions?: Json\n        }\n        Returns: Json\n      }\n    }\n    Enums: {\n      [_ in never]: never\n    }\n    CompositeTypes: {\n      [_ in never]: never\n    }\n  }\n  public: {\n    Tables: {\n      assistant_collections: {\n        Row: {\n          assistant_id: string\n          collection_id: string\n          created_at: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          assistant_id: string\n          collection_id: string\n          created_at?: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          assistant_id?: string\n          collection_id?: string\n          created_at?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"assistant_collections_assistant_id_fkey\"\n            columns: [\"assistant_id\"]\n            isOneToOne: false\n            referencedRelation: \"assistants\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"assistant_collections_collection_id_fkey\"\n            columns: [\"collection_id\"]\n            isOneToOne: false\n            referencedRelation: \"collections\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"assistant_collections_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      assistant_files: {\n        Row: {\n          assistant_id: string\n          created_at: string\n          file_id: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          assistant_id: string\n          created_at?: string\n          file_id: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          assistant_id?: string\n          created_at?: string\n          file_id?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"assistant_files_assistant_id_fkey\"\n            columns: [\"assistant_id\"]\n            isOneToOne: false\n            referencedRelation: \"assistants\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"assistant_files_file_id_fkey\"\n            columns: [\"file_id\"]\n            isOneToOne: false\n            referencedRelation: \"files\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"assistant_files_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      assistant_tools: {\n        Row: {\n          assistant_id: string\n          created_at: string\n          tool_id: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          assistant_id: string\n          created_at?: string\n          tool_id: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          assistant_id?: string\n          created_at?: string\n          tool_id?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"assistant_tools_assistant_id_fkey\"\n            columns: [\"assistant_id\"]\n            isOneToOne: false\n            referencedRelation: \"assistants\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"assistant_tools_tool_id_fkey\"\n            columns: [\"tool_id\"]\n            isOneToOne: false\n            referencedRelation: \"tools\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"assistant_tools_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      assistant_workspaces: {\n        Row: {\n          assistant_id: string\n          created_at: string\n          updated_at: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Insert: {\n          assistant_id: string\n          created_at?: string\n          updated_at?: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Update: {\n          assistant_id?: string\n          created_at?: string\n          updated_at?: string | null\n          user_id?: string\n          workspace_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"assistant_workspaces_assistant_id_fkey\"\n            columns: [\"assistant_id\"]\n            isOneToOne: false\n            referencedRelation: \"assistants\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"assistant_workspaces_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"assistant_workspaces_workspace_id_fkey\"\n            columns: [\"workspace_id\"]\n            isOneToOne: false\n            referencedRelation: \"workspaces\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      assistants: {\n        Row: {\n          context_length: number\n          created_at: string\n          description: string\n          embeddings_provider: string\n          folder_id: string | null\n          id: string\n          image_path: string\n          include_profile_context: boolean\n          include_workspace_instructions: boolean\n          model: string\n          name: string\n          prompt: string\n          sharing: string\n          temperature: number\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          context_length: number\n          created_at?: string\n          description: string\n          embeddings_provider: string\n          folder_id?: string | null\n          id?: string\n          image_path: string\n          include_profile_context: boolean\n          include_workspace_instructions: boolean\n          model: string\n          name: string\n          prompt: string\n          sharing?: string\n          temperature: number\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          context_length?: number\n          created_at?: string\n          description?: string\n          embeddings_provider?: string\n          folder_id?: string | null\n          id?: string\n          image_path?: string\n          include_profile_context?: boolean\n          include_workspace_instructions?: boolean\n          model?: string\n          name?: string\n          prompt?: string\n          sharing?: string\n          temperature?: number\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"assistants_folder_id_fkey\"\n            columns: [\"folder_id\"]\n            isOneToOne: false\n            referencedRelation: \"folders\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"assistants_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      chat_files: {\n        Row: {\n          chat_id: string\n          created_at: string\n          file_id: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          chat_id: string\n          created_at?: string\n          file_id: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          chat_id?: string\n          created_at?: string\n          file_id?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"chat_files_chat_id_fkey\"\n            columns: [\"chat_id\"]\n            isOneToOne: false\n            referencedRelation: \"chats\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"chat_files_file_id_fkey\"\n            columns: [\"file_id\"]\n            isOneToOne: false\n            referencedRelation: \"files\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"chat_files_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      chats: {\n        Row: {\n          assistant_id: string | null\n          context_length: number\n          created_at: string\n          embeddings_provider: string\n          folder_id: string | null\n          id: string\n          include_profile_context: boolean\n          include_workspace_instructions: boolean\n          model: string\n          name: string\n          prompt: string\n          sharing: string\n          temperature: number\n          updated_at: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Insert: {\n          assistant_id?: string | null\n          context_length: number\n          created_at?: string\n          embeddings_provider: string\n          folder_id?: string | null\n          id?: string\n          include_profile_context: boolean\n          include_workspace_instructions: boolean\n          model: string\n          name: string\n          prompt: string\n          sharing?: string\n          temperature: number\n          updated_at?: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Update: {\n          assistant_id?: string | null\n          context_length?: number\n          created_at?: string\n          embeddings_provider?: string\n          folder_id?: string | null\n          id?: string\n          include_profile_context?: boolean\n          include_workspace_instructions?: boolean\n          model?: string\n          name?: string\n          prompt?: string\n          sharing?: string\n          temperature?: number\n          updated_at?: string | null\n          user_id?: string\n          workspace_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"chats_assistant_id_fkey\"\n            columns: [\"assistant_id\"]\n            isOneToOne: false\n            referencedRelation: \"assistants\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"chats_folder_id_fkey\"\n            columns: [\"folder_id\"]\n            isOneToOne: false\n            referencedRelation: \"folders\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"chats_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"chats_workspace_id_fkey\"\n            columns: [\"workspace_id\"]\n            isOneToOne: false\n            referencedRelation: \"workspaces\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      collection_files: {\n        Row: {\n          collection_id: string\n          created_at: string\n          file_id: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          collection_id: string\n          created_at?: string\n          file_id: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          collection_id?: string\n          created_at?: string\n          file_id?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"collection_files_collection_id_fkey\"\n            columns: [\"collection_id\"]\n            isOneToOne: false\n            referencedRelation: \"collections\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"collection_files_file_id_fkey\"\n            columns: [\"file_id\"]\n            isOneToOne: false\n            referencedRelation: \"files\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"collection_files_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      collection_workspaces: {\n        Row: {\n          collection_id: string\n          created_at: string\n          updated_at: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Insert: {\n          collection_id: string\n          created_at?: string\n          updated_at?: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Update: {\n          collection_id?: string\n          created_at?: string\n          updated_at?: string | null\n          user_id?: string\n          workspace_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"collection_workspaces_collection_id_fkey\"\n            columns: [\"collection_id\"]\n            isOneToOne: false\n            referencedRelation: \"collections\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"collection_workspaces_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"collection_workspaces_workspace_id_fkey\"\n            columns: [\"workspace_id\"]\n            isOneToOne: false\n            referencedRelation: \"workspaces\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      collections: {\n        Row: {\n          created_at: string\n          description: string\n          folder_id: string | null\n          id: string\n          name: string\n          sharing: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          created_at?: string\n          description: string\n          folder_id?: string | null\n          id?: string\n          name: string\n          sharing?: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          created_at?: string\n          description?: string\n          folder_id?: string | null\n          id?: string\n          name?: string\n          sharing?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"collections_folder_id_fkey\"\n            columns: [\"folder_id\"]\n            isOneToOne: false\n            referencedRelation: \"folders\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"collections_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      file_items: {\n        Row: {\n          content: string\n          created_at: string\n          file_id: string\n          id: string\n          local_embedding: string | null\n          openai_embedding: string | null\n          sharing: string\n          tokens: number\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          content: string\n          created_at?: string\n          file_id: string\n          id?: string\n          local_embedding?: string | null\n          openai_embedding?: string | null\n          sharing?: string\n          tokens: number\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          content?: string\n          created_at?: string\n          file_id?: string\n          id?: string\n          local_embedding?: string | null\n          openai_embedding?: string | null\n          sharing?: string\n          tokens?: number\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"file_items_file_id_fkey\"\n            columns: [\"file_id\"]\n            isOneToOne: false\n            referencedRelation: \"files\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"file_items_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      file_workspaces: {\n        Row: {\n          created_at: string\n          file_id: string\n          updated_at: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Insert: {\n          created_at?: string\n          file_id: string\n          updated_at?: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Update: {\n          created_at?: string\n          file_id?: string\n          updated_at?: string | null\n          user_id?: string\n          workspace_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"file_workspaces_file_id_fkey\"\n            columns: [\"file_id\"]\n            isOneToOne: false\n            referencedRelation: \"files\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"file_workspaces_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"file_workspaces_workspace_id_fkey\"\n            columns: [\"workspace_id\"]\n            isOneToOne: false\n            referencedRelation: \"workspaces\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      files: {\n        Row: {\n          created_at: string\n          description: string\n          file_path: string\n          folder_id: string | null\n          id: string\n          name: string\n          sharing: string\n          size: number\n          tokens: number\n          type: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          created_at?: string\n          description: string\n          file_path: string\n          folder_id?: string | null\n          id?: string\n          name: string\n          sharing?: string\n          size: number\n          tokens: number\n          type: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          created_at?: string\n          description?: string\n          file_path?: string\n          folder_id?: string | null\n          id?: string\n          name?: string\n          sharing?: string\n          size?: number\n          tokens?: number\n          type?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"files_folder_id_fkey\"\n            columns: [\"folder_id\"]\n            isOneToOne: false\n            referencedRelation: \"folders\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"files_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      folders: {\n        Row: {\n          created_at: string\n          description: string\n          id: string\n          name: string\n          type: string\n          updated_at: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Insert: {\n          created_at?: string\n          description: string\n          id?: string\n          name: string\n          type: string\n          updated_at?: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Update: {\n          created_at?: string\n          description?: string\n          id?: string\n          name?: string\n          type?: string\n          updated_at?: string | null\n          user_id?: string\n          workspace_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"folders_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"folders_workspace_id_fkey\"\n            columns: [\"workspace_id\"]\n            isOneToOne: false\n            referencedRelation: \"workspaces\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      message_file_items: {\n        Row: {\n          created_at: string\n          file_item_id: string\n          message_id: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          created_at?: string\n          file_item_id: string\n          message_id: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          created_at?: string\n          file_item_id?: string\n          message_id?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"message_file_items_file_item_id_fkey\"\n            columns: [\"file_item_id\"]\n            isOneToOne: false\n            referencedRelation: \"file_items\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"message_file_items_message_id_fkey\"\n            columns: [\"message_id\"]\n            isOneToOne: false\n            referencedRelation: \"messages\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"message_file_items_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      messages: {\n        Row: {\n          assistant_id: string | null\n          chat_id: string\n          content: string\n          created_at: string\n          id: string\n          image_paths: string[]\n          model: string\n          role: string\n          sequence_number: number\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          assistant_id?: string | null\n          chat_id: string\n          content: string\n          created_at?: string\n          id?: string\n          image_paths: string[]\n          model: string\n          role: string\n          sequence_number: number\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          assistant_id?: string | null\n          chat_id?: string\n          content?: string\n          created_at?: string\n          id?: string\n          image_paths?: string[]\n          model?: string\n          role?: string\n          sequence_number?: number\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"messages_assistant_id_fkey\"\n            columns: [\"assistant_id\"]\n            isOneToOne: false\n            referencedRelation: \"assistants\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"messages_chat_id_fkey\"\n            columns: [\"chat_id\"]\n            isOneToOne: false\n            referencedRelation: \"chats\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"messages_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      model_workspaces: {\n        Row: {\n          created_at: string\n          model_id: string\n          updated_at: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Insert: {\n          created_at?: string\n          model_id: string\n          updated_at?: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Update: {\n          created_at?: string\n          model_id?: string\n          updated_at?: string | null\n          user_id?: string\n          workspace_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"model_workspaces_model_id_fkey\"\n            columns: [\"model_id\"]\n            isOneToOne: false\n            referencedRelation: \"models\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"model_workspaces_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"model_workspaces_workspace_id_fkey\"\n            columns: [\"workspace_id\"]\n            isOneToOne: false\n            referencedRelation: \"workspaces\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      models: {\n        Row: {\n          api_key: string\n          base_url: string\n          context_length: number\n          created_at: string\n          description: string\n          folder_id: string | null\n          id: string\n          model_id: string\n          name: string\n          sharing: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          api_key: string\n          base_url: string\n          context_length?: number\n          created_at?: string\n          description: string\n          folder_id?: string | null\n          id?: string\n          model_id: string\n          name: string\n          sharing?: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          api_key?: string\n          base_url?: string\n          context_length?: number\n          created_at?: string\n          description?: string\n          folder_id?: string | null\n          id?: string\n          model_id?: string\n          name?: string\n          sharing?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"models_folder_id_fkey\"\n            columns: [\"folder_id\"]\n            isOneToOne: false\n            referencedRelation: \"folders\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"models_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      preset_workspaces: {\n        Row: {\n          created_at: string\n          preset_id: string\n          updated_at: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Insert: {\n          created_at?: string\n          preset_id: string\n          updated_at?: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Update: {\n          created_at?: string\n          preset_id?: string\n          updated_at?: string | null\n          user_id?: string\n          workspace_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"preset_workspaces_preset_id_fkey\"\n            columns: [\"preset_id\"]\n            isOneToOne: false\n            referencedRelation: \"presets\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"preset_workspaces_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"preset_workspaces_workspace_id_fkey\"\n            columns: [\"workspace_id\"]\n            isOneToOne: false\n            referencedRelation: \"workspaces\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      presets: {\n        Row: {\n          context_length: number\n          created_at: string\n          description: string\n          embeddings_provider: string\n          folder_id: string | null\n          id: string\n          include_profile_context: boolean\n          include_workspace_instructions: boolean\n          model: string\n          name: string\n          prompt: string\n          sharing: string\n          temperature: number\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          context_length: number\n          created_at?: string\n          description: string\n          embeddings_provider: string\n          folder_id?: string | null\n          id?: string\n          include_profile_context: boolean\n          include_workspace_instructions: boolean\n          model: string\n          name: string\n          prompt: string\n          sharing?: string\n          temperature: number\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          context_length?: number\n          created_at?: string\n          description?: string\n          embeddings_provider?: string\n          folder_id?: string | null\n          id?: string\n          include_profile_context?: boolean\n          include_workspace_instructions?: boolean\n          model?: string\n          name?: string\n          prompt?: string\n          sharing?: string\n          temperature?: number\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"presets_folder_id_fkey\"\n            columns: [\"folder_id\"]\n            isOneToOne: false\n            referencedRelation: \"folders\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"presets_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      profiles: {\n        Row: {\n          anthropic_api_key: string | null\n          azure_openai_35_turbo_id: string | null\n          azure_openai_45_turbo_id: string | null\n          azure_openai_45_vision_id: string | null\n          azure_openai_api_key: string | null\n          azure_openai_embeddings_id: string | null\n          azure_openai_endpoint: string | null\n          bio: string\n          created_at: string\n          display_name: string\n          google_gemini_api_key: string | null\n          groq_api_key: string | null\n          has_onboarded: boolean\n          id: string\n          image_path: string\n          image_url: string\n          mistral_api_key: string | null\n          openai_api_key: string | null\n          openai_organization_id: string | null\n          openrouter_api_key: string | null\n          perplexity_api_key: string | null\n          profile_context: string\n          updated_at: string | null\n          use_azure_openai: boolean\n          user_id: string\n          username: string\n        }\n        Insert: {\n          anthropic_api_key?: string | null\n          azure_openai_35_turbo_id?: string | null\n          azure_openai_45_turbo_id?: string | null\n          azure_openai_45_vision_id?: string | null\n          azure_openai_api_key?: string | null\n          azure_openai_embeddings_id?: string | null\n          azure_openai_endpoint?: string | null\n          bio: string\n          created_at?: string\n          display_name: string\n          google_gemini_api_key?: string | null\n          groq_api_key?: string | null\n          has_onboarded?: boolean\n          id?: string\n          image_path: string\n          image_url: string\n          mistral_api_key?: string | null\n          openai_api_key?: string | null\n          openai_organization_id?: string | null\n          openrouter_api_key?: string | null\n          perplexity_api_key?: string | null\n          profile_context: string\n          updated_at?: string | null\n          use_azure_openai: boolean\n          user_id: string\n          username: string\n        }\n        Update: {\n          anthropic_api_key?: string | null\n          azure_openai_35_turbo_id?: string | null\n          azure_openai_45_turbo_id?: string | null\n          azure_openai_45_vision_id?: string | null\n          azure_openai_api_key?: string | null\n          azure_openai_embeddings_id?: string | null\n          azure_openai_endpoint?: string | null\n          bio?: string\n          created_at?: string\n          display_name?: string\n          google_gemini_api_key?: string | null\n          groq_api_key?: string | null\n          has_onboarded?: boolean\n          id?: string\n          image_path?: string\n          image_url?: string\n          mistral_api_key?: string | null\n          openai_api_key?: string | null\n          openai_organization_id?: string | null\n          openrouter_api_key?: string | null\n          perplexity_api_key?: string | null\n          profile_context?: string\n          updated_at?: string | null\n          use_azure_openai?: boolean\n          user_id?: string\n          username?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"profiles_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: true\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      prompt_workspaces: {\n        Row: {\n          created_at: string\n          prompt_id: string\n          updated_at: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Insert: {\n          created_at?: string\n          prompt_id: string\n          updated_at?: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Update: {\n          created_at?: string\n          prompt_id?: string\n          updated_at?: string | null\n          user_id?: string\n          workspace_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"prompt_workspaces_prompt_id_fkey\"\n            columns: [\"prompt_id\"]\n            isOneToOne: false\n            referencedRelation: \"prompts\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"prompt_workspaces_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"prompt_workspaces_workspace_id_fkey\"\n            columns: [\"workspace_id\"]\n            isOneToOne: false\n            referencedRelation: \"workspaces\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      prompts: {\n        Row: {\n          content: string\n          created_at: string\n          folder_id: string | null\n          id: string\n          name: string\n          sharing: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          content: string\n          created_at?: string\n          folder_id?: string | null\n          id?: string\n          name: string\n          sharing?: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          content?: string\n          created_at?: string\n          folder_id?: string | null\n          id?: string\n          name?: string\n          sharing?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"prompts_folder_id_fkey\"\n            columns: [\"folder_id\"]\n            isOneToOne: false\n            referencedRelation: \"folders\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"prompts_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      tool_workspaces: {\n        Row: {\n          created_at: string\n          tool_id: string\n          updated_at: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Insert: {\n          created_at?: string\n          tool_id: string\n          updated_at?: string | null\n          user_id: string\n          workspace_id: string\n        }\n        Update: {\n          created_at?: string\n          tool_id?: string\n          updated_at?: string | null\n          user_id?: string\n          workspace_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"tool_workspaces_tool_id_fkey\"\n            columns: [\"tool_id\"]\n            isOneToOne: false\n            referencedRelation: \"tools\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"tool_workspaces_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"tool_workspaces_workspace_id_fkey\"\n            columns: [\"workspace_id\"]\n            isOneToOne: false\n            referencedRelation: \"workspaces\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      tools: {\n        Row: {\n          created_at: string\n          custom_headers: Json\n          description: string\n          folder_id: string | null\n          id: string\n          name: string\n          schema: Json\n          sharing: string\n          updated_at: string | null\n          url: string\n          user_id: string\n        }\n        Insert: {\n          created_at?: string\n          custom_headers?: Json\n          description: string\n          folder_id?: string | null\n          id?: string\n          name: string\n          schema?: Json\n          sharing?: string\n          updated_at?: string | null\n          url: string\n          user_id: string\n        }\n        Update: {\n          created_at?: string\n          custom_headers?: Json\n          description?: string\n          folder_id?: string | null\n          id?: string\n          name?: string\n          schema?: Json\n          sharing?: string\n          updated_at?: string | null\n          url?: string\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"tools_folder_id_fkey\"\n            columns: [\"folder_id\"]\n            isOneToOne: false\n            referencedRelation: \"folders\"\n            referencedColumns: [\"id\"]\n          },\n          {\n            foreignKeyName: \"tools_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n      workspaces: {\n        Row: {\n          created_at: string\n          default_context_length: number\n          default_model: string\n          default_prompt: string\n          default_temperature: number\n          description: string\n          embeddings_provider: string\n          id: string\n          image_path: string\n          include_profile_context: boolean\n          include_workspace_instructions: boolean\n          instructions: string\n          is_home: boolean\n          name: string\n          sharing: string\n          updated_at: string | null\n          user_id: string\n        }\n        Insert: {\n          created_at?: string\n          default_context_length: number\n          default_model: string\n          default_prompt: string\n          default_temperature: number\n          description: string\n          embeddings_provider: string\n          id?: string\n          image_path?: string\n          include_profile_context: boolean\n          include_workspace_instructions: boolean\n          instructions: string\n          is_home?: boolean\n          name: string\n          sharing?: string\n          updated_at?: string | null\n          user_id: string\n        }\n        Update: {\n          created_at?: string\n          default_context_length?: number\n          default_model?: string\n          default_prompt?: string\n          default_temperature?: number\n          description?: string\n          embeddings_provider?: string\n          id?: string\n          image_path?: string\n          include_profile_context?: boolean\n          include_workspace_instructions?: boolean\n          instructions?: string\n          is_home?: boolean\n          name?: string\n          sharing?: string\n          updated_at?: string | null\n          user_id?: string\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"workspaces_user_id_fkey\"\n            columns: [\"user_id\"]\n            isOneToOne: false\n            referencedRelation: \"users\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n    }\n    Views: {\n      [_ in never]: never\n    }\n    Functions: {\n      create_duplicate_messages_for_new_chat: {\n        Args: {\n          old_chat_id: string\n          new_chat_id: string\n          new_user_id: string\n        }\n        Returns: undefined\n      }\n      delete_message_including_and_after: {\n        Args: {\n          p_user_id: string\n          p_chat_id: string\n          p_sequence_number: number\n        }\n        Returns: undefined\n      }\n      delete_messages_including_and_after: {\n        Args: {\n          p_user_id: string\n          p_chat_id: string\n          p_sequence_number: number\n        }\n        Returns: undefined\n      }\n      delete_storage_object: {\n        Args: {\n          bucket: string\n          object: string\n        }\n        Returns: Record<string, unknown>\n      }\n      delete_storage_object_from_bucket: {\n        Args: {\n          bucket_name: string\n          object_path: string\n        }\n        Returns: Record<string, unknown>\n      }\n      match_file_items_local: {\n        Args: {\n          query_embedding: string\n          match_count?: number\n          file_ids?: string[]\n        }\n        Returns: {\n          id: string\n          file_id: string\n          content: string\n          tokens: number\n          similarity: number\n        }[]\n      }\n      match_file_items_openai: {\n        Args: {\n          query_embedding: string\n          match_count?: number\n          file_ids?: string[]\n        }\n        Returns: {\n          id: string\n          file_id: string\n          content: string\n          tokens: number\n          similarity: number\n        }[]\n      }\n      non_private_assistant_exists: {\n        Args: {\n          p_name: string\n        }\n        Returns: boolean\n      }\n      non_private_file_exists: {\n        Args: {\n          p_name: string\n        }\n        Returns: boolean\n      }\n      non_private_workspace_exists: {\n        Args: {\n          p_name: string\n        }\n        Returns: boolean\n      }\n    }\n    Enums: {\n      [_ in never]: never\n    }\n    CompositeTypes: {\n      [_ in never]: never\n    }\n  }\n  storage: {\n    Tables: {\n      buckets: {\n        Row: {\n          allowed_mime_types: string[] | null\n          avif_autodetection: boolean | null\n          created_at: string | null\n          file_size_limit: number | null\n          id: string\n          name: string\n          owner: string | null\n          owner_id: string | null\n          public: boolean | null\n          updated_at: string | null\n        }\n        Insert: {\n          allowed_mime_types?: string[] | null\n          avif_autodetection?: boolean | null\n          created_at?: string | null\n          file_size_limit?: number | null\n          id: string\n          name: string\n          owner?: string | null\n          owner_id?: string | null\n          public?: boolean | null\n          updated_at?: string | null\n        }\n        Update: {\n          allowed_mime_types?: string[] | null\n          avif_autodetection?: boolean | null\n          created_at?: string | null\n          file_size_limit?: number | null\n          id?: string\n          name?: string\n          owner?: string | null\n          owner_id?: string | null\n          public?: boolean | null\n          updated_at?: string | null\n        }\n        Relationships: []\n      }\n      migrations: {\n        Row: {\n          executed_at: string | null\n          hash: string\n          id: number\n          name: string\n        }\n        Insert: {\n          executed_at?: string | null\n          hash: string\n          id: number\n          name: string\n        }\n        Update: {\n          executed_at?: string | null\n          hash?: string\n          id?: number\n          name?: string\n        }\n        Relationships: []\n      }\n      objects: {\n        Row: {\n          bucket_id: string | null\n          created_at: string | null\n          id: string\n          last_accessed_at: string | null\n          metadata: Json | null\n          name: string | null\n          owner: string | null\n          owner_id: string | null\n          path_tokens: string[] | null\n          updated_at: string | null\n          version: string | null\n        }\n        Insert: {\n          bucket_id?: string | null\n          created_at?: string | null\n          id?: string\n          last_accessed_at?: string | null\n          metadata?: Json | null\n          name?: string | null\n          owner?: string | null\n          owner_id?: string | null\n          path_tokens?: string[] | null\n          updated_at?: string | null\n          version?: string | null\n        }\n        Update: {\n          bucket_id?: string | null\n          created_at?: string | null\n          id?: string\n          last_accessed_at?: string | null\n          metadata?: Json | null\n          name?: string | null\n          owner?: string | null\n          owner_id?: string | null\n          path_tokens?: string[] | null\n          updated_at?: string | null\n          version?: string | null\n        }\n        Relationships: [\n          {\n            foreignKeyName: \"objects_bucketId_fkey\"\n            columns: [\"bucket_id\"]\n            isOneToOne: false\n            referencedRelation: \"buckets\"\n            referencedColumns: [\"id\"]\n          },\n        ]\n      }\n    }\n    Views: {\n      [_ in never]: never\n    }\n    Functions: {\n      can_insert_object: {\n        Args: {\n          bucketid: string\n          name: string\n          owner: string\n          metadata: Json\n        }\n        Returns: undefined\n      }\n      extension: {\n        Args: {\n          name: string\n        }\n        Returns: string\n      }\n      filename: {\n        Args: {\n          name: string\n        }\n        Returns: string\n      }\n      foldername: {\n        Args: {\n          name: string\n        }\n        Returns: string[]\n      }\n      get_size_by_bucket: {\n        Args: Record<PropertyKey, never>\n        Returns: {\n          size: number\n          bucket_id: string\n        }[]\n      }\n      search: {\n        Args: {\n          prefix: string\n          bucketname: string\n          limits?: number\n          levels?: number\n          offsets?: number\n          search?: string\n          sortcolumn?: string\n          sortorder?: string\n        }\n        Returns: {\n          name: string\n          id: string\n          updated_at: string\n          created_at: string\n          last_accessed_at: string\n          metadata: Json\n        }[]\n      }\n    }\n    Enums: {\n      [_ in never]: never\n    }\n    CompositeTypes: {\n      [_ in never]: never\n    }\n  }\n}\n\ntype PublicSchema = Database[Extract<keyof Database, \"public\">]\n\nexport type Tables<\n  PublicTableNameOrOptions extends\n    | keyof (PublicSchema[\"Tables\"] & PublicSchema[\"Views\"])\n    | { schema: keyof Database },\n  TableName extends PublicTableNameOrOptions extends { schema: keyof Database }\n    ? keyof (Database[PublicTableNameOrOptions[\"schema\"]][\"Tables\"] &\n        Database[PublicTableNameOrOptions[\"schema\"]][\"Views\"])\n    : never = never,\n> = PublicTableNameOrOptions extends { schema: keyof Database }\n  ? (Database[PublicTableNameOrOptions[\"schema\"]][\"Tables\"] &\n      Database[PublicTableNameOrOptions[\"schema\"]][\"Views\"])[TableName] extends {\n      Row: infer R\n    }\n    ? R\n    : never\n  : PublicTableNameOrOptions extends keyof (PublicSchema[\"Tables\"] &\n        PublicSchema[\"Views\"])\n    ? (PublicSchema[\"Tables\"] &\n        PublicSchema[\"Views\"])[PublicTableNameOrOptions] extends {\n        Row: infer R\n      }\n      ? R\n      : never\n    : never\n\nexport type TablesInsert<\n  PublicTableNameOrOptions extends\n    | keyof PublicSchema[\"Tables\"]\n    | { schema: keyof Database },\n  TableName extends PublicTableNameOrOptions extends { schema: keyof Database }\n    ? keyof Database[PublicTableNameOrOptions[\"schema\"]][\"Tables\"]\n    : never = never,\n> = PublicTableNameOrOptions extends { schema: keyof Database }\n  ? Database[PublicTableNameOrOptions[\"schema\"]][\"Tables\"][TableName] extends {\n      Insert: infer I\n    }\n    ? I\n    : never\n  : PublicTableNameOrOptions extends keyof PublicSchema[\"Tables\"]\n    ? PublicSchema[\"Tables\"][PublicTableNameOrOptions] extends {\n        Insert: infer I\n      }\n      ? I\n      : never\n    : never\n\nexport type TablesUpdate<\n  PublicTableNameOrOptions extends\n    | keyof PublicSchema[\"Tables\"]\n    | { schema: keyof Database },\n  TableName extends PublicTableNameOrOptions extends { schema: keyof Database }\n    ? keyof Database[PublicTableNameOrOptions[\"schema\"]][\"Tables\"]\n    : never = never,\n> = PublicTableNameOrOptions extends { schema: keyof Database }\n  ? Database[PublicTableNameOrOptions[\"schema\"]][\"Tables\"][TableName] extends {\n      Update: infer U\n    }\n    ? U\n    : never\n  : PublicTableNameOrOptions extends keyof PublicSchema[\"Tables\"]\n    ? PublicSchema[\"Tables\"][PublicTableNameOrOptions] extends {\n        Update: infer U\n      }\n      ? U\n      : never\n    : never\n\nexport type Enums<\n  PublicEnumNameOrOptions extends\n    | keyof PublicSchema[\"Enums\"]\n    | { schema: keyof Database },\n  EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }\n    ? keyof Database[PublicEnumNameOrOptions[\"schema\"]][\"Enums\"]\n    : never = never,\n> = PublicEnumNameOrOptions extends { schema: keyof Database }\n  ? Database[PublicEnumNameOrOptions[\"schema\"]][\"Enums\"][EnumName]\n  : PublicEnumNameOrOptions extends keyof PublicSchema[\"Enums\"]\n    ? PublicSchema[\"Enums\"][PublicEnumNameOrOptions]\n    : never\n\n"
  },
  {
    "path": "tailwind.config.ts",
    "content": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  darkMode: ['class'],\n  content: [\n    './pages/**/*.{ts,tsx}',\n    './components/**/*.{ts,tsx}',\n    './app/**/*.{ts,tsx}',\n    './src/**/*.{ts,tsx}'\n  ],\n  theme: {\n    container: {\n      center: true,\n      padding: '2rem',\n      screens: {\n        '2xl': '1400px'\n      }\n    },\n    extend: {\n      colors: {\n        border: 'hsl(var(--border))',\n        input: 'hsl(var(--input))',\n        ring: 'hsl(var(--ring))',\n        background: 'hsl(var(--background))',\n        foreground: 'hsl(var(--foreground))',\n        primary: {\n          DEFAULT: 'hsl(var(--primary))',\n          foreground: 'hsl(var(--primary-foreground))'\n        },\n        secondary: {\n          DEFAULT: 'hsl(var(--secondary))',\n          foreground: 'hsl(var(--secondary-foreground))'\n        },\n        destructive: {\n          DEFAULT: 'hsl(var(--destructive))',\n          foreground: 'hsl(var(--destructive-foreground))'\n        },\n        muted: {\n          DEFAULT: 'hsl(var(--muted))',\n          foreground: 'hsl(var(--muted-foreground))'\n        },\n        accent: {\n          DEFAULT: 'hsl(var(--accent))',\n          foreground: 'hsl(var(--accent-foreground))'\n        },\n        popover: {\n          DEFAULT: 'hsl(var(--popover))',\n          foreground: 'hsl(var(--popover-foreground))'\n        },\n        card: {\n          DEFAULT: 'hsl(var(--card))',\n          foreground: 'hsl(var(--card-foreground))'\n        }\n      },\n      borderRadius: {\n        lg: 'var(--radius)',\n        md: 'calc(var(--radius) - 2px)',\n        sm: 'calc(var(--radius) - 4px)'\n      },\n      keyframes: {\n        'accordion-down': {\n          from: { height: 0 },\n          to: { height: 'var(--radix-accordion-content-height)' }\n        },\n        'accordion-up': {\n          from: { height: 'var(--radix-accordion-content-height)' },\n          to: { height: 0 }\n        }\n      },\n      animation: {\n        'accordion-down': 'accordion-down 0.2s ease-out',\n        'accordion-up': 'accordion-up 0.2s ease-out'\n      }\n    }\n  },\n  plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \"./.next/types/**/*.ts\",\n    \"i18nConfig.js\"\n  ],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "types/announcement.ts",
    "content": "export interface Announcement {\n  id: string\n  title: string\n  content: string\n  read: boolean\n  link: string\n  date: string\n}\n"
  },
  {
    "path": "types/assistant-retrieval-item.ts",
    "content": "export interface AssistantRetrievalItem {\n  id: string\n  name: string\n  type: string\n}\n"
  },
  {
    "path": "types/chat-file.tsx",
    "content": "export interface ChatFile {\n  id: string\n  name: string\n  type: string\n  file: File | null\n}\n"
  },
  {
    "path": "types/chat-message.ts",
    "content": "import { Tables } from \"@/supabase/types\"\n\nexport interface ChatMessage {\n  message: Tables<\"messages\">\n  fileItems: string[]\n}\n"
  },
  {
    "path": "types/chat.ts",
    "content": "import { Tables } from \"@/supabase/types\"\nimport { ChatMessage, LLMID } from \".\"\n\nexport interface ChatSettings {\n  model: LLMID\n  prompt: string\n  temperature: number\n  contextLength: number\n  includeProfileContext: boolean\n  includeWorkspaceInstructions: boolean\n  embeddingsProvider: \"openai\" | \"local\"\n}\n\nexport interface ChatPayload {\n  chatSettings: ChatSettings\n  workspaceInstructions: string\n  chatMessages: ChatMessage[]\n  assistant: Tables<\"assistants\"> | null\n  messageFileItems: Tables<\"file_items\">[]\n  chatFileItems: Tables<\"file_items\">[]\n}\n\nexport interface ChatAPIPayload {\n  chatSettings: ChatSettings\n  messages: Tables<\"messages\">[]\n}\n"
  },
  {
    "path": "types/collection-file.ts",
    "content": "export interface CollectionFile {\n  id: string\n  name: string\n  type: string\n}\n"
  },
  {
    "path": "types/content-type.ts",
    "content": "export type ContentType =\n  | \"chats\"\n  | \"presets\"\n  | \"prompts\"\n  | \"files\"\n  | \"collections\"\n  | \"assistants\"\n  | \"tools\"\n  | \"models\"\n"
  },
  {
    "path": "types/error-response.ts",
    "content": "import { z } from \"zod\"\n\nexport type ErrorResponse = {\n  error: {\n    code: number\n    message: string\n  }\n}\n\nexport const ErrorResponseSchema = z.object({\n  error: z.object({\n    code: z.number({ coerce: true }).default(500),\n    message: z.string().default(\"Internal Server Error\")\n  })\n})\n"
  },
  {
    "path": "types/file-item-chunk.ts",
    "content": "export type FileItemChunk = {\n  content: string\n  tokens: number\n}\n"
  },
  {
    "path": "types/images/assistant-image.ts",
    "content": "export interface AssistantImage {\n  assistantId: string\n  path: string\n  base64: any // base64 image\n  url: string\n}\n"
  },
  {
    "path": "types/images/message-image.ts",
    "content": "export interface MessageImage {\n  messageId: string\n  path: string\n  base64: any // base64 image\n  url: string\n  file: File | null\n}\n"
  },
  {
    "path": "types/images/workspace-image.ts",
    "content": "export interface WorkspaceImage {\n  workspaceId: string\n  path: string\n  base64: any // base64 image\n  url: string\n}\n"
  },
  {
    "path": "types/index.ts",
    "content": "export * from \"./announcement\"\nexport * from \"./assistant-retrieval-item\"\nexport * from \"./chat\"\nexport * from \"./chat-file\"\nexport * from \"./chat-message\"\nexport * from \"./collection-file\"\nexport * from \"./content-type\"\nexport * from \"./file-item-chunk\"\nexport * from \"./images/assistant-image\"\nexport * from \"./images/message-image\"\nexport * from \"./images/workspace-image\"\nexport * from \"./llms\"\nexport * from \"./models\"\nexport * from \"./sharing\"\nexport * from \"./sidebar-data\"\n"
  },
  {
    "path": "types/key-type.ts",
    "content": "export type EnvKey =\n  | \"OPENAI_API_KEY\"\n  | \"ANTHROPIC_API_KEY\"\n  | \"GOOGLE_GEMINI_API_KEY\"\n  | \"MISTRAL_API_KEY\"\n  | \"GROQ_API_KEY\"\n  | \"PERPLEXITY_API_KEY\"\n  | \"AZURE_OPENAI_API_KEY\"\n"
  },
  {
    "path": "types/llms.ts",
    "content": "import { ModelProvider } from \".\"\n\nexport type LLMID =\n  | OpenAILLMID\n  | GoogleLLMID\n  | AnthropicLLMID\n  | MistralLLMID\n  | GroqLLMID\n  | PerplexityLLMID\n\n// OpenAI Models (UPDATED 5/13/24)\nexport type OpenAILLMID =\n  | \"gpt-4o\" // GPT-4o\n  | \"gpt-4-turbo-preview\" // GPT-4 Turbo\n  | \"gpt-4-vision-preview\" // GPT-4 Vision\n  | \"gpt-4\" // GPT-4\n  | \"gpt-3.5-turbo\" // Updated GPT-3.5 Turbo\n\n// Google Models\nexport type GoogleLLMID =\n  | \"gemini-pro\" // Gemini Pro\n  | \"gemini-pro-vision\" // Gemini Pro Vision\n  | \"gemini-1.5-pro-latest\" // Gemini 1.5 Pro\n  | \"gemini-1.5-flash\" // Gemini 1.5 Flash\n\n// Anthropic Models\nexport type AnthropicLLMID =\n  | \"claude-2.1\" // Claude 2\n  | \"claude-instant-1.2\" // Claude Instant\n  | \"claude-3-haiku-20240307\" // Claude 3 Haiku\n  | \"claude-3-sonnet-20240229\" // Claude 3 Sonnet\n  | \"claude-3-opus-20240229\" // Claude 3 Opus\n  | \"claude-3-5-sonnet-20240620\" // Claude 3.5 Sonnet\n\n// Mistral Models\nexport type MistralLLMID =\n  | \"mistral-tiny\" // Mistral Tiny\n  | \"mistral-small-latest\" // Mistral Small\n  | \"mistral-medium-latest\" // Mistral Medium\n  | \"mistral-large-latest\" // Mistral Large\n\nexport type GroqLLMID =\n  | \"llama3-8b-8192\" // LLaMA3-8b\n  | \"llama3-70b-8192\" // LLaMA3-70b\n  | \"mixtral-8x7b-32768\" // Mixtral-8x7b\n  | \"gemma-7b-it\" // Gemma-7b IT\n\n// Perplexity Models (UPDATED 1/31/24)\nexport type PerplexityLLMID =\n  | \"pplx-7b-online\" // Perplexity Online 7B\n  | \"pplx-70b-online\" // Perplexity Online 70B\n  | \"pplx-7b-chat\" // Perplexity Chat 7B\n  | \"pplx-70b-chat\" // Perplexity Chat 70B\n  | \"mixtral-8x7b-instruct\" // Mixtral 8x7B Instruct\n  | \"mistral-7b-instruct\" // Mistral 7B Instruct\n  | \"llama-2-70b-chat\" // Llama2 70B Chat\n  | \"codellama-34b-instruct\" // CodeLlama 34B Instruct\n  | \"codellama-70b-instruct\" // CodeLlama 70B Instruct\n  | \"sonar-small-chat\" // Sonar Small Chat\n  | \"sonar-small-online\" // Sonar Small Online\n  | \"sonar-medium-chat\" // Sonar Medium Chat\n  | \"sonar-medium-online\" // Sonar Medium Online\n\nexport interface LLM {\n  modelId: LLMID\n  modelName: string\n  provider: ModelProvider\n  hostedId: string\n  platformLink: string\n  imageInput: boolean\n  pricing?: {\n    currency: string\n    unit: string\n    inputCost: number\n    outputCost?: number\n  }\n}\n\nexport interface OpenRouterLLM extends LLM {\n  maxContext: number\n}\n"
  },
  {
    "path": "types/models.ts",
    "content": "export type ModelProvider =\n  | \"openai\"\n  | \"google\"\n  | \"anthropic\"\n  | \"mistral\"\n  | \"groq\"\n  | \"perplexity\"\n  | \"ollama\"\n  | \"openrouter\"\n  | \"custom\"\n"
  },
  {
    "path": "types/sharing.ts",
    "content": "export type Sharing = \"private\" | \"public\" | \"unlisted\"\n"
  },
  {
    "path": "types/sidebar-data.ts",
    "content": "import { Tables } from \"@/supabase/types\"\n\nexport type DataListType =\n  | Tables<\"collections\">[]\n  | Tables<\"chats\">[]\n  | Tables<\"presets\">[]\n  | Tables<\"prompts\">[]\n  | Tables<\"files\">[]\n  | Tables<\"assistants\">[]\n  | Tables<\"tools\">[]\n  | Tables<\"models\">[]\n\nexport type DataItemType =\n  | Tables<\"collections\">\n  | Tables<\"chats\">\n  | Tables<\"presets\">\n  | Tables<\"prompts\">\n  | Tables<\"files\">\n  | Tables<\"assistants\">\n  | Tables<\"tools\">\n  | Tables<\"models\">\n"
  },
  {
    "path": "types/valid-keys.ts",
    "content": "export enum VALID_ENV_KEYS {\n  OPENAI_API_KEY = \"OPENAI_API_KEY\",\n  ANTHROPIC_API_KEY = \"ANTHROPIC_API_KEY\",\n  GOOGLE_GEMINI_API_KEY = \"GOOGLE_GEMINI_API_KEY\",\n  MISTRAL_API_KEY = \"MISTRAL_API_KEY\",\n  GROQ_API_KEY = \"GROQ_API_KEY\",\n  PERPLEXITY_API_KEY = \"PERPLEXITY_API_KEY\",\n  AZURE_OPENAI_API_KEY = \"AZURE_OPENAI_API_KEY\",\n  OPENROUTER_API_KEY = \"OPENROUTER_API_KEY\",\n\n  OPENAI_ORGANIZATION_ID = \"OPENAI_ORGANIZATION_ID\",\n\n  AZURE_OPENAI_ENDPOINT = \"AZURE_OPENAI_ENDPOINT\",\n  AZURE_GPT_35_TURBO_NAME = \"AZURE_GPT_35_TURBO_NAME\",\n  AZURE_GPT_45_VISION_NAME = \"AZURE_GPT_45_VISION_NAME\",\n  AZURE_GPT_45_TURBO_NAME = \"AZURE_GPT_45_TURBO_NAME\",\n  AZURE_EMBEDDINGS_NAME = \"AZURE_EMBEDDINGS_NAME\"\n}\n"
  },
  {
    "path": "worker/index.js",
    "content": "self.__WB_DISABLE_DEV_LOGS = true\n"
  }
]