Full Code of GeoRetina/chat2geo for AI

main 8186b6c97b9c cached
255 files
752.7 KB
194.3k tokens
389 symbols
1 requests
Download .txt
Showing preview only (827K chars total). Download the full file or copy to clipboard to get everything.
Repository: GeoRetina/chat2geo
Branch: main
Commit: 8186b6c97b9c
Files: 255
Total size: 752.7 KB

Directory structure:
gitextract_tqshek9r/

├── .eslintrc.json
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app/
│   ├── (auth)/
│   │   ├── api/
│   │   │   └── auth/
│   │   │       ├── confirm/
│   │   │       │   └── route.ts
│   │   │       └── esri/
│   │   │           ├── authorize/
│   │   │           │   └── route.ts
│   │   │           └── callback/
│   │   │               └── route.ts
│   │   ├── layout.tsx
│   │   ├── login/
│   │   │   ├── actions.ts
│   │   │   ├── layout.tsx
│   │   │   └── page.tsx
│   │   └── styles.css
│   ├── (broadcast)/
│   │   ├── api/
│   │   │   └── services/
│   │   │       └── esri/
│   │   │           ├── fetch-layers-list/
│   │   │           │   └── route.ts
│   │   │           └── fetch-selected-layer/
│   │   │               └── route.ts
│   │   ├── layout.tsx
│   │   ├── services/
│   │   │   └── esri/
│   │   │       └── fetch-layers/
│   │   │           └── page.tsx
│   │   └── styles.css
│   ├── (main)/
│   │   ├── actions/
│   │   │   └── get-user-profile.ts
│   │   ├── api/
│   │   │   ├── chat/
│   │   │   │   ├── chat-history/
│   │   │   │   │   └── route.ts
│   │   │   │   └── route.ts
│   │   │   ├── gee/
│   │   │   │   ├── request-geospatial-analysis/
│   │   │   │   │   └── route.ts
│   │   │   │   └── request-loading-geospatial-data/
│   │   │   │       └── route.ts
│   │   │   ├── sendfeedback/
│   │   │   │   └── route.ts
│   │   │   ├── services/
│   │   │   │   └── google-maps/
│   │   │   │       ├── basemaps/
│   │   │   │       │   ├── roadmap/
│   │   │   │       │   │   └── route.ts
│   │   │   │       │   └── satellite/
│   │   │   │       │       └── route.ts
│   │   │   │       ├── geocode/
│   │   │   │       │   └── route.ts
│   │   │   │       └── places/
│   │   │   │           └── route.ts
│   │   │   ├── user-usage/
│   │   │   │   └── route.ts
│   │   │   └── web-scraper/
│   │   │       └── route.ts
│   │   ├── chat/
│   │   │   └── [id]/
│   │   │       ├── layout.tsx
│   │   │       ├── loading.tsx
│   │   │       └── page.tsx
│   │   ├── chat-history/
│   │   │   ├── layout.tsx
│   │   │   ├── loading.tsx
│   │   │   └── page.tsx
│   │   ├── integrations/
│   │   │   ├── layout.tsx
│   │   │   ├── loading.tsx
│   │   │   └── page.tsx
│   │   ├── knowledge-base/
│   │   │   ├── layout.tsx
│   │   │   ├── loading.tsx
│   │   │   └── page.tsx
│   │   ├── layout.tsx
│   │   ├── loading.tsx
│   │   ├── page.tsx
│   │   └── styles.css
│   └── actions/
│       └── rag-actions.ts
├── components/
│   ├── changelog-modal.tsx
│   ├── client-hydrator.tsx
│   ├── client-wrapper.tsx
│   ├── document-viewer.tsx
│   ├── feedback.tsx
│   ├── loading-widgets/
│   │   ├── loading-for-widget.tsx
│   │   └── loading-primary.tsx
│   ├── main-sidebar/
│   │   ├── app-setttings.tsx
│   │   └── main-sidebar.tsx
│   ├── notices/
│   │   ├── privacy-policy.tsx
│   │   └── terms-of-services.tsx
│   ├── services/
│   │   └── esri/
│   │       └── add-arcgis-layers.tsx
│   ├── theme-provider.tsx
│   └── ui/
│       ├── alert-dialog.tsx
│       ├── alert.tsx
│       ├── badge.tsx
│       ├── button.tsx
│       ├── card.tsx
│       ├── checkbox.tsx
│       ├── confirmation-modal.tsx
│       ├── dialog.tsx
│       ├── dropdown-menu.tsx
│       ├── form.tsx
│       ├── input-text-confirm.tsx
│       ├── input.tsx
│       ├── label.tsx
│       ├── popover.tsx
│       ├── scroll-area.tsx
│       ├── select.tsx
│       ├── separator.tsx
│       ├── table.tsx
│       ├── textarea.tsx
│       ├── theme-mode-toggle.tsx
│       └── tooltip.tsx
├── components.json
├── custom-configs/
│   ├── ai-assistants.ts
│   ├── charts-config.ts
│   ├── integrations.ts
│   └── project-config.ts
├── db-schema/
│   └── schema.sql
├── features/
│   ├── charts/
│   │   ├── components/
│   │   │   ├── charts-display.tsx
│   │   │   └── charts.tsx
│   │   └── utils/
│   │       └── select-chart-type.ts
│   ├── chat/
│   │   ├── components/
│   │   │   ├── artifacts-sidebar/
│   │   │   │   └── artifacts-sidebar.tsx
│   │   │   ├── chat-response-box/
│   │   │   │   ├── capabilities-banner.tsx
│   │   │   │   ├── chat-message/
│   │   │   │   │   └── chat-message.tsx
│   │   │   │   ├── chat-response-box.tsx
│   │   │   │   └── in-response-tool-calling-results/
│   │   │   │       ├── display-in-chat-analysis-map/
│   │   │   │       │   └── display-in-chat-analysis-map-btn.tsx
│   │   │   │       ├── draft-report/
│   │   │   │       │   ├── draft-report.tsx
│   │   │   │       │   └── drafted-report-btn.tsx
│   │   │   │       ├── knowledge-base-citation/
│   │   │   │       │   └── citation-badge.tsx
│   │   │   │       └── tool-calling-results.tsx
│   │   │   ├── chat.tsx
│   │   │   ├── external-assets/
│   │   │   │   └── assets-modal.tsx
│   │   │   ├── input/
│   │   │   │   ├── chat-input-box.tsx
│   │   │   │   ├── chat-input-buttons/
│   │   │   │   │   ├── assistants-list-dropup-in-chat-input.tsx
│   │   │   │   │   ├── attachments-list-dropup-in-chat-input.tsx
│   │   │   │   │   ├── chat-input-buttons.tsx
│   │   │   │   │   ├── current-session-assets-dropup.tsx
│   │   │   │   │   ├── open-database-in-chat-input-btn.tsx
│   │   │   │   │   └── select-roi-on-map-btn.tsx
│   │   │   │   ├── chat-input-dropzone.tsx
│   │   │   │   ├── map-tools-dropup.tsx
│   │   │   │   └── slash-menu-for-map-layers.tsx
│   │   │   ├── text-analysis-suggestions/
│   │   │   │   └── text-analysis-suggestions.tsx
│   │   │   └── thumbnails-analysis-suggestions/
│   │   │       └── thumbnails-analysis-suggestions.tsx
│   │   ├── hooks/
│   │   │   └── use-slash-menu-map-layers-list.ts
│   │   ├── stores/
│   │   │   ├── use-attachments-store.ts
│   │   │   ├── use-chat-response-sources-store.ts
│   │   │   └── use-drafted-report-store.ts
│   │   ├── ui/
│   │   │   └── fadeIn-with-delay.tsx
│   │   └── utils/
│   │       ├── drag-and-drop-file-analyzer.ts
│   │       ├── general-utils.ts
│   │       ├── slash-menu-utils.ts
│   │       ├── tool-calling-results-validation.ts
│   │       ├── use-Textarea-resize.ts
│   │       ├── use-drag-and-drop-file-import.ts
│   │       └── use-slash-command-menu.ts
│   ├── chat-history/
│   │   └── components/
│   │       ├── chat-history-row.tsx
│   │       ├── chat-history-table-skeleton.tsx
│   │       ├── chat-history-table.tsx
│   │       ├── chat-history.tsx
│   │       └── indeterminate-checkbox.tsx
│   ├── integrations/
│   │   └── components/
│   │       ├── integration-actions.tsx
│   │       ├── integration-header.tsx
│   │       ├── integration-item.tsx
│   │       ├── integration-list.tsx
│   │       ├── integration-status.tsx
│   │       └── integrations-page.tsx
│   ├── knowledge-base/
│   │   ├── actions/
│   │   │   └── document-actions.ts
│   │   ├── components/
│   │   │   ├── add-group-modal.tsx
│   │   │   ├── documents-table.tsx
│   │   │   ├── edit-document-modal.tsx
│   │   │   ├── knolwedge-base.tsx
│   │   │   ├── knowledge-base-sidebar.tsx
│   │   │   └── max-docs-alert-dialog.tsx
│   │   ├── lib/
│   │   │   └── generate-embeddings.ts
│   │   └── utils/
│   │       └── transform-metadata-to-citation.ts
│   ├── maps/
│   │   ├── components/
│   │   │   ├── address-search.tsx
│   │   │   ├── attribute-table/
│   │   │   │   ├── attribute-table-controls.tsx
│   │   │   │   └── attribute-table.tsx
│   │   │   ├── map-badge.tsx
│   │   │   ├── map-container.tsx
│   │   │   ├── map-custom-controls/
│   │   │   │   ├── map-custom-controls.tsx
│   │   │   │   └── map-roi-controls.tsx
│   │   │   └── map-panels/
│   │   │       ├── map-chart-panel/
│   │   │       │   └── map-chart-panel.tsx
│   │   │       └── map-layers-panel/
│   │   │           ├── color-picker-popover.tsx
│   │   │           ├── map-layers-panel.tsx
│   │   │           └── map-legend.tsx
│   │   ├── hooks/
│   │   │   ├── use-handle-click/
│   │   │   │   ├── use-handle-click.ts
│   │   │   │   ├── use-map-controls.ts
│   │   │   │   ├── use-query-drawing.ts
│   │   │   │   ├── use-remove-query-features.ts
│   │   │   │   └── use-roi-drawing.ts
│   │   │   ├── use-map/
│   │   │   │   ├── use-add-arcgis-layers.ts
│   │   │   │   ├── use-add-attached-layers.ts
│   │   │   │   ├── use-add-gee-layers.ts
│   │   │   │   ├── use-add-roi-from-session.ts
│   │   │   │   ├── use-basemap-toggle.ts
│   │   │   │   ├── use-map-initialization.ts
│   │   │   │   ├── use-map.ts
│   │   │   │   ├── use-update-layer-style.ts
│   │   │   │   └── use-zoom-to-geometry.ts
│   │   │   └── use-map-cursor.ts
│   │   ├── stores/
│   │   │   ├── map-queries-stores/
│   │   │   │   ├── useQueryOutputReadyFromVectorLayerStore.ts
│   │   │   │   ├── useQueryRasterFromVectorLayerStore.ts
│   │   │   │   └── useQueryReadyStore.ts
│   │   │   ├── plots-stores/
│   │   │   │   ├── useChartRequestedTypeStore.ts
│   │   │   │   ├── usePlotReadyDataStore.ts
│   │   │   │   └── usePlotReadyFromVectorLayerStore.ts
│   │   │   ├── use-agol-layers-store.ts
│   │   │   ├── use-color-picker-store.ts
│   │   │   ├── use-cursor-store.ts
│   │   │   ├── use-drawn-feature-on-map-store.ts
│   │   │   ├── use-function-store.ts
│   │   │   ├── use-gee-ouput-store.ts
│   │   │   ├── use-geojson-store.ts
│   │   │   ├── use-layer-selection-store.ts
│   │   │   ├── use-map-badge-store.ts
│   │   │   ├── use-map-display-store.ts
│   │   │   ├── use-map-layer-store.ts
│   │   │   ├── use-map-legend-store.ts
│   │   │   ├── use-map-zoom-request-store.ts
│   │   │   ├── use-roi-store.ts
│   │   │   └── use-table-store.ts
│   │   └── utils/
│   │       ├── add-drawn-layer-to-map.ts
│   │       ├── add-gee-layer-to-map.ts
│   │       ├── add-geocoded-point-to-map.ts
│   │       ├── add-layers-to-map/
│   │       │   └── addGeojsonLayer.ts
│   │       ├── add-roi-layer-to-map.ts
│   │       ├── authentication-utils/
│   │       │   └── gee-auth.ts
│   │       ├── gee-eval-utils.ts
│   │       ├── general-checks.ts
│   │       ├── geometry-utils.ts
│   │       ├── initialize-map.ts
│   │       ├── other-utils.ts
│   │       ├── setup-map-attributions.ts
│   │       └── type-guards.ts
│   ├── text-editor/
│   │   ├── components/
│   │   │   ├── dynamic-text-editor.tsx
│   │   │   └── text-editor.tsx
│   │   └── schema/
│   │       └── text-editor-schema.tsx
│   ├── ui/
│   │   ├── modals/
│   │   │   └── file-upload-modal.tsx
│   │   └── toast-message.tsx
│   └── user-profile/
│       └── components/
│           └── user-profile-modal.tsx
├── hooks/
│   └── docs-hooks/
│       ├── use-document-upload.ts
│       └── use-document-viewer.ts
├── lib/
│   ├── auth.ts
│   ├── changelog.ts
│   ├── database/
│   │   ├── chat/
│   │   │   ├── queries.ts
│   │   │   └── tools.ts
│   │   └── usage.ts
│   ├── fetchers/
│   │   ├── chat.ts
│   │   └── services/
│   │       └── esri/
│   │           ├── fetch-layers-list.ts
│   │           └── fetch-selected-layer.ts
│   ├── geospatial/
│   │   └── gee/
│   │       ├── analysis-functions/
│   │       │   ├── heat-analysis/
│   │       │   │   └── urban-heat-island-analysis.ts
│   │       │   ├── lancover-landuse-mapping/
│   │       │   │   ├── google-dynamic-world-landcover-mapping.ts
│   │       │   │   ├── landcover-change-mapping.ts
│   │       │   │   └── sentinel-landcover-landuse-mapping.ts
│   │       │   └── pollution-analysis/
│   │       │       └── air-pollution-analysis.ts
│   │       ├── extract-values-from-gee-layer/
│   │       │   ├── extract-values-from-gee-layer.ts
│   │       │   └── geospatial-analyses/
│   │       │       ├── extract-values-from-air-pollution-map.ts
│   │       │       ├── extract-values-from-google-dynamic-world-map.ts
│   │       │       ├── extract-values-from-landcover-change-map.ts
│   │       │       ├── extract-values-from-sentinel-landcover-landuse-map.ts
│   │       │       └── extract-values-from-urban-heat-island-map.ts
│   │       └── load-data/
│   │           └── load-raster-data.ts
│   └── utils.ts
├── middleware.ts
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── stores/
│   ├── use-buttons-store.ts
│   ├── use-integration-store.ts
│   ├── use-loading-store.ts
│   ├── use-sidebar-button-stores.ts
│   ├── use-toast-message-store.ts
│   └── use-user-profile-store.ts
├── tailwind.config.ts
├── tsconfig.json
├── types/
│   └── global.d.ts
└── utils/
    ├── general/
    │   ├── document-utils.ts
    │   └── general-utils.ts
    ├── reset-chat-stores.ts
    ├── service-handlers/
    │   └── esri.ts
    ├── supabase/
    │   ├── client.ts
    │   ├── middleware.ts
    │   └── server.ts
    └── validation-utils/
        └── validation-utils.ts

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

================================================
FILE: .eslintrc.json
================================================
{
  "extends": ["next/core-web-vitals", "next/typescript"],
  "rules": {
    "@typescript-eslint/no-unused-vars": "warn",
    "@typescript-eslint/no-explicit-any": "warn",
    "react/display-name": "warn",
    "react-hooks/exhaustive-deps": "warn",
    "prefer-const": "warn"
  }
}


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

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

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

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

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

# vercel
.vercel

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


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Chat2Geo
Thank you for considering contributing to Chat2Geo!
Here are the instructions on how to contribute to this project to ensure consistency throughout the development.

## 1. Fork and Clone

1. Fork this repo (click the "Fork" button on top-right).
2. Clone your fork:
   ```bash
   https://github.com/GeoRetina/chat2geo.git

## 3. Pick an issue or be assigned one
  - You can either pick an issue to work on or be assigned one.
  - If there is an issue not listed, please create one.

## 4. Create a new branch off of `main` for each issue or feature and name it based on the following pattern
  ```bash
   <type>/<issue-id>-<short-description>

    where:
     - <type>: The purpose of the branch. Common types:
          feat: For new features.
          fix: For bug fixes.
          chore: For maintenance or non-functional changes.
          refactor: For code refactoring.
          docs: For documentation updates.
          test: For testing-related changes.
    - <issue-id>: The issue/feature ID from your issue tracker (e.g., GitHub, Jira). This helps link branches to specific tickets.
    - <short-description>: A concise, kebab-case description of the work being done.
```
## 5. Create a Pull Request (PR) for merging to the main
  - Merging to the main is only possible by PR and review.


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

Copyright (c) 2025 GeoRetina Inc.

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

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

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


================================================
FILE: README.md
================================================
> [!IMPORTANT]  
> **This repository is archived and no longer maintained.**  
> Please use the hosted version at [https://chat2geo.georetina.ai](https://chat2geo.georetina.ai), which is production-ready, more feature-rich, and actively maintained.


# Chat2Geo: A ChatGPT-Like Web App for Remote-Sensing-Based Geospatial Analysis

Chat2Geo is a Next.js 15 application providing a chatbot-like user interface for performing remote-sensing-based geospatial analyses. It leverages Google Earth Engine (GEE) in the backend to process and analyze various remote sensing datasets in real time. Users can upload their own vector data, run advanced geospatial queries, and integrate the results with an AI Assistant for specialized tasks such as **land cover mapping**, **change detection**, and **air pollutant monitoring**. 

Chat2Geo also has advanced knowledge retrieval based on Retrieval-augmented generation (RAG), which can integrate geospatial analysis with non-geospatial/textual information. 

The app also has authentication and database integrations, making it almost a complete package. 

Chat2Geo inherits a large portion of its building blocks from the GRAI 2.0 app that is under development at GeoRetina (www.georetina.com). In parallel with GRAI 2.0 (which will be merged to Chat2Geo once it's stable), we will also keep Chat2Geo updated for the community.

### 🌍 Try Chat2Geo: 
https://chat2geo.georetina.ai

----

https://github.com/user-attachments/assets/d9940a0e-10c8-4d0e-9ec9-3dfd0966c664



## Contributing 🛠️

 - If you're interested in contributing to this project, please contact us at `shahabj.github@gmail.com`.
 - If you are a new contributor, please first check out our [Contributing Guidelines](./CONTRIBUTING.md) to get started.


## Table of Contents

- [Features](#features-)
- [Tech Stack](#tech-stack-)
- [Getting Started](#getting-started-)
- [Current Analyses](#available-geospatial-analyses-)
- [Considerations](#considerations)

---

## Features ✨

1. **Chat-Style Interface**

   - Interact with the system using natural language.
   - The AI Assistant can execute various **geospatial functions** on your behalf.

2. **Google Earth Engine Integration**

   - Real-time access to satellite imagery and remote sensing datasets.
   - Seamless backend processing for large-scale geospatial computations.

3. **Import Your Own Vector Data**

   - Upload and manage personal vector layers.
   - Integrate your data with Earth Engine operations for advanced queries.

4. **Analysis Toolkit**

   - **Air Pollutants**
   - **Urban Heat Island (UHI)** metrics
   - **Land Cover** mapping & **Change Detection**
   - Custom AI models deployed on **Vertex AI** for certain land cover tasks

5. **RAG & Knowledge Base**
   - Enables a Retrieval-Augmented Generation (RAG) workflow.
   - Upload documents to build a local knowledge base.
   - The AI Assistant can then combine geospatial insights with custom document knowledge.


## Tech Stack 💻
- Next.js
- Google Cloud Platform (GCP):
   - Google Earth Engine (remote-sensing data invocation and processing)
   - Vertex AI (custom AI vision models)
   - Cloud Run
- Vercel AI
- OpenAI (ChatGPT API)
- Supabase (database and authentication)
- LangChain (RAG)
- Turf (for spatial operations)
- Maplibre GL (for displaying maps)

## Getting Started 🚀

1. Clone the repo

2. Install dependencies

   ```bash
   npm install
   ```

3. Create a Google Earth Engine (GEE) account and project, otherwise no analysis can be done. Note that GEE is currently only free for non-commercial use:
   - https://earthengine.google.com
   
5. Set up the environment variables

- Create a `.env.local` file (or similar) with the required credentials for:

  - Your base url:
     ```
     BASE_URL=http://localhost:3000   # Change it if you're using a different port. In production, you should set it to the url of the deployment. 
     ```
  - Google Cloud Platform (GCP):
    ```
      GOOGLE_MAPS_API_KEY=           # API key for Google maps. You can replace Google Maps with OSM if you want.
      VERTEXTAI_ENDPOINT_BASE_URL=   # Base URL if you use your own custom models.
      GEE_CLOUD_RUN_URL=             # URL for invoking a model hosted on VertexAI using cloud functions.
      GCP_BUCKET_NAME=               # Bucket name to store the land-cover map generated by your custom model (if applicable).
      GCP_SERVICE_ACCOUNT_KEY=       # Service Account key needed for GEE functions, depending on your GCP configurations. Make sure it has all the required permissions to use GEE.
    ```

  - Large Language Model (LLM) API Key:
    ```
    OPENAI_API_KEY=            # It shouldn't be necessarily OpenAI, thought. You can change it to any other API supported by Vercel AI SDK. However, you need to make some changes to the Chat API route.
    ```
  - For the database & authentication, the app uses Supabase. So you need the Supabase API keys as well:

        NEXT_PUBLIC_SUPABASE_URL=
        NEXT_PUBLIC_SUPABASE_ANON_KEY=

  - If you want Esri integration, you also need the following keys in your env (skip this part if you don't want this integration):

        ARCGIS_CLIENT_ID=
        ARCGIS_CLIENT_SECRET=
        ARCGIS_REDIRECT_URI=

- For feedback submission, I just used a simple email-based pipeline based on Mailgun (skip this part if you don't want this feature):

        MAILGUN_API_KEY=
        MAILGUN_DOMAIN=
        RECIPIENT_EMAIL=
        SENDER_EMAIL=

5. Run the develpment server

       npm run dev


Visit http://localhost:3000 to view the application.


<a name="custom_anchor_name"></a>
## How to Set up Supabase Database, Storage Bucket, & Authentication 🛢️

Supabase has a free-tier, generous plan that you can use to work with the app.

As mentioned above, the database (PostgreSQL) and authentication are both hosted on Supabase. To set up them, you can either use the local dev (https://supabase.com/docs/guides/local-development/cli/getting-started) or online (https://supabase.com/docs/guides/database/overview).
To set up the database and Supabase auth online, you need to create a supabase project & create the required databases and auth. 
You can find the database schema of the app in the `db-schema` folder.

If you want to also use the Knowledge Base feature, you need to create a storage bucket on Supabase as well. The name of the bucket should be `documents_bucket`. This is where the PDF docs you upload to the Knowledge Base are stored. You can set up the bucket by going to the following link:
 - https://supabase.com/dashboard/project/_/storage/buckets

## Available Geospatial Analyses 📊

The app includes the following geospatial analyses:

| #  | Analysis Type                                    | Description |
|----|------------------------------------------------|-------------|
| 1  | **Urban Heat Island (UHI) Analysis**           | Evaluates temperature variations in urban areas compared to rural surroundings. |
| 2  | **Land-Use/Land-Cover Mapping**                | Uses Google DynamicWorld to classify land cover types. |
| 3  | **Land-Use/Land-Cover Change Mapping**         | Detects changes in land use over time using Google DynamicWorld. |
| 4  | **Air Pollution Analysis** *(Not fully implemented)* | Analyzes air pollution patterns and trends. |




## Considerations💡
- Note that all remote-sensing geospatial analyses, at least for now, are based on GEE in this app. So, if you don't set up your GEE environment correctly, no analysis can be done.
- It should be noted that this app is not yet ready for production. The app has known bugs, and perhaps unknown ones 😁 Some functionalities have not been implemented yet.
- I may have forgotten to include some steps in setting up the app! 😅 If there's missing information in the instructions, please open an issue and let me know to update the instructions accordingly.
- GEE-based geospatial analyses are just simple examples of how such analyses can be implemented and added. Some of them are using data that may not be up-to-date. As a result, care should be taken while interpreting the results.
- There are parts that should be refactored or re-designed either because they could have been used/invoked in a better place, or because they should've been implemented in a much better manner.


## Frequently Asked Questions (FAQ) 📌

<details>
  <summary>🔹 General Questions</summary>
  
  **❓ Is this project free to use?**  
  *Yes! This open-source version is free to use under the terms of its license. However, note that Google Earth Engine has restrictions on commercial usage.*
  
</details>

<details>
  <summary>🔹 Support &amp; Contributions</summary>
  
  **❓ How can I get support for issues?**  
  - *If you encounter a bug or have a feature request, please [open an issue](../../issues) on GitHub.*  
  - *Be as detailed as possible when describing your issue (include screenshots, step-by-step explanations, error logs, and any relevant details). Abstract or vague questions will not be answered.*  
  - *For other questions, feel free to reach out at [shahabj.github@gmail.com](mailto:shahabj.github@gmail.com).*
  
  **❓ How can I contribute?**  
  *We welcome contributions! Please check out the [Contributing Guidelines](./CONTRIBUTING.md) before submitting a pull request or opening an issue. Your help in improving this project is greatly appreciated.*
  
</details>

<details>
  <summary>🔹 Features &amp; Customization</summary>
  
  **❓ Can I request additional analyses or features?**  
  *Absolutely! You can:*
  - *Suggest a feature by opening an issue.*
  - *Fork the repository and implement your own changes.*  
  *For advanced or custom solutions, please see [GRAI 2.0 (Enterprise Version)](#enterprise-version-grai-20) below.*
  
  **❓ Can I use my own geospatial datasets?**  
  *Yes! The app allows you to import vector data and integrate it with Google Earth Engine for custom analyses. For raster data, at least for now, you need to either host them on GEE or a GCP bucket.*
  
</details>

<details>
  <summary>🔹 Enterprise Version: GRAI 2.0</summary>
  
  **❓ What is GRAI 2.0?**  
  *GRAI 2.0 is the enterprise version of this project, offering:*
  - *Custom-built solutions tailored to specific client needs.*
  - *Additional analyses &amp; AI models not included in the open-source version.*
  - *Continuous updates &amp; premium support.*
  
  **❓ How do I get access to GRAI 2.0?**  
  *For enterprise inquiries, please visit the [GeoRetina Contact Page](https://www.georetina.com/contact).*
  
</details>

<details>
  <summary>🔹 Technical &amp; Setup Questions</summary>
  
  **❓ I'm facing issues with setup. What should I do?**  
  1. *Check that your environment variables are properly set in `.env.local`.*
  2. *To get past the login page, you need to first set up a Supabase Auth as described in [Supabase Setup](#how-to-set-up-supabase-database-storage-bucket--authentication-%EF%B8%8F).*
  3. *Check your database configurations.*
  4. *Confirm your Google Earth Engine configuration.*
  5. *Refer to the [Getting Started](#getting-started) section in this README.*
  6. *If issues persist, [open an issue](../../issues).*
  
</details>

*Have a question not listed here? Feel free to [open an issue](../../issues) or reach out via email!* 🚀






================================================
FILE: app/(auth)/api/auth/confirm/route.ts
================================================
import { type EmailOtpType } from "@supabase/supabase-js";
import { type NextRequest, NextResponse } from "next/server";

import { createClient } from "@/utils/supabase/server";

// Creating a handler to a GET request to route /auth/confirm
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const token_hash = searchParams.get("token_hash");
  const type = searchParams.get("type") as EmailOtpType | null;
  const next = "/";

  // Create redirect link without the secret token
  const redirectTo = request.nextUrl.clone();
  redirectTo.pathname = next;
  redirectTo.searchParams.delete("token_hash");
  redirectTo.searchParams.delete("type");

  if (token_hash && type) {
    const supabase = await createClient();

    const { error } = await supabase.auth.verifyOtp({
      type,
      token_hash,
    });
    if (!error) {
      redirectTo.searchParams.delete("next");
      return NextResponse.redirect(redirectTo);
    }
  }

  // return the user to an error page with some instructions
  redirectTo.pathname = "/login";
  return NextResponse.redirect(redirectTo);
}


================================================
FILE: app/(auth)/api/auth/esri/authorize/route.ts
================================================
import { NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";

export async function GET() {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();
  if (error || !data.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }

  const { ARCGIS_CLIENT_ID, ARCGIS_REDIRECT_URI } = process.env;

  const authorizationUrl = `https://www.arcgis.com/sharing/rest/oauth2/authorize?client_id=${ARCGIS_CLIENT_ID}&response_type=code&redirect_uri=${encodeURIComponent(
    ARCGIS_REDIRECT_URI!
  )}`;

  return NextResponse.redirect(authorizationUrl);
}


================================================
FILE: app/(auth)/api/auth/esri/callback/route.ts
================================================
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";
import cookie from "cookie";

export async function GET(req: NextRequest) {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();
  if (error || !data?.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }

  const { searchParams } = new URL(req.url);
  const code = searchParams.get("code");

  if (!code) {
    return NextResponse.json(
      { error: "Authorization code is missing" },
      { status: 400 }
    );
  }

  const params = new URLSearchParams();
  params.append("client_id", process.env.ARCGIS_CLIENT_ID!);
  params.append("client_secret", process.env.ARCGIS_CLIENT_SECRET!);
  params.append("grant_type", "authorization_code");
  params.append("code", code);
  params.append("redirect_uri", process.env.ARCGIS_REDIRECT_URI!);
  params.append("f", "json");

  const baseUrl = process.env.BASE_URL;

  try {
    const tokenResponse = await fetch(
      "https://www.arcgis.com/sharing/rest/oauth2/token",
      {
        method: "POST",
        body: params,
      }
    );

    const tokenData = await tokenResponse.json();

    if (tokenData.error) {
      throw new Error(tokenData.error.message);
    }

    const response = NextResponse.redirect(
      `${baseUrl}/services/esri/fetch-layers`
    );

    // Store the token securely in an HttpOnly, Secure cookie
    response.cookies.set("arcgis_access_token", tokenData.access_token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "strict",
      maxAge: tokenData.expires_in,
    });

    return response;
  } catch (error) {
    console.error("Error during OAuth2 callback:", error);
    return NextResponse.json(
      { error: "Failed to exchange authorization code for token" },
      { status: 500 }
    );
  }
}


================================================
FILE: app/(auth)/layout.tsx
================================================
import localFont from "next/font/local";
import "./styles.css";
import { Toaster } from "react-hot-toast";
import ToastMessage from "@/features/ui/toast-message";
import { ThemeProvider } from "@/components/theme-provider";
import { Analytics } from "@vercel/analytics/next";

const geistSans = localFont({
  src: "./fonts/GeistVF.woff",
  variable: "--font-geist-sans",
  weight: "100 900",
});
const geistMono = localFont({
  src: "./fonts/GeistMonoVF.woff",
  variable: "--font-geist-mono",
  weight: "100 900",
});

// Define metadata for the root layout
export const metadata = {
  title: "Login to Chat2Geo",
  description: "Login to access AI-powered geospatial analytics",
};

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


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

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { createClient } from "@/utils/supabase/server";
import { cookies } from "next/headers";
import { z } from "zod";

// Define the validation schema using Zod
const loginSchema = z.object({
  email: z.string().email("Invalid email format."),
  password: z.string(),
});

export async function login(formData: FormData) {
  const supabase = await createClient();

  // Parse and validate the input data
  const formDataObject = {
    email: formData.get("email") as string,
    password: formData.get("password") as string,
  };

  const validation = loginSchema.safeParse(formDataObject);
  if (!validation.success) {
    // Return validation errors
    return {
      error: validation.error.errors.map((err) => err.message).join(", "),
    };
  }

  const { email, password } = validation.data;

  const { data: authData, error: authError } =
    await supabase.auth.signInWithPassword({ email, password });

  if (authError) {
    return { error: authError.message };
  }

  const { data: userRoles, error: roleError } = await supabase
    .from("user_roles")
    .select("name, role, organization, license_start, license_end")
    .eq("email", email)
    .single();

  if (roleError || !userRoles) {
    return { error: "User not found or error occurred." };
  }

  const { name, role, organization, license_start, license_end } = userRoles;

  const licenseStartString = license_start;
  const licenseEndString = license_end;
  const currentDate = new Date();
  const licenseStartDate = new Date(license_start);
  const licenseEndDate = new Date(license_end);

  if (currentDate < licenseStartDate || currentDate > licenseEndDate) {
    return {
      error:
        "Your license has expired or not yet started. Please contact support.",
    };
  }

  return {
    success: true,
    userEmail: email,
    userName: name,
    userRole: role,
    userOrganization: organization,
    licenseStartString,
    licenseEndString,
  };
}

export async function logout() {
  const supabase = await createClient();

  await supabase.auth.signOut();

  (await cookies()).delete("arcgis_access_token");

  revalidatePath("/");
  redirect("/login");
}


================================================
FILE: app/(auth)/login/layout.tsx
================================================
export default async function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="relative flex min-h-screen">
      <main className="flex-grow">{children}</main>
    </div>
  );
}


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

import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState, FormEvent, useTransition } from "react";

import { login } from "@/app/(auth)/login/actions";
import { useUserStore } from "@/stores/use-user-profile-store";
import PrivacyPolicy from "@/components/notices/privacy-policy";
import TermsOfService from "@/components/notices/terms-of-services";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Loader2 } from "lucide-react";

export default function Login() {
  const [error, setError] = useState<string | null>(null);
  const [isPending, startTransition] = useTransition();
  const [loading, setLoading] = useState<boolean>(false);
  const router = useRouter();
  const { setUserData } = useUserStore();
  const [openTerms, setOpenTerms] = useState(false);
  const [openPrivacy, setOpenPrivacy] = useState(false);

  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setError(null);
    setLoading(true);

    const formData = new FormData(event.currentTarget);
    const result = await login(formData);

    if (result?.error) {
      setError(result.error);
    } else {
      // Update user store with returned data
      setUserData(
        result.userName!,
        result.userEmail!,
        result.userRole!,
        result.userOrganization!,
        result.licenseStartString!,
        result.licenseEndString!
      );
      startTransition(() => {
        router.push("/");
      });
    }

    setLoading(false);
  };

  return (
    <>
      <TermsOfService open={openTerms} onOpenChange={setOpenTerms} />
      <PrivacyPolicy open={openPrivacy} onOpenChange={setOpenPrivacy} />

      {/* Main container for md+ screens */}
      <div className="container relative hidden h-full flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
        {/* Left column with background and testimonial */}
        <div className="relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex">
          <div className="absolute inset-0 bg-gray-950" />
          <div className="relative z-20 mt-auto">
            <blockquote className="space-y-2">
              <p className="text-lg">
                Let powerful AI solutions help you better address various
                environmental challenges.
              </p>
            </blockquote>
          </div>
        </div>

        {/* Right column with the actual login form */}
        <div className="lg:p-8">
          <div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
            <div className="flex flex-col space-y-2 text-center">
              <h1 className="text-2xl font-semibold tracking-tight">
                Sign in to your account
              </h1>
              <p className="text-sm text-muted-foreground">
                Enter your credentials below
              </p>
            </div>

            <form onSubmit={handleSubmit} className="space-y-4">
              {/* Email Field */}
              <div className="flex flex-col space-y-1">
                <Label htmlFor="email">Email</Label>
                <Input
                  id="email"
                  name="email"
                  type="email"
                  placeholder="name@company.com"
                  autoComplete="email"
                  required
                />
              </div>

              {/* Password Field */}
              <div className="flex flex-col space-y-1">
                <Label htmlFor="password">Password</Label>
                <Input
                  id="password"
                  name="password"
                  type="password"
                  placeholder="••••••••"
                  autoComplete="current-password"
                  required
                />
              </div>

              {/* Error Alert */}
              {error && (
                <p className="text-destructive text-sm">
                  <strong>Error:</strong> {error}
                </p>
              )}

              {/* Submit Button */}
              <Button
                type="submit"
                disabled={loading || isPending}
                className="w-full"
              >
                {(loading || isPending) && <Loader2 className="animate-spin" />}
                Sign in
              </Button>
            </form>

            <p className="px-8 text-center text-sm text-muted-foreground">
              By clicking continue, you agree to our{" "}
              <button
                className="underline underline-offset-4 hover:text-primary"
                onClick={() => setOpenTerms(true)}
              >
                Terms of Service
              </button>{" "}
              and{" "}
              <button
                className="underline underline-offset-4 hover:text-primary"
                onClick={() => setOpenPrivacy(true)}
              >
                Privacy Policy
              </button>
              .
            </p>
          </div>
        </div>
      </div>
    </>
  );
}


================================================
FILE: app/(auth)/styles.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 47.4% 11.2%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 47.4% 11.2%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 47.4% 11.2%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --primary-blue: 220 90% 56%;
    --primary-blue-foreground: 210 40% 98%;
    --primary-green: 145 63% 42%;
    --primary-green-foreground: 210 40% 98%;
    --secondary: 220 13% 92%; /* Neutral light gray */
    --secondary-foreground: 220 10% 40%; /* Neutral medium gray */
    --accent: 220 15% 85%; /* Neutral soft gray */
    --accent-foreground: 220 20% 12%; /* Neutral deep gray */
    --destructive: 0 100% 50%;
    --destructive-foreground: 210 40% 98%;
    --warning: 45 100% 50%;
    --warning-foreground: 45 100% 15%;
    --info: 200 98% 48%;
    --info-foreground: 210 40% 98%;
    --ring: 215 20.2% 65.1%;
    --radius: 0.5rem;
  }

  .dark {
    /* Base */
    --background: 240 2% 12%; /* #1f1f21 */
    --foreground: 0 0% 95%; /* Very light neutral */

    /* Darker variant for cards/popovers */
    --card: 220 3% 10%; /* #18191a */
    --card-foreground: 0 0% 95%;
    --popover: 220 3% 10%; /* #18191a */
    --popover-foreground: 0 0% 95%;

    /* Muted */
    --muted: 240 2% 16%; /* Slightly lighter than background */
    --muted-foreground: 0 0% 60%; /* Subdued text */

    /* Interactive elements */
    --accent: 240 2% 18%; /* Subtle highlight */
    --accent-foreground: 0 0% 98%;

    /* Borders and rings */
    --border: 240 2% 20%; /* Visible but subtle */
    --input: 240 2% 20%;
    --ring: 240 2% 20%;

    /* Primary */
    --primary: 0 0% 98%; /* Almost white */
    --primary-foreground: 240 2% 12%; /* Same as background */

    /* Secondary */
    --secondary: 240 2% 22%; /* Lighter than accent */
    --secondary-foreground: 0 0% 98%;

    /* Semantic colors - coordinated with our neutral theme */
    --primary-blue: 220 90% 56%;
    --primary-blue-foreground: 210 40% 98%;

    --primary-green: 145 63% 42%;
    --primary-green-foreground: 210 40% 98%;

    --destructive: 0 50% 35%; /* More muted red */
    --destructive-foreground: 0 0% 98%;

    --warning: 45 100% 50%;
    --warning-foreground: 45 100% 15%;
    --info: 200 98% 48%;
    --info-foreground: 210 40% 98%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply font-sans antialiased bg-background text-foreground;
  }
}


================================================
FILE: app/(broadcast)/api/services/esri/fetch-layers-list/route.ts
================================================
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";

export async function GET(req: NextRequest) {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();
  if (error || !data?.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }
  const accessToken = req.cookies.get("arcgis_access_token")?.value;

  if (!accessToken) {
    return NextResponse.json(
      {
        error: "No access token found. User needs to authenticate with ArcGIS.",
      },
      { status: 401 }
    );
  }

  const portalUrl = `https://www.arcgis.com/sharing/rest/portals/self?f=json&token=${accessToken}`;

  try {
    const portalResponse = await fetch(portalUrl);
    const portalData = await portalResponse.json();

    if (!portalResponse.ok) {
      throw new Error("Failed to fetch organization ID");
    }
    const orgId = portalData.id;
    if (!orgId) {
      throw new Error("Organization ID not found in portalData");
    }

    const searchUrl = `https://www.arcgis.com/sharing/rest/search?q=orgid:${orgId} (type:"Feature Service")&f=json&token=${accessToken}`;
    const servicesResponse = await fetch(searchUrl);

    if (!servicesResponse.ok) {
      throw new Error(
        `Failed to fetch services from ArcGIS: ${servicesResponse.statusText}`
      );
    }

    const servicesData = await servicesResponse.json();

    return NextResponse.json(servicesData);
  } catch (error) {
    console.error("Error fetching layers or feature services:", error);
    return NextResponse.json(
      { error: "Failed to retrieve services or organization information" },
      { status: 500 }
    );
  }
}


================================================
FILE: app/(broadcast)/api/services/esri/fetch-selected-layer/route.ts
================================================
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";

export async function GET(req: NextRequest) {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();
  if (error || !data?.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }

  const token = req.cookies.get("arcgis_access_token")?.value;

  if (!token) {
    return NextResponse.json(
      { error: "Access token is missing" },
      { status: 401 }
    );
  }

  const { searchParams } = new URL(req.url);
  const layerUrl = searchParams.get("layerUrl");

  if (!layerUrl) {
    return NextResponse.json(
      { error: "Layer URL is missing" },
      { status: 400 }
    );
  }

  try {
    const queryUrl = `${layerUrl}/0/query?f=pgeojson&where=1=1`;
    const response = await fetch(queryUrl, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    if (!response.ok) {
      throw new Error(`Failed to import layer: ${response.statusText}`);
    }

    const layerData = await response.json();
    return NextResponse.json(layerData);
  } catch (error) {
    console.error("Error importing AGOL layer:", error);
    return NextResponse.json(
      { error: "Failed to import layer" },
      { status: 500 }
    );
  }
}


================================================
FILE: app/(broadcast)/layout.tsx
================================================
import localFont from "next/font/local";
import "./styles.css";
import { Toaster } from "react-hot-toast";
import ToastMessage from "@/features/ui/toast-message";
import { ThemeProvider } from "@/components/theme-provider";

const geistSans = localFont({
  src: "./fonts/GeistVF.woff",
  variable: "--font-geist-sans",
  weight: "100 900",
});
const geistMono = localFont({
  src: "./fonts/GeistMonoVF.woff",
  variable: "--font-geist-mono",
  weight: "100 900",
});

// Define metadata for the root layout
export const metadata = {
  title: "Chat2Geo",
  description: "AI-powered geospatial analytics",
};

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


================================================
FILE: app/(broadcast)/services/esri/fetch-layers/page.tsx
================================================
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
import AddArcGisLayerClient from "@/components/services/esri/add-arcgis-layers";

export default async function AddArcGisLayerPage() {
  const supabase = await createClient();
  const { data: authResults, error } = await supabase.auth.getUser();

  if (error || !authResults?.user) {
    redirect("/login");
  }

  return <AddArcGisLayerClient />;
}


================================================
FILE: app/(broadcast)/styles.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 47.4% 11.2%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 47.4% 11.2%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 47.4% 11.2%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --primary-blue: 220 90% 56%;
    --primary-blue-foreground: 210 40% 98%;
    --primary-green: 145 63% 42%;
    --primary-green-foreground: 210 40% 98%;
    --secondary: 220 13% 92%; /* Neutral light gray */
    --secondary-foreground: 220 10% 40%; /* Neutral medium gray */
    --accent: 220 15% 85%; /* Neutral soft gray */
    --accent-foreground: 220 20% 12%; /* Neutral deep gray */
    --destructive: 0 100% 50%;
    --destructive-foreground: 210 40% 98%;
    --warning: 45 100% 50%;
    --warning-foreground: 45 100% 15%;
    --info: 200 98% 48%;
    --info-foreground: 210 40% 98%;
    --ring: 215 20.2% 65.1%;
    --radius: 0.5rem;
  }

  .dark {
    /* Base */
    --background: 240 2% 12%; /* #1f1f21 */
    --foreground: 0 0% 95%; /* Very light neutral */

    /* Darker variant for cards/popovers */
    --card: 220 3% 10%; /* #18191a */
    --card-foreground: 0 0% 95%;
    --popover: 220 3% 10%; /* #18191a */
    --popover-foreground: 0 0% 95%;

    /* Muted */
    --muted: 240 2% 16%; /* Slightly lighter than background */
    --muted-foreground: 0 0% 60%; /* Subdued text */

    /* Interactive elements */
    --accent: 240 2% 18%; /* Subtle highlight */
    --accent-foreground: 0 0% 98%;

    /* Borders and rings */
    --border: 240 2% 20%; /* Visible but subtle */
    --input: 240 2% 20%;
    --ring: 240 2% 20%;

    /* Primary */
    --primary: 0 0% 98%; /* Almost white */
    --primary-foreground: 240 2% 12%; /* Same as background */

    /* Secondary */
    --secondary: 240 2% 22%; /* Lighter than accent */
    --secondary-foreground: 0 0% 98%;

    /* Semantic colors - coordinated with our neutral theme */
    --primary-blue: 220 90% 56%;
    --primary-blue-foreground: 210 40% 98%;

    --primary-green: 145 63% 42%;
    --primary-green-foreground: 210 40% 98%;

    --destructive: 0 50% 35%; /* More muted red */
    --destructive-foreground: 0 0% 98%;

    --warning: 45 100% 50%;
    --warning-foreground: 45 100% 15%;
    --info: 200 98% 48%;
    --info-foreground: 210 40% 98%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply font-sans antialiased bg-background text-foreground;
  }
}


================================================
FILE: app/(main)/actions/get-user-profile.ts
================================================
"use server";
import { createClient } from "@/utils/supabase/server";

export async function getUserProfile() {
  const supabase = await createClient();

  const {
    data: { user },
    error: authError,
  } = await supabase.auth.getUser();

  // If no user or no email, return null
  if (authError || !user || !user.email) {
    return null;
  }

  const { data: userData, error: userError } = await supabase
    .from("user_roles")
    .select("name, role, organization, license_start, license_end")
    .eq("email", user.email)
    .single();

  if (userError || !userData) {
    return null;
  }

  // Now we know user.email is a string
  return {
    email: user.email,
    name: userData.name,
    role: userData.role,
    organization: userData.organization,
    licenseStart: userData.license_start,
    licenseEnd: userData.license_end,
  };
}


================================================
FILE: app/(main)/api/chat/chat-history/route.ts
================================================
import { createClient } from "@/utils/supabase/server";
import { getChatsByUser } from "@/lib/database/chat/queries";
import { NextResponse } from "next/server";

interface Chat {
  id: string;
}

export async function GET() {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();
  const userId = data.user?.id;

  if (error || !data?.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }

  const chats = (await getChatsByUser(userId as string)) as Chat[];

  return NextResponse.json(chats);
}


================================================
FILE: app/(main)/api/chat/route.ts
================================================
import { openai } from "@ai-sdk/openai";
import { azure } from "@ai-sdk/azure"; // You can also use Azure's hosted GPT models. More info: https://sdk.vercel.ai/providers/ai-sdk-providers
import {
  type Message,
  type CoreUserMessage,
  streamText,
  convertToCoreMessages,
} from "ai";

import { createClient } from "@/utils/supabase/server";
import { z } from "zod";

import { NextResponse } from "next/server";

import {
  getChatById,
  saveChat,
  saveMessages,
  searchGeeDatasets,
} from "@/lib/database/chat/queries";
import {
  getUsageForUser,
  getUserRoleAndTier,
  incrementRequestCount,
} from "@/lib/database/usage";
import { getPermissionSet } from "@/lib/auth";
import {
  requestGeospatialAnalysis,
  requestLoadingGeospatialData,
  requestRagQuery,
  draftReport,
  requestWebScraping,
} from "@/lib/database/chat/tools";
import {
  generateUUID,
  sanitizeResponseMessages,
  getMostRecentUserMessage,
  generateTitleFromUserMessage,
  getFormattedDate,
} from "@/features/chat/utils/general-utils";

// export const maxDuration = 30;

export async function POST(request: Request) {
  const {
    id,
    messages,
    selectedRoiGeometryInChat,
    mapLayersNames,
  }: {
    id: string;
    messages: Array<Message>;
    modelId: string;
    selectedRoiGeometryInChat: any;
    mapLayersNames: string[];
  } = await request.json();

  const supabase = await createClient();
  const { data: authResult, error: authError } = await supabase.auth.getUser();
  if (authError || !authResult?.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }

  const userId = authResult.user.id;
  // Fetch the user's role + subscription
  const userRoleRecord = await getUserRoleAndTier(userId);
  if (!userRoleRecord) {
    return NextResponse.json(
      { error: "Failed to get role/subscription" },
      { status: 403 }
    );
  }

  const { role, subscription_tier: subscriptionTier } = userRoleRecord;
  const { maxRequests, maxArea } = await getPermissionSet(
    role,
    subscriptionTier
  );
  const usage = await getUsageForUser(userId);
  if (usage.requests_count >= maxRequests) {
    return NextResponse.json(
      { error: "Request limit exceeded" },
      { status: 403 }
    );
  }

  const cookieStore = request.headers.get("cookie");
  const chat = await getChatById(id);

  const coreMessages = convertToCoreMessages(messages);
  const userMessage = getMostRecentUserMessage(coreMessages);

  // Increment usage count
  await incrementRequestCount(userId);

  if (!chat) {
    const generatedTitle = await generateTitleFromUserMessage({
      message: messages[0] as CoreUserMessage,
    });
    await saveChat({ id: id, title: generatedTitle });
  }

  const userMessageId = generateUUID();
  await saveMessages({
    messages: [
      {
        ...userMessage,
        id: userMessageId,
        createdAt: new Date(),
        chatId: id,
      },
    ],
  });

  // System instructions
  const systemInstructions = `Today is ${getFormattedDate()}. You are an AI Assistant specializing in geospatial analytics. 
  Be kind, warm, and professional. Use emojis where appropriate to enhance user experience. 
  When user asks for a geospatial analysis or data, never ask for the location unless you run the analysis and you get a corresponding error. Users provide the name of their region of interest (ROI) data when requesting an analysis.
  Always highlight important outputs and provide help in interpreting results. NEVER include map URLs or map legends/palette (like classes) in your responses.
  Refuse to answer questions irrelevant to geospatial analytics or the platform's context. You have access to several tools. If running a tool fails, and you thought you would be to fix it with a change, try 3 times until you fix it.
  IF USER ASKS FOR DRAFTING REPORTS, YOU SHOULD RUN THE "draftReport" TOOL, AND JUST CONFIRM THE DRAFTING OF THE REPORT. YOU SHOULD NOT EVER DRAFT REPORT IN THE CHAT."
  You also have access to a tool that can load geospatial data. First, run the tool that searches the database containing GEE datasets information to find the datasets best match user's request. Afterwards, run the web scraper tool to find extra info such as how to set the visualization parameter (pay attention to the code snippet from the official doc you will recieve). After that provide a short summary of what data with what parameters you're going to load to make sure if it's exactly what the user needs. After everything goes well and the user confirmed the details of the analysis to run, use all the information to load the dataset. 
  Another tool you have access to is a RAG query tool that you can use to answer questions you don't know the answer to.
  Before running any geospatial analysis, make sure the layer name doesn't already exist in the map layers. No geospatial analysis is available for the year 2025, so you SHOULD NOT run analysis for 2025 even if the user asks for it.
  When executing analyes (not ragQueryRetrieval, though):
  1. Always provide a clear summary of what was analyzed
  2. Highlight key findings and patterns in the data,
  3. Try to tabulate some parts of the results/descriptions for the sake of clarity.`;

  // Prepend system instructions to the conversation as a separate message for the AI
  const systemMessage = {
    role: "assistant", // Change role to "assistant" to avoid unhandled role errors
    content: systemInstructions,
  };

  // Add the system message at the beginning of the conversation
  const processedMessages = [
    systemMessage,
    ...messages.filter((msg: any) => msg.role !== "system"),
  ] as Array<Message>;

  const result = await streamText({
    model: openai("gpt-4o"),
    // model: azure("gpt-4o"),  // You can also use Azure's hosted GPT models
    maxSteps: 5,
    messages: convertToCoreMessages(processedMessages),
    onFinish: async ({ response }) => {
      if (userId) {
        try {
          const responseMessagesWithoutIncompleteToolCalls =
            sanitizeResponseMessages(response.messages);

          await saveMessages({
            messages: responseMessagesWithoutIncompleteToolCalls.map(
              (message) => {
                const messageId = generateUUID();

                return {
                  id: messageId,
                  chatId: id,
                  draftedReportId: null,
                  role: message.role,
                  content: message.content,
                  createdAt: new Date(),
                };
              }
            ),
          });
        } catch (error) {
          console.error("Failed to save chat");
        }
      }
    },

    tools: {
      requestGeospatialAnalysis: {
        description: `Today is ${getFormattedDate()}, so you should be able to help the user with requests by up to this date. No analysis should be done for the year of 2025 as analyses are not yet ready for the new year.
          After running an analysis: 1. Provide a clear summary of what was analyzed and why, 2. Explain the key findings and their significance. NEVER PROVIDE MAP URLs or MAP LEGENDS FROM THE ANALYSES IN THE RESPONSE. Also the maximum area the user can request analysis for is ${maxArea} sq km. per request.
          It should be noted that the land cover map (start date: 2015) and bi-temporal land cover change map (start date: 2015) are based on Sentinel-2 imagery, UHI (start date: 2015) is based on Landsat imagery. For all "CHANGE" maps, the user must provide "startDate2 and endDate2". If in doubt about an analysis (e.g., it may not exactly match the analysis we have), you have to double check with the user.`,
        parameters: z.object({
          functionType: z.string()
            .describe(`The type of analysis to execute. It can be one of the following:
            'Urban Heat Island (UHI) Analysis',
            'Land Use/Land Cover Maps',
            'Land Use/Land Cover Change Maps'.`),
          startDate1String: z
            .string()
            .describe(
              "The start date for the first period. The date format should be 'YYYY-MM-DD'. But convert any other date format the user gives you to that one."
            ),
          endDate1String: z
            .string()
            .describe(
              "The end date for the first period. The date format should be 'YYYY-MM-DD'. But convert any other date format the user gives you to that one."
            ),
          startDate2String: z
            .string()
            .optional()
            .describe(
              "The start date for the second period. The date format should be 'YYYY-MM-DD'. But convert any other date format the user gives you to that one."
            ),
          endDate2String: z
            .string()
            .optional()
            .describe(
              "The end date for the second period. The date format should be 'YYYY-MM-DD'. But convert any other date format the user gives you to that one."
            ),
          aggregationMethod: z.string().describe(
            `The method to use for aggregating the data. It means that in a time-series, what method is used to aggregate data for a given point/pixel in the final map/analysis delivered. For land use/land cover mapping, it's always "Median", and thus you don't need to ask user for that. It can be one of the following:
            'Mean',
            'Median',
            'Min',
            'Max',
            . Note that the user may not provide it, so by default its value should be 'Max', and you should not ask the user to tell you what method to use. If the default value is used, make sure to mention it in the response to user that your analysis is based on the maximum va.
          `
          ),
          layerName: z
            .string()
            .describe(
              "The name of the layer to be displayed. You ask the user about it if they don't provide it. Otherwise, use a name based on the function type, but make sure the name is concise and descriptive. "
            ),
          title: z
            .string()
            .optional()
            .describe(
              "Briefly describe the title of the analysis in one sentence confirming you're working on the user's request."
            ),
        }),
        execute: async (args) =>
          requestGeospatialAnalysis({
            ...args,
            cookieStore,
            selectedRoiGeometryInChat,
            maxArea,
          }),
      },
      requestLoadingGeospatialData: {
        description: `The user has requested loading and visualizing geospatial data. You should load the data based on the user's request.`,
        parameters: z.object({
          geospatialDataType: z.string().describe(
            `The type of geospatial data to load. It can be one of the following:
      'Load GEE Data'`
          ),
          selectedRoiGeometry: z
            .object({
              type: z.string().optional(),
              coordinates: z.array(z.array(z.array(z.number()))).optional(),
            })
            .optional()
            .describe(
              "The selected region of interest (ROI) geometry. You should run the analysis based on the user's request."
            ),
          dataType: z
            .string()
            .describe(
              `The type of data to load. It can be one of the following: 'Image', 'ImageCollection'.`
            ),

          divideValue: z
            .number()
            .describe(
              `The value to divide the image by. If based on the scraped data you didn't find it, use your logic to see if it should be set based on the dataset. Sometimes, the division is done within a "cloud mask" function, so you should extract its value from there in that case. If you decide not to set it, set it to 1.`
            ),
          datasetId: z.string().describe("The ID of the GEE dataset to load."),
          startDate: z
            .string()
            .describe(
              "The start date for the data to load. The date format should be 'YYYY-MM-DD'. But convert any other date format the user gives you to that one."
            ),
          endDate: z
            .string()
            .describe(
              "The end date for the data to load. The date format should be 'YYYY-MM-DD'. But convert any other date format the user gives you to that one."
            ),
          visParams: z.union([
            // single-band case
            z.object({
              bands: z.array(z.string()).length(1),
              palette: z.array(z.string()),
              min: z.number().optional(),
              max: z.number().optional(),
            }),
            // multi-band case
            z.object({
              bands: z.array(z.string()),
              min: z.number().optional(),
              max: z.number().optional(),
            }),
          ])
            .describe(`You should set the visualization parameters best matching user's request for the data to load and best way of visualization:
            1) If you want to combine more than one band for visualization, set visParams using the bands: [...] attribute.
            2) Otherwise, use the palette: [...] attribute (and do not include bands).
            As an example, RGB visualization should be set as: {bands: ['red', 'green', 'blue']}. Forest loss should be using pellete if it's one band.
      `),
          labelNames: z
            .array(z.string())
            .describe(
              "The label names for the data to load. You should run the analysis based on the user's request. Choose the closet label names even if it doesn't 100% match what you already know. Infer it."
            ),
          layerName: z
            .string()
            .describe(
              "The name of the layer to be displayed. You ask the user about it if they don't provide it. Otherwise, use a name based on the function type, but make sure the name is concise and descriptive. "
            ),
          title: z
            .string()
            .optional()
            .describe(
              "Briefly describe the title of the analysis in one sentence confirming you're working on the user's request."
            ),
        }),
        execute: async (args) => {
          return requestLoadingGeospatialData({
            ...args,
            cookieStore,
            selectedRoiGeometryInChat,
          });
        },
      },
      searchGeeDatasets: {
        description: `Find the datasets available in Google Earth Engine (GEE) that best match the user's query.`,
        parameters: z.object({
          query: z.string().describe("The name of the dataset to search."),
          startDate: z
            .string()
            .optional()
            .describe(
              "The start date for the data to load based on the scraping results. This could be the year or the date in a format. This shows the start date the data is available."
            ),
          endDate: z
            .string()
            .optional()
            .describe(
              "The end date for the data to load based on the scraping result. This could be the year or the date in a format. This shows the end date the data is available."
            ),
          title: z
            .string()
            .optional()
            .describe(
              "Briefly describe the title of the analysis in one sentence confirming you're working on the user's request."
            ),
        }),
        execute: async (args) => {
          const result = searchGeeDatasets(args.query);
          return result;
        },
      },

      scrapeWebpage: {
        description:
          "Scrape the webpage of the GEE dataset to learn what dataset_id, how data is visualized, legends, any division by a value, etc. you should use for the the requested dataset. For example, one of the things you should learn is whether you need to have a band combination (e.g., [b1, b2, b3]) or a palette (e.g., ['red', 'green', 'blue']) to visualize the image.",
        parameters: z.object({
          url: z
            .string()
            .describe(
              "The asset URL of the webpage to scrape. The name of the column you're scraping for this parameter should be 'asset_url'."
            ),
          title: z
            .string()
            .optional()
            .describe(
              "Briefly describe the title of the analysis in one sentence confirming you're working on the user's request."
            ),
        }),

        execute: async (args) => {
          return requestWebScraping(args);
        },
      },
      requestRagQuery: {
        description: `The user has some documents with which a RAG has been built. If you're asked a question that you didn't know the answer, run the requestRagQuery tool that is based on user's documents to get the answer.`,
        parameters: z.object({
          query: z.string().describe("The user's query text."),
          title: z
            .string()
            .optional()
            .describe(
              "Briefly describe the title of the analysis in one sentence confirming you're working on the user's request."
            ),
        }),
        execute: async (args) => requestRagQuery({ ...args, cookieStore }),
      },
      draftReport: {
        description: `When this tool is called, draft a report that summarizes the analyses and their results. The report should be concise and easy to understand, highlighting the key findings and insights. Markdown is supported.`,
        parameters: z.object({
          messages: z
            .array(z.object({}))
            .describe(
              "The messages exchanged between the user and the you. You should use relevant messages in the chat to generate the report the user requested. Make sure you format the report in a standard way with all the common structures."
            ),
          title: z
            .string()
            .optional()
            .describe(
              "Briefly describe the title of the report to be drafted in one sentence confirming you're working on the user's request."
            ),
          reportFileName: z
            .string()
            .optional()
            .describe("Provide a concise name for the report file."),
        }),
        execute: async (args) =>
          draftReport({ ...args, messages: processedMessages }),
      },
      checkMapLayersNames: {
        description:
          "Here are the the names of the current map layers. If you run a geospatial analysis, and you select a name for the layer, you should should first check the layer names to make sure the name you selected is not already in use. You shouldn't output any message regarding the name you select.",
        parameters: z.object({
          layerName: z
            .string()
            .describe("The name of the layer to be displayed."),
          title: z
            .string()
            .optional()
            .describe(
              "Briefly describe the title of the analysis in one sentence confirming you're working on the user's request."
            ),
        }),

        execute: async (args) => {
          return mapLayersNames;
        },
      },
    },
  });

  return result.toDataStreamResponse();
}


================================================
FILE: app/(main)/api/gee/request-geospatial-analysis/route.ts
================================================
import { createClient } from "@/utils/supabase/server";
import { NextResponse } from "next/server";
import { NextRequest } from "next/server";
import { geeAuthenticate } from "@/features/maps/utils/authentication-utils/gee-auth";
import { urbanHeatIslandAnalysis } from "@/lib/geospatial/gee/analysis-functions/heat-analysis/urban-heat-island-analysis";
import { airPollutionAnalysis } from "@/lib/geospatial/gee/analysis-functions/pollution-analysis/air-pollution-analysis";
import { sentinelLandcoverLanduseMapping } from "@/lib/geospatial/gee/analysis-functions/lancover-landuse-mapping/sentinel-landcover-landuse-mapping";
import { convertToEeGeometry } from "@/features/maps/utils/geometry-utils";
import googleDynamicWorldMapping from "@/lib/geospatial/gee/analysis-functions/lancover-landuse-mapping/google-dynamic-world-landcover-mapping";
import landcoverChangeMapping from "@/lib/geospatial/gee/analysis-functions/lancover-landuse-mapping/landcover-change-mapping";

const validAnalysisOptionsForVulnerabilityMapBuilder: MultiAnalysisOptionsTypeForVulnerabilityMapBuilderType[] =
  ["Air Pollutants", "Flood Risk", "Urban Heat Island (UHI)"];
const validAnalysisOptionsForAtmosphericGasAnalysis: MultiAnalysisOptionsTypeForAirPollutantsAnalysisType[] =
  ["CO", "NO2", "CH4", "Aerosols"];

export async function POST(req: NextRequest) {
  const supabase = await createClient();

  const { data, error } = await supabase.auth.getUser();

  if (error || !data?.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }

  // Parse the request body as JSON
  let body;
  try {
    body = await req.json();
  } catch (err) {
    console.error(err);
    return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
  }

  const {
    functionType,
    selectedRoiGeometry,
    aggregationMethod,
    startDate1,
    endDate1,
    startDate2,
    endDate2,
    multiAnalysisOptions,
  } = body;

  // Validate the ROI geometry
  if (!selectedRoiGeometry) {
    return NextResponse.json(
      {
        error:
          "No Region of Interest (ROI) was provided. Please provide an ROI.",
      },
      { status: 400 }
    );
  }

  // If the geometry comes in as a string, decode and parse it
  let geometry = selectedRoiGeometry;
  if (typeof selectedRoiGeometry === "string") {
    try {
      const geometryString = decodeURIComponent(selectedRoiGeometry);
      geometry = JSON.parse(geometryString);
    } catch (err) {
      return NextResponse.json(
        { error: "Invalid geometry JSON" },
        { status: 400 }
      );
    }
  }

  try {
    await initializeGee();

    const eeReadyGeometry = convertToEeGeometry(geometry);

    let result;
    switch (functionType) {
      case "Air Pollution Analysis":
        if (
          multiAnalysisOptions.every((option: any) =>
            validAnalysisOptionsForAtmosphericGasAnalysis.includes(
              option as MultiAnalysisOptionsTypeForAirPollutantsAnalysisType
            )
          )
        ) {
          result = await airPollutionAnalysis(
            geometry,
            multiAnalysisOptions as MultiAnalysisOptionsTypeForAirPollutantsAnalysisType[],
            startDate1,
            endDate1,
            aggregationMethod as AggregationMethodTypeNumerical,
            startDate2,
            endDate2
          );
        } else {
          throw new Error(
            "Invalid analysis option for Air Pollutants Analysis"
          );
        }
        break;
      case "Land Use/Land Cover Maps":
        result = await googleDynamicWorldMapping(
          eeReadyGeometry,
          startDate1,
          endDate1
        );
        break;

      case "Land Use/Land Cover Change Maps":
        result = await landcoverChangeMapping(
          eeReadyGeometry,
          startDate1,
          endDate1,
          startDate2,
          endDate2
        );
        break;

      case "Urban Heat Island (UHI) Analysis":
        result = await urbanHeatIslandAnalysis(
          eeReadyGeometry,
          startDate1,
          endDate1,
          aggregationMethod as AggregationMethodTypeNumerical
        );
        break;

      default:
        throw new Error("Invalid function type");
    }

    return NextResponse.json(result, { status: 200 });
  } catch (error: any) {
    console.error(error);
    return NextResponse.json({ result: error.message }, { status: 404 });
  }
}

// Function to initialize Google Earth Engine
const initializeGee = async () => {
  await geeAuthenticate();
};


================================================
FILE: app/(main)/api/gee/request-loading-geospatial-data/route.ts
================================================
import { createClient } from "@/utils/supabase/server";
import { NextResponse } from "next/server";
import { NextRequest } from "next/server";
import { geeAuthenticate } from "@/features/maps/utils/authentication-utils/gee-auth";
import { convertToEeGeometry } from "@/features/maps/utils/geometry-utils";
import { loadRasterData } from "@/lib/geospatial/gee/load-data/load-raster-data";

export async function POST(req: NextRequest) {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();
  if (error || !data?.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }

  let body;
  try {
    body = await req.json();
  } catch (err) {
    console.error("Error parsing request body:", err);
    return NextResponse.json(
      { error: "Invalid request format" },
      { status: 400 }
    );
  }

  const {
    geospatialDataType,
    dataType,
    selectedRoiGeometry,
    divideValue,
    datasetId,
    startDate,
    endDate,
    visParams,
    labelNames,
  } = body;

  if (!selectedRoiGeometry) {
    return NextResponse.json(
      {
        error:
          "No Region of Interest (ROI) was provided. Please provide an ROI.",
      },
      { status: 400 }
    );
  }

  let geometry = selectedRoiGeometry;

  if (typeof selectedRoiGeometry === "string") {
    try {
      const geometryString = decodeURIComponent(selectedRoiGeometry);
      geometry = JSON.parse(geometryString);
    } catch (err) {
      return NextResponse.json(
        { error: "Invalid geometry format" },
        { status: 400 }
      );
    }
  }

  try {
    await initializeGee();
    const eeReadyGeometry = convertToEeGeometry(geometry);

    switch (geospatialDataType) {
      case "Load GEE Data": {
        if (!datasetId || !startDate || !endDate || !visParams || !labelNames) {
          return NextResponse.json(
            { error: "Missing required parameters" },
            { status: 400 }
          );
        }

        const result = await loadRasterData(
          datasetId,
          dataType,
          eeReadyGeometry,
          startDate,
          endDate,
          divideValue,
          visParams,
          labelNames
        );

        if (!result) {
          return NextResponse.json(
            { error: "Failed to load raster data" },
            { status: 500 }
          );
        }

        return NextResponse.json(result);
      }

      default:
        return NextResponse.json(
          { error: "Invalid geospatial data type" },
          { status: 400 }
        );
    }
  } catch (error: any) {
    console.error("Error processing request:", error);
    return NextResponse.json(
      { error: error.message || "Failed to process geospatial data" },
      { status: 500 }
    );
  }
}

const initializeGee = async () => {
  await geeAuthenticate();
};


================================================
FILE: app/(main)/api/sendfeedback/route.ts
================================================
// It's a simple email-based feedback form. The user sends a message, and it gets sent to a recipient email address.

import { createClient } from "@/utils/supabase/server";
import FormData from "form-data";
import Mailgun from "mailgun.js";
import { NextResponse } from "next/server";

const API_KEY = process.env.MAILGUN_API_KEY || "";
const DOMAIN = process.env.MAILGUN_DOMAIN || "";
const ALLOWED_ORIGIN = process.env.NEXT_PUBLIC_APP_URL!;
const RECIPIENT_EMAIL = process.env.RECIPIENT_EMAIL;
const SENDER_EMAIL = process.env.SENDER_EMAIL;

export async function OPTIONS() {
  return new NextResponse(null, {
    status: 200,
    headers: {
      "Access-Control-Allow-Origin": ALLOWED_ORIGIN,
      "Access-Control-Allow-Methods": "POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
  });
}

export async function POST(req: Request) {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();
  const user = data?.user;
  if (error || !data.user) {
    return new NextResponse(JSON.stringify({ error: "Unauthenticated!" }), {
      status: 401,
    });
  }

  const contentType = req.headers.get("content-type") || "";
  if (!contentType.includes("application/json")) {
    return new NextResponse(
      JSON.stringify({ error: "Unsupported media type. Please send JSON." }),
      { status: 415 }
    );
  }

  let body;
  try {
    body = await req.json();
  } catch (err) {
    console.error("Error parsing JSON:", err);
    return new NextResponse(
      JSON.stringify({ error: "Invalid JSON payload." }),
      { status: 400 }
    );
  }
  const { message } = body || {};
  if (!message) {
    return new NextResponse(
      JSON.stringify({
        error: "All fields (message) are required.",
      }),
      { status: 400 }
    );
  }

  try {
    const mailgun = new Mailgun(FormData);
    const client = mailgun.client({ username: "api", key: API_KEY });

    const messageData = {
      from: `Chat2Geo ${SENDER_EMAIL}`,
      to: RECIPIENT_EMAIL,
      subject: "New Feedback for Chat2Geo!",
      text: `Hello,
  
      You have a new form entry from: ${user?.email}.
  
      ${message}
      `,
    };
    await client.messages.create(DOMAIN, messageData);
    return new NextResponse(JSON.stringify({ submitted: true }), {
      status: 200,
    });
  } catch (mailErr: any) {
    console.error("Error sending email:", mailErr);
    return new NextResponse(JSON.stringify({ error: "Failed to send email" }), {
      status: 500,
    });
  }
}


================================================
FILE: app/(main)/api/services/google-maps/basemaps/roadmap/route.ts
================================================
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";

export async function GET(request: NextRequest) {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();
  if (error || !data.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }

  const { searchParams } = new URL(request.url);
  const x = searchParams.get("x");
  const y = searchParams.get("y");
  const z = searchParams.get("z");
  const GOOGLE_MAPS_API_KEY = process.env.GOOGLE_MAPS_API_KEY;

  if (!GOOGLE_MAPS_API_KEY) {
    return NextResponse.json(
      { error: "API key is not configured" },
      { status: 500 }
    );
  }

  if (!x || !y || !z) {
    return NextResponse.json({ error: "Missing parameters" }, { status: 400 });
  }

  const tileUrl = `https://maps.googleapis.com/maps/vt?lyrs=r&x=${x}&y=${y}&z=${z}&key=${GOOGLE_MAPS_API_KEY}`;
  return NextResponse.redirect(tileUrl);
}


================================================
FILE: app/(main)/api/services/google-maps/basemaps/satellite/route.ts
================================================
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";

export async function GET(request: NextRequest) {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();
  if (error || !data.user) {
    return NextResponse.json({ error: "Unauthenticated!" }, { status: 401 });
  }

  const { searchParams } = new URL(request.url);
  const x = searchParams.get("x");
  const y = searchParams.get("y");
  const z = searchParams.get("z");
  const GOOGLE_MAPS_API_KEY = process.env.GOOGLE_MAPS_API_KEY;

  if (!GOOGLE_MAPS_API_KEY) {
    return NextResponse.json(
      { error: "API key is not configured" },
      { status: 500 }
    );
  }

  if (!x || !y || !z) {
    return NextResponse.json({ error: "Missing parameters" }, { status: 400 });
  }

  const tileUrl = `https://maps.googleapis.com/maps/vt?lyrs=s&x=${x}&y=${y}&z=${z}&key=${GOOGLE_MAPS_API_KEY}`;
  return NextResponse.redirect(tileUrl);
}


================================================
FILE: app/(main)/api/services/google-maps/geocode/route.ts
================================================
import { NextResponse } from "next/server";
import { Client } from "@googlemaps/google-maps-services-js";

const client = new Client({});

export async function POST(request: Request) {
  const { address } = await request.json();

  if (!address) {
    return NextResponse.json({ error: "Address is required" }, { status: 400 });
  }

  try {
    const response = await client.geocode({
      params: {
        address,
        key: process.env.GOOGLE_MAPS_API_KEY || "",
      },
    });

    return NextResponse.json(response.data);
  } catch (error) {
    console.error("Geocoding error:", error);
    return NextResponse.json(
      { error: "Failed to geocode address" },
      { status: 500 }
    );
  }
}


================================================
FILE: app/(main)/api/services/google-maps/places/route.ts
================================================
import { NextResponse } from "next/server";

export async function GET() {
  const GOOGLE_MAPS_API_KEY = process.env.GOOGLE_MAPS_API_KEY;

  if (!GOOGLE_MAPS_API_KEY) {
    return NextResponse.json({ error: "API key is missing" }, { status: 500 });
  }

  const scriptUrl = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&libraries=places`;

  return NextResponse.json({ scriptUrl });
}


================================================
FILE: app/(main)/api/user-usage/route.ts
================================================
import { NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";
import { getUsageForUser, getUserRoleAndTier } from "@/lib/database/usage";
import { getPermissionSet } from "@/lib/auth";

export async function GET() {
  const supabase = await createClient();

  const { data: authData, error: authError } = await supabase.auth.getUser();
  if (authError || !authData?.user) {
    return NextResponse.json({ error: "Unauthenticated" }, { status: 401 });
  }
  const userId = authData.user.id;

  const usage = await getUsageForUser(userId);

  const userRoleRecord = await getUserRoleAndTier(userId);
  if (!userRoleRecord) {
    return NextResponse.json(
      { error: "Role or subscription not found" },
      { status: 404 }
    );
  }

  const { role, subscription_tier } = userRoleRecord;
  const { maxRequests, maxDocs, maxArea } = await getPermissionSet(
    role,
    subscription_tier
  );

  return NextResponse.json({
    requests_count: usage.requests_count,
    knowledge_base_docs_count: usage.knowledge_base_docs_count,
    maxRequests,
    maxDocs,
    maxArea,
  });
}


================================================
FILE: app/(main)/api/web-scraper/route.ts
================================================
import { NextResponse } from "next/server";
import * as cheerio from "cheerio";

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

    if (!url) {
      return NextResponse.json(
        { error: "URL parameter is required" },
        { status: 400 }
      );
    }

    const response = await fetch(url, {
      headers: {
        "User-Agent":
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        Accept:
          "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
        "Cache-Control": "no-cache",
        Pragma: "no-cache",
      },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const html = await response.text();
    const $ = cheerio.load(html);

    // Initialize content structure focused on code snippets
    const content = {
      title: $("title").text().trim(),
      codeSnippets: [] as Array<{
        language?: string;
        code: string;
        context?: string; // Optional heading or context where the code was found
      }>,
    };

    // Look for code blocks in pre and code tags
    $("pre, code").each((_, element) => {
      const $el = $(element);
      const code = $el.text().trim();

      // Skip empty code blocks
      if (!code) return;

      // Try to determine the language
      const language = $el.attr("class")?.match(/language-(\w+)/)?.[1];

      // Get surrounding context (e.g., nearest heading)
      const context = $el
        .closest("section")
        .find("h1, h2, h3, h4, h5, h6")
        .first()
        .text()
        .trim();

      // Avoid duplicates
      if (!content.codeSnippets.some((snippet) => snippet.code === code)) {
        content.codeSnippets.push({
          language,
          code,
          context: context || undefined,
        });
      }
    });

    return NextResponse.json({
      success: true,
      data: content,
      debug: {
        url: response.url,
        snippetsFound: content.codeSnippets.length,
      },
    });
  } catch (error) {
    console.error("Scraping error:", error);
    return NextResponse.json(
      {
        error: "Failed to scrape the webpage",
        details: error instanceof Error ? error.message : "Unknown error",
      },
      { status: 500 }
    );
  }
}


================================================
FILE: app/(main)/chat/[id]/layout.tsx
================================================
import React from "react";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";

export default async function ChatLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const supabase = await createClient();
  const {
    data: { user },
    error,
  } = await supabase.auth.getUser();

  if (error || !user) {
    redirect("/login");
  }

  return (
    <div className="relative flex min-h-screen">
      <main className="flex-grow">{children}</main>
    </div>
  );
}


================================================
FILE: app/(main)/chat/[id]/loading.tsx
================================================
"use client";

import React from "react";

export default function Loading() {
  return (
    <div className="page-loading-overlay">
      <div className="page-loading-spinner"></div>
    </div>
  );
}


================================================
FILE: app/(main)/chat/[id]/page.tsx
================================================
import MainChatPage from "@/features/chat/components/chat";
import { getChatById, getMessagesByChatId } from "@/lib/database/chat/queries";
import { notFound } from "next/navigation";

import { convertToUIMessages } from "@/features/chat/utils/general-utils";

export default async function Page(props: { params: Promise<{ id: string }> }) {
  const params = await props.params;
  const { id } = params;
  const chat = await getChatById(id);

  if (!chat) {
    notFound();
  }

  const initialMessages = await getMessagesByChatId(id);

  return (
    <MainChatPage
      chatId={id}
      initialMessages={convertToUIMessages(initialMessages as any)}
    />
  );
}


================================================
FILE: app/(main)/chat-history/layout.tsx
================================================
import React from "react";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";

export default async function ChatHistoryLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const supabase = await createClient();
  const {
    data: { user },
    error,
  } = await supabase.auth.getUser();

  if (error || !user) {
    redirect("/login");
  }

  return (
    <div className="relative flex min-h-screen">
      <main className="flex-grow">{children}</main>
    </div>
  );
}


================================================
FILE: app/(main)/chat-history/loading.tsx
================================================
import ChatHistoryTableSkeleton from "@/features/chat-history/components/chat-history-table-skeleton";

const loading = () => {
  return <ChatHistoryTableSkeleton />;
};

export default loading;


================================================
FILE: app/(main)/chat-history/page.tsx
================================================
export const dynamic = "force-dynamic";

import React from "react";
import ChatHistory from "@/features/chat-history/components/chat-history";

const ChatHistoryPage = async () => {
  return <ChatHistory />;
};

export default ChatHistoryPage;


================================================
FILE: app/(main)/integrations/layout.tsx
================================================
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";

export default async function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  const supabase = await createClient();
  const {
    data: { user },
    error,
  } = await supabase.auth.getUser();

  if (error || !user) {
    redirect("/login");
  }

  return (
    <div className="relative flex min-h-screen">
      <main className="flex-grow">{children}</main>
    </div>
  );
}


================================================
FILE: app/(main)/integrations/loading.tsx
================================================
"use client";

import React from "react";

export default function Loading() {
  return (
    <div className="page-loading-overlay">
      <div className="page-loading-spinner"></div>
    </div>
  );
}


================================================
FILE: app/(main)/integrations/page.tsx
================================================
export const dynamic = "force-dynamic";

import React from "react";
import IntegrationsPage from "@/features/integrations/components/integrations-page";

const Integrations = async () => {
  return <IntegrationsPage />;
};

export default Integrations;


================================================
FILE: app/(main)/knowledge-base/layout.tsx
================================================
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";

export default async function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  const supabase = await createClient();
  const {
    data: { user },
    error,
  } = await supabase.auth.getUser();

  if (error || !user) {
    redirect("/login");
  }

  return (
    <div className="relative flex min-h-screen">
      <main className="flex-grow">{children}</main>
    </div>
  );
}


================================================
FILE: app/(main)/knowledge-base/loading.tsx
================================================
"use client";

import React from "react";

export default function Loading() {
  return (
    <div className="page-loading-overlay">
      <div className="page-loading-spinner"></div>
    </div>
  );
}


================================================
FILE: app/(main)/knowledge-base/page.tsx
================================================
export const dynamic = "force-dynamic";

import KnowledgeBase from "@/features/knowledge-base/components/knolwedge-base";
import { fetchDocumentFiles } from "@/features/knowledge-base/actions/document-actions";

const KnowledgeBasePage = async () => {
  const documents = await fetchDocumentFiles();

  return <KnowledgeBase initialDocuments={documents} />;
};

export default KnowledgeBasePage;


================================================
FILE: app/(main)/layout.tsx
================================================
import localFont from "next/font/local";
import "./styles.css";
import "maplibre-gl/dist/maplibre-gl.css";
import "@blocknote/mantine/style.css";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
import { Analytics } from "@vercel/analytics/next";
import { getUserProfile } from "./actions/get-user-profile";
import ClientWrapper from "@/components/client-wrapper";
import { TooltipProvider } from "@/components/ui/tooltip";

const geistSans = localFont({
  src: "./fonts/GeistVF.woff",
  variable: "--font-geist-sans",
  weight: "100 900",
});
const geistMono = localFont({
  src: "./fonts/GeistMonoVF.woff",
  variable: "--font-geist-mono",
  weight: "100 900",
});

// Define metadata for the root layout
export const metadata = {
  title: "Chat2Geo",
  description: "AI-powered geospatial analyticse",
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const supabase = await createClient();
  const { data: authResults, error } = await supabase.auth.getUser();
  if (error || !authResults?.user) {
    redirect("/login");
  }
  const userProfile = await getUserProfile();
  return (
    <html lang="en" suppressHydrationWarning>
      <TooltipProvider>
        <body
          className={`${geistSans.variable} ${geistMono.variable} font-sans`}
        >
          <ClientWrapper userProfile={userProfile}>{children}</ClientWrapper>
          <Analytics />
        </body>
      </TooltipProvider>
    </html>
  );
}


================================================
FILE: app/(main)/loading.tsx
================================================
"use client";

import React from "react";

export default function Loading() {
  return (
    <div className="page-loading-overlay">
      <div className="page-loading-spinner"></div>
    </div>
  );
}


================================================
FILE: app/(main)/page.tsx
================================================
export const dynamic = "force-dynamic";

import MainChatPage from "@/features/chat/components/chat";
import "@blocknote/mantine/style.css";
import { generateUUID } from "@/features/chat/utils/general-utils";

export default async function Home() {
  const chatId = generateUUID();

  return (
    <div>
      <MainChatPage chatId={chatId} initialMessages={[]} />
    </div>
  );
}


================================================
FILE: app/(main)/styles.css
================================================
/* -------------------------------------------
  Tailwind Base Imports
------------------------------------------- */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* -------------------------------------------
  Theme Variables & Base Styles
------------------------------------------- */
@layer base {
  :root {
    /* Light Theme Variables */
    --background: 0 0% 100%;
    --foreground: 222.2 47.4% 11.2%;

    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;

    --popover: 0 0% 100%;
    --popover-foreground: 222.2 47.4% 11.2%;

    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;

    --card: 0 0% 100%;
    --card-foreground: 222.2 47.4% 11.2%;

    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;

    --primary-blue: 220 90% 56%;
    --primary-blue-foreground: 210 40% 98%;

    --primary-green: 145 63% 42%;
    --primary-green-foreground: 210 40% 98%;

    --secondary: 220 13% 92%;
    --secondary-foreground: 220 10% 40%;

    --accent: 220 15% 85%;
    --accent-foreground: 220 20% 12%;

    --destructive: 0 100% 50%;
    --destructive-foreground: 210 40% 98%;

    --warning: 45 100% 50%;
    --warning-foreground: 45 100% 15%;

    --info: 200 98% 48%;
    --info-foreground: 210 40% 98%;

    --ring: 215 20.2% 65.1%;
    --radius: 0.5rem;

    --shimmer-highlight: rgba(255, 255, 255, 0.6);
  }

  .dark {
    /* Dark Theme Variables */

    /* Base */
    --background: 240 2% 12%; /* #1f1f21 */
    --foreground: 0 0% 95%; /* Very light neutral */

    /* Cards/Popovers */
    --card: 220 3% 10%; /* #18191a */
    --card-foreground: 0 0% 95%;
    --popover: 220 3% 10%;
    --popover-foreground: 0 0% 95%;

    /* Muted */
    --muted: 240 2% 16%;
    --muted-foreground: 0 0% 60%;

    /* Interactive elements */
    --accent: 240 2% 18%;
    --accent-foreground: 0 0% 98%;

    /* Borders & Rings */
    --border: 240 2% 20%;
    --input: 240 2% 20%;
    --ring: 240 2% 20%;

    /* Primary */
    --primary: 0 0% 98%;
    --primary-foreground: 240 2% 12%;

    /* Secondary */
    --secondary: 240 2% 22%;
    --secondary-foreground: 0 0% 98%;

    /* Semantic Colors */
    --primary-blue: 220 90% 56%;
    --primary-blue-foreground: 210 40% 98%;

    --primary-green: 145 63% 42%;
    --primary-green-foreground: 210 40% 98%;

    --destructive: 0 50% 35%;
    --destructive-foreground: 0 0% 98%;

    --warning: 45 100% 50%;
    --warning-foreground: 45 100% 15%;

    --info: 200 98% 48%;
    --info-foreground: 210 40% 98%;

    --shimmer-highlight: rgba(255, 255, 255, 0.1);
  }

  /* Universal defaults */
  * {
    @apply border-border;
  }

  body {
    @apply font-sans antialiased bg-background text-foreground;
  }
}

/* -------------------------------------------
   Keyframes & Animation Classes
------------------------------------------- */
@keyframes shimmer {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: 0% 0;
  }
}

.animate-shimmer {
  display: inline-block;
  color: transparent;
  background: linear-gradient(
    90deg,
    #1e90ff,
    #34d399,
    #a3e635,
    #ffd700,
    #ff8c00,
    #ff69b4,
    #ff007f,
    #7928ca,
    #1e90ff
  );
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  animation: shimmer 3s linear infinite;
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.fade-in {
  animation: fadeIn 0.6s ease-in-out;
}

/* -------------------------------------------
  MapLibre GL Styles
------------------------------------------- */

/* Remove default focus ring for MapLibre canvas */
.maplibregl-canvas:focus {
  outline: none !important;
  box-shadow: none !important;
}

/* Attribution text */
.maplibregl-ctrl-attrib-inner {
  font-size: 12px !important;
  white-space: nowrap;
  padding: 2px 2px;
}

/* Popup content for light theme (default) */
.maplibregl-popup-content {
  background-color: hsl(var(--background));
  color: hsl(var(--foreground));
  border-radius: var(--radius);
  padding: 8px 10px;
  box-shadow: 0 2px 6px hsl(var(--foreground) / 0.2);
}

.maplibregl-popup-tip {
  border-top-color: hsl(var(--background)) !important;
}

/* Popup content for dark theme */
:is([data-theme="dark"], .dark) .maplibregl-popup-content {
  background-color: hsl(var(--background));
  color: hsl(var(--foreground));
}

:is([data-theme="dark"], .dark) .maplibregl-popup-tip {
  border-top-color: hsl(var(--background)) !important;
}

/* Scale control (light) */
.maplibregl-ctrl.maplibregl-ctrl-scale {
  background-color: hsl(var(--background) / 0.75);
  border-color: hsl(var(--muted-foreground));
  color: hsl(var(--foreground));
}

/* Base for maplibre control buttons */
.maplibregl-ctrl button {
  background-color: hsl(var(--background));
}

/* Control group styling (light) */
.maplibregl-ctrl-group {
  background: hsl(var(--background));
  display: inline-flex !important;
  flex-direction: row !important;
}

/* Reset button styles, remove default borders */
.maplibregl-ctrl-group > button {
  border: none !important;
  border-radius: 0 !important;
}

/* Add divider between adjacent buttons */
.maplibregl-ctrl-group > button + button {
  border-left: 2px solid hsl(var(--border)) !important;
}

/* Round left/right edges of group */
.maplibregl-ctrl-group > button:first-of-type {
  border-top-left-radius: var(--radius) !important;
  border-bottom-left-radius: var(--radius) !important;
}
.maplibregl-ctrl-group > button:last-of-type {
  border-top-right-radius: var(--radius) !important;
  border-bottom-right-radius: var(--radius) !important;
}

/* Add subtle box-shadow for group */
.maplibregl-ctrl-group:not(:empty) {
  box-shadow: 0 0 0 2px hsl(var(--border) / 0.1);
}

/* Dark theme overrides for scale control & buttons */
:is([data-theme="dark"], .dark) .maplibregl-ctrl.maplibregl-ctrl-scale {
  background-color: hsl(var(--background) / 0.75);
  border-color: hsl(var(--muted-foreground));
  color: hsl(var(--foreground));
}
:is([data-theme="dark"], .dark) .maplibregl-ctrl button {
  background-color: hsl(var(--background));
}
:is([data-theme="dark"], .dark) .maplibregl-ctrl-group {
  background: hsl(var(--background));
}
:is([data-theme="dark"], .dark) .maplibregl-ctrl-group:not(:empty) {
  box-shadow: 0 0 0 2px hsl(var(--border) / 0.1);
}
:is([data-theme="dark"], .dark) .maplibregl-ctrl button .maplibregl-ctrl-icon {
  filter: invert(1) brightness(100);
}
:is([data-theme="dark"], .dark) .maplibregl-ctrl button:hover {
  background-color: hsl(var(--muted));
}

/* Attribution control (light) */
.maplibregl-ctrl.maplibregl-ctrl-attrib {
  background-color: hsl(var(--background) / 0.6);
  border-radius: var(--radius);
}
.maplibregl-ctrl-attrib-inner {
  background-color: transparent;
  color: hsl(var(--foreground));
  border-radius: var(--radius);
}

/* Attribution control (dark) */
:is([data-theme="dark"], .dark) .maplibregl-ctrl.maplibregl-ctrl-attrib {
  background-color: hsl(var(--background) / 0.6);
  color: hsl(var(--foreground));
  border-radius: var(--radius);
}
:is([data-theme="dark"], .dark) .maplibregl-ctrl-attrib-inner {
  background-color: transparent;
  color: hsl(var(--foreground));
  border-radius: var(--radius);
}

/* -------------------------------------------
  BN Container Overrides
------------------------------------------- */
.bn-container[data-color-scheme="light"] {
  --bn-colors-editor-background: hsl(var(--background)) !important;
}

.bn-container[data-color-scheme="dark"] {
  --bn-colors-editor-background: hsl(var(--background)) !important;
}

/* -------------------------------------------
  Misc. Utilities
------------------------------------------- */
.custom-scrollbar {
  scrollbar-width: thin; /* Firefox */
}

/* Loading overlay */
.page-loading-overlay {
  @apply fixed inset-0 z-[9999] flex items-center justify-center bg-transparent;
}

/* Loading spinner */
.page-loading-spinner {
  @apply inline-block h-10 w-10 animate-spin rounded-full border-4 border-blue-400 border-t-transparent;
}


================================================
FILE: app/actions/rag-actions.ts
================================================
"use server";
import { createClient } from "@/utils/supabase/server";
import { WebPDFLoader } from "@langchain/community/document_loaders/web/pdf";
import { SupabaseVectorStore } from "@langchain/community/vectorstores/supabase";
import { generateEmbeddings } from "@/features/knowledge-base/lib/generate-embeddings";
import { generateChunks } from "@/features/knowledge-base/lib/generate-embeddings";
import { cleanString } from "@/utils/general/general-utils";

export async function saveRagDocument(
  file: any,
  numberOfPages: number,
  folderId: string | null
) {
  const supabase = await createClient();
  const { data: authResult, error: userError } = await supabase.auth.getUser();
  if (userError || !authResult?.user) {
    throw new Error("Unauthenticated!");
  }

  const bucketName = "documents_bucket";
  const filePath = `${authResult.user.id}/${file.name}`;

  const { error: uploadError } = await supabase.storage
    .from(bucketName)
    .upload(filePath, file);

  if (uploadError)
    throw new Error(`Failed to upload file: ${uploadError.message}`);

  const { data: signedUrlData, error: signedUrlError } = await supabase.storage
    .from(bucketName)
    .createSignedUrl(filePath, 60 * 60 * 1); // Signed URL valid for 1 hour

  if (signedUrlError)
    throw new Error(`Failed to create signed URL: ${signedUrlError.message}`);

  const fileSignedURL = signedUrlData?.signedUrl;

  if (!fileSignedURL) {
    throw new Error("Failed to retrieve the file's signed URL.");
  }

  const { data: fileData, error: fileError } = await supabase
    .from("document_files")
    .insert({
      name: file.name,
      owner: authResult.user.id,
      number_of_pages: numberOfPages,
      file_path: fileSignedURL,
      folder_id: folderId ?? null,
    })
    .select()
    .single<DocumentFile>();

  if (fileError) throw fileError;

  const loader = new WebPDFLoader(file);
  const output = await loader.load();

  const docs = output.map((d) => ({
    ...d,
    metadata: {
      ...d.metadata,
      fileName: file.name,
      fileId: fileData.id,
      ownerId: authResult.user.id,
    },
  }));

  const splittedDocs = await generateChunks.splitDocuments(docs);

  const contents = splittedDocs.map((doc) => doc.pageContent);
  const embeddings = await generateEmbeddings.embedDocuments(contents);

  const sanitizedEmbeddingsData = splittedDocs.map((doc, index) => ({
    content: cleanString(doc.pageContent),
    metadata: doc.metadata,
    embedding: embeddings[index],
    file_id: fileData.id,
  }));

  const { error: embeddingsError } = await supabase
    .from("embeddings")
    .insert(sanitizedEmbeddingsData);

  if (embeddingsError) {
    console.error("Insert Error Details:", embeddingsError);
    throw new Error(`Failed to insert embeddings: ${embeddingsError.message}`);
  }

  return fileData;
}

export async function answerQuery(query: string) {
  const supabase = await createClient();
  const { data: authResult, error: userError } = await supabase.auth.getUser();
  if (userError || !authResult?.user) {
    throw new Error("Unauthenticated!");
  }

  const userId = authResult.user.id;

  const vectorStore = await SupabaseVectorStore.fromExistingIndex(
    generateEmbeddings,
    {
      client: supabase,
      tableName: "embeddings",
      queryName: "search_documents_by_similarity",
    }
  );

  const retriever = vectorStore.asRetriever({
    k: 5,
    filter: { owner: userId },
  });

  let topMatches = await retriever._getRelevantDocuments(query);

  topMatches = topMatches.filter((match) => match.metadata.similarity > 0.4);

  const matchesByPage = topMatches.reduce((acc, doc) => {
    const pageNumber = doc.metadata.loc.pageNumber;
    const currentBest = acc[pageNumber];
    if (
      !currentBest ||
      doc.metadata.similarity > currentBest.metadata.similarity
    ) {
      acc[pageNumber] = doc;
    }
    return acc;
  }, {} as Record<number, (typeof topMatches)[number]>);

  return Object.values(matchesByPage);
}


================================================
FILE: components/changelog-modal.tsx
================================================
"use client";
import React, { useState, useEffect } from "react";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import ReactMarkdown from "react-markdown";
import { changelog } from "@/lib/changelog";

export default function ChangelogModal() {
  const [hasMounted, setHasMounted] = useState(false);
  const [open, setOpen] = useState(false);

  useEffect(() => {
    setHasMounted(true);
    if (changelog.length > 0) {
      const newest = changelog[0];
      const storedVersion = localStorage.getItem("changelog_last_seen_version");
      if (storedVersion !== newest.version) {
        setOpen(true);
      }
    }
  }, []);

  if (!hasMounted) return null;

  const latestEntry = changelog[0];
  if (!latestEntry) return null;

  return (
    <Dialog
      open={open}
      onOpenChange={(value) => {
        setOpen(value);
        if (!value) {
          localStorage.setItem(
            "changelog_last_seen_version",
            latestEntry.version
          );
        }
      }}
    >
      <DialogContent className="max-w-xl">
        <DialogHeader>
          <DialogTitle>
            Chat2Geo Updated (Version {latestEntry.version}) -{" "}
            {latestEntry.date}
          </DialogTitle>
        </DialogHeader>

        <ReactMarkdown className="prose dark:prose-invert max-w-none">
          {latestEntry.content}
        </ReactMarkdown>
      </DialogContent>
    </Dialog>
  );
}


================================================
FILE: components/client-hydrator.tsx
================================================
"use client";
import { useEffect } from "react";
import { useUserStore } from "@/stores/use-user-profile-store";

interface ClientHydratorProps {
  userProfile: {
    email: string;
    name: string;
    role: string;
    organization: string;
    licenseStart: string;
    licenseEnd: string;
  } | null;
}

export default function ClientHydrator({ userProfile }: ClientHydratorProps) {
  const { setUserData } = useUserStore();

  useEffect(() => {
    if (userProfile) {
      setUserData(
        userProfile.name,
        userProfile.email,
        userProfile.role,
        userProfile.organization,
        userProfile.licenseStart,
        userProfile.licenseEnd
      );
    }
  }, [userProfile, setUserData]);

  return null;
}


================================================
FILE: components/client-wrapper.tsx
================================================
// components/client-wrapper.tsx
"use client";

import React from "react";
import { ThemeProvider } from "@/components/theme-provider";
import { Toaster } from "react-hot-toast";
import ToastMessage from "@/features/ui/toast-message";
import MainSidebar from "@/components/main-sidebar/main-sidebar";
import ClientHydrator from "@/components/client-hydrator";
import ChangelogModal from "@/components/changelog-modal";

export default function ClientWrapper({
  userProfile,
  children,
}: {
  userProfile: any;
  children: React.ReactNode;
}) {
  return (
    <ThemeProvider
      attribute="class"
      defaultTheme="light"
      enableSystem
      disableTransitionOnChange
    >
      <ClientHydrator userProfile={userProfile} />
      <ChangelogModal />
      <Toaster />
      <ToastMessage />

      <MainSidebar />
      {children}
    </ThemeProvider>
  );
}


================================================
FILE: components/document-viewer.tsx
================================================
"use client";

import React from "react";
import { useDocumentViewer } from "@/hooks/docs-hooks/use-document-viewer";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

interface DocumentViewerProps {
  documentName: string;
  pageNumber: number;
  onClose: () => void;
}

export function DocumentViewer({
  documentName,
  pageNumber,
  onClose,
}: DocumentViewerProps) {
  const { pdfUrl, error, isLoading } = useDocumentViewer(documentName);

  // Render any error in a Dialog
  if (error) {
    return (
      <Dialog open={true} onOpenChange={() => onClose()}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle className="text-red-600">Error</DialogTitle>
            <DialogDescription>{error}</DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button onClick={onClose}>Close</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    );
  }

  // Otherwise, show the PDF in a Dialog
  return (
    <Dialog open={!!pdfUrl} onOpenChange={() => onClose()}>
      <DialogContent className="max-w-5xl w-full h-[95vh] flex flex-col">
        <DialogHeader>
          <DialogTitle>
            {documentName} (Page {pageNumber})
          </DialogTitle>
        </DialogHeader>

        <div className="flex-1 overflow-hidden">
          {/* If pdfUrl is present, show an iframe with #page=pageNumber */}
          {pdfUrl && (
            <iframe
              src={`${pdfUrl}#page=${pageNumber}`}
              width="100%"
              height="100%"
            />
          )}
        </div>

        <DialogFooter></DialogFooter>
      </DialogContent>
    </Dialog>
  );
}


================================================
FILE: components/feedback.tsx
================================================
"use client";

import React, { useState } from "react";
import { Rnd } from "react-rnd";
import { Card } from "@/components/ui/card";
import {
  CardHeader,
  CardTitle,
  CardContent,
  CardFooter,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
// Lucide icons
import { X, Send, Loader2 } from "lucide-react";

import useToastMessageStore from "@/stores/use-toast-message-store";

interface FeedbackFloatingProps {
  isOpen: boolean;
  onClose: () => void;
}

export function FeedbackFloating({ isOpen, onClose }: FeedbackFloatingProps) {
  const [feedback, setFeedback] = useState("");

  // Track submit/loading state
  const [isSubmitting, setIsSubmitting] = useState(false);

  const setToastMessage = useToastMessageStore(
    (state) => state.setToastMessage
  );

  // Draggable + Resizable state for the popup
  const [panelPosition, setPanelPosition] = useState({ x: 300, y: 120 });
  const [panelSize, setPanelSize] = useState({ width: 400, height: 300 });

  if (!isOpen) return null;

  async function handleSubmit() {
    setIsSubmitting(true);
    try {
      const res = await fetch("/api/sendfeedback", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ message: feedback }),
      });

      if (res.ok) {
        setToastMessage("Thanks for your feedback!", "success");
        setFeedback("");
        onClose();
      } else {
        setToastMessage("Something went wrong. Please try again!", "error");
      }
    } catch (err) {
      console.error(err);
      setToastMessage("Error sending feedback.", "error");
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    <Rnd
      position={panelPosition}
      size={{ width: panelSize.width, height: panelSize.height }}
      onDragStop={(e, d) => setPanelPosition({ x: d.x, y: d.y })}
      onResizeStop={(e, dir, ref, delta, pos) => {
        setPanelSize({
          width: parseInt(ref.style.width, 10),
          height: parseInt(ref.style.height, 10),
        });
        setPanelPosition({ x: pos.x, y: pos.y });
      }}
      minWidth={320}
      minHeight={200}
      bounds="parent"
      dragHandleClassName="drag-handle"
      className="fixed z-[9999]"
    >
      <Card className="w-full h-full flex flex-col">
        <CardHeader
          className="
            drag-handle
            border-b border-stone-300 dark:border-stone-600
            px-4 py-2
          "
        >
          <div className="flex w-full items-center justify-between cursor-move">
            <CardTitle>Feedback</CardTitle>
            <Button
              variant="ghost"
              size="icon"
              onClick={onClose}
              className="hover:bg-transparent text-muted-foreground hover:text-foreground"
            >
              <X size={16} />
            </Button>
          </div>
        </CardHeader>

        <CardContent className="flex-1 p-4 overflow-hidden flex flex-col min-h-0">
          <Textarea
            placeholder="Please let use know your requests or feedback, or any issues/bugs you came across while using the app."
            value={feedback}
            onChange={(e) => setFeedback(e.target.value)}
            className="
              flex-1 h-full w-full resize-none
              border-stone-300 dark:border-stone-600
            "
          />
        </CardContent>

        <CardFooter className="border-t border-stone-300 dark:border-stone-600 p-3 flex justify-end">
          <Button
            onClick={handleSubmit}
            disabled={isSubmitting}
            className="flex items-center gap-2"
          >
            {isSubmitting ? (
              // Spinner icon
              <Loader2 size={16} className="animate-spin" />
            ) : (
              // Normal send icon
              <Send size={16} />
            )}
            {isSubmitting ? "Sending..." : "Submit"}
          </Button>
        </CardFooter>
      </Card>
    </Rnd>
  );
}


================================================
FILE: components/loading-widgets/loading-for-widget.tsx
================================================
import React from "react";

interface LoadingWidgetForContainerProps {
  loadingSize: "xs" | "sm" | "md" | "lg" | "xl";
  color?:
    | "white"
    | "blue"
    | "red"
    | "green"
    | "yellow"
    | "gray"
    | "purple"
    | "pink";
}

const LoadingWidgetForContainer: React.FC<LoadingWidgetForContainerProps> = ({
  loadingSize = "sm",
  color = "white",
}) => {
  // Define size mappings
  const sizeClasses = {
    xs: "w-4 h-4 border-2",
    sm: "w-6 h-6 border-2",
    md: "w-8 h-8 border-4",
    lg: "w-10 h-10 border-4",
    xl: "w-12 h-12 border-4",
  };

  // Define color mappings
  const colorClasses = {
    white: "border-white",
    blue: "border-blue-500",
    red: "border-red-500",
    green: "border-green-500",
    yellow: "border-yellow-500",
    gray: "border-gray-500",
    purple: "border-purple-500",
    pink: "border-pink-500",
  };

  return (
    <div
      className={`inline-block rounded-full border-t-transparent animate-spin ${sizeClasses[loadingSize]} ${colorClasses[color]}`}
    ></div>
  );
};

export default LoadingWidgetForContainer;


================================================
FILE: components/loading-widgets/loading-primary.tsx
================================================
import React from "react";
import useLoadingStore from "@/stores/use-loading-store";

const LoadingWidget = () => {
  const { isLoading, isLocal } = useLoadingStore();

  if (!isLoading) return null;

  return (
    <>
      {/* Background overlay */}
      <div className="absolute inset-0 bg-muted opacity-70 z-[1000] h-full w-full"></div>
      {/* Spinner */}
      <div className="absolute inset-0 flex items-center justify-center z-[2000] h-full w-full">
        <div className="inline-block w-8 h-8 border-4 border-blue-400 border-t-transparent rounded-full animate-spin"></div>
      </div>
    </>
  );
};

export default LoadingWidget;


================================================
FILE: components/main-sidebar/app-setttings.tsx
================================================
import React from "react";
import { IconSettings } from "@tabler/icons-react";
import { useButtonsStore } from "@/stores/use-buttons-store";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ThemeModeToggle } from "@/components/ui/theme-mode-toggle";
import { Label } from "@/components/ui/label";
import { Tooltip } from "react-tooltip";

const appVersion = process.env.NEXT_PUBLIC_APP_VERSION;

const AppSettings = () => {
  const isSidebarCollapsed = useButtonsStore(
    (state) => state.isSidebarCollapsed
  );

  return (
    <section className="z-[5000]">
      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          <div className="px-4 py-2 pb-6 text-accent-foreground cursor-pointer text-sm font-normal">
            <div
              className={`flex items-center px-3 py-2 gap-4 w-full rounded-xl text-gray-100 hover:bg-muted dark:hover:bg-muted-foreground/20 hover:text-foreground ${
                isSidebarCollapsed ? "justify-center" : "justify-start"
              }`}
              data-tooltip-content="Open settings"
              data-tooltip-id="settings"
            >
              <button className="">
                <IconSettings stroke={1.5} className="h-7 w-7 flex-shrink-0" />
              </button>
              {!isSidebarCollapsed && <span>Settings</span>}
            </div>
          </div>
        </DropdownMenuTrigger>

        <DropdownMenuContent
          side="left"
          align="end"
          className="w-64 dark:text-accent-foreground bg-background dark:bg-accent"
        >
          <DropdownMenuLabel>Settings</DropdownMenuLabel>
          <DropdownMenuSeparator />

          <DropdownMenuGroup>
            <div className="p-2">
              <h3 className="text-sm font-medium mb-1">Appearance</h3>
              <p className="text-xs text-muted-foreground mb-2">
                Customize how the application looks
              </p>
              <div className="flex items-center justify-between">
                <Label htmlFor="theme-toggle" className="text-sm">
                  Theme
                </Label>
                <ThemeModeToggle />
              </div>
            </div>
          </DropdownMenuGroup>

          <DropdownMenuSeparator />

          <div className="p-2 pt-1 text-xs text-muted-foreground">
            <p className="mb-1">
              Version: <strong>{appVersion}</strong>
            </p>
          </div>
        </DropdownMenuContent>
      </DropdownMenu>
      <Tooltip
        id="settings"
        place="right"
        style={{
          backgroundColor: "white",
          color: "black",
          position: "fixed",
          zIndex: 9999,
          padding: "8px",
          borderRadius: "4px",
          boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
          fontWeight: "500",
        }}
        hidden={!isSidebarCollapsed}
      />
    </section>
  );
};

export default AppSettings;


================================================
FILE: components/main-sidebar/main-sidebar.tsx
================================================
"use client";
import React, { useState } from "react";
import { Tooltip } from "react-tooltip";
import useSidebarButtonStores, {
  Pages,
} from "@/stores/use-sidebar-button-stores";
import {
  IconEdit,
  IconBook,
  IconDatabaseImport,
  IconCirclesRelation,
  IconCircleChevronLeft,
  IconCircleChevronRight,
  IconMessage,
} from "@tabler/icons-react";

import { useButtonsStore } from "@/stores/use-buttons-store";
import { useRouter } from "next/navigation";
import { Separator } from "@/components/ui/separator";
import AppSettings from "./app-setttings";
import UserProfile from "@/features/user-profile/components/user-profile-modal";
import { resetChatStores } from "@/utils/reset-chat-stores";
import { FeedbackFloating } from "../feedback";

const MainSidebar = () => {
  const setPageToOpen = useSidebarButtonStores((state) => state.setPageToOpen);
  const pageToOpen = useSidebarButtonStores((state) => state.pageToOpen);

  const isSidebarCollapsed = useButtonsStore(
    (state) => state.isSidebarCollapsed
  );
  const toggleSidebarCollapse = useButtonsStore(
    (state) => state.toggleSidebarCollapse
  );

  const [isFeedbackOpen, setIsFeedbackOpen] = useState(false);

  const router = useRouter();

  function handleOpenPage(page: Pages) {
    setPageToOpen(page);
    if (page === Pages.NewChat) {
      resetChatStores();
    }
    router.push(`/${page}`);
  }

  // Helper to build class names conditionally
  function getButtonClasses(page: Pages, extraClasses?: string) {
    const base =
      `flex items-center gap-4 px-3 py-2 rounded-xl w-full cursor-pointer ${
        isSidebarCollapsed ? "justify-center" : "justify-start"
      } ${extraClasses || ""}`.trim();

    // If this button is for the active page, use "active" styles only:
    if (pageToOpen === page && page !== Pages.NewChat) {
      return `${base} bg-stone-300 text-gray-800`;
    }

    return `${base} text-gray-100 hover:bg-muted dark:hover:bg-muted-foreground/20 hover:text-foreground`;
  }

  return (
    <>
      <div
        className={`fixed top-0 left-0 h-screen bg-[#6C7782] z-[2000] dark:bg-accent ${
          isSidebarCollapsed ? "w-20" : "w-64"
        } flex flex-col shadow-lg transition-all duration-300 ease-in-out overflow-hidden`}
      >
        {/* Navigation Links */}
        <nav className="flex-grow px-4 py-6 space-y-5 pt-14 text-sm font-normal">
          <button
            className={getButtonClasses(Pages.NewChat, "mb-10")}
            data-tooltip-content="Start a new session"
            data-tooltip-id="new-session"
            onClick={() => handleOpenPage(Pages.NewChat)}
          >
            <IconEdit stroke={1.5} className="h-7 w-7 flex-shrink-0" />
            {!isSidebarCollapsed && (
              <span className="whitespace-nowrap">New Session</span>
            )}
          </button>

          <button
            className={getButtonClasses(Pages.ChatHistory)}
            data-tooltip-content="View session history"
            data-tooltip-id="session-history"
            onClick={() => handleOpenPage(Pages.ChatHistory)}
          >
            <IconBook stroke={1.5} className="h-7 w-7 flex-shrink-0" />
            {!isSidebarCollapsed && (
              <span className="whitespace-nowrap">Session History</span>
            )}
          </button>
          <button
            className={getButtonClasses(Pages.KnowledgeBase)}
            data-tooltip-content="Manage knowledge base documents"
            data-tooltip-id="knowledge-base"
            onClick={() => handleOpenPage(Pages.KnowledgeBase)}
          >
            <IconDatabaseImport
              stroke={1.5}
              className="h-7 w-7 flex-shrink-0"
            />
            {!isSidebarCollapsed && (
              <span className="whitespace-nowrap">Knowledge Base</span>
            )}
          </button>

          <button
            className={getButtonClasses(Pages.Integrations)}
            data-tooltip-content="Manage integrations"
            data-tooltip-id="integrations"
            onClick={() => handleOpenPage(Pages.Integrations)}
          >
            <IconCirclesRelation
              stroke={1.5}
              className="h-7 w-7 flex-shrink-0"
            />
            {!isSidebarCollapsed && (
              <span className="whitespace-nowrap">Integrations</span>
            )}
          </button>

          {/* Feedback Button (placed inside nav, using same style) */}
          <button
            onClick={() => setIsFeedbackOpen(true)}
            className={getButtonClasses(Pages.NewChat, "mb-3 translate-y-10")}
            data-tooltip-content="Send feedback"
            data-tooltip-id="feedback"
          >
            <IconMessage stroke={1.5} className="h-7 w-7 flex-shrink-0" />
            {!isSidebarCollapsed && <span>Feedback</span>}
          </button>
        </nav>

        {/* Footer */}
        <UserProfile />
        <AppSettings />

        <Separator className="my-0 bg-gray-200 dark:bg-gray-200" />

        <div
          className="px-4 py-2 pt-1 cursor-pointer text-sm font-normal"
          onClick={toggleSidebarCollapse}
        >
          <div
            className={`flex items-center text-gray-100 px-3 py-2 gap-4 w-full rounded-xl hover:bg-muted dark:hover:bg-muted-foreground/20 hover:text-foreground ${
              isSidebarCollapsed ? "justify-center" : "justify-start"
            }`}
            data-tooltip-content="Toggle sidebar"
            data-tooltip-id="toggle-sidebar"
          >
            <div>
              {isSidebarCollapsed ? (
                <IconCircleChevronRight
                  stroke={1.5}
                  className="h-7 w-7 flex-shrink-0"
                />
              ) : (
                <IconCircleChevronLeft
                  stroke={1.5}
                  className="h-7 w-7 flex-shrink-0"
                />
              )}
            </div>
            {!isSidebarCollapsed && <span>Collapse</span>}
          </div>
        </div>

        {/* Tooltips */}
        <Tooltip
          id="new-session"
          place="right"
          style={{
            backgroundColor: "white",
            color: "black",
            position: "fixed",
            zIndex: 9999,
            padding: "8px",
            borderRadius: "4px",
            boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
            fontWeight: "500",
          }}
          hidden={!isSidebarCollapsed}
        />
        <Tooltip
          id="session-history"
          place="right"
          style={{
            backgroundColor: "white",
            color: "black",
            position: "fixed",
            zIndex: 9999,
            padding: "8px",
            borderRadius: "4px",
            boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
            fontWeight: "500",
          }}
          hidden={!isSidebarCollapsed}
        />
        <Tooltip
          id="build-center"
          place="right"
          style={{
            backgroundColor: "white",
            color: "black",
            position: "fixed",
            zIndex: 9999,
            padding: "8px",
            borderRadius: "4px",
            boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
            fontWeight: "500",
          }}
          hidden={!isSidebarCollapsed}
        />
        <Tooltip
          id="knowledge-base"
          place="right"
          style={{
            backgroundColor: "white",
            color: "black",
            position: "fixed",
            zIndex: 9999,
            padding: "8px",
            borderRadius: "4px",
            boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
            fontWeight: "500",
          }}
          hidden={!isSidebarCollapsed}
        />
        <Tooltip
          id="integrations"
          place="right"
          style={{
            backgroundColor: "white",
            color: "black",
            position: "fixed",
            zIndex: 9999,
            padding: "8px",
            borderRadius: "4px",
            boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
            fontWeight: "500",
          }}
          hidden={!isSidebarCollapsed}
        />
        <Tooltip
          id="feedback"
          place="right"
          style={{
            backgroundColor: "white",
            color: "black",
            position: "fixed",
            zIndex: 9999,
            padding: "8px",
            borderRadius: "4px",
            boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
            fontWeight: "500",
          }}
          hidden={!isSidebarCollapsed}
        />
        <Tooltip
          id="toggle-sidebar"
          place="right"
          style={{
            backgroundColor: "white",
            color: "black",
            position: "fixed",
            zIndex: 9999,
            padding: "8px",
            borderRadius: "4px",
            boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
            fontWeight: "500",
          }}
          hidden={!isSidebarCollapsed}
        />
      </div>
      {/* Feedback Floating Panel */}
      <FeedbackFloating
        isOpen={isFeedbackOpen}
        onClose={() => setIsFeedbackOpen(false)}
      />
    </>
  );
};

export default MainSidebar;


================================================
FILE: components/notices/privacy-policy.tsx
================================================
"use client";

import * as React from "react";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

interface PrivacyPolicyModalProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
}

export default function PrivacyPolicy({
  open,
  onOpenChange,
}: PrivacyPolicyModalProps) {
  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="max-h-[80vh] overflow-y-auto">
        <DialogHeader>
          <DialogTitle>Privacy Policy</DialogTitle>
        </DialogHeader>

        <div className="space-y-4 text-sm leading-relaxed">
          <p>
            <strong>Effective Date:</strong> January 06, 2025
          </p>
          <p>
            Your privacy is important to us. This Privacy Policy explains how we
            collect, use, share, and protect your personal information when you
            use the Chat2Geo platform (“the Service”).
          </p>

          <h2 className="font-semibold">1. Information We Collect</h2>
          <p>
            <strong>1.1 Personal Information</strong>
            <br />
            We may collect personal information such as your name, email
            address, and any additional information you provide when you create
            an account or use the Service.
            <br />
            <strong>Note:</strong> We do not record or collect IP addresses.
          </p>
          <p>
            <strong>1.2 Uploaded Content</strong>
            <br />
            We store the content you upload to the Service (e.g., text data,
            vector files, or documents) to provide you with geospatial analyses
            and related features. This includes data processed in your chats and
            any documents you integrate with the Service.
          </p>
          <p>
            <strong>1.3 Interaction Data</strong>
            <br />
            We may collect basic information about how you navigate or interact
            with the Service (e.g., feature usage, timestamps of actions) solely
            for improving user experience and maintaining platform stability. We
            do not collect IP addresses or other network identifiers.
          </p>

          <h2 className="font-semibold">2. How We Use Your Information</h2>
          <p>
            <strong>2.1 Providing the Service</strong>
            <br />
            We use your information exclusively to operate and maintain the
            Service, including running analyses, generating reports, and
            displaying results for your use. We do not share or sell your data
            to any third party for their own use.
          </p>
          <p>
            <strong>2.2 Improvement and Development</strong>
            <br />
            We may use aggregated or anonymized information about overall
            feature usage for research and development to enhance and refine our
            services. This data will not identify you or your specific User
            Data.
          </p>
          <p>
            <strong>2.3 Communication</strong>
            <br />
            We may use your contact information to send you administrative or
            technical notices, updates, and other information directly relevant
            to your use of the Service.
          </p>

          <h2 className="font-semibold">3. Sharing and Disclosure</h2>
          <p>
            <strong>3.1 Limited Disclosure to Service Providers</strong>
            <br />
            We may share minimal information with trusted service providers who
            help us operate and improve the Service. Such providers are bound by
            confidentiality and are prohibited from using the information for
            any purpose other than providing services to us.
          </p>
          <p>
            <strong>3.2 Legal Requirements</strong>
            <br />
            We may disclose your information if required by law or in response
            to valid legal processes, such as a subpoena or court order.
          </p>
          <p>
            <strong>3.3 Business Transfers</strong>
            <br />
            In the event of a merger, acquisition, or sale of assets, your
            information may be transferred as part of that transaction. We will
            notify you if any such transfer occurs.
          </p>

          <h2 className="font-semibold">4. Data Retention and Deletion</h2>
          <p>
            <strong>4.1 Storage Period</strong>
            <br />
            We retain your data as long as you have an active account or as
            needed to provide you with the Service. For beta testers, data and
            history may be removed upon completion of the beta period, as stated
            in our Terms of Service.
          </p>
          <p>
            <strong>4.2 Deletion Requests</strong>
            <br />
            You can delete your data at any time by following the instructions
            within the Service or by contacting us directly. Once deleted, your
            data may not be recoverable.
          </p>

          <h2 className="font-semibold">5. Security Measures</h2>
          <p>
            We take reasonable measures to protect your information from
            unauthorized access, alteration, disclosure, or destruction.
            However, no method of data transmission or storage is 100% secure,
            and we cannot guarantee absolute security.
          </p>

          <h2 className="font-semibold">6. Children’s Privacy</h2>
          <p>
            Because this Service provides specialized geospatial analysis tools
            intended for professional or academic use, it is not marketed toward
            or intended for use by individuals under the age of 18. We do not
            knowingly collect personal information from minors. If you are a
            parent or guardian and believe we may have inadvertently collected
            information from a minor, please contact us immediately.
          </p>

          <h2 className="font-semibold">7. Changes to This Privacy Policy</h2>
          <p>
            We may update this Privacy Policy from time to time to reflect
            changes in our practices. We will notify you by updating the
            “Effective Date” at the top of this page. Your continued use of the
            Service after any changes indicate your acceptance of the new
            Privacy Policy.
          </p>
          <p className="text-xs mt-4">Last Updated: January 06, 2025</p>
        </div>

        <DialogFooter></DialogFooter>
      </DialogContent>
    </Dialog>
  );
}


================================================
FILE: components/notices/terms-of-services.tsx
================================================
"use client";

import * as React from "react";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

interface TermsOfServiceModalProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
}

export default function TermsOfService({
  open,
  onOpenChange,
}: TermsOfServiceModalProps) {
  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="max-h-[80vh] overflow-y-auto">
        <DialogHeader>
          <DialogTitle>Terms of Service</DialogTitle>
        </DialogHeader>

        <div className="space-y-4 text-sm leading-relaxed">
          <p>
            <strong>Effective Date:</strong> January 06, 2025
          </p>
          <p>
            Welcome to the Chat2Geo platform (“the Service”). By accessing or
            using the Service, you agree to be bound by these Terms of Service
            (“Terms”). If you do not agree with any part of these Terms, you
            must not use the Service.
          </p>

          <h2 className="font-semibold">1. Beta Testing Program</h2>
          <p>
            <strong>1.1 Limited Access</strong>
            <br />
            Access to the beta version of the Service is granted by selection
            only. Each license is personal to the individual user and is
            non-transferable.
          </p>
          <p>
            <strong>1.2 Beta Features</strong>
            <br />
            Because this is a beta version, certain features may be in
            development or subject to change without notice. The Service may not
            operate as intended, and you may encounter bugs, errors, or other
            issues.
          </p>
          <p>
            <strong>1.3 Feedback</strong>
            <br />
            We welcome feedback about your experience with the Service. You
            grant us a non-exclusive, perpetual, irrevocable, royalty-free
            license to use, modify, and incorporate any feedback you provide
            into our products or services.
          </p>

          <h2 className="font-semibold">2. User Accounts</h2>
          <p>
            <strong>2.1 Account Creation</strong>
            <br />
            To use certain features of the Service, you may be required to
            create an account. You agree to provide accurate, current, and
            complete information during the registration process and to update
            such information to keep it accurate and complete.
          </p>
          <p>
            <strong>2.2 Account Security</strong>
            <br />
            You are responsible for maintaining the confidentiality of your
            account credentials and for all activities that occur under your
            account. You must immediately notify us of any unauthorized use of
            your account or any other breach of security.
          </p>

          <h2 className="font-semibold">3. User Content and Data</h2>
          <p>
            <strong>3.1 Data Storage</strong>
            <br />
            Your chats, analyses, and documents uploaded to the Service (“User
            Data”) are stored in our systems so that you can review or reload
            them at a later time.
          </p>
          <p>
            <strong>3.2 Ownership of User Data</strong>
            <br />
            You retain all ownership rights to content you upload. By uploading
            content to the Service, you grant us a limited, non-exclusive right
            to store, reproduce, and process your content solely for the purpose
            of providing the Service to you.
          </p>
          <p>
            <strong>3.3 Exclusive Use</strong>
            <br />
            Your session history and any documents you upload are only used for
            your own purposes, namely to facilitate and enhance the geospatial
            analyses you conduct. We do not share or use your content for any
            other purpose.
          </p>

          <h2 className="font-semibold">4. License and Usage</h2>
          <p>
            <strong>4.1 License Grant</strong>
            <br />
            Subject to these Terms, we grant you a limited, non-transferable,
            non-exclusive, revocable license to use the Service for lawful
            purposes.
          </p>
          <p>
            <strong>4.2 Prohibited Conduct</strong>
            <br />
            You agree not to use the Service to:
          </p>
          <ul className="list-disc list-inside ml-4">
            <li>
              Violate any local, state, national, or international law or
              regulation.
            </li>
            <li>
              Infringe or misappropriate the intellectual property rights of any
              third party.
            </li>
            <li>
              Upload harmful or disruptive materials, such as malware or
              viruses.
            </li>
            <li>
              Perform analyses or operations you are not authorized to execute.
            </li>
          </ul>

          <h2 className="font-semibold">5. Intellectual Property</h2>
          <p>
            All intellectual property rights in the Service, including any
            trademarks, logos, designs, or underlying technology, are owned or
            licensed by us. Nothing in these Terms grants you any right, title,
            or interest in our intellectual property except as expressly set
            forth herein.
          </p>

          <h2 className="font-semibold">6. Disclaimers</h2>
          <p>
            <strong>6.1 Beta Disclaimer</strong>
            <br />
            The Service is provided on an “as is” and “as available” basis. As
            this is a beta version, no warranties or guarantees of performance,
            reliability, or availability are provided.
          </p>
          <p>
            <strong>6.2 No Warranty</strong>
            <br />
            We disclaim any and all warranties, express or implied, including
            but not limited to merchantability, fitness for a particular
            purpose, and non-infringement.
          </p>

          <h2 className="font-semibold">7. Limitation of Liability</h2>
          <p>
            To the maximum extent permitted by law, we shall not be liable for
            any indirect, incidental, special, consequential, or exemplary
            damages arising out of or in connection with the use or inability to
            use the Service, even if we have been advised of the possibility of
            such damages.
          </p>

          <h2 className="font-semibold">8. Termination and Data Removal</h2>
          <p>
            <strong>8.1 Termination</strong>
            <br />
            We may terminate or suspend access to the Service at any time
            without prior notice or liability for any reason. You may also
            discontinue use of the Service at any time.
          </p>
          <p>
            <strong>8.2 Data Removal</strong>
            <br />
            Upon the end of your beta testing, all of your data and history will
            be permanently removed if not already deleted by you, and this
            removal is irreversible.
          </p>

          <h2 className="font-semibold">
            9. Governing Law and Dispute Resolution
          </h2>
          <p>
            These Terms shall be governed by and construed in accordance with
            the laws of the jurisdiction in which our company is registered,
            without regard to its conflict of law provisions. Any dispute
            arising under these Terms shall be resolved exclusively in the
            courts within that jurisdiction.
          </p>

          <h2 className="font-semibold">10. Changes to the Terms</h2>
          <p>
            We reserve the right to modify these Terms at any time. If we make
            material changes, we will notify you by updating the “Effective
            Date” at the top of this page. Your continued use of the Service
            after such changes constitute acceptance of the modified Terms.
          </p>

          <p className="text-xs mt-4">Last Updated: January 06, 2025</p>
        </div>

        <DialogFooter></DialogFooter>
      </DialogContent>
    </Dialog>
  );
}


================================================
FILE: components/services/esri/add-arcgis-layers.tsx
================================================
"use client";

import { useEffect } from "react";
import { fetchAgolLayersList } from "@/lib/fetchers/services/esri/fetch-layers-list";
import { useAgolLayersStore } from "@/features/maps/stores/use-agol-layers-store";

export default function AddArcGisLayerClient() {
  const setAvailableAgolLayers = useAgolLayersStore(
    (state) => state.setAvailableAgolLayers
  );

  useEffect(() => {
    const broadcastChannel = new BroadcastChannel("esriChannel");

    const fetchAndSetLayers = async () => {
      try {
        const layers = await fetchAgolLayersList();
        if (!layers || layers.length === 0) {
          throw new Error("No layers found.");
        }

        const connectionStatus = "connected";
        broadcastChannel.postMessage({ layers, connectionStatus });
      } catch (error) {
        console.error("Error fetching AGOL layers:", error);
      } finally {
        broadcastChannel.close();
        if (window.opener) {
          window.close();
        }
      }
    };

    fetchAndSetLayers();

    return () => {
      broadcastChannel.close();
    };
  }, [setAvailableAgolLayers]);

  return (
    <div className="flex items-center justify-center h-screen">
      <h1 className="text-lg font-bold">Loading Layers...</h1>
    </div>
  );
}


================================================
FILE: components/theme-provider.tsx
================================================
"use client";

import * as React from "react";
const NextThemesProvider = dynamic(
  () => import("next-themes").then((e) => e.ThemeProvider),
  {
    ssr: false,
  }
);

export type ThemeProviderProps = React.ComponentProps<
  typeof NextThemesProvider
>;
import dynamic from "next/dynamic";

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}


================================================
FILE: components/ui/alert-dialog.tsx
================================================
"use client";

import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";

import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";

const AlertDialog = AlertDialogPrimitive.Root;

const AlertDialogTrigger = AlertDialogPrimitive.Trigger;

const AlertDialogPortal = AlertDialogPrimitive.Portal;

const AlertDialogOverlay = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
  <AlertDialogPrimitive.Overlay
    className={cn(
      "fixed inset-0 z-[9999] bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
      className
    )}
    {...props}
    ref={ref}
  />
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;

const AlertDialogContent = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
  <AlertDialogPortal>
    <AlertDialogOverlay />
    <AlertDialogPrimitive.Content
      ref={ref}
      className={cn(
        "fixed left-[50%] top-[50%] z-[9999] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-gray-200 bg-white p-6 shadow-lg duration-200 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%] sm:rounded-lg dark:border-stone-600 dark:bg-secondary",
        className
      )}
      {...props}
    />
  </AlertDialogPortal>
));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;

const AlertDialogHeader = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col space-y-2 text-center sm:text-left",
      className
    )}
    {...props}
  />
);
AlertDialogHeader.displayName = "AlertDialogHeader";

const AlertDialogFooter = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
      className
    )}
    {...props}
  />
);
AlertDialogFooter.displayName = "AlertDialogFooter";

const AlertDialogTitle = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
  <AlertDialogPrimitive.Title
    ref={ref}
    className={cn("text-lg font-semibold", className)}
    {...props}
  />
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;

const AlertDialogDescription = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Description>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
  <AlertDialogPrimitive.Description
    ref={ref}
    className={cn("text-sm text-primary/90", className)}
    {...props}
  />
));
AlertDialogDescription.displayName =
  AlertDialogPrimitive.Description.displayName;

const AlertDialogAction = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Action>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
  <AlertDialogPrimitive.Action
    ref={ref}
    className={cn(buttonVariants(), className)}
    {...props}
  />
));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;

const AlertDialogCancel = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
  <AlertDialogPrimitive.Cancel
    ref={ref}
    className={cn(
      buttonVariants({ variant: "outline" }),
      "mt-2 sm:mt-0",
      className
    )}
    {...props}
  />
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;

export {
  AlertDialog,
  AlertDialogPortal,
  AlertDialogOverlay,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogFooter,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogAction,
  AlertDialogCancel,
};


================================================
FILE: components/ui/alert.tsx
================================================
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const alertVariants = cva(
  "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
  {
    variants: {
      variant: {
        default: "bg-background text-foreground",
        destructive:
          "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

const Alert = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
  <div
    ref={ref}
    role="alert"
    className={cn(alertVariants({ variant }), className)}
    {...props}
  />
))
Alert.displayName = "Alert"

const AlertTitle = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
  <h5
    ref={ref}
    className={cn("mb-1 font-medium leading-none tracking-tight", className)}
    {...props}
  />
))
AlertTitle.displayName = "AlertTitle"

const AlertDescription = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("text-sm [&_p]:leading-relaxed", className)}
    {...props}
  />
))
AlertDescription.displayName = "AlertDescription"

export { Alert, AlertTitle, AlertDescription }


================================================
FILE: components/ui/badge.tsx
================================================
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
  "inline-flex items-center rounded-full border border-gray-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-gray-950 focus:ring-offset-2 dark:border-gray-800 dark:focus:ring-gray-300",
  {
    variants: {
      variant: {
        default:
          "border-transparent bg-gray-900 text-gray-50 hover:bg-gray-900/80 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/80",
        secondary:
          "border-transparent bg-gray-100 text-gray-900 hover:bg-gray-100/80 dark:bg-gray-800 dark:text-gray-50 dark:hover:bg-gray-800/80",
        destructive:
          "border-transparent bg-red-500 text-gray-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/80",
        outline: "text-gray-950 dark:text-gray-50",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

export interface BadgeProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
  return (
    <div className={cn(badgeVariants({ variant }), className)} {...props} />
  )
}

export { Badge, badgeVariants }


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

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

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-30 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive:
          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline:
          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        warning: "bg-warning text-warning-foreground hover:bg-warning/90",
        info: "bg-info text-info-foreground hover:bg-info/90",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
        "primary-blue":
          "bg-primary-blue text-primary-blue-foreground hover:bg-primary-blue/90",
        "primary-green":
          "bg-primary-green text-primary-green-foreground hover:bg-primary-green/90",
      },
      size: {
        default: "h-10 px-4 py-2",
        xs: "h-7 rounded-sm px-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

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

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

export { Button, buttonVariants };


================================================
FILE: components/ui/card.tsx
================================================
import * as React from "react";

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

const Card = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn(
      "rounded-lg border border-stone-300 bg-secondary shadow-sm dark:border-stone-600",
      className
    )}
    {...props}
  />
));
Card.displayName = "Card";

const CardHeader = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("flex flex-col space-y-1.5 p-6", className)}
    {...props}
  />
));
CardHeader.displayName = "CardHeader";

const CardTitle = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn(
      "text-2xl font-semibold leading-none tracking-tight",
      className
    )}
    {...props}
  />
));
CardTitle.displayName = "CardTitle";

const CardDescription = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("text-sm text-gray-500 dark:text-gray-400", className)}
    {...props}
  />
));
CardDescription.displayName = "CardDescription";

const CardContent = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";

const CardFooter = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("flex items-center p-6 pt-0", className)}
    {...props}
  />
));
CardFooter.displayName = "CardFooter";

export {
  Card,
  CardHeader,
  CardFooter,
  CardTitle,
  CardDescription,
  CardContent,
};


================================================
FILE: components/ui/checkbox.tsx
================================================
"use client"

import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"

import { cn } from "@/lib/utils"

const Checkbox = React.forwardRef<
  React.ElementRef<typeof CheckboxPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
  <CheckboxPrimitive.Root
    ref={ref}
    className={cn(
      "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
      className
    )}
    {...props}
  >
    <CheckboxPrimitive.Indicator
      className={cn("flex items-center justify-center text-current")}
    >
      <Check className="h-4 w-4" />
    </CheckboxPrimitive.Indicator>
  </CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName

export { Checkbox }


================================================
FILE: components/ui/confirmation-modal.tsx
================================================
import { Loader2 } from "lucide-react";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";

interface ConfirmationModalProps {
  isOpen?: boolean;
  title?: string;
  message?: string;
  cancelText?: string;
  confirmText?: string;
  onCancel?: () => void;
  onConfirm?: () => Promise<void> | void;
  confirmButtonClassName?: string;
  /** NEW: Add an isDeleting or loading prop */
  isDeleting?: boolean;
}

const ConfirmationModal = ({
  isOpen = false,
  title = "Confirm Action",
  message = "Are you sure you want to proceed? This action cannot be undone.",
  cancelText = "Cancel",
  confirmText = "Confirm",
  onCancel = () => {},
  onConfirm = () => {},
  confirmButtonClassName = "bg-red-500 hover:bg-red-600",
  isDeleting = false,
}: ConfirmationModalProps) => {
  return (
    <AlertDialog
      open={isOpen}
      onOpenChange={(open) => {
        // If user tries to close while isDeleting, ignore that attempt
        if (!open && !isDeleting) {
          onCancel();
        }
      }}
    >
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>{title}</AlertDialogTitle>
          <AlertDialogDescription>{message}</AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel onClick={onCancel} disabled={isDeleting}>
            {cancelText}
          </AlertDialogCancel>
          {/* Replace AlertDialogAction with a normal Button */}
          <Button
            onClick={onConfirm}
            disabled={isDeleting}
            className={cn(confirmButtonClassName)}
          >
            {isDeleting && <Loader2 className="h-4 w-4 animate-spin" />}
            {confirmText}
          </Button>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  );
};

export default ConfirmationModal;


================================================
FILE: components/ui/dialog.tsx
================================================
"use client";

import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";

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

const Dialog = DialogPrimitive.Root;

const DialogTrigger = DialogPrimitive.Trigger;

const DialogPortal = DialogPrimitive.Portal;

const DialogClose = DialogPrimitive.Close;

const DialogOverlay = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Overlay>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Overlay
    ref={ref}
    className={cn(
      "fixed inset-0 z-[9999] bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
      className
    )}
    {...props}
  />
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;

const DialogContent = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
  <DialogPortal>
    <DialogOverlay />
    <DialogPrimitive.Content
      ref={ref}
      className={cn(
        "fixed left-[50%] top-[50%] z-[9999] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-gray-200 bg-white p-6 shadow-lg duration-200 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%] sm:rounded-lg dark:border-gray-800 dark:bg-secondary",
        className
      )}
      {...props}
    >
      {children}
      <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 disabled:pointer-events-none data-[state=open]:bg-gray-100 data-[state=open]:text-gray-500 dark:ring-offset-gray-950 dark:focus:ring-gray-300 dark:data-[state=open]:bg-secondary-800 dark:data-[state=open]:text-gray-400">
        <X className="h-4 w-4" />
        <span className="sr-only">Close</span>
      </DialogPrimitive.Close>
    </DialogPrimitive.Content>
  </DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;

const DialogHeader = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col space-y-1.5 text-center sm:text-left",
      className
    )}
    {...props}
  />
);
DialogHeader.displayName = "DialogHeader";

const DialogFooter = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
      className
    )}
    {...props}
  />
);
DialogFooter.displayName = "DialogFooter";

const DialogTitle = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Title
    ref={ref}
    className={cn(
      "text-lg font-semibold leading-none tracking-tight",
      className
    )}
    {...props}
  />
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;

const DialogDescription = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Description>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Description
    ref={ref}
    className={cn("text-sm text-gray-500 dark:text-gray-400", className)}
    {...props}
  />
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;

export {
  Dialog,
  DialogPortal,
  DialogOverlay,
  DialogClose,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogFooter,
  DialogTitle,
  DialogDescription,
};


================================================
FILE: components/ui/dropdown-menu.tsx
================================================
"use client";

import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { Check, ChevronRight, Circle } from "lucide-react";

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

const DropdownMenu = DropdownMenuPrimitive.Root;

const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;

const DropdownMenuGroup = DropdownMenuPrimitive.Group;

const DropdownMenuPortal = DropdownMenuPrimitive.Portal;

const DropdownMenuSub = DropdownMenuPrimitive.Sub;

const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;

const DropdownMenuSubTrigger = React.forwardRef<
  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
    inset?: boolean;
  }
>(({ className, inset, children, ...props }, ref) => (
  <DropdownMenuPrimitive.SubTrigger
    ref={ref}
    className={cn(
      "flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
      inset && "pl-8",
      className
    )}
    {...props}
  >
    {children}
    <ChevronRight className="ml-auto" />
  </DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName =
  DropdownMenuPrimitive.SubTrigger.displayName;

const DropdownMenuSubContent = React.forwardRef<
  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
  <DropdownMenuPrimitive.SubContent
    ref={ref}
    className={cn(
      "z-[1000] min-w-[8rem] overflow-hidden rounded-xl border bg-popover p-1 text-popover-foreground shadow-lg 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",
      className
    )}
    {...props}
  />
));
DropdownMenuSubContent.displayName =
  DropdownMenuPrimitive.SubContent.displayName;

const DropdownMenuContent = React.forwardRef<
  React.ElementRef<typeof DropdownMenuPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
  <DropdownMenuPrimitive.Portal>
    <DropdownMenuPrimitive.Content
      ref={ref}
      sideOffset={sideOffset}
      className={cn(
        "z-[1000] min-w-[8rem] overflow-hidden rounded-xl border bg-popover p-1 text-popover-foreground shadow-md 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",
        className
      )}
      {...props}
    />
  </DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;

const DropdownMenuItem = React.forwardRef<
  React.ElementRef<typeof DropdownMenuPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
    inset?: boolean;
  }
>(({ className, inset, ...props }, ref) => (
  <DropdownMenuPrimitive.Item
    ref={ref}
    className={cn(
      "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
      inset && "pl-8",
      className
    )}
    {...props}
  />
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;

const DropdownMenuCheckboxItem = React.forwardRef<
  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
  <DropdownMenuPrimitive.CheckboxItem
    ref={ref}
    className={cn(
      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
      className
    )}
    checked={checked}
    {...props}
  >
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
      <DropdownMenuPrimitive.ItemIndicator>
        <Check className="h-4 w-4" />
      </DropdownMenuPrimitive.ItemIndicator>
    </span>
    {children}
  </DropdownMenuPrimitive.CheckboxItem>
));
DropdownMenuCheckboxItem.displayName =
  DropdownMenuPrimitive.CheckboxItem.displayName;

const DropdownMenuRadioItem = React.forwardRef<
  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
  <DropdownMenuPrimitive.RadioItem
    ref={ref}
    className={cn(
      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
      className
    )}
    {...props}
  >
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
      <DropdownMenuPrimitive.ItemIndicator>
        <Circle className="h-2 w-2 fill-current" />
      </DropdownMenuPrimitive.ItemIndicator>
    </span>
    {children}
  </DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;

const DropdownMenuLabel = React.forwardRef<
  React.ElementRef<typeof DropdownMenuPrimitive.Label>,
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
    inset?: boolean;
  }
>(({ className, inset, ...props }, ref) => (
  <DropdownMenuPrimitive.Label
    ref={ref}
    className={cn(
      "px-2 py-1.5 text-sm font-semibold",
      inset && "pl-8",
      className
    )}
    {...props}
  />
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;

const DropdownMenuSeparator = React.forwardRef<
  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
  <DropdownMenuPrimitive.Separator
    ref={ref}
    className={cn("-mx-1 my-1 h-px bg-muted", className)}
    {...props}
  />
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;

const DropdownMenuShortcut = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
  return (
    <span
      className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
      {...props}
    />
  );
};
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";

export {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuGroup,
  DropdownMenuPortal,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuRadioGroup,
};


================================================
FILE: components/ui/form.tsx
================================================
"use client"

import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
  Controller,
  ControllerProps,
  FieldPath,
  FieldValues,
  FormProvider,
  useFormContext,
} from "react-hook-form"

import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"

const Form = FormProvider

type FormFieldContextValue<
  TFieldValues extends FieldValues = FieldValues,
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
  name: TName
}

const FormFieldContext = React.createContext<FormFieldContextValue>(
  {} as FormFieldContextValue
)

const FormField = <
  TFieldValues extends FieldValues = FieldValues,
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
  ...props
}: ControllerProps<TFieldValues, TName>) => {
  return (
    <FormFieldContext.Provider value={{ name: props.name }}>
      <Controller {...props} />
    </FormFieldContext.Provider>
  )
}

const useFormField = () => {
  const fieldContext = React.useContext(FormFieldContext)
  const itemContext = React.useContext(FormItemContext)
  const { getFieldState, formState } = useFormContext()

  const fieldState = getFieldState(fieldContext.name, formState)

  if (!fieldContext) {
    throw new Error("useFormField should be used within <FormField>")
  }

  const { id } = itemContext

  return {
    id,
    name: fieldContext.name,
    formItemId: `${id}-form-item`,
    formDescriptionId: `${id}-form-item-description`,
    formMessageId: `${id}-form-item-message`,
    ...fieldState,
  }
}

type FormItemContextValue = {
  id: string
}

const FormItemContext = React.createContext<FormItemContextValue>(
  {} as FormItemContextValue
)

const FormItem = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
  const id = React.useId()

  return (
    <FormItemContext.Provider value={{ id }}>
      <div ref={ref} className={cn("space-y-2", className)} {...props} />
    </FormItemContext.Provider>
  )
})
FormItem.displayName = "FormItem"

const FormLabel = React.forwardRef<
  React.ElementRef<typeof LabelPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
  const { error, formItemId } = useFormField()

  return (
    <Label
      ref={ref}
      className={cn(error && "text-destructive", className)}
      htmlFor={formItemId}
      {...props}
    />
  )
})
FormLabel.displayName = "FormLabel"

const FormControl = React.forwardRef<
  React.ElementRef<typeof Slot>,
  React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()

  return (
    <Slot
      ref={ref}
      id={formItemId}
      aria-describedby={
        !error
          ? `${formDescriptionId}`
          : `${formDescriptionId} ${formMessageId}`
      }
      aria-invalid={!!error}
      {...props}
    />
  )
})
FormControl.displayName = "FormControl"

const FormDescription = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
  const { formDescriptionId } = useFormField()

  return (
    <p
      ref={ref}
      id={formDescriptionId}
      className={cn("text-sm text-muted-foreground", className)}
      {...props}
    />
  )
})
FormDescription.displayName = "FormDescription"

const FormMessage = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
  const { error, formMessageId } = useFormField()
  const body = error ? String(error?.message) : children

  if (!body) {
    return null
  }

  return (
    <p
      ref={ref}
      id={formMessageId}
      className={cn("text-sm font-medium text-destructive", className)}
      {...props}
    >
      {body}
    </p>
  )
})
FormMessage.displayName = "FormMessage"

export {
  useFormField,
  Form,
  FormItem,
  FormLabel,
  FormControl,
  FormDescription,
  FormMessage,
  FormField,
}


================================================
FILE: components/ui/input-text-confirm.tsx
================================================
import React, { useState, useEffect, useRef } from "react";
import { Button } from "./button";
import { Input } from "@/components/ui/input";

interface InputTextConfirmProps {
  isOpen: boolean;
  onClose: () => void;
  onSubmit: (value: string) => void;
  title: string;
  placeholder?: string;
  initialValue?: string;
}

const InputTextConfirm: React.FC<InputTextConfirmProps> = ({
  isOpen,
  onClose,
  onSubmit,
  title,
  placeholder = "Enter ROI name...",
  initialValue = "",
}) => {
  const [inputValue, setInputValue] = useState(initialValue);
  const popoverRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (isOpen) {
      setInputValue(initialValue);
    }
  }, [isOpen, initialValue]);

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (
        popoverRef.current &&
        !popoverRef.current.contains(event.target as Node)
      ) {
        onClose();
      }
    };

    if (isOpen) {
      document.addEventListener("mousedown", handleClickOutside);
    }

    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [isOpen, onClose]);

  if (!isOpen) return null;

  const handleSubmit = () => {
    onSubmit(inputValue);
    onClose();
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter") {
      handleSubmit();
    } else if (e.key === "Escape") {
      onClose();
    }
  };

  return (
    <div
      ref={popoverRef}
      className="absolute bottom-full mb-2 bg-background rounded-lg p-4 w-64 shadow-xl border border-stone-300 dark:border-stone-600"
    >
      <h2 className="text-md font-bold mb-3 text-foreground">{title}</h2>

      <Input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        onKeyDown={handleKeyDown}
        placeholder={placeholder}
      />

      <div className="flex justify-end space-x-2 pt-4">
        <Button
          onClick={onClose}
          variant="ghost"
          size="xs"
          className="text-foreground"
        >
          Cancel
        </Button>
        <Button onClick={handleSubmit} variant="primary-blue" size="xs">
          Confirm
        </Button>
      </div>
    </div>
  );
};

export default InputTextConfirm;


================================================
FILE: components/ui/input.tsx
================================================
import * as React from "react";

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

const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
  ({ className, type, ...props }, ref) => {
    return (
      <input
        type={type}
        className={cn(
          "flex h-10 w-full rounded-md border border-gray-200 bg-white px-3 py-2 text-base dark:text-primary ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-gray-950 placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:border-gray-800 dark:bg-accent dark:ring-offset-accent dark:file:text-gray-50 dark:placeholder:text-gray-400 dark:focus-visible:ring-gray-300",
          className
        )}
        ref={ref}
        {...props}
      />
    );
  }
);
Input.displayName = "Input";

export { Input };


================================================
FILE: components/ui/label.tsx
================================================
"use client"

import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const labelVariants = cva(
  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)

const Label = React.forwardRef<
  React.ElementRef<typeof LabelPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
    VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
  <LabelPrimitive.Root
    ref={ref}
    className={cn(labelVariants(), className)}
    {...props}
  />
))
Label.displayName = LabelPrimitive.Root.displayName

export { Label }


================================================
FILE: components/ui/popover.tsx
================================================
"use client";

import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";

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

const Popover = PopoverPrimitive.Root;

const PopoverTrigger = PopoverPrimitive.Trigger;

const PopoverContent = React.forwardRef<
  React.ElementRef<typeof PopoverPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
  <PopoverPrimitive.Portal>
    <PopoverPrimitive.Content
      ref={ref}
      align={align}
      sideOffset={sideOffset}
      className={cn(
        "z-[9999] w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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",
        className
      )}
      {...props}
    />
  </PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;

export { Popover, PopoverTrigger, PopoverContent };


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

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

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

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

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

export { ScrollArea, ScrollBar };


================================================
FILE: components/ui/select.tsx
================================================
"use client"

import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"

import { cn } from "@/lib/utils"

const Select = SelectPrimitive.Root

const SelectGroup = SelectPrimitive.Group

const SelectValue = SelectPrimitive.Value

const SelectTrigger = React.forwardRef<
  React.ElementRef<typeof SelectPrimitive.Trigger>,
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
  <SelectPrimitive.Trigger
    ref={ref}
    className={cn(
      "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
      className
    )}
    {...props}
  >
    {children}
    <SelectPrimitive.Icon asChild>
      <ChevronDown className="h-4 w-4 opacity-50" />
    </SelectPrimitive.Icon>
  </SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName

const SelectScrollUpButton = React.forwardRef<
  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
  <SelectPrimitive.ScrollUpButton
    ref={ref}
    className={cn(
      "flex cursor-default items-center justify-center py-1",
      className
    )}
    {...props}
  >
    <ChevronUp className="h-4 w-4" />
  </SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName

const SelectScrollDownButton = React.forwardRef<
  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
  <SelectPrimitive.ScrollDownButton
    ref={ref}
    className={cn(
      "flex cursor-default items-center justify-center py-1",
      className
    )}
    {...props}
  >
    <ChevronDown className="h-4 w-4" />
  </SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
  SelectPrimitive.ScrollDownButton.displayName

const SelectContent = React.forwardRef<
  React.ElementRef<typeof SelectPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
  <SelectPrimitive.Portal>
    <SelectPrimitive.Content
      ref={ref}
      className={cn(
        "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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",
        position === "popper" &&
          "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
        className
      )}
      position={position}
      {...props}
    >
      <SelectScrollUpButton />
      <SelectPrimitive.Viewport
        className={cn(
          "p-1",
          position === "popper" &&
            "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
        )}
      >
        {children}
      </SelectPrimitive.Viewport>
      <SelectScrollDownButton />
    </SelectPrimitive.Content>
  </SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName

const SelectLabel = React.forwardRef<
  React.ElementRef<typeof SelectPrimitive.Label>,
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
  <SelectPrimitive.Label
    ref={ref}
    className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
    {...props}
  />
))
SelectLabel.displayName = SelectPrimitive.Label.displayName

const SelectItem = React.forwardRef<
  React.ElementRef<typeof SelectPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
  <SelectPrimitive.Item
    ref={ref}
    className={cn(
      "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
      className
    )}
    {...props}
  >
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
      <SelectPrimitive.ItemIndicator>
        <Check className="h-4 w-4" />
      </SelectPrimitive.ItemIndicator>
    </span>

    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
  </SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName

const SelectSeparator = React.forwardRef<
  React.ElementRef<typeof SelectPrimitive.Separator>,
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
  <SelectPrimitive.Separator
    ref={ref}
    className={cn("-mx-1 my-1 h-px bg-muted", className)}
    {...props}
  />
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName

export {
  Select,
  SelectGroup,
  SelectValue,
  SelectTrigger,
  SelectContent,
  SelectLabel,
  SelectItem,
  SelectSeparator,
  SelectScrollUpButton,
  SelectScrollDownButton,
}


================================================
FILE: components/ui/separator.tsx
================================================
"use client";

import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";

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

const Separator = React.forwardRef<
  React.ElementRef<typeof SeparatorPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
  (
    { className, orientation = "horizontal", decorative = true, ...props },
    ref
  ) => (
    <SeparatorPrimitive.Root
      ref={ref}
      decorative={decorative}
      orientation={orientation}
      className={cn(
        "shrink-0 bg-stone-300 dark:bg-stone-700",
        orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
        className
      )}
      {...props}
    />
  )
);
Separator.displayName = SeparatorPrimitive.Root.displayName;

export { Separator };


================================================
FILE: components/ui/table.tsx
================================================
import * as React from "react";

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

const Table = React.forwardRef<
  HTMLTableElement,
  React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
  // <div className="relative w-full overflow-auto rounded-2xl border border-stone-300 dark:border-stone-600">
  <table
    ref={ref}
    className={cn("w-full caption-bottom text-sm ", className)}
    {...props}
  />
  // </div>
));
Table.displayName = "Table";

const TableHeader = React.forwardRef<
  HTMLTableSectionElement,
  React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
  <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
));
TableHeader.displayName = "TableHeader";

const TableBody = React.forwardRef<
  HTMLTableSectionElement,
  React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
  <tbody
    ref={ref}
    className={cn("[&_tr:last-child]:border-0", className)}
    {...props}
  />
));
TableBody.displayName = "TableBody";

const TableFooter = React.forwardRef<
  HTMLTableSectionElement,
  React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
  <tfoot
    ref={ref}
    className={cn(
      "border-t bg-gray-100/50 font-medium [&>tr]:last:border-b-0 dark:bg-gray-800/50",
      className
    )}
    {...props}
  />
));
TableFooter.displayName = "TableFooter";

const TableRow = React.forwardRef<
  HTMLTableRowElement,
  React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
  <tr
    ref={ref}
    className={cn(
      "border-b transition-colors data-[state=selected]:bg-muted hover:bg-muted/50 dark:hover:bg-muted/30 dark:data-[state=selected]:bg-muted",
      className
    )}
    {...props}
  />
));
TableRow.displayName = "TableRow";

const TableHead = React.forwardRef<
  HTMLTableCellElement,
  React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
  <th
    ref={ref}
    className={cn(
      "h-12 px-4 text-left align-middle font-bold bg-gray-200 dark:bg-accent text-primary/80 [&:has([role=checkbox])]:pr-0",
      className
    )}
    {...props}
  />
));
TableHead.displayName = "TableHead";

const TableCell = React.forwardRef<
  HTMLTableCellElement,
  React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
  <td
    ref={ref}
    className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
    {...props}
  />
));
TableCell.displayName = "TableCell";

const TableCaption = React.forwardRef<
  HTMLTableCaptionElement,
  React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
  <caption
    ref={ref}
    className={cn("mt-4 text-sm text-gray-500 dark:text-gray-400", className)}
    {...props}
  />
));
TableCaption.displayName = "TableCaption";

export {
  Table,
  TableHeader,
  TableBody,
  TableFooter,
  TableHead,
  TableRow,
  TableCell,
  TableCaption,
};


================================================
FILE: components/ui/textarea.tsx
================================================
import * as React from "react"

import { cn } from "@/lib/utils"

const Textarea = React.forwardRef<
  HTMLTextAreaElement,
  React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
  return (
    <textarea
      className={cn(
        "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
        className
      )}
      ref={ref}
      {...props}
    />
  )
})
Textarea.displayName = "Textarea"

export { Textarea }


================================================
FILE: components/ui/theme-mode-toggle.tsx
================================================
"use client";

import * as React from "react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";

import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

export function ThemeModeToggle() {
  const { setTheme } = useTheme();

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button
          variant="outline"
          size="icon"
          className="bg-forground border border-stone-300 dark:border-stone-600"
        >
          <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
          <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
          <span className="sr-only">Toggle theme</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="center" side="right">
        <DropdownMenuItem onClick={() => setTheme("light")}>
          Light
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("dark")}>
          Dark
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("system")}>
          System
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}


================================================
FILE: components/ui/tooltip.tsx
================================================
"use client";

import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";

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

const TooltipProvider = TooltipPrimitive.Provider;

const Tooltip = TooltipPrimitive.Root;

const TooltipTrigger = TooltipPrimitive.Trigger;

const TooltipContent = React.forwardRef<
  React.ElementRef<typeof TooltipPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
  <TooltipPrimitive.Content
    ref={ref}
    sideOffset={sideOffset}
    className={cn(
      "z-[9999] overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md 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",
      className
    )}
    {...props}
  />
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };


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


================================================
FILE: custom-configs/ai-assistants.ts
================================================
export const AI_ASSISTANTS: AI_ASSISTANTS_TYPE = {
  default: ["GPT-4o"],
  custom: [],
  icons: {
    "GPT-4o": "/vendors-logos/openai.svg",
  },
};


================================================
FILE: custom-configs/charts-config.ts
================================================
export const chartColors = {
  timeSeriesChart: ["#00A8E8"],
  barChartNumerical: ["#00A8E8"],
  stackedBarChartStats: ["#00A8E8", "#D1495B"],
  dualBarChartNumerical: ["#00A8E8", "#D1495B"],
  dualTimeSeriesChart: ["#00A8E8", "#D1495B"],
};


================================================
FILE: custom-configs/integrations.ts
================================================
export const INTEGRATION_SERVICES: IntegrationService[] = [
  {
    id: "arcgis",
    name: "Esri Feature Server",
    description:
      "Connect to ArcGIS Feature Servers to import and manage spatial data",
    icon: "/vendors-logos/esri.svg",
    status: "not_connected",
  },
];


================================================
FILE: custom-configs/project-config.ts
================================================
const projectConfigs = {
  initialFlyToCoords: { lat: 44.235128, lng: -76.592403 },
} as const;

export default projectConfigs;


================================================
FILE: db-schema/schema.sql
================================================


SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;


CREATE SCHEMA IF NOT EXISTS "public";

CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA public;


ALTER SCHEMA "public" OWNER TO "pg_database_owner";


COMMENT ON SCHEMA "public" IS 'standard public schema';



CREATE TYPE "public"."subscription_tier_enum" AS ENUM (
    'Essentials',
    'Pro',
    'Enterprise'
);


ALTER TYPE "public"."subscription_tier_enum" OWNER TO "postgres";


CREATE TYPE "public"."user_role_enum" AS ENUM (
    'ADMIN',
    'USER',
    'TRIAL',
    'VIEWER'
);


ALTER TYPE "public"."user_role_enum" OWNER TO "postgres";


CREATE OR REPLACE FUNCTION "public"."create_user_usage_row"() RETURNS "trigger"
    LANGUAGE "plpgsql"
    AS $$
BEGIN
  -- Insert a matching row in user_usage
  INSERT INTO public.user_usage (user_id)
  VALUES (NEW.id);

  RETURN NEW;
END;
$$;


ALTER FUNCTION "public"."create_user_usage_row"() OWNER TO "postgres";


CREATE OR REPLACE FUNCTION "public"."handle_new_auth_user"() RETURNS "trigger"
    LANGUAGE "plpgsql" SECURITY DEFINER
    AS $$
BEGIN
  INSERT INTO public.user_roles (
    id,
    name,
    email,
    organization,
    role,
    license_start,
    license_end,
    subscription_tier
  )
  VALUES (
    NEW.id,
    COALESCE(NEW.raw_user_meta_data->>'full_name', 'New User'),
    NEW.email,
    '',
    'TRIAL',
    CURRENT_DATE,
    CURRENT_DATE + 14,
    'Essentials'
  );
  RETURN NEW;
END;
$$;


ALTER FUNCTION "public"."handle_new_auth_user"() OWNER TO "postgres";


CREATE OR REPLACE FUNCTION "public"."search_documents_by_similarity"("query_embedding" "public"."vector", "match_count" integer DEFAULT NULL::integer, "filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" bigint, "content" "text", "metadata" "jsonb")
    LANGUAGE "plpgsql"
    AS $$
BEGIN
  RETURN QUERY
  SELECT
    e.id,
    e.content,
    e.metadata
      || jsonb_build_object('similarity', 1 - (e.embedding <=> query_embedding)) AS metadata
  FROM embeddings e
  JOIN document_files f ON f.id = e.file_id
  WHERE
 f.owner = (filter->>'owner')::uuid

  ORDER BY
    e.embedding <=> query_embedding
  LIMIT match_count;
END;
$$;


ALTER FUNCTION "public"."search_documents_by_similarity"("query_embedding" "public"."vector", "match_count" integer, "filter" "jsonb") OWNER TO "postgres";


CREATE OR REPLACE FUNCTION "public"."search_documents_by_similarity"("query_embedding" "public"."vector", "match_count" integer DEFAULT NULL::integer, "owner_uuid" "uuid" DEFAULT NULL::"uuid", "metadata_filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" bigint, "content" "text", "metadata" "jsonb")
    LANGUAGE "plpgsql"
    AS $$
BEGIN
  RETURN QUERY
  SELECT
    e.id,
    e.content,
    e.metadata
      || jsonb_build_object('similarity', 1 - (e.embedding <=> query_embedding)) AS metadata
  FROM embeddings e
  JOIN document_files f ON f.id = e.file_id
  WHERE
    -- If owner_uuid is provided, filter on that
    (owner_uuid IS NULL OR f.owner = owner_uuid)
    
    -- If metadata_filter is not empty, check that e.metadata contains it
    AND (
      metadata_filter = '{}' 
      OR e.metadata @> metadata_filter
    )
  ORDER BY
    e.embedding <=> query_embedding
  LIMIT match_count;
END;
$$;


ALTER FUNCTION "public"."search_documents_by_similarity"("query_embedding" "public"."vector", "match_count" integer, "owner_uuid" "uuid", "metadata_filter" "jsonb") OWNER TO "postgres";


CREATE OR REPLACE FUNCTION "public"."search_gee_datasets_ft"("query" "text") RETURNS TABLE("id" integer, "dataset_id" "text", "asset_url" "text", "type" "text", "start_date" "date", "end_date" "date", "title" "text", "rank" real)
    LANGUAGE "plpgsql"
    AS $$
Download .txt
gitextract_tqshek9r/

├── .eslintrc.json
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app/
│   ├── (auth)/
│   │   ├── api/
│   │   │   └── auth/
│   │   │       ├── confirm/
│   │   │       │   └── route.ts
│   │   │       └── esri/
│   │   │           ├── authorize/
│   │   │           │   └── route.ts
│   │   │           └── callback/
│   │   │               └── route.ts
│   │   ├── layout.tsx
│   │   ├── login/
│   │   │   ├── actions.ts
│   │   │   ├── layout.tsx
│   │   │   └── page.tsx
│   │   └── styles.css
│   ├── (broadcast)/
│   │   ├── api/
│   │   │   └── services/
│   │   │       └── esri/
│   │   │           ├── fetch-layers-list/
│   │   │           │   └── route.ts
│   │   │           └── fetch-selected-layer/
│   │   │               └── route.ts
│   │   ├── layout.tsx
│   │   ├── services/
│   │   │   └── esri/
│   │   │       └── fetch-layers/
│   │   │           └── page.tsx
│   │   └── styles.css
│   ├── (main)/
│   │   ├── actions/
│   │   │   └── get-user-profile.ts
│   │   ├── api/
│   │   │   ├── chat/
│   │   │   │   ├── chat-history/
│   │   │   │   │   └── route.ts
│   │   │   │   └── route.ts
│   │   │   ├── gee/
│   │   │   │   ├── request-geospatial-analysis/
│   │   │   │   │   └── route.ts
│   │   │   │   └── request-loading-geospatial-data/
│   │   │   │       └── route.ts
│   │   │   ├── sendfeedback/
│   │   │   │   └── route.ts
│   │   │   ├── services/
│   │   │   │   └── google-maps/
│   │   │   │       ├── basemaps/
│   │   │   │       │   ├── roadmap/
│   │   │   │       │   │   └── route.ts
│   │   │   │       │   └── satellite/
│   │   │   │       │       └── route.ts
│   │   │   │       ├── geocode/
│   │   │   │       │   └── route.ts
│   │   │   │       └── places/
│   │   │   │           └── route.ts
│   │   │   ├── user-usage/
│   │   │   │   └── route.ts
│   │   │   └── web-scraper/
│   │   │       └── route.ts
│   │   ├── chat/
│   │   │   └── [id]/
│   │   │       ├── layout.tsx
│   │   │       ├── loading.tsx
│   │   │       └── page.tsx
│   │   ├── chat-history/
│   │   │   ├── layout.tsx
│   │   │   ├── loading.tsx
│   │   │   └── page.tsx
│   │   ├── integrations/
│   │   │   ├── layout.tsx
│   │   │   ├── loading.tsx
│   │   │   └── page.tsx
│   │   ├── knowledge-base/
│   │   │   ├── layout.tsx
│   │   │   ├── loading.tsx
│   │   │   └── page.tsx
│   │   ├── layout.tsx
│   │   ├── loading.tsx
│   │   ├── page.tsx
│   │   └── styles.css
│   └── actions/
│       └── rag-actions.ts
├── components/
│   ├── changelog-modal.tsx
│   ├── client-hydrator.tsx
│   ├── client-wrapper.tsx
│   ├── document-viewer.tsx
│   ├── feedback.tsx
│   ├── loading-widgets/
│   │   ├── loading-for-widget.tsx
│   │   └── loading-primary.tsx
│   ├── main-sidebar/
│   │   ├── app-setttings.tsx
│   │   └── main-sidebar.tsx
│   ├── notices/
│   │   ├── privacy-policy.tsx
│   │   └── terms-of-services.tsx
│   ├── services/
│   │   └── esri/
│   │       └── add-arcgis-layers.tsx
│   ├── theme-provider.tsx
│   └── ui/
│       ├── alert-dialog.tsx
│       ├── alert.tsx
│       ├── badge.tsx
│       ├── button.tsx
│       ├── card.tsx
│       ├── checkbox.tsx
│       ├── confirmation-modal.tsx
│       ├── dialog.tsx
│       ├── dropdown-menu.tsx
│       ├── form.tsx
│       ├── input-text-confirm.tsx
│       ├── input.tsx
│       ├── label.tsx
│       ├── popover.tsx
│       ├── scroll-area.tsx
│       ├── select.tsx
│       ├── separator.tsx
│       ├── table.tsx
│       ├── textarea.tsx
│       ├── theme-mode-toggle.tsx
│       └── tooltip.tsx
├── components.json
├── custom-configs/
│   ├── ai-assistants.ts
│   ├── charts-config.ts
│   ├── integrations.ts
│   └── project-config.ts
├── db-schema/
│   └── schema.sql
├── features/
│   ├── charts/
│   │   ├── components/
│   │   │   ├── charts-display.tsx
│   │   │   └── charts.tsx
│   │   └── utils/
│   │       └── select-chart-type.ts
│   ├── chat/
│   │   ├── components/
│   │   │   ├── artifacts-sidebar/
│   │   │   │   └── artifacts-sidebar.tsx
│   │   │   ├── chat-response-box/
│   │   │   │   ├── capabilities-banner.tsx
│   │   │   │   ├── chat-message/
│   │   │   │   │   └── chat-message.tsx
│   │   │   │   ├── chat-response-box.tsx
│   │   │   │   └── in-response-tool-calling-results/
│   │   │   │       ├── display-in-chat-analysis-map/
│   │   │   │       │   └── display-in-chat-analysis-map-btn.tsx
│   │   │   │       ├── draft-report/
│   │   │   │       │   ├── draft-report.tsx
│   │   │   │       │   └── drafted-report-btn.tsx
│   │   │   │       ├── knowledge-base-citation/
│   │   │   │       │   └── citation-badge.tsx
│   │   │   │       └── tool-calling-results.tsx
│   │   │   ├── chat.tsx
│   │   │   ├── external-assets/
│   │   │   │   └── assets-modal.tsx
│   │   │   ├── input/
│   │   │   │   ├── chat-input-box.tsx
│   │   │   │   ├── chat-input-buttons/
│   │   │   │   │   ├── assistants-list-dropup-in-chat-input.tsx
│   │   │   │   │   ├── attachments-list-dropup-in-chat-input.tsx
│   │   │   │   │   ├── chat-input-buttons.tsx
│   │   │   │   │   ├── current-session-assets-dropup.tsx
│   │   │   │   │   ├── open-database-in-chat-input-btn.tsx
│   │   │   │   │   └── select-roi-on-map-btn.tsx
│   │   │   │   ├── chat-input-dropzone.tsx
│   │   │   │   ├── map-tools-dropup.tsx
│   │   │   │   └── slash-menu-for-map-layers.tsx
│   │   │   ├── text-analysis-suggestions/
│   │   │   │   └── text-analysis-suggestions.tsx
│   │   │   └── thumbnails-analysis-suggestions/
│   │   │       └── thumbnails-analysis-suggestions.tsx
│   │   ├── hooks/
│   │   │   └── use-slash-menu-map-layers-list.ts
│   │   ├── stores/
│   │   │   ├── use-attachments-store.ts
│   │   │   ├── use-chat-response-sources-store.ts
│   │   │   └── use-drafted-report-store.ts
│   │   ├── ui/
│   │   │   └── fadeIn-with-delay.tsx
│   │   └── utils/
│   │       ├── drag-and-drop-file-analyzer.ts
│   │       ├── general-utils.ts
│   │       ├── slash-menu-utils.ts
│   │       ├── tool-calling-results-validation.ts
│   │       ├── use-Textarea-resize.ts
│   │       ├── use-drag-and-drop-file-import.ts
│   │       └── use-slash-command-menu.ts
│   ├── chat-history/
│   │   └── components/
│   │       ├── chat-history-row.tsx
│   │       ├── chat-history-table-skeleton.tsx
│   │       ├── chat-history-table.tsx
│   │       ├── chat-history.tsx
│   │       └── indeterminate-checkbox.tsx
│   ├── integrations/
│   │   └── components/
│   │       ├── integration-actions.tsx
│   │       ├── integration-header.tsx
│   │       ├── integration-item.tsx
│   │       ├── integration-list.tsx
│   │       ├── integration-status.tsx
│   │       └── integrations-page.tsx
│   ├── knowledge-base/
│   │   ├── actions/
│   │   │   └── document-actions.ts
│   │   ├── components/
│   │   │   ├── add-group-modal.tsx
│   │   │   ├── documents-table.tsx
│   │   │   ├── edit-document-modal.tsx
│   │   │   ├── knolwedge-base.tsx
│   │   │   ├── knowledge-base-sidebar.tsx
│   │   │   └── max-docs-alert-dialog.tsx
│   │   ├── lib/
│   │   │   └── generate-embeddings.ts
│   │   └── utils/
│   │       └── transform-metadata-to-citation.ts
│   ├── maps/
│   │   ├── components/
│   │   │   ├── address-search.tsx
│   │   │   ├── attribute-table/
│   │   │   │   ├── attribute-table-controls.tsx
│   │   │   │   └── attribute-table.tsx
│   │   │   ├── map-badge.tsx
│   │   │   ├── map-container.tsx
│   │   │   ├── map-custom-controls/
│   │   │   │   ├── map-custom-controls.tsx
│   │   │   │   └── map-roi-controls.tsx
│   │   │   └── map-panels/
│   │   │       ├── map-chart-panel/
│   │   │       │   └── map-chart-panel.tsx
│   │   │       └── map-layers-panel/
│   │   │           ├── color-picker-popover.tsx
│   │   │           ├── map-layers-panel.tsx
│   │   │           └── map-legend.tsx
│   │   ├── hooks/
│   │   │   ├── use-handle-click/
│   │   │   │   ├── use-handle-click.ts
│   │   │   │   ├── use-map-controls.ts
│   │   │   │   ├── use-query-drawing.ts
│   │   │   │   ├── use-remove-query-features.ts
│   │   │   │   └── use-roi-drawing.ts
│   │   │   ├── use-map/
│   │   │   │   ├── use-add-arcgis-layers.ts
│   │   │   │   ├── use-add-attached-layers.ts
│   │   │   │   ├── use-add-gee-layers.ts
│   │   │   │   ├── use-add-roi-from-session.ts
│   │   │   │   ├── use-basemap-toggle.ts
│   │   │   │   ├── use-map-initialization.ts
│   │   │   │   ├── use-map.ts
│   │   │   │   ├── use-update-layer-style.ts
│   │   │   │   └── use-zoom-to-geometry.ts
│   │   │   └── use-map-cursor.ts
│   │   ├── stores/
│   │   │   ├── map-queries-stores/
│   │   │   │   ├── useQueryOutputReadyFromVectorLayerStore.ts
│   │   │   │   ├── useQueryRasterFromVectorLayerStore.ts
│   │   │   │   └── useQueryReadyStore.ts
│   │   │   ├── plots-stores/
│   │   │   │   ├── useChartRequestedTypeStore.ts
│   │   │   │   ├── usePlotReadyDataStore.ts
│   │   │   │   └── usePlotReadyFromVectorLayerStore.ts
│   │   │   ├── use-agol-layers-store.ts
│   │   │   ├── use-color-picker-store.ts
│   │   │   ├── use-cursor-store.ts
│   │   │   ├── use-drawn-feature-on-map-store.ts
│   │   │   ├── use-function-store.ts
│   │   │   ├── use-gee-ouput-store.ts
│   │   │   ├── use-geojson-store.ts
│   │   │   ├── use-layer-selection-store.ts
│   │   │   ├── use-map-badge-store.ts
│   │   │   ├── use-map-display-store.ts
│   │   │   ├── use-map-layer-store.ts
│   │   │   ├── use-map-legend-store.ts
│   │   │   ├── use-map-zoom-request-store.ts
│   │   │   ├── use-roi-store.ts
│   │   │   └── use-table-store.ts
│   │   └── utils/
│   │       ├── add-drawn-layer-to-map.ts
│   │       ├── add-gee-layer-to-map.ts
│   │       ├── add-geocoded-point-to-map.ts
│   │       ├── add-layers-to-map/
│   │       │   └── addGeojsonLayer.ts
│   │       ├── add-roi-layer-to-map.ts
│   │       ├── authentication-utils/
│   │       │   └── gee-auth.ts
│   │       ├── gee-eval-utils.ts
│   │       ├── general-checks.ts
│   │       ├── geometry-utils.ts
│   │       ├── initialize-map.ts
│   │       ├── other-utils.ts
│   │       ├── setup-map-attributions.ts
│   │       └── type-guards.ts
│   ├── text-editor/
│   │   ├── components/
│   │   │   ├── dynamic-text-editor.tsx
│   │   │   └── text-editor.tsx
│   │   └── schema/
│   │       └── text-editor-schema.tsx
│   ├── ui/
│   │   ├── modals/
│   │   │   └── file-upload-modal.tsx
│   │   └── toast-message.tsx
│   └── user-profile/
│       └── components/
│           └── user-profile-modal.tsx
├── hooks/
│   └── docs-hooks/
│       ├── use-document-upload.ts
│       └── use-document-viewer.ts
├── lib/
│   ├── auth.ts
│   ├── changelog.ts
│   ├── database/
│   │   ├── chat/
│   │   │   ├── queries.ts
│   │   │   └── tools.ts
│   │   └── usage.ts
│   ├── fetchers/
│   │   ├── chat.ts
│   │   └── services/
│   │       └── esri/
│   │           ├── fetch-layers-list.ts
│   │           └── fetch-selected-layer.ts
│   ├── geospatial/
│   │   └── gee/
│   │       ├── analysis-functions/
│   │       │   ├── heat-analysis/
│   │       │   │   └── urban-heat-island-analysis.ts
│   │       │   ├── lancover-landuse-mapping/
│   │       │   │   ├── google-dynamic-world-landcover-mapping.ts
│   │       │   │   ├── landcover-change-mapping.ts
│   │       │   │   └── sentinel-landcover-landuse-mapping.ts
│   │       │   └── pollution-analysis/
│   │       │       └── air-pollution-analysis.ts
│   │       ├── extract-values-from-gee-layer/
│   │       │   ├── extract-values-from-gee-layer.ts
│   │       │   └── geospatial-analyses/
│   │       │       ├── extract-values-from-air-pollution-map.ts
│   │       │       ├── extract-values-from-google-dynamic-world-map.ts
│   │       │       ├── extract-values-from-landcover-change-map.ts
│   │       │       ├── extract-values-from-sentinel-landcover-landuse-map.ts
│   │       │       └── extract-values-from-urban-heat-island-map.ts
│   │       └── load-data/
│   │           └── load-raster-data.ts
│   └── utils.ts
├── middleware.ts
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── stores/
│   ├── use-buttons-store.ts
│   ├── use-integration-store.ts
│   ├── use-loading-store.ts
│   ├── use-sidebar-button-stores.ts
│   ├── use-toast-message-store.ts
│   └── use-user-profile-store.ts
├── tailwind.config.ts
├── tsconfig.json
├── types/
│   └── global.d.ts
└── utils/
    ├── general/
    │   ├── document-utils.ts
    │   └── general-utils.ts
    ├── reset-chat-stores.ts
    ├── service-handlers/
    │   └── esri.ts
    ├── supabase/
    │   ├── client.ts
    │   ├── middleware.ts
    │   └── server.ts
    └── validation-utils/
        └── validation-utils.ts
Download .txt
SYMBOL INDEX (389 symbols across 190 files)

FILE: app/(auth)/api/auth/confirm/route.ts
  function GET (line 7) | async function GET(request: NextRequest) {

FILE: app/(auth)/api/auth/esri/authorize/route.ts
  function GET (line 4) | async function GET() {

FILE: app/(auth)/api/auth/esri/callback/route.ts
  function GET (line 5) | async function GET(req: NextRequest) {

FILE: app/(auth)/layout.tsx
  function RootLayout (line 25) | function RootLayout({

FILE: app/(auth)/login/actions.ts
  function login (line 15) | async function login(formData: FormData) {
  function logout (line 77) | async function logout() {

FILE: app/(auth)/login/layout.tsx
  function Layout (line 1) | async function Layout({

FILE: app/(auth)/login/page.tsx
  function Login (line 17) | function Login() {

FILE: app/(broadcast)/api/services/esri/fetch-layers-list/route.ts
  function GET (line 4) | async function GET(req: NextRequest) {

FILE: app/(broadcast)/api/services/esri/fetch-selected-layer/route.ts
  function GET (line 4) | async function GET(req: NextRequest) {

FILE: app/(broadcast)/layout.tsx
  function RootLayout (line 24) | function RootLayout({

FILE: app/(broadcast)/services/esri/fetch-layers/page.tsx
  function AddArcGisLayerPage (line 5) | async function AddArcGisLayerPage() {

FILE: app/(main)/actions/get-user-profile.ts
  function getUserProfile (line 4) | async function getUserProfile() {

FILE: app/(main)/api/chat/chat-history/route.ts
  type Chat (line 5) | interface Chat {
  function GET (line 9) | async function GET() {

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

FILE: app/(main)/api/gee/request-geospatial-analysis/route.ts
  function POST (line 17) | async function POST(req: NextRequest) {

FILE: app/(main)/api/gee/request-loading-geospatial-data/route.ts
  function POST (line 8) | async function POST(req: NextRequest) {

FILE: app/(main)/api/sendfeedback/route.ts
  constant API_KEY (line 8) | const API_KEY = process.env.MAILGUN_API_KEY || "";
  constant DOMAIN (line 9) | const DOMAIN = process.env.MAILGUN_DOMAIN || "";
  constant ALLOWED_ORIGIN (line 10) | const ALLOWED_ORIGIN = process.env.NEXT_PUBLIC_APP_URL!;
  constant RECIPIENT_EMAIL (line 11) | const RECIPIENT_EMAIL = process.env.RECIPIENT_EMAIL;
  constant SENDER_EMAIL (line 12) | const SENDER_EMAIL = process.env.SENDER_EMAIL;
  function OPTIONS (line 14) | async function OPTIONS() {
  function POST (line 25) | async function POST(req: Request) {

FILE: app/(main)/api/services/google-maps/basemaps/roadmap/route.ts
  function GET (line 4) | async function GET(request: NextRequest) {

FILE: app/(main)/api/services/google-maps/basemaps/satellite/route.ts
  function GET (line 4) | async function GET(request: NextRequest) {

FILE: app/(main)/api/services/google-maps/geocode/route.ts
  function POST (line 6) | async function POST(request: Request) {

FILE: app/(main)/api/services/google-maps/places/route.ts
  function GET (line 3) | async function GET() {

FILE: app/(main)/api/user-usage/route.ts
  function GET (line 6) | async function GET() {

FILE: app/(main)/api/web-scraper/route.ts
  function GET (line 4) | async function GET(request: Request) {

FILE: app/(main)/chat-history/layout.tsx
  function ChatHistoryLayout (line 5) | async function ChatHistoryLayout({

FILE: app/(main)/chat/[id]/layout.tsx
  function ChatLayout (line 5) | async function ChatLayout({

FILE: app/(main)/chat/[id]/loading.tsx
  function Loading (line 5) | function Loading() {

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

FILE: app/(main)/integrations/layout.tsx
  function Layout (line 4) | async function Layout({

FILE: app/(main)/integrations/loading.tsx
  function Loading (line 5) | function Loading() {

FILE: app/(main)/knowledge-base/layout.tsx
  function Layout (line 4) | async function Layout({

FILE: app/(main)/knowledge-base/loading.tsx
  function Loading (line 5) | function Loading() {

FILE: app/(main)/layout.tsx
  function RootLayout (line 29) | async function RootLayout({

FILE: app/(main)/loading.tsx
  function Loading (line 5) | function Loading() {

FILE: app/(main)/page.tsx
  function Home (line 7) | async function Home() {

FILE: app/actions/rag-actions.ts
  function saveRagDocument (line 9) | async function saveRagDocument(
  function answerQuery (line 94) | async function answerQuery(query: string) {

FILE: components/changelog-modal.tsx
  function ChangelogModal (line 12) | function ChangelogModal() {

FILE: components/client-hydrator.tsx
  type ClientHydratorProps (line 5) | interface ClientHydratorProps {
  function ClientHydrator (line 16) | function ClientHydrator({ userProfile }: ClientHydratorProps) {

FILE: components/client-wrapper.tsx
  function ClientWrapper (line 12) | function ClientWrapper({

FILE: components/document-viewer.tsx
  type DocumentViewerProps (line 15) | interface DocumentViewerProps {
  function DocumentViewer (line 21) | function DocumentViewer({

FILE: components/feedback.tsx
  type FeedbackFloatingProps (line 19) | interface FeedbackFloatingProps {
  function FeedbackFloating (line 24) | function FeedbackFloating({ isOpen, onClose }: FeedbackFloatingProps) {

FILE: components/loading-widgets/loading-for-widget.tsx
  type LoadingWidgetForContainerProps (line 3) | interface LoadingWidgetForContainerProps {

FILE: components/main-sidebar/main-sidebar.tsx
  function handleOpenPage (line 40) | function handleOpenPage(page: Pages) {
  function getButtonClasses (line 49) | function getButtonClasses(page: Pages, extraClasses?: string) {

FILE: components/notices/privacy-policy.tsx
  type PrivacyPolicyModalProps (line 14) | interface PrivacyPolicyModalProps {
  function PrivacyPolicy (line 19) | function PrivacyPolicy({

FILE: components/notices/terms-of-services.tsx
  type TermsOfServiceModalProps (line 14) | interface TermsOfServiceModalProps {
  function TermsOfService (line 19) | function TermsOfService({

FILE: components/services/esri/add-arcgis-layers.tsx
  function AddArcGisLayerClient (line 7) | function AddArcGisLayerClient() {

FILE: components/theme-provider.tsx
  type ThemeProviderProps (line 11) | type ThemeProviderProps = React.ComponentProps<
  function ThemeProvider (line 16) | function ThemeProvider({ children, ...props }: ThemeProviderProps) {

FILE: components/ui/badge.tsx
  type BadgeProps (line 26) | interface BadgeProps
  function Badge (line 30) | function Badge({ className, variant, ...props }: BadgeProps) {

FILE: components/ui/button.tsx
  type ButtonProps (line 43) | interface ButtonProps

FILE: components/ui/confirmation-modal.tsx
  type ConfirmationModalProps (line 15) | interface ConfirmationModalProps {

FILE: components/ui/form.tsx
  type FormFieldContextValue (line 20) | type FormFieldContextValue<
  type FormItemContextValue (line 67) | type FormItemContextValue = {

FILE: components/ui/input-text-confirm.tsx
  type InputTextConfirmProps (line 5) | interface InputTextConfirmProps {

FILE: components/ui/theme-mode-toggle.tsx
  function ThemeModeToggle (line 15) | function ThemeModeToggle() {

FILE: custom-configs/ai-assistants.ts
  constant AI_ASSISTANTS (line 1) | const AI_ASSISTANTS: AI_ASSISTANTS_TYPE = {

FILE: custom-configs/integrations.ts
  constant INTEGRATION_SERVICES (line 1) | const INTEGRATION_SERVICES: IntegrationService[] = [

FILE: db-schema/schema.sql
  function "public" (line 48) | CREATE OR REPLACE FUNCTION "public"."create_user_usage_row"() RETURNS "t...
  function "public" (line 64) | CREATE OR REPLACE FUNCTION "public"."handle_new_auth_user"() RETURNS "tr...
  function "public" (line 96) | CREATE OR REPLACE FUNCTION "public"."search_documents_by_similarity"("qu...
  function "public" (line 121) | CREATE OR REPLACE FUNCTION "public"."search_documents_by_similarity"("qu...
  function "public" (line 152) | CREATE OR REPLACE FUNCTION "public"."search_gee_datasets_ft"("query" "te...
  function "public" (line 176) | CREATE OR REPLACE FUNCTION "public"."update_user_usage_docs_count"() RET...
  type "public" (line 207) | CREATE TABLE IF NOT EXISTS "public"."chats" (
  type "public" (line 218) | CREATE TABLE IF NOT EXISTS "public"."document_files" (
  type "public" (line 243) | CREATE TABLE IF NOT EXISTS "public"."drafted_reports" (
  type "public" (line 255) | CREATE TABLE IF NOT EXISTS "public"."embeddings" (
  type "public" (line 282) | CREATE TABLE IF NOT EXISTS "public"."gee_datasets" (
  type "public" (line 316) | CREATE TABLE IF NOT EXISTS "public"."messages" (
  type "public" (line 330) | CREATE TABLE IF NOT EXISTS "public"."user_roles" (
  type "public" (line 346) | CREATE TABLE IF NOT EXISTS "public"."user_usage" (
  type "public" (line 421) | CREATE INDEX "gee_datasets_dataset_id_idx" ON "public"."gee_datasets" US...
  type "public" (line 425) | CREATE INDEX "gee_datasets_search_idx" ON "public"."gee_datasets" USING ...
  type "public" (line 429) | CREATE INDEX "gee_datasets_title_idx" ON "public"."gee_datasets" USING "...

FILE: features/charts/components/charts-display.tsx
  type ChartStatsDisplayProps (line 14) | interface ChartStatsDisplayProps {

FILE: features/charts/components/charts.tsx
  type ChartProps (line 15) | interface ChartProps {
  function Chart (line 24) | function Chart({
  type TimeseriesNumericalQueryChartProps (line 187) | interface TimeseriesNumericalQueryChartProps {
  type ScoreData (line 192) | type ScoreData = { name: string; score: number | string };
  type ScoresBarChartProps (line 194) | type ScoresBarChartProps = {
  type TimeseriesChartProps (line 298) | interface TimeseriesChartProps {
  type TimeSeriesStats (line 886) | type TimeSeriesStats = {
  type BoxPlotTimeseriesChartProps (line 897) | type BoxPlotTimeseriesChartProps = {
  type BarChartPercentageProps (line 1058) | type BarChartPercentageProps = {
  type BarChartNumericalProps (line 1186) | type BarChartNumericalProps = {
  type DualBarChartNumericalProps (line 1295) | type DualBarChartNumericalProps = {
  type BarChartStatsProps (line 1608) | type BarChartStatsProps = {
  type StackedBarChartStatisticsProps (line 1743) | type StackedBarChartStatisticsProps = {
  type StackedBarChartPercentageProps (line 1907) | type StackedBarChartPercentageProps = {
  type GasData (line 2054) | type GasData = {
  type CombinedStackedBarChartStatisticsProps (line 2062) | type CombinedStackedBarChartStatisticsProps = {
  type LandcoverDistributions (line 2266) | interface LandcoverDistributions {
  type StackedBarChartForLandcoverChangeMapProps (line 2271) | interface StackedBarChartForLandcoverChangeMapProps {
  type PieChartStatsProps (line 2406) | interface PieChartStatsProps {
  type ValueType (line 2502) | type ValueType = {
  type InputArgsType (line 2508) | type InputArgsType = {
  type HistogramChartProps (line 2515) | type HistogramChartProps = {

FILE: features/charts/utils/select-chart-type.ts
  type ChartTypes (line 1) | enum ChartTypes {

FILE: features/chat-history/components/chat-history-row.tsx
  type ChatHistory (line 21) | interface ChatHistory {
  type ChatHistoryRowProps (line 27) | interface ChatHistoryRowProps {

FILE: features/chat-history/components/chat-history-table.tsx
  type ChatHistory (line 16) | interface ChatHistory {
  type ChatHistoryTableProps (line 22) | interface ChatHistoryTableProps {

FILE: features/chat-history/components/chat-history.tsx
  type ChatHistory (line 14) | interface ChatHistory {
  function ChatHistory (line 20) | function ChatHistory() {

FILE: features/chat-history/components/indeterminate-checkbox.tsx
  type IndeterminateCheckboxProps (line 3) | interface IndeterminateCheckboxProps {
  function IndeterminateCheckbox (line 10) | function IndeterminateCheckbox(props: IndeterminateCheckboxProps) {

FILE: features/chat/components/chat-response-box/capabilities-banner.tsx
  function CapabilitiesBanner (line 8) | function CapabilitiesBanner() {

FILE: features/chat/components/chat-response-box/chat-message/chat-message.tsx
  type ChatMessageProps (line 10) | interface ChatMessageProps {

FILE: features/chat/components/chat-response-box/chat-response-box.tsx
  type MessageCompletionState (line 37) | interface MessageCompletionState {
  type ChatResponseBoxProps (line 42) | interface ChatResponseBoxProps {

FILE: features/chat/components/chat-response-box/in-response-tool-calling-results/draft-report/draft-report.tsx
  type DraftReportProps (line 5) | interface DraftReportProps {

FILE: features/chat/components/chat-response-box/in-response-tool-calling-results/draft-report/drafted-report-btn.tsx
  type DraftedReportBtnProps (line 5) | interface DraftedReportBtnProps {

FILE: features/chat/components/chat-response-box/in-response-tool-calling-results/knowledge-base-citation/citation-badge.tsx
  type Citation (line 32) | interface Citation {
  type CitationBadgeProps (line 38) | interface CitationBadgeProps {
  type CitationItemProps (line 63) | interface CitationItemProps {

FILE: features/chat/components/chat-response-box/in-response-tool-calling-results/tool-calling-results.tsx
  type ToolCallingResultsProps (line 9) | interface ToolCallingResultsProps {

FILE: features/chat/components/external-assets/assets-modal.tsx
  type AssetsModalProps (line 49) | interface AssetsModalProps {

FILE: features/chat/components/input/chat-input-box.tsx
  type ChatInputBoxProps (line 29) | interface ChatInputBoxProps {

FILE: features/chat/components/input/chat-input-buttons/chat-input-buttons.tsx
  type ChatInputButtonsProps (line 26) | interface ChatInputButtonsProps {

FILE: features/chat/components/input/chat-input-buttons/current-session-assets-dropup.tsx
  type Attachment (line 16) | interface Attachment {

FILE: features/chat/components/input/chat-input-buttons/open-database-in-chat-input-btn.tsx
  type OpenDatabaseInChatInputBtnProps (line 4) | interface OpenDatabaseInChatInputBtnProps {

FILE: features/chat/components/input/chat-input-buttons/select-roi-on-map-btn.tsx
  type SelectRoiOnMapBtnProps (line 6) | interface SelectRoiOnMapBtnProps {
  function handleClick (line 16) | function handleClick() {

FILE: features/chat/components/input/chat-input-dropzone.tsx
  type ChatInputDropzoneProps (line 1) | interface ChatInputDropzoneProps {

FILE: features/chat/components/input/map-tools-dropup.tsx
  type MapToolsDropupProps (line 13) | interface MapToolsDropupProps {

FILE: features/chat/components/input/slash-menu-for-map-layers.tsx
  type CommandMenuProps (line 5) | interface CommandMenuProps {

FILE: features/chat/hooks/use-slash-menu-map-layers-list.ts
  type MenuPosition (line 9) | interface MenuPosition {
  type UseSlashMenuMapLayersListProps (line 15) | interface UseSlashMenuMapLayersListProps {

FILE: features/chat/stores/use-attachments-store.ts
  type AttachmentFile (line 5) | interface AttachmentFile {
  type AttachmentStore (line 14) | interface AttachmentStore {

FILE: features/chat/stores/use-chat-response-sources-store.ts
  type Page (line 4) | interface Page {
  type Source (line 10) | interface Source {
  type ChatSourcesState (line 16) | interface ChatSourcesState {

FILE: features/chat/stores/use-drafted-report-store.ts
  type ReportState (line 3) | interface ReportState {

FILE: features/chat/ui/fadeIn-with-delay.tsx
  type FadeInWithDelayProps (line 3) | interface FadeInWithDelayProps {

FILE: features/chat/utils/drag-and-drop-file-analyzer.ts
  function dragAndDropFileAnalyzer (line 10) | async function dragAndDropFileAnalyzer(file: File): Promise<any> {

FILE: features/chat/utils/general-utils.ts
  function generateTitleFromUserMessage (line 14) | async function generateTitleFromUserMessage({
  function getFormattedDate (line 32) | function getFormattedDate(): string {
  function getLocalStorage (line 40) | function getLocalStorage(key: string) {
  function generateUUID (line 47) | function generateUUID(): string {
  function isQueryUuid (line 51) | function isQueryUuid(name: string) {
  function sanitizeResponseMessages (line 62) | function sanitizeResponseMessages(
  function getMostRecentUserMessage (line 101) | function getMostRecentUserMessage(messages: Array<CoreMessage>) {
  function addToolMessageToChat (line 106) | function addToolMessageToChat({
  function convertToUIMessages (line 139) | function convertToUIMessages(messages: Array<any>): Array<Message> {
  function checkUserUsageInChat (line 179) | function checkUserUsageInChat(

FILE: features/chat/utils/slash-menu-utils.ts
  constant MAX_LIST_LENGTH (line 5) | const MAX_LIST_LENGTH = 8;
  constant COMMAND_REG (line 8) | const COMMAND_REG = /\B\/([\-+\w]*)$/;

FILE: features/chat/utils/tool-calling-results-validation.ts
  type GeospatialAnalysisResult (line 1) | interface GeospatialAnalysisResult {
  function validateToolCallingResults (line 10) | function validateToolCallingResults(

FILE: features/chat/utils/use-drag-and-drop-file-import.ts
  type AddAttachmentFunction (line 8) | type AddAttachmentFunction = (file: File) => void;

FILE: features/chat/utils/use-slash-command-menu.ts
  type MenuPosition (line 7) | interface MenuPosition {
  type UseSlashCommandMenuProps (line 13) | interface UseSlashCommandMenuProps {

FILE: features/integrations/components/integration-actions.tsx
  type IntegrationActionsProps (line 4) | interface IntegrationActionsProps {

FILE: features/integrations/components/integration-header.tsx
  type IntegrationHeaderProps (line 4) | interface IntegrationHeaderProps {

FILE: features/integrations/components/integration-item.tsx
  type IntegrationItemProps (line 5) | interface IntegrationItemProps {

FILE: features/integrations/components/integration-list.tsx
  type IntegrationListProps (line 4) | interface IntegrationListProps {

FILE: features/integrations/components/integration-status.tsx
  type IntegrationStatusProps (line 4) | interface IntegrationStatusProps {

FILE: features/knowledge-base/actions/document-actions.ts
  function fetchDocumentFiles (line 10) | async function fetchDocumentFiles(): Promise<DocumentFile[]> {
  function fetchByDocumentName (line 50) | async function fetchByDocumentName(
  function deleteDocumentFile (line 104) | async function deleteDocumentFile(
  function processAndUploadDocumentFile (line 176) | async function processAndUploadDocumentFile({

FILE: features/knowledge-base/components/add-group-modal.tsx
  type AddGroupModalProps (line 12) | interface AddGroupModalProps {

FILE: features/knowledge-base/components/documents-table.tsx
  type DocumentsTableProps (line 20) | interface DocumentsTableProps {

FILE: features/knowledge-base/components/edit-document-modal.tsx
  type EditDocumentModalProps (line 4) | interface EditDocumentModalProps {

FILE: features/knowledge-base/components/knolwedge-base.tsx
  type KnowledgeBaseProps (line 27) | interface KnowledgeBaseProps {

FILE: features/knowledge-base/components/knowledge-base-sidebar.tsx
  type SidebarProps (line 11) | interface SidebarProps {

FILE: features/knowledge-base/components/max-docs-alert-dialog.tsx
  type MaxDocsAlertDialogProps (line 15) | interface MaxDocsAlertDialogProps {
  function MaxDocsAlertDialog (line 24) | function MaxDocsAlertDialog({

FILE: features/knowledge-base/utils/transform-metadata-to-citation.ts
  type RawMatch (line 3) | interface RawMatch {
  type Citation (line 23) | interface Citation {
  function transformMetadataToCitations (line 29) | function transformMetadataToCitations(items: RawMatch[]): Citation[] {

FILE: features/maps/components/address-search.tsx
  type GeocodeResponse (line 20) | interface GeocodeResponse {
  function AddressSearch (line 25) | function AddressSearch() {

FILE: features/maps/components/attribute-table/attribute-table-controls.tsx
  type AttributeTableControlsProps (line 19) | interface AttributeTableControlsProps {
  type RasterLayerProps (line 26) | interface RasterLayerProps {
  function handleQueryRaster (line 61) | function handleQueryRaster() {

FILE: features/maps/components/attribute-table/attribute-table.tsx
  constant MIN_PANEL_HEIGHT (line 33) | const MIN_PANEL_HEIGHT = 150;

FILE: features/maps/components/map-badge.tsx
  type MapBadgeProps (line 4) | interface MapBadgeProps {

FILE: features/maps/components/map-container.tsx
  constant MAP_CONTAINER_ID (line 27) | const MAP_CONTAINER_ID = "map";

FILE: features/maps/components/map-custom-controls/map-roi-controls.tsx
  function handleRoiConfirm (line 27) | function handleRoiConfirm() {
  function handleRoiFinalize (line 31) | function handleRoiFinalize(name: string) {

FILE: features/maps/components/map-panels/map-layers-panel/color-picker-popover.tsx
  function ColorPickerPopover (line 13) | function ColorPickerPopover() {

FILE: features/maps/components/map-panels/map-layers-panel/map-layers-panel.tsx
  function DraggablePortal (line 54) | function DraggablePortal({
  function MapLayersPanel (line 89) | function MapLayersPanel() {

FILE: features/maps/components/map-panels/map-layers-panel/map-legend.tsx
  type LegendProps (line 5) | interface LegendProps {
  function generateColorGradient (line 13) | function generateColorGradient(min: number, max: number, palette: string...
  type LegendContainerProps (line 24) | interface LegendContainerProps {
  function LegendContainer (line 29) | function LegendContainer({ title, children }: LegendContainerProps) {

FILE: features/maps/hooks/use-handle-click/use-handle-click.ts
  function useHandleClick (line 15) | function useHandleClick() {

FILE: features/maps/hooks/use-handle-click/use-map-controls.ts
  function useMapControls (line 13) | function useMapControls(map: Map | null) {

FILE: features/maps/hooks/use-handle-click/use-query-drawing.ts
  type UseQueryDrawingProps (line 31) | interface UseQueryDrawingProps {
  function useQueryDrawing (line 39) | function useQueryDrawing({

FILE: features/maps/hooks/use-handle-click/use-remove-query-features.ts
  function useRemoveQueryFeatures (line 12) | function useRemoveQueryFeatures(map: Map | null) {

FILE: features/maps/hooks/use-handle-click/use-roi-drawing.ts
  type UseRoiDrawingProps (line 15) | interface UseRoiDrawingProps {
  function useRoiDrawing (line 27) | function useRoiDrawing({

FILE: features/maps/hooks/use-map/use-add-arcgis-layers.ts
  function useAddArcGisLayers (line 11) | function useAddArcGisLayers(mapInstance: Map | null) {

FILE: features/maps/hooks/use-map/use-add-attached-layers.ts
  function useAddAttachedLayers (line 10) | function useAddAttachedLayers(mapInstance: Map | null) {

FILE: features/maps/hooks/use-map/use-add-gee-layers.ts
  function useAddGeeLayers (line 9) | function useAddGeeLayers(mapInstance: Map | null) {

FILE: features/maps/hooks/use-map/use-add-roi-from-session.ts
  function useAddRoiFromSession (line 9) | function useAddRoiFromSession(mapInstance: Map | null) {

FILE: features/maps/hooks/use-map/use-basemap-toggle.ts
  type UseBasemapToggleParams (line 9) | interface UseBasemapToggleParams {
  function useBasemapToggle (line 16) | function useBasemapToggle({

FILE: features/maps/hooks/use-map/use-map-initialization.ts
  type UseMapInitializationProps (line 9) | interface UseMapInitializationProps {
  function useMapInitialization (line 17) | function useMapInitialization({

FILE: features/maps/hooks/use-map/use-map.ts
  function useMap (line 15) | function useMap(containerId: string) {

FILE: features/maps/hooks/use-map/use-update-layer-style.ts
  function useUpdateLayerColor (line 8) | function useUpdateLayerColor(mapInstance: Map | null) {

FILE: features/maps/hooks/use-map/use-zoom-to-geometry.ts
  function useZoomToGeometry (line 17) | function useZoomToGeometry(mapInstance: Map | null) {

FILE: features/maps/stores/map-queries-stores/useQueryOutputReadyFromVectorLayerStore.ts
  type QueryState (line 3) | interface QueryState {

FILE: features/maps/stores/map-queries-stores/useQueryRasterFromVectorLayerStore.ts
  type QueryRasterFromVectorLayerState (line 3) | interface QueryRasterFromVectorLayerState {

FILE: features/maps/stores/map-queries-stores/useQueryReadyStore.ts
  type QueryState (line 3) | interface QueryState {

FILE: features/maps/stores/plots-stores/useChartRequestedTypeStore.ts
  type ChartTypes (line 3) | enum ChartTypes {
  type ChartRequestedTypeStore (line 135) | interface ChartRequestedTypeStore {

FILE: features/maps/stores/plots-stores/usePlotReadyDataStore.ts
  type PlotReadyState (line 3) | interface PlotReadyState {

FILE: features/maps/stores/plots-stores/usePlotReadyFromVectorLayerStore.ts
  type PlotReadyState (line 3) | interface PlotReadyState {

FILE: features/maps/stores/use-agol-layers-store.ts
  type Layer (line 10) | interface Layer {
  type AgolLayers (line 17) | interface AgolLayers {

FILE: features/maps/stores/use-color-picker-store.ts
  type ColorStore (line 3) | interface ColorStore {

FILE: features/maps/stores/use-cursor-store.ts
  type CursorState (line 3) | interface CursorState {

FILE: features/maps/stores/use-drawn-feature-on-map-store.ts
  type FeatureStore (line 3) | interface FeatureStore {

FILE: features/maps/stores/use-function-store.ts
  type FunctionConfig (line 3) | type FunctionConfig = {
  type FunctionStore (line 16) | type FunctionStore = {

FILE: features/maps/stores/use-gee-ouput-store.ts
  type GeeOutputItem (line 3) | interface GeeOutputItem {
  type GeeOutputState (line 11) | interface GeeOutputState {

FILE: features/maps/stores/use-geojson-store.ts
  type GeojsonItem (line 3) | interface GeojsonItem {
  type GeojsonState (line 8) | interface GeojsonState {

FILE: features/maps/stores/use-layer-selection-store.ts
  type LayerState (line 3) | interface LayerState {

FILE: features/maps/stores/use-map-badge-store.ts
  type BadgeState (line 3) | interface BadgeState {

FILE: features/maps/stores/use-map-display-store.ts
  type MapDisplayState (line 3) | interface MapDisplayState {

FILE: features/maps/stores/use-map-layer-store.ts
  type MapLayersState (line 8) | interface MapLayersState {

FILE: features/maps/stores/use-map-legend-store.ts
  type LegendConfig (line 4) | type LegendConfig = {
  type LegendEntry (line 12) | interface LegendEntry {
  type MapLegendState (line 18) | interface MapLegendState {

FILE: features/maps/stores/use-map-zoom-request-store.ts
  type AddressSearchProps (line 5) | interface AddressSearchProps {
  type ZoomInLayerState (line 10) | interface ZoomInLayerState {

FILE: features/maps/stores/use-roi-store.ts
  type ROIStoreState (line 4) | interface ROIStoreState {

FILE: features/maps/stores/use-table-store.ts
  type TableState (line 3) | interface TableState {

FILE: features/maps/utils/add-drawn-layer-to-map.ts
  function getRandomBrightColor (line 5) | function getRandomBrightColor(): string {

FILE: features/maps/utils/add-roi-layer-to-map.ts
  function getRandomBrightColor (line 4) | function getRandomBrightColor(): string {

FILE: features/maps/utils/authentication-utils/gee-auth.ts
  function geeAuthenticate (line 6) | async function geeAuthenticate(): Promise<void> {

FILE: features/maps/utils/gee-eval-utils.ts
  function getMapId (line 1) | function getMapId(image: any, vis: any) {
  function evaluate (line 10) | function evaluate(obj: any) {

FILE: features/maps/utils/geometry-utils.ts
  function convertToEeGeometry (line 172) | function convertToEeGeometry(geometry: any) {
  function convertEeGeometryToGeoJSON (line 197) | function convertEeGeometryToGeoJSON(eeGeom: any) {
  function convertCoordinatesToGeoJson (line 209) | function convertCoordinatesToGeoJson(coordinates: {
  function convertToNDecimals (line 216) | function convertToNDecimals(value: number, decimals: number): number {
  function flattenPolygonCoordinates (line 220) | function flattenPolygonCoordinates(coords?: number[][][]): number[][] {

FILE: features/maps/utils/other-utils.ts
  function stringifyNested (line 1) | function stringifyNested(value: any): string {
  function formatQueryForTable (line 33) | function formatQueryForTable(query: any): string {

FILE: features/maps/utils/type-guards.ts
  function isFeature (line 2) | function isFeature(obj: unknown): obj is Feature {
  function isROIGeometry (line 14) | function isROIGeometry(obj: unknown): obj is ROIGeometry {

FILE: features/text-editor/components/text-editor.tsx
  function CustomSlashMenu (line 32) | function CustomSlashMenu(
  type TextEditorProps (line 77) | interface TextEditorProps {

FILE: features/ui/modals/file-upload-modal.tsx
  type Folder (line 16) | interface Folder {
  type FileUploadModalProps (line 21) | interface FileUploadModalProps {

FILE: features/ui/toast-message.tsx
  function ToastMessage (line 14) | function ToastMessage() {

FILE: features/user-profile/components/user-profile-modal.tsx
  function UserProfile (line 22) | function UserProfile() {

FILE: hooks/docs-hooks/use-document-upload.ts
  type FileUploadHookProps (line 6) | type FileUploadHookProps = {

FILE: lib/auth.ts
  type UserRole (line 4) | type UserRole = "ADMIN" | "USER" | "TRIAL";
  type SubscriptionTier (line 5) | type SubscriptionTier = "Essentials" | "Pro" | "Enterprise";
  type PermissionSet (line 7) | interface PermissionSet {
  constant PERMISSION_MATRIX (line 14) | const PERMISSION_MATRIX: Record<
  function getPermissionSet (line 70) | async function getPermissionSet(

FILE: lib/changelog.ts
  type ChangelogEntry (line 1) | interface ChangelogEntry {

FILE: lib/database/chat/queries.ts
  function getChatsByUser (line 6) | async function getChatsByUser(userId: string) {
  function saveChat (line 32) | async function saveChat({ id, title }: { id: string; title: string }) {
  function getChatById (line 64) | async function getChatById(chatId: string) {
  function saveMessages (line 88) | async function saveMessages({
  function getMessagesByChatId (line 130) | async function getMessagesByChatId(chatId: string) {
  function getDraftedReportById (line 155) | async function getDraftedReportById(draftedReportId: string) {
  function deleteChatById (line 187) | async function deleteChatById(chatId: string) {
  function searchGeeDatasets (line 240) | async function searchGeeDatasets(query: string) {

FILE: lib/database/chat/tools.ts
  function requestGeospatialAnalysis (line 11) | async function requestGeospatialAnalysis(args: any) {
  function requestLoadingGeospatialData (line 139) | async function requestLoadingGeospatialData(args: any) {
  function requestRagQuery (line 253) | async function requestRagQuery(args: any) {
  function draftReport (line 267) | async function draftReport(args: any) {
  function requestWebScraping (line 316) | async function requestWebScraping(args: any) {

FILE: lib/database/usage.ts
  function incrementRequestCount (line 4) | async function incrementRequestCount(userId: string) {
  function getUsageForUser (line 37) | async function getUsageForUser(userId: string) {
  function getUserRoleAndTier (line 52) | async function getUserRoleAndTier(userId: string) {

FILE: lib/fetchers/chat.ts
  function fetchChatHistory (line 1) | async function fetchChatHistory() {

FILE: lib/geospatial/gee/analysis-functions/heat-analysis/urban-heat-island-analysis.ts
  type AggregationMethodType (line 7) | type AggregationMethodType = "Median" | "Mean" | "Max" | "Min";

FILE: lib/geospatial/gee/analysis-functions/lancover-landuse-mapping/google-dynamic-world-landcover-mapping.ts
  type LegendConfig (line 6) | interface LegendConfig {
  type DynamicWorldResult (line 11) | interface DynamicWorldResult {
  function googleDynamicWorldMapping (line 19) | async function googleDynamicWorldMapping(

FILE: lib/geospatial/gee/analysis-functions/lancover-landuse-mapping/landcover-change-mapping.ts
  type LandCoverChangeResult (line 6) | interface LandCoverChangeResult {
  constant DW_LABELS (line 15) | const DW_LABELS = [
  constant DW_PALETTE (line 28) | const DW_PALETTE = [
  constant PROB_THRESHOLD (line 40) | const PROB_THRESHOLD = 0.5;
  function computeClassDistribution (line 45) | async function computeClassDistribution(
  function landcoverChangeMapping (line 86) | async function landcoverChangeMapping(

FILE: lib/geospatial/gee/analysis-functions/pollution-analysis/air-pollution-analysis.ts
  type MonitoringResult (line 7) | interface MonitoringResult {

FILE: lib/geospatial/gee/extract-values-from-gee-layer/extract-values-from-gee-layer.ts
  type inputArgsType (line 8) | type inputArgsType = {
  function extractValuesFromGeeMap (line 33) | async function extractValuesFromGeeMap({

FILE: lib/geospatial/gee/extract-values-from-gee-layer/geospatial-analyses/extract-values-from-air-pollution-map.ts
  type InputArgsType (line 4) | type InputArgsType = {
  type AggregationMethodType (line 14) | type AggregationMethodType = "Mean" | "Median" | "Max" | "Min";
  function extractValuesFromAirPollutionMap (line 16) | async function extractValuesFromAirPollutionMap({

FILE: lib/geospatial/gee/extract-values-from-gee-layer/geospatial-analyses/extract-values-from-google-dynamic-world-map.ts
  type InputArgsType (line 4) | type InputArgsType = {
  function extractValuesFromDynamicWorldMap (line 10) | async function extractValuesFromDynamicWorldMap({

FILE: lib/geospatial/gee/extract-values-from-gee-layer/geospatial-analyses/extract-values-from-landcover-change-map.ts
  constant PROB_THRESHOLD (line 3) | const PROB_THRESHOLD = 0.5;
  type InputArgsType (line 5) | type InputArgsType = {
  function extractValuesFromLandcoverChangeMap (line 13) | async function extractValuesFromLandcoverChangeMap({
  function parseGeometry (line 178) | function parseGeometry(featureGeom: any) {

FILE: lib/geospatial/gee/extract-values-from-gee-layer/geospatial-analyses/extract-values-from-sentinel-landcover-landuse-map.ts
  type InputArgsType (line 5) | type InputArgsType = {
  function extractValuesFromSentinelLandcoverLanduseMap (line 12) | async function extractValuesFromSentinelLandcoverLanduseMap({
  function waitForTaskCompletion (line 113) | async function waitForTaskCompletion(taskId: string) {

FILE: lib/geospatial/gee/extract-values-from-gee-layer/geospatial-analyses/extract-values-from-urban-heat-island-map.ts
  type inputArgsType (line 6) | type inputArgsType = {
  type AggregationMethodType (line 13) | type AggregationMethodType = "Mean" | "Median" | "Max" | "Min";
  function extractValuesFromUrbanHeatIslandMap (line 15) | async function extractValuesFromUrbanHeatIslandMap({

FILE: lib/geospatial/gee/load-data/load-raster-data.ts
  type RasterDataResult (line 6) | interface RasterDataResult {
  type visParams (line 17) | interface visParams {
  function loadRasterData (line 23) | async function loadRasterData(

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

FILE: middleware.ts
  function middleware (line 4) | async function middleware(request: NextRequest) {

FILE: stores/use-buttons-store.ts
  type ButtonGroup (line 3) | type ButtonGroup = "draw_point" | "draw_polygon" | "simple_select" | null;
  type BuildCenterButton (line 4) | type BuildCenterButton =
  type WorkflowActionButton (line 11) | type WorkflowActionButton = "stop" | "run" | "queue";
  type AiAppBuilderActionButton (line 14) | type AiAppBuilderActionButton = "run" | "stop" | "queue" | "save";
  type ButtonsStore (line 16) | interface ButtonsStore {

FILE: stores/use-integration-store.ts
  type IntegrationState (line 5) | interface IntegrationState {

FILE: stores/use-loading-store.ts
  type LoadingState (line 3) | interface LoadingState {

FILE: stores/use-sidebar-button-stores.ts
  type Pages (line 4) | enum Pages {
  type SidebarButtonStoreState (line 16) | interface SidebarButtonStoreState {

FILE: stores/use-toast-message-store.ts
  type ToastStore (line 4) | interface ToastStore {

FILE: stores/use-user-profile-store.ts
  type UserState (line 4) | interface UserState {

FILE: types/global.d.ts
  type MultiAnalysisOptionsType (line 17) | type MultiAnalysisOptionsType =
  type MultiAnalysisFunctionNames (line 25) | type MultiAnalysisFunctionNames =
  type MultiAnalysisOptionsTypeForVulnerabilityMapBuilderType (line 29) | type MultiAnalysisOptionsTypeForVulnerabilityMapBuilderType =
  type MultiAnalysisOptionsTypeForAirPollutantsAnalysisType (line 34) | type MultiAnalysisOptionsTypeForAirPollutantsAnalysisType =
  type AirPollutantsUnits (line 40) | type AirPollutantsUnits = "mol/m²" | "ppm" | "ppb" | "index";
  type MultiAnalysisFunctionsConfigType (line 45) | type MultiAnalysisFunctionsConfigType = {
  type AggregationMethodType (line 57) | type AggregationMethodType =
  type AggregationMethodTypeCategorical (line 65) | type AggregationMethodTypeCategorical = "Mode" | "90th Percentile";
  type AggregationMethodTypeNumerical (line 67) | type AggregationMethodTypeNumerical = "Mean" | "Median" | "Max" | "Min";
  type AI_ASSISTANTS_TYPE (line 72) | type AI_ASSISTANTS_TYPE = {
  type BasemapType (line 81) | type BasemapType = "satellite" | "osm";
  type CitationBadgeProps (line 86) | type CitationBadgeProps = {
  type UHIMetrics (line 99) | type UHIMetrics = UHIMetric[];
  type ProcessDocumentFileProps (line 104) | type ProcessDocumentFileProps = {
  type MenuPosition (line 112) | type MenuPosition = {
  type ServiceType (line 121) | type ServiceType =
  type ServiceStatus (line 130) | type ServiceStatus = "connected" | "not_connected";
  type MultiAnalysisSettingsModalProps (line 139) | interface MultiAnalysisSettingsModalProps {
  type UHIMetric (line 147) | interface UHIMetric {
  type GeeOutputItem (line 157) | interface GeeOutputItem {
  type Source (line 168) | interface Source {
  type LegendConfig (line 176) | interface LegendConfig {
  type GeospatialAnalysisResult (line 186) | interface GeospatialAnalysisResult {
  type ToolCallingMessageResults (line 198) | interface ToolCallingMessageResults {
  type ChatHistory (line 210) | interface ChatHistory {
  type ChatResponseBoxProps (line 219) | interface ChatResponseBoxProps {
  type TableColumn (line 227) | interface TableColumn<T> {
  type User (line 237) | interface User {
  type DocumentFile (line 246) | interface DocumentFile {
  type TableAction (line 258) | interface TableAction<T> {
  type TextEditorProps (line 268) | interface TextEditorProps {
  type ChatInputBoxProps (line 275) | interface ChatInputBoxProps {
  type ROIGeometry (line 286) | interface ROIGeometry {
  type IntegrationService (line 296) | interface IntegrationService {
  type Feature (line 308) | interface Feature {
  type ArcGISLayer (line 324) | interface ArcGISLayer {
  type MapLayer (line 334) | interface MapLayer {

FILE: utils/general/general-utils.ts
  function extractYear (line 9) | function extractYear(dateString: string): number {
  function cleanString (line 28) | function cleanString(str: string): string {
  function removeExtension (line 32) | function removeExtension(filename: string): string {

FILE: utils/reset-chat-stores.ts
  function resetChatStores (line 26) | function resetChatStores() {

FILE: utils/supabase/client.ts
  function createClient (line 3) | function createClient() {

FILE: utils/supabase/middleware.ts
  function updateSession (line 4) | async function updateSession(request: NextRequest) {

FILE: utils/supabase/server.ts
  function createClient (line 4) | async function createClient() {

FILE: utils/validation-utils/validation-utils.ts
  function isValidUrl (line 3) | function isValidUrl(url: string): boolean {
  function sanitizeUrl (line 12) | function sanitizeUrl(url: string): string {
  function isValidLayerName (line 16) | function isValidLayerName(name: string): boolean {
  function sanitizeLayerName (line 20) | function sanitizeLayerName(name: string): string {
  function validateAndSanitizeUrl (line 24) | function validateAndSanitizeUrl(url: string): string | null {
  function validateAndSanitizeLayerName (line 32) | function validateAndSanitizeLayerName(name: string): string | null {
Condensed preview — 255 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (827K chars).
[
  {
    "path": ".eslintrc.json",
    "chars": 282,
    "preview": "{\n  \"extends\": [\"next/core-web-vitals\", \"next/typescript\"],\n  \"rules\": {\n    \"@typescript-eslint/no-unused-vars\": \"warn\""
  },
  {
    "path": ".gitignore",
    "chars": 472,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1328,
    "preview": "# Contributing to Chat2Geo\nThank you for considering contributing to Chat2Geo!\nHere are the instructions on how to contr"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2025 GeoRetina Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 11294,
    "preview": "> [!IMPORTANT]  \n> **This repository is archived and no longer maintained.**  \n> Please use the hosted version at [https"
  },
  {
    "path": "app/(auth)/api/auth/confirm/route.ts",
    "chars": 1124,
    "preview": "import { type EmailOtpType } from \"@supabase/supabase-js\";\nimport { type NextRequest, NextResponse } from \"next/server\";"
  },
  {
    "path": "app/(auth)/api/auth/esri/authorize/route.ts",
    "chars": 657,
    "preview": "import { NextResponse } from \"next/server\";\nimport { createClient } from \"@/utils/supabase/server\";\n\nexport async functi"
  },
  {
    "path": "app/(auth)/api/auth/esri/callback/route.ts",
    "chars": 1930,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { createClient } from \"@/utils/supabase/server\";\nimport "
  },
  {
    "path": "app/(auth)/layout.tsx",
    "chars": 1222,
    "preview": "import localFont from \"next/font/local\";\nimport \"./styles.css\";\nimport { Toaster } from \"react-hot-toast\";\nimport ToastM"
  },
  {
    "path": "app/(auth)/login/actions.ts",
    "chars": 2255,
    "preview": "\"use server\";\n\nimport { revalidatePath } from \"next/cache\";\nimport { redirect } from \"next/navigation\";\nimport { createC"
  },
  {
    "path": "app/(auth)/login/layout.tsx",
    "chars": 220,
    "preview": "export default async function Layout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <div className="
  },
  {
    "path": "app/(auth)/login/page.tsx",
    "chars": 5248,
    "preview": "\"use client\";\n\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\""
  },
  {
    "path": "app/(auth)/styles.css",
    "chars": 2656,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --f"
  },
  {
    "path": "app/(broadcast)/api/services/esri/fetch-layers-list/route.ts",
    "chars": 1731,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { createClient } from \"@/utils/supabase/server\";\n\nexport"
  },
  {
    "path": "app/(broadcast)/api/services/esri/fetch-selected-layer/route.ts",
    "chars": 1348,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { createClient } from \"@/utils/supabase/server\";\n\nexport"
  },
  {
    "path": "app/(broadcast)/layout.tsx",
    "chars": 1123,
    "preview": "import localFont from \"next/font/local\";\nimport \"./styles.css\";\nimport { Toaster } from \"react-hot-toast\";\nimport ToastM"
  },
  {
    "path": "app/(broadcast)/services/esri/fetch-layers/page.tsx",
    "chars": 450,
    "preview": "import { createClient } from \"@/utils/supabase/server\";\nimport { redirect } from \"next/navigation\";\nimport AddArcGisLaye"
  },
  {
    "path": "app/(broadcast)/styles.css",
    "chars": 2656,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --f"
  },
  {
    "path": "app/(main)/actions/get-user-profile.ts",
    "chars": 855,
    "preview": "\"use server\";\nimport { createClient } from \"@/utils/supabase/server\";\n\nexport async function getUserProfile() {\n  const "
  },
  {
    "path": "app/(main)/api/chat/chat-history/route.ts",
    "chars": 577,
    "preview": "import { createClient } from \"@/utils/supabase/server\";\nimport { getChatsByUser } from \"@/lib/database/chat/queries\";\nim"
  },
  {
    "path": "app/(main)/api/chat/route.ts",
    "chars": 19285,
    "preview": "import { openai } from \"@ai-sdk/openai\";\nimport { azure } from \"@ai-sdk/azure\"; // You can also use Azure's hosted GPT m"
  },
  {
    "path": "app/(main)/api/gee/request-geospatial-analysis/route.ts",
    "chars": 4539,
    "preview": "import { createClient } from \"@/utils/supabase/server\";\nimport { NextResponse } from \"next/server\";\nimport { NextRequest"
  },
  {
    "path": "app/(main)/api/gee/request-loading-geospatial-data/route.ts",
    "chars": 2867,
    "preview": "import { createClient } from \"@/utils/supabase/server\";\nimport { NextResponse } from \"next/server\";\nimport { NextRequest"
  },
  {
    "path": "app/(main)/api/sendfeedback/route.ts",
    "chars": 2547,
    "preview": "// It's a simple email-based feedback form. The user sends a message, and it gets sent to a recipient email address.\n\nim"
  },
  {
    "path": "app/(main)/api/services/google-maps/basemaps/roadmap/route.ts",
    "chars": 996,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { createClient } from \"@/utils/supabase/server\";\n\nexport"
  },
  {
    "path": "app/(main)/api/services/google-maps/basemaps/satellite/route.ts",
    "chars": 996,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { createClient } from \"@/utils/supabase/server\";\n\nexport"
  },
  {
    "path": "app/(main)/api/services/google-maps/geocode/route.ts",
    "chars": 712,
    "preview": "import { NextResponse } from \"next/server\";\nimport { Client } from \"@googlemaps/google-maps-services-js\";\n\nconst client "
  },
  {
    "path": "app/(main)/api/services/google-maps/places/route.ts",
    "chars": 407,
    "preview": "import { NextResponse } from \"next/server\";\n\nexport async function GET() {\n  const GOOGLE_MAPS_API_KEY = process.env.GOO"
  },
  {
    "path": "app/(main)/api/user-usage/route.ts",
    "chars": 1122,
    "preview": "import { NextResponse } from \"next/server\";\nimport { createClient } from \"@/utils/supabase/server\";\nimport { getUsageFor"
  },
  {
    "path": "app/(main)/api/web-scraper/route.ts",
    "chars": 2494,
    "preview": "import { NextResponse } from \"next/server\";\nimport * as cheerio from \"cheerio\";\n\nexport async function GET(request: Requ"
  },
  {
    "path": "app/(main)/chat/[id]/layout.tsx",
    "chars": 525,
    "preview": "import React from \"react\";\nimport { createClient } from \"@/utils/supabase/server\";\nimport { redirect } from \"next/naviga"
  },
  {
    "path": "app/(main)/chat/[id]/loading.tsx",
    "chars": 202,
    "preview": "\"use client\";\n\nimport React from \"react\";\n\nexport default function Loading() {\n  return (\n    <div className=\"page-loadi"
  },
  {
    "path": "app/(main)/chat/[id]/page.tsx",
    "chars": 666,
    "preview": "import MainChatPage from \"@/features/chat/components/chat\";\nimport { getChatById, getMessagesByChatId } from \"@/lib/data"
  },
  {
    "path": "app/(main)/chat-history/layout.tsx",
    "chars": 532,
    "preview": "import React from \"react\";\nimport { createClient } from \"@/utils/supabase/server\";\nimport { redirect } from \"next/naviga"
  },
  {
    "path": "app/(main)/chat-history/loading.tsx",
    "chars": 195,
    "preview": "import ChatHistoryTableSkeleton from \"@/features/chat-history/components/chat-history-table-skeleton\";\n\nconst loading = "
  },
  {
    "path": "app/(main)/chat-history/page.tsx",
    "chars": 244,
    "preview": "export const dynamic = \"force-dynamic\";\n\nimport React from \"react\";\nimport ChatHistory from \"@/features/chat-history/com"
  },
  {
    "path": "app/(main)/integrations/layout.tsx",
    "chars": 494,
    "preview": "import { createClient } from \"@/utils/supabase/server\";\nimport { redirect } from \"next/navigation\";\n\nexport default asyn"
  },
  {
    "path": "app/(main)/integrations/loading.tsx",
    "chars": 202,
    "preview": "\"use client\";\n\nimport React from \"react\";\n\nexport default function Loading() {\n  return (\n    <div className=\"page-loadi"
  },
  {
    "path": "app/(main)/integrations/page.tsx",
    "chars": 253,
    "preview": "export const dynamic = \"force-dynamic\";\n\nimport React from \"react\";\nimport IntegrationsPage from \"@/features/integration"
  },
  {
    "path": "app/(main)/knowledge-base/layout.tsx",
    "chars": 494,
    "preview": "import { createClient } from \"@/utils/supabase/server\";\nimport { redirect } from \"next/navigation\";\n\nexport default asyn"
  },
  {
    "path": "app/(main)/knowledge-base/loading.tsx",
    "chars": 202,
    "preview": "\"use client\";\n\nimport React from \"react\";\n\nexport default function Loading() {\n  return (\n    <div className=\"page-loadi"
  },
  {
    "path": "app/(main)/knowledge-base/page.tsx",
    "chars": 396,
    "preview": "export const dynamic = \"force-dynamic\";\n\nimport KnowledgeBase from \"@/features/knowledge-base/components/knolwedge-base\""
  },
  {
    "path": "app/(main)/layout.tsx",
    "chars": 1531,
    "preview": "import localFont from \"next/font/local\";\nimport \"./styles.css\";\nimport \"maplibre-gl/dist/maplibre-gl.css\";\nimport \"@bloc"
  },
  {
    "path": "app/(main)/loading.tsx",
    "chars": 202,
    "preview": "\"use client\";\n\nimport React from \"react\";\n\nexport default function Loading() {\n  return (\n    <div className=\"page-loadi"
  },
  {
    "path": "app/(main)/page.tsx",
    "chars": 381,
    "preview": "export const dynamic = \"force-dynamic\";\n\nimport MainChatPage from \"@/features/chat/components/chat\";\nimport \"@blocknote/"
  },
  {
    "path": "app/(main)/styles.css",
    "chars": 8091,
    "preview": "/* -------------------------------------------\n  Tailwind Base Imports\n------------------------------------------- */\n@t"
  },
  {
    "path": "app/actions/rag-actions.ts",
    "chars": 3994,
    "preview": "\"use server\";\nimport { createClient } from \"@/utils/supabase/server\";\nimport { WebPDFLoader } from \"@langchain/community"
  },
  {
    "path": "components/changelog-modal.tsx",
    "chars": 1464,
    "preview": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  "
  },
  {
    "path": "components/client-hydrator.tsx",
    "chars": 738,
    "preview": "\"use client\";\nimport { useEffect } from \"react\";\nimport { useUserStore } from \"@/stores/use-user-profile-store\";\n\ninterf"
  },
  {
    "path": "components/client-wrapper.tsx",
    "chars": 869,
    "preview": "// components/client-wrapper.tsx\n\"use client\";\n\nimport React from \"react\";\nimport { ThemeProvider } from \"@/components/t"
  },
  {
    "path": "components/document-viewer.tsx",
    "chars": 1794,
    "preview": "\"use client\";\n\nimport React from \"react\";\nimport { useDocumentViewer } from \"@/hooks/docs-hooks/use-document-viewer\";\nim"
  },
  {
    "path": "components/feedback.tsx",
    "chars": 4060,
    "preview": "\"use client\";\n\nimport React, { useState } from \"react\";\nimport { Rnd } from \"react-rnd\";\nimport { Card } from \"@/compone"
  },
  {
    "path": "components/loading-widgets/loading-for-widget.tsx",
    "chars": 1081,
    "preview": "import React from \"react\";\n\ninterface LoadingWidgetForContainerProps {\n  loadingSize: \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n"
  },
  {
    "path": "components/loading-widgets/loading-primary.tsx",
    "chars": 646,
    "preview": "import React from \"react\";\nimport useLoadingStore from \"@/stores/use-loading-store\";\n\nconst LoadingWidget = () => {\n  co"
  },
  {
    "path": "components/main-sidebar/app-setttings.tsx",
    "chars": 3047,
    "preview": "import React from \"react\";\nimport { IconSettings } from \"@tabler/icons-react\";\nimport { useButtonsStore } from \"@/stores"
  },
  {
    "path": "components/main-sidebar/main-sidebar.tsx",
    "chars": 9179,
    "preview": "\"use client\";\nimport React, { useState } from \"react\";\nimport { Tooltip } from \"react-tooltip\";\nimport useSidebarButtonS"
  },
  {
    "path": "components/notices/privacy-policy.tsx",
    "chars": 6771,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  Dial"
  },
  {
    "path": "components/notices/terms-of-services.tsx",
    "chars": 8458,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  Dial"
  },
  {
    "path": "components/services/esri/add-arcgis-layers.tsx",
    "chars": 1276,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { fetchAgolLayersList } from \"@/lib/fetchers/services/esri/fetc"
  },
  {
    "path": "components/theme-provider.tsx",
    "chars": 445,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nconst NextThemesProvider = dynamic(\n  () => import(\"next-themes\").then((e"
  },
  {
    "path": "components/ui/alert-dialog.tsx",
    "chars": 4512,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\";\n\nim"
  },
  {
    "path": "components/ui/alert.tsx",
    "chars": 1584,
    "preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
  },
  {
    "path": "components/ui/badge.tsx",
    "chars": 1344,
    "preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
  },
  {
    "path": "components/ui/button.tsx",
    "chars": 2303,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"cla"
  },
  {
    "path": "components/ui/card.tsx",
    "chars": 1920,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Card = React.forwardRef<\n  HTMLDivElement,\n  R"
  },
  {
    "path": "components/ui/checkbox.tsx",
    "chars": 1070,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Chec"
  },
  {
    "path": "components/ui/confirmation-modal.tsx",
    "chars": 2069,
    "preview": "import { Loader2 } from \"lucide-react\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogC"
  },
  {
    "path": "components/ui/dialog.tsx",
    "chars": 3989,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { X } f"
  },
  {
    "path": "components/ui/dropdown-menu.tsx",
    "chars": 7421,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\ni"
  },
  {
    "path": "components/ui/form.tsx",
    "chars": 4099,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { Slot } fro"
  },
  {
    "path": "components/ui/input-text-confirm.tsx",
    "chars": 2285,
    "preview": "import React, { useState, useEffect, useRef } from \"react\";\nimport { Button } from \"./button\";\nimport { Input } from \"@/"
  },
  {
    "path": "components/ui/input.tsx",
    "chars": 949,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Input = React.forwardRef<HTMLInputElement, Rea"
  },
  {
    "path": "components/ui/label.tsx",
    "chars": 724,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { cva, type "
  },
  {
    "path": "components/ui/popover.tsx",
    "chars": 1257,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\n\nimport { cn"
  },
  {
    "path": "components/ui/scroll-area.tsx",
    "chars": 1692,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\";\n\nimpo"
  },
  {
    "path": "components/ui/select.tsx",
    "chars": 5629,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { Check, C"
  },
  {
    "path": "components/ui/separator.tsx",
    "chars": 798,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\";\n\nimport "
  },
  {
    "path": "components/ui/table.tsx",
    "chars": 2951,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Table = React.forwardRef<\n  HTMLTableElement,\n"
  },
  {
    "path": "components/ui/textarea.tsx",
    "chars": 689,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Textarea = React.forwardRef<\n  HTMLTextAreaEleme"
  },
  {
    "path": "components/ui/theme-mode-toggle.tsx",
    "chars": 1375,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Moon, Sun } from \"lucide-react\";\nimport { useTheme } from \"next-"
  },
  {
    "path": "components/ui/tooltip.tsx",
    "chars": 1173,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\n\nimport { cn"
  },
  {
    "path": "components.json",
    "chars": 441,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n"
  },
  {
    "path": "custom-configs/ai-assistants.ts",
    "chars": 150,
    "preview": "export const AI_ASSISTANTS: AI_ASSISTANTS_TYPE = {\n  default: [\"GPT-4o\"],\n  custom: [],\n  icons: {\n    \"GPT-4o\": \"/vendo"
  },
  {
    "path": "custom-configs/charts-config.ts",
    "chars": 242,
    "preview": "export const chartColors = {\n  timeSeriesChart: [\"#00A8E8\"],\n  barChartNumerical: [\"#00A8E8\"],\n  stackedBarChartStats: ["
  },
  {
    "path": "custom-configs/integrations.ts",
    "chars": 283,
    "preview": "export const INTEGRATION_SERVICES: IntegrationService[] = [\n  {\n    id: \"arcgis\",\n    name: \"Esri Feature Server\",\n    d"
  },
  {
    "path": "custom-configs/project-config.ts",
    "chars": 128,
    "preview": "const projectConfigs = {\n  initialFlyToCoords: { lat: 44.235128, lng: -76.592403 },\n} as const;\n\nexport default projectC"
  },
  {
    "path": "db-schema/schema.sql",
    "chars": 20709,
    "preview": "\n\nSET statement_timeout = 0;\nSET lock_timeout = 0;\nSET idle_in_transaction_session_timeout = 0;\nSET client_encoding = 'U"
  },
  {
    "path": "features/charts/components/charts-display.tsx",
    "chars": 12009,
    "preview": "\"use client\";\n\nimport React, { useEffect, useState } from \"react\";\nimport selectChartType from \"@/features/charts/utils/"
  },
  {
    "path": "features/charts/components/charts.tsx",
    "chars": 66485,
    "preview": "\"use client\";\nimport React, { useRef, useEffect, useMemo } from \"react\";\nimport * as echarts from \"echarts\";\nimport { ch"
  },
  {
    "path": "features/charts/utils/select-chart-type.ts",
    "chars": 2579,
    "preview": "enum ChartTypes {\n  BarChartNumerical = \"barChartNumerical\",\n  DualBarChartNumerical = \"dualBarChartNumerical\",\n  BarCha"
  },
  {
    "path": "features/chat/components/artifacts-sidebar/artifacts-sidebar.tsx",
    "chars": 4472,
    "preview": "\"use client\";\nimport React, { use, useEffect } from \"react\";\nimport { useButtonsStore } from \"@/stores/use-buttons-store"
  },
  {
    "path": "features/chat/components/chat-response-box/capabilities-banner.tsx",
    "chars": 4434,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Alert, AlertTitle, AlertDescription } from \"@/components/ui/aler"
  },
  {
    "path": "features/chat/components/chat-response-box/chat-message/chat-message.tsx",
    "chars": 3126,
    "preview": "\"use client\";\nimport React, { useEffect } from \"react\";\nimport { IconSparkles } from \"@tabler/icons-react\";\nimport React"
  },
  {
    "path": "features/chat/components/chat-response-box/chat-response-box.tsx",
    "chars": 23498,
    "preview": "\"use client\";\n\nimport React, {\n  useEffect,\n  useRef,\n  useState,\n  useCallback,\n  startTransition,\n} from \"react\";\n\nimp"
  },
  {
    "path": "features/chat/components/chat-response-box/in-response-tool-calling-results/display-in-chat-analysis-map/display-in-chat-analysis-map-btn.tsx",
    "chars": 2649,
    "preview": "import React from \"react\";\nimport { IconMap } from \"@tabler/icons-react\";\nimport useMapDisplayStore from \"@/features/map"
  },
  {
    "path": "features/chat/components/chat-response-box/in-response-tool-calling-results/draft-report/draft-report.tsx",
    "chars": 382,
    "preview": "\"use client\";\nimport React from \"react\";\nimport TextEditor from \"@/features/text-editor/components/text-editor\";\n\ninterf"
  },
  {
    "path": "features/chat/components/chat-response-box/in-response-tool-calling-results/draft-report/drafted-report-btn.tsx",
    "chars": 1141,
    "preview": "import React, { useEffect } from \"react\";\nimport useDraftedReportStore from \"@/features/chat/stores/use-drafted-report-s"
  },
  {
    "path": "features/chat/components/chat-response-box/in-response-tool-calling-results/knowledge-base-citation/citation-badge.tsx",
    "chars": 3112,
    "preview": "import React, { useState } from \"react\";\nimport { DocumentViewer } from \"@/components/document-viewer\";\nimport {\n  IconB"
  },
  {
    "path": "features/chat/components/chat-response-box/in-response-tool-calling-results/tool-calling-results.tsx",
    "chars": 3224,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { ChartStatsDisplay } from \"@/features/charts/components/char"
  },
  {
    "path": "features/chat/components/chat.tsx",
    "chars": 272,
    "preview": "import ChatResponseBox from \"./chat-response-box/chat-response-box\";\n\nconst MainChatPage = async ({\n  chatId,\n  initialM"
  },
  {
    "path": "features/chat/components/external-assets/assets-modal.tsx",
    "chars": 8166,
    "preview": "\"use client\";\n\nimport React, { useState } from \"react\";\nimport { useAgolLayersStore } from \"@/features/maps/stores/use-a"
  },
  {
    "path": "features/chat/components/input/chat-input-box.tsx",
    "chars": 6015,
    "preview": "\"use client\";\nimport React, {\n  useRef,\n  memo,\n  useState,\n  useEffect,\n  useMemo,\n  RefObject,\n} from \"react\";\nimport "
  },
  {
    "path": "features/chat/components/input/chat-input-buttons/assistants-list-dropup-in-chat-input.tsx",
    "chars": 3010,
    "preview": "import React, { useState } from \"react\";\nimport { IconChevronDown } from \"@tabler/icons-react\";\nimport { useTheme } from"
  },
  {
    "path": "features/chat/components/input/chat-input-buttons/attachments-list-dropup-in-chat-input.tsx",
    "chars": 1841,
    "preview": "import React from \"react\";\nimport { IconTrash } from \"@tabler/icons-react\";\nimport { useAttachmentStore } from \"@/featur"
  },
  {
    "path": "features/chat/components/input/chat-input-buttons/chat-input-buttons.tsx",
    "chars": 3608,
    "preview": "import { useState } from \"react\";\n\n// Lucide Icons\nimport {\n  IconArrowUp,\n  IconPaperclip,\n  IconPlayerStopFilled,\n} fr"
  },
  {
    "path": "features/chat/components/input/chat-input-buttons/current-session-assets-dropup.tsx",
    "chars": 7336,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { IconServer, IconX } from \"@tabler/icons-react\";\nimport Atta"
  },
  {
    "path": "features/chat/components/input/chat-input-buttons/open-database-in-chat-input-btn.tsx",
    "chars": 851,
    "preview": "import { IconDatabase } from \"@tabler/icons-react\";\nimport React from \"react\";\n\ninterface OpenDatabaseInChatInputBtnProp"
  },
  {
    "path": "features/chat/components/input/chat-input-buttons/select-roi-on-map-btn.tsx",
    "chars": 1348,
    "preview": "import { IconSquareRoundedPlus2 } from \"@tabler/icons-react\";\nimport useROIStore from \"@/features/maps/stores/use-roi-st"
  },
  {
    "path": "features/chat/components/input/chat-input-dropzone.tsx",
    "chars": 835,
    "preview": "interface ChatInputDropzoneProps {\n  isDragActive: boolean;\n}\nconst ChatInputDropzone = ({ isDragActive }: ChatInputDrop"
  },
  {
    "path": "features/chat/components/input/map-tools-dropup.tsx",
    "chars": 2969,
    "preview": "import React, { useState, useEffect, useRef } from \"react\";\nimport { IconBackpack, IconX } from \"@tabler/icons-react\";\ni"
  },
  {
    "path": "features/chat/components/input/slash-menu-for-map-layers.tsx",
    "chars": 2252,
    "preview": "\"use client\";\n\nimport { Layers } from \"lucide-react\";\n\ninterface CommandMenuProps {\n  commands: string[];\n  index: numbe"
  },
  {
    "path": "features/chat/components/text-analysis-suggestions/text-analysis-suggestions.tsx",
    "chars": 2727,
    "preview": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport { Tooltip } from \"react-tooltip\";\n\n// Full list"
  },
  {
    "path": "features/chat/components/thumbnails-analysis-suggestions/thumbnails-analysis-suggestions.tsx",
    "chars": 1964,
    "preview": "\"use client\";\n\nimport React from \"react\";\nimport Image from \"next/image\";\nimport { Tooltip } from \"react-tooltip\";\n\ncons"
  },
  {
    "path": "features/chat/hooks/use-slash-menu-map-layers-list.ts",
    "chars": 756,
    "preview": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport {\n  filterSlashMenuMapLayers,\n  MAX_LIST_LENGTH,\n} from \"@/featur"
  },
  {
    "path": "features/chat/stores/use-attachments-store.ts",
    "chars": 1992,
    "preview": "import { create } from \"zustand\";\nimport { checkLayerName } from \"@/features/maps/utils/general-checks\";\n\n// Define the "
  },
  {
    "path": "features/chat/stores/use-chat-response-sources-store.ts",
    "chars": 1139,
    "preview": "import { create } from \"zustand\";\n\n// Define the page structure for sources\ninterface Page {\n  page: number;\n  score: nu"
  },
  {
    "path": "features/chat/stores/use-drafted-report-store.ts",
    "chars": 1006,
    "preview": "import { create } from \"zustand\";\n\ninterface ReportState {\n  draftedReport: string | null; // Holds the report content, "
  },
  {
    "path": "features/chat/ui/fadeIn-with-delay.tsx",
    "chars": 1100,
    "preview": "import React, { useEffect, useState } from \"react\";\n\ninterface FadeInWithDelayProps {\n  delay: number;\n  opacityDuration"
  },
  {
    "path": "features/chat/utils/drag-and-drop-file-analyzer.ts",
    "chars": 2593,
    "preview": "import proj4 from \"proj4\";\n//@ts-ignore\nimport { reproject } from \"reproject\";\n//@ts-ignore\nimport epsg from \"epsg\";\nimp"
  },
  {
    "path": "features/chat/utils/general-utils.ts",
    "chars": 5459,
    "preview": "import type {\n  CoreAssistantMessage,\n  CoreMessage,\n  CoreToolMessage,\n  Message,\n  ToolInvocation,\n} from \"ai\";\nimport"
  },
  {
    "path": "features/chat/utils/slash-menu-utils.ts",
    "chars": 1188,
    "preview": "\"use client\";\n\nimport { createRegexRenderer } from \"rich-textarea\";\n\nexport const MAX_LIST_LENGTH = 8;\n\n// Keep this the"
  },
  {
    "path": "features/chat/utils/tool-calling-results-validation.ts",
    "chars": 1583,
    "preview": "interface GeospatialAnalysisResult {\n  functionType?: string; // Optional properties to match ToolCallingMessageResults\n"
  },
  {
    "path": "features/chat/utils/use-Textarea-resize.ts",
    "chars": 594,
    "preview": "import { useState, useLayoutEffect, RefObject } from \"react\";\nimport { RichTextareaHandle } from \"rich-textarea\";\n\nexpor"
  },
  {
    "path": "features/chat/utils/use-drag-and-drop-file-import.ts",
    "chars": 1811,
    "preview": "import { useCallback } from \"react\";\nimport { useDropzone } from \"react-dropzone\";\nimport { dragAndDropFileAnalyzer } fr"
  },
  {
    "path": "features/chat/utils/use-slash-command-menu.ts",
    "chars": 2962,
    "preview": "import { useState, useEffect, useRef } from \"react\";\nimport { RichTextareaHandle } from \"rich-textarea\";\nimport { COMMAN"
  },
  {
    "path": "features/chat-history/components/chat-history-row.tsx",
    "chars": 3791,
    "preview": "\"use client\";\n\nimport { FC, useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { TableCell, Ta"
  },
  {
    "path": "features/chat-history/components/chat-history-table-skeleton.tsx",
    "chars": 1565,
    "preview": "import { FC } from \"react\";\nimport {\n  Table,\n  TableBody,\n  TableHead,\n  TableHeader,\n  TableRow,\n  TableCell,\n} from \""
  },
  {
    "path": "features/chat-history/components/chat-history-table.tsx",
    "chars": 3328,
    "preview": "import { FC, useMemo } from \"react\";\nimport ChatHistoryRow from \"./chat-history-row\";\nimport {\n  Table,\n  TableBody,\n  T"
  },
  {
    "path": "features/chat-history/components/chat-history.tsx",
    "chars": 4749,
    "preview": "\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport ChatHistoryTable from \"./chat-history-ta"
  },
  {
    "path": "features/chat-history/components/indeterminate-checkbox.tsx",
    "chars": 745,
    "preview": "import React, { useEffect, useRef } from \"react\";\n\ninterface IndeterminateCheckboxProps {\n  checked: boolean;\n  indeterm"
  },
  {
    "path": "features/integrations/components/integration-actions.tsx",
    "chars": 640,
    "preview": "import React from \"react\";\nimport { Button } from \"@/components/ui/button\";\n\ninterface IntegrationActionsProps {\n  servi"
  },
  {
    "path": "features/integrations/components/integration-header.tsx",
    "chars": 892,
    "preview": "import { Separator } from \"@/components/ui/separator\";\nimport React from \"react\";\n\ninterface IntegrationHeaderProps {\n  "
  },
  {
    "path": "features/integrations/components/integration-item.tsx",
    "chars": 1346,
    "preview": "import React from \"react\";\nimport { IntegrationStatus } from \"./integration-status\";\nimport { IntegrationActions } from "
  },
  {
    "path": "features/integrations/components/integration-list.tsx",
    "chars": 757,
    "preview": "import React from \"react\";\nimport { IntegrationItem } from \"./integration-item\";\n\ninterface IntegrationListProps {\n  ser"
  },
  {
    "path": "features/integrations/components/integration-status.tsx",
    "chars": 447,
    "preview": "import React from \"react\";\nimport { Button } from \"@/components/ui/button\";\n\ninterface IntegrationStatusProps {\n  isConn"
  },
  {
    "path": "features/integrations/components/integrations-page.tsx",
    "chars": 2176,
    "preview": "\"use client\";\n\nimport React, { use, useEffect } from \"react\";\n\nimport { IntegrationHeader } from \"./integration-header\";"
  },
  {
    "path": "features/knowledge-base/actions/document-actions.ts",
    "chars": 5926,
    "preview": "\"use server\";\nimport {\n  handlePdfFile,\n  handleDocxFile,\n  handleTextFile,\n} from \"@/utils/general/document-utils\";\nimp"
  },
  {
    "path": "features/knowledge-base/components/add-group-modal.tsx",
    "chars": 1350,
    "preview": "import React from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogFooter,\n} from \"@/"
  },
  {
    "path": "features/knowledge-base/components/documents-table.tsx",
    "chars": 4621,
    "preview": "import React from \"react\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@"
  },
  {
    "path": "features/knowledge-base/components/edit-document-modal.tsx",
    "chars": 2091,
    "preview": "import React from \"react\";\nimport { X } from \"lucide-react\";\n\ninterface EditDocumentModalProps {\n  editDocumentId: any;\n"
  },
  {
    "path": "features/knowledge-base/components/knolwedge-base.tsx",
    "chars": 11900,
    "preview": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport { useButtonsStore } from \"@/stores/use-buttons-"
  },
  {
    "path": "features/knowledge-base/components/knowledge-base-sidebar.tsx",
    "chars": 3933,
    "preview": "import React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  IconDotsVertical,\n  IconFolder,\n "
  },
  {
    "path": "features/knowledge-base/components/max-docs-alert-dialog.tsx",
    "chars": 1153,
    "preview": "\"use client\";\n\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialo"
  },
  {
    "path": "features/knowledge-base/lib/generate-embeddings.ts",
    "chars": 559,
    "preview": "import { OpenAIEmbeddings } from \"@langchain/openai\";\nimport { RecursiveCharacterTextSplitter } from \"langchain/text_spl"
  },
  {
    "path": "features/knowledge-base/utils/transform-metadata-to-citation.ts",
    "chars": 1424,
    "preview": "// transformMetadataToCitations.ts\n\ninterface RawMatch {\n  pageContent: string;\n  metadata: {\n    loc: {\n      pageNumbe"
  },
  {
    "path": "features/maps/components/address-search.tsx",
    "chars": 5995,
    "preview": "\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { Button } from \"@/components/ui/butto"
  },
  {
    "path": "features/maps/components/attribute-table/attribute-table-controls.tsx",
    "chars": 4990,
    "preview": "\"use client\";\n\nimport React, { useState, useEffect } from \"react\";\nimport { IconFocusCentered, IconTrash } from \"@tabler"
  },
  {
    "path": "features/maps/components/attribute-table/attribute-table.tsx",
    "chars": 13605,
    "preview": "import React, { useState, useEffect, useMemo, memo, useRef } from \"react\";\nimport { Rnd } from \"react-rnd\";\nimport {\n  u"
  },
  {
    "path": "features/maps/components/map-badge.tsx",
    "chars": 1342,
    "preview": "import { Separator } from \"@/components/ui/separator\";\nimport React from \"react\";\n\ninterface MapBadgeProps {\n  type?: \"d"
  },
  {
    "path": "features/maps/components/map-container.tsx",
    "chars": 3697,
    "preview": "\"use client\";\n\nimport React, { memo } from \"react\";\nimport { Maximize, Minimize } from \"lucide-react\";\nimport useMapDisp"
  },
  {
    "path": "features/maps/components/map-custom-controls/map-custom-controls.tsx",
    "chars": 3845,
    "preview": "import React from \"react\";\nimport {\n  MousePointerClick,\n  SquareMousePointer,\n  Layers,\n  BarChart3,\n  Menu,\n  Table2 a"
  },
  {
    "path": "features/maps/components/map-custom-controls/map-roi-controls.tsx",
    "chars": 3126,
    "preview": "import React, { useState } from \"react\";\nimport { useButtonsStore } from \"@/stores/use-buttons-store\";\nimport useROIStor"
  },
  {
    "path": "features/maps/components/map-panels/map-chart-panel/map-chart-panel.tsx",
    "chars": 1565,
    "preview": "import React, { useEffect } from \"react\";\nimport { ArrowRight } from \"lucide-react\";\nimport {\n  ChartQueryDisplay,\n  Cha"
  },
  {
    "path": "features/maps/components/map-panels/map-layers-panel/color-picker-popover.tsx",
    "chars": 1624,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { HexColorPicker } from \"react-colorful\";\nimport {\n  Popover,\n  Po"
  },
  {
    "path": "features/maps/components/map-panels/map-layers-panel/map-layers-panel.tsx",
    "chars": 17181,
    "preview": "\"use client\";\n\nimport React, {\n  useState,\n  useCallback,\n  useEffect,\n  ReactNode,\n  CSSProperties,\n} from \"react\";\nimp"
  },
  {
    "path": "features/maps/components/map-panels/map-layers-panel/map-legend.tsx",
    "chars": 9166,
    "preview": "import React from \"react\";\nimport chroma from \"chroma-js\";\nimport useMapLegendStore from \"../../../stores/use-map-legend"
  },
  {
    "path": "features/maps/hooks/use-handle-click/use-handle-click.ts",
    "chars": 1188,
    "preview": "\"use client\";\nimport { useState } from \"react\";\nimport useMapLayersStore from \"../../stores/use-map-layer-store\";\nimport"
  },
  {
    "path": "features/maps/hooks/use-handle-click/use-map-controls.ts",
    "chars": 2205,
    "preview": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport maplibregl, { Map } from \"maplibre-gl\";\nimport MapboxDr"
  },
  {
    "path": "features/maps/hooks/use-handle-click/use-query-drawing.ts",
    "chars": 10700,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Map } from \"maplibre-gl\";\nimport maplibregl from \"maplibre-gl"
  },
  {
    "path": "features/maps/hooks/use-handle-click/use-remove-query-features.ts",
    "chars": 1243,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Map } from \"maplibre-gl\";\nimport useTableStore from \"../../st"
  },
  {
    "path": "features/maps/hooks/use-handle-click/use-roi-drawing.ts",
    "chars": 6008,
    "preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Map } from \"maplibre-gl\";\nimport MapboxDraw from \"@"
  },
  {
    "path": "features/maps/hooks/use-map/use-add-arcgis-layers.ts",
    "chars": 1395,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Map } from \"maplibre-gl\";\nimport { useAgolLayersStore } from "
  },
  {
    "path": "features/maps/hooks/use-map/use-add-attached-layers.ts",
    "chars": 1228,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Map } from \"maplibre-gl\";\nimport useMapLayersStore from \"../."
  },
  {
    "path": "features/maps/hooks/use-map/use-add-gee-layers.ts",
    "chars": 1105,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Map } from \"maplibre-gl\";\nimport { useGeeOutputStore } from \""
  },
  {
    "path": "features/maps/hooks/use-map/use-add-roi-from-session.ts",
    "chars": 1013,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Map } from \"maplibre-gl\";\nimport useMapLayersStore from \"../."
  },
  {
    "path": "features/maps/hooks/use-map/use-basemap-toggle.ts",
    "chars": 2251,
    "preview": "\"use client\";\n\nimport { useEffect, RefObject } from \"react\";\nimport { Map, AttributionControl } from \"maplibre-gl\";\nimpo"
  },
  {
    "path": "features/maps/hooks/use-map/use-map-initialization.ts",
    "chars": 2029,
    "preview": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { Map, ScaleControl, AttributionControl } from \"maplibr"
  },
  {
    "path": "features/maps/hooks/use-map/use-map.ts",
    "chars": 1967,
    "preview": "\"use client\";\n\nimport { useState, useRef } from \"react\";\nimport { Map, AttributionControl } from \"maplibre-gl\";\nimport u"
  },
  {
    "path": "features/maps/hooks/use-map/use-update-layer-style.ts",
    "chars": 1718,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Map } from \"maplibre-gl\";\nimport useMapLayersStore from \"../."
  },
  {
    "path": "features/maps/hooks/use-map/use-zoom-to-geometry.ts",
    "chars": 3279,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Map } from \"maplibre-gl\";\nimport useMapLayersStore from \"../."
  },
  {
    "path": "features/maps/hooks/use-map-cursor.ts",
    "chars": 1610,
    "preview": "import { useEffect } from \"react\";\nimport useCursorStore from \"@/features/maps/stores/use-cursor-store\";\nimport useMapLa"
  },
  {
    "path": "features/maps/stores/map-queries-stores/useQueryOutputReadyFromVectorLayerStore.ts",
    "chars": 529,
    "preview": "import { create } from \"zustand\";\n\ninterface QueryState {\n  queryOutputReadyFromVectorLayer: boolean;\n  setQueryOutputRe"
  },
  {
    "path": "features/maps/stores/map-queries-stores/useQueryRasterFromVectorLayerStore.ts",
    "chars": 478,
    "preview": "import { create } from \"zustand\";\n\ninterface QueryRasterFromVectorLayerState {\n  isQueryActive: boolean;\n  setIsQueryAct"
  },
  {
    "path": "features/maps/stores/map-queries-stores/useQueryReadyStore.ts",
    "chars": 357,
    "preview": "import { create } from \"zustand\";\n\ninterface QueryState {\n  queryReady: boolean;\n  setQueryReady: (value: boolean) => vo"
  },
  {
    "path": "features/maps/stores/plots-stores/useChartRequestedTypeStore.ts",
    "chars": 3395,
    "preview": "import { create } from \"zustand\";\n\nenum ChartTypes {\n  BarChart,\n  LineChart,\n  BubbleChart,\n  ScatterPlot,\n  BoxPlot,\n "
  },
  {
    "path": "features/maps/stores/plots-stores/usePlotReadyDataStore.ts",
    "chars": 605,
    "preview": "import { create } from \"zustand\";\n\ninterface PlotReadyState {\n  plotReadyData: {\n    data: any | null;\n    chartType: st"
  },
  {
    "path": "features/maps/stores/plots-stores/usePlotReadyFromVectorLayerStore.ts",
    "chars": 2201,
    "preview": "import { create } from \"zustand\";\n\ninterface PlotReadyState {\n  plotReadyDataFromVectorLayer: {\n    data: any | null;\n  "
  },
  {
    "path": "features/maps/stores/use-agol-layers-store.ts",
    "chars": 3790,
    "preview": "import { create } from \"zustand\";\nimport {\n  isValidUrl,\n  sanitizeUrl,\n  isValidLayerName,\n  sanitizeLayerName,\n} from "
  },
  {
    "path": "features/maps/stores/use-color-picker-store.ts",
    "chars": 742,
    "preview": "import { create } from \"zustand\";\n\ninterface ColorStore {\n  pickedColor: { color: string; layerName: string | null };\n  "
  },
  {
    "path": "features/maps/stores/use-cursor-store.ts",
    "chars": 376,
    "preview": "import { create } from \"zustand\";\n\ninterface CursorState {\n  cursorState: string;\n  setCursorState: (cursor: string) => "
  },
  {
    "path": "features/maps/stores/use-drawn-feature-on-map-store.ts",
    "chars": 3131,
    "preview": "import { create } from \"zustand\";\n\ninterface FeatureStore {\n  drawnFeaturesOnMap: Feature[];\n  selectedDrawnFeature: Fea"
  },
  {
    "path": "features/maps/stores/use-function-store.ts",
    "chars": 2369,
    "preview": "import { create } from \"zustand\";\n\ntype FunctionConfig = {\n  functionType: string | null;\n  startDate: Date | null;\n  en"
  },
  {
    "path": "features/maps/stores/use-gee-ouput-store.ts",
    "chars": 2607,
    "preview": "import { create } from \"zustand\";\n\ninterface GeeOutputItem {\n  layerName: string;\n  urlFormat?: string;\n  mapStats?: any"
  },
  {
    "path": "features/maps/stores/use-geojson-store.ts",
    "chars": 1182,
    "preview": "import { create } from \"zustand\";\n\ninterface GeojsonItem {\n  name: string;\n  data: any;\n}\n\ninterface GeojsonState {\n  ge"
  },
  {
    "path": "features/maps/stores/use-layer-selection-store.ts",
    "chars": 498,
    "preview": "import { create } from \"zustand\";\n\ninterface LayerState {\n  selectedRasterLayer: {\n    layerName: string;\n  };\n\n  setSel"
  },
  {
    "path": "features/maps/stores/use-map-badge-store.ts",
    "chars": 686,
    "preview": "import { create } from \"zustand\";\n\ninterface BadgeState {\n  text: string;\n  secondaryText: string;\n  setText: (text: str"
  },
  {
    "path": "features/maps/stores/use-map-display-store.ts",
    "chars": 2606,
    "preview": "import { create } from \"zustand\";\n\ninterface MapDisplayState {\n  isMapReady: boolean;\n  mapMaximizeRequested: boolean;\n "
  },
  {
    "path": "features/maps/stores/use-map-layer-store.ts",
    "chars": 8840,
    "preview": "import { create } from \"zustand\";\nimport { useGeeOutputStore } from \"./use-gee-ouput-store\";\nimport useROIStore from \"./"
  },
  {
    "path": "features/maps/stores/use-map-legend-store.ts",
    "chars": 1447,
    "preview": "import { create } from \"zustand\";\n\n// Define the type for the configuration object\ntype LegendConfig = {\n  min?: number;"
  },
  {
    "path": "features/maps/stores/use-map-zoom-request-store.ts",
    "chars": 2077,
    "preview": "import { create } from \"zustand\";\nimport useROIStore from \"./use-roi-store\";\nimport useMapLayersStore from \"./use-map-la"
  },
  {
    "path": "features/maps/stores/use-roi-store.ts",
    "chars": 4500,
    "preview": "import { create } from \"zustand\";\nimport { checkLayerName } from \"../utils/general-checks\";\n\ninterface ROIStoreState {\n "
  },
  {
    "path": "features/maps/stores/use-table-store.ts",
    "chars": 1771,
    "preview": "import { create } from \"zustand\";\n\ninterface TableState {\n  vectorLayersInTable: string[];\n  selectedVectorLayerInTable:"
  },
  {
    "path": "features/maps/utils/add-drawn-layer-to-map.ts",
    "chars": 3909,
    "preview": "import { Map, Popup } from \"maplibre-gl\";\nimport useColorPickerStore from \"@/features/maps/stores/use-color-picker-store"
  },
  {
    "path": "features/maps/utils/add-gee-layer-to-map.ts",
    "chars": 765,
    "preview": "import { Map } from \"maplibre-gl\";\n\nexport const addGeeLayerToMap = async (\n  map: Map,\n  urlFormat: string,\n  layerName"
  },
  {
    "path": "features/maps/utils/add-geocoded-point-to-map.ts",
    "chars": 2315,
    "preview": "import { Map } from \"maplibre-gl\";\n\nexport const addGeocodedPointToMap = async (\n  map: Map,\n  geojsonData: any,\n  layer"
  },
  {
    "path": "features/maps/utils/add-layers-to-map/addGeojsonLayer.ts",
    "chars": 1918,
    "preview": "import { Map } from \"maplibre-gl\";\nimport useColorPickerStore from \"@/features/maps/stores/use-color-picker-store\";\n\ncon"
  },
  {
    "path": "features/maps/utils/add-roi-layer-to-map.ts",
    "chars": 2917,
    "preview": "import { Map, Popup } from \"maplibre-gl\";\nimport useColorPickerStore from \"@/features/maps/stores/use-color-picker-store"
  },
  {
    "path": "features/maps/utils/authentication-utils/gee-auth.ts",
    "chars": 1106,
    "preview": "\"use server\";\nimport ee from \"@google/earthengine\";\nimport { GoogleAuth } from \"google-auth-library\";\nimport { createCli"
  },
  {
    "path": "features/maps/utils/gee-eval-utils.ts",
    "chars": 448,
    "preview": "export function getMapId(image: any, vis: any) {\n  return new Promise((resolve, reject) => {\n    image.getMapId(vis, (ob"
  },
  {
    "path": "features/maps/utils/general-checks.ts",
    "chars": 287,
    "preview": "export const checkLayerName = (layerName: string, existingNames: string[]) => {\n  let uniqueLayerName = layerName;\n  let"
  }
]

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

About this extraction

This page contains the full source code of the GeoRetina/chat2geo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 255 files (752.7 KB), approximately 194.3k tokens, and a symbol index with 389 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!