Full Code of caioricciuti/duck-ui for AI

main 6cbc5df4ac5d cached
173 files
925.3 KB
226.1k tokens
409 symbols
1 requests
Download .txt
Showing preview only (982K chars total). Download the full file or copy to clipboard to get everything.
Repository: caioricciuti/duck-ui
Branch: main
Commit: 6cbc5df4ac5d
Files: 173
Total size: 925.3 KB

Directory structure:
gitextract_fb4zvf6o/

├── .dockerignore
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   └── feature.yml
│   └── workflows/
│       ├── 2-docker-build.yml
│       └── ci.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── .prettierrc
├── Dockerfile
├── LICENSE.md
├── README.md
├── components.json
├── docker-compose.yml
├── eslint.config.js
├── index.html
├── inject-env.js
├── package.json
├── public/
│   └── databases/
│       ├── README.md
│       └── manifest.json
├── serve.json
├── src/
│   ├── components/
│   │   ├── charts/
│   │   │   ├── ChartVisualizationPro.tsx
│   │   │   ├── UPlotChart.tsx
│   │   │   └── tooltipPlugin.ts
│   │   ├── cloud/
│   │   │   ├── CloudBrowser.tsx
│   │   │   └── CloudConnectionModal.tsx
│   │   ├── command-palette/
│   │   │   └── CommandPalette.tsx
│   │   ├── common/
│   │   │   ├── FloatingActionButton.tsx
│   │   │   └── ImportOptionsPopover.tsx
│   │   ├── connection/
│   │   │   └── ConnectionsModal.tsx
│   │   ├── duck-brain/
│   │   │   ├── DuckBrainCodeBlock.tsx
│   │   │   ├── DuckBrainInput.tsx
│   │   │   ├── DuckBrainMessages.tsx
│   │   │   ├── DuckBrainPanel.tsx
│   │   │   ├── MarkdownContent.tsx
│   │   │   ├── ResultsArtifact.tsx
│   │   │   └── SchemaAutocomplete.tsx
│   │   ├── editor/
│   │   │   ├── SqlEditor.tsx
│   │   │   └── monacoConfig.ts
│   │   ├── explorer/
│   │   │   ├── ColumnNode.tsx
│   │   │   ├── DataExplorer.tsx
│   │   │   ├── FileImporter.tsx
│   │   │   └── TreeNode.tsx
│   │   ├── folders/
│   │   │   └── FolderBrowser.tsx
│   │   ├── layout/
│   │   │   ├── ConnectionSwitcher.tsx
│   │   │   ├── MobileNavDrawer.tsx
│   │   │   └── Sidebar.tsx
│   │   ├── notebook/
│   │   │   ├── MarkdownRenderer.tsx
│   │   │   ├── NotebookCell.tsx
│   │   │   ├── NotebookTab.tsx
│   │   │   └── NotebookToolbar.tsx
│   │   ├── profile/
│   │   │   ├── PasswordDialog.tsx
│   │   │   ├── ProfileAvatar.tsx
│   │   │   ├── ProfileEditor.tsx
│   │   │   └── ProfilePicker.tsx
│   │   ├── saved-queries/
│   │   │   ├── SaveQueryDialog.tsx
│   │   │   └── SavedQueriesPanel.tsx
│   │   ├── table/
│   │   │   ├── CellValueViewer.tsx
│   │   │   ├── ColumnStatsPanel.tsx
│   │   │   └── DuckUItable.tsx
│   │   ├── theme/
│   │   │   ├── mode-toggle.tsx
│   │   │   └── theme-provider.tsx
│   │   ├── ui/
│   │   │   ├── accordion.tsx
│   │   │   ├── alert-dialog.tsx
│   │   │   ├── alert.tsx
│   │   │   ├── aspect-ratio.tsx
│   │   │   ├── badge.tsx
│   │   │   ├── breadcrumb.tsx
│   │   │   ├── button.tsx
│   │   │   ├── card.tsx
│   │   │   ├── checkbox.tsx
│   │   │   ├── collapsible.tsx
│   │   │   ├── command.tsx
│   │   │   ├── context-menu.tsx
│   │   │   ├── dialog.tsx
│   │   │   ├── drawer.tsx
│   │   │   ├── dropdown-menu.tsx
│   │   │   ├── form.tsx
│   │   │   ├── hover-card.tsx
│   │   │   ├── input.tsx
│   │   │   ├── label.tsx
│   │   │   ├── menubar.tsx
│   │   │   ├── multi-select.tsx
│   │   │   ├── navigation-menu.tsx
│   │   │   ├── pagination.tsx
│   │   │   ├── popover.tsx
│   │   │   ├── progress.tsx
│   │   │   ├── radio-group.tsx
│   │   │   ├── resizable.tsx
│   │   │   ├── scroll-area.tsx
│   │   │   ├── select.tsx
│   │   │   ├── separator.tsx
│   │   │   ├── sheet.tsx
│   │   │   ├── skeleton.tsx
│   │   │   ├── sonner.tsx
│   │   │   ├── switch.tsx
│   │   │   ├── table.tsx
│   │   │   ├── tabs.tsx
│   │   │   ├── textarea.tsx
│   │   │   └── tooltip.tsx
│   │   └── workspace/
│   │       ├── BrainTab.tsx
│   │       ├── ConnectionsTab.tsx
│   │       ├── ExplainPlanViewer.tsx
│   │       ├── HomeTab.tsx
│   │       ├── QueryHistory.tsx
│   │       ├── SettingsTab.tsx
│   │       ├── SortableTab.tsx
│   │       ├── SqlTab.tsx
│   │       └── WorkspaceTabs.tsx
│   ├── hooks/
│   │   └── useQueryFromURL.ts
│   ├── index.css
│   ├── lib/
│   │   ├── chartDataTransform.ts
│   │   ├── chartExport.ts
│   │   ├── chartUtils.ts
│   │   ├── cloudStorage/
│   │   │   ├── index.ts
│   │   │   └── testHttpfs.ts
│   │   ├── duckBrain/
│   │   │   ├── index.ts
│   │   │   ├── models.config.ts
│   │   │   ├── prompts/
│   │   │   │   └── text-to-sql.ts
│   │   │   ├── providers/
│   │   │   │   ├── anthropic.provider.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── openai.provider.ts
│   │   │   │   └── types.ts
│   │   │   ├── schemaFormatter.ts
│   │   │   ├── sqlParser.ts
│   │   │   ├── webllm.service.ts
│   │   │   └── webllm.worker.ts
│   │   ├── fileSystem/
│   │   │   └── index.ts
│   │   ├── sqlSanitize.ts
│   │   └── utils.ts
│   ├── main.tsx
│   ├── pages/
│   │   └── Home.tsx
│   ├── services/
│   │   ├── duckdb/
│   │   │   ├── __tests__/
│   │   │   │   ├── resultParser.test.ts
│   │   │   │   └── utils.test.ts
│   │   │   ├── externalConnection.ts
│   │   │   ├── index.ts
│   │   │   ├── opfsConnection.ts
│   │   │   ├── resultParser.ts
│   │   │   ├── schemaFetcher.ts
│   │   │   ├── utils.ts
│   │   │   └── wasmConnection.ts
│   │   └── persistence/
│   │       ├── __tests__/
│   │       │   ├── crypto.test.ts
│   │       │   └── migrations.test.ts
│   │       ├── crypto.ts
│   │       ├── fallback.ts
│   │       ├── index.ts
│   │       ├── migrations.ts
│   │       ├── repositories/
│   │       │   ├── aiConfigRepository.ts
│   │       │   ├── connectionRepository.ts
│   │       │   ├── index.ts
│   │       │   ├── profileRepository.ts
│   │       │   ├── queryHistoryRepository.ts
│   │       │   ├── savedQueryRepository.ts
│   │       │   ├── settingsRepository.ts
│   │       │   └── workspaceRepository.ts
│   │       └── systemDb.ts
│   ├── store/
│   │   ├── index.ts
│   │   ├── slices/
│   │   │   ├── connectionSlice.ts
│   │   │   ├── duckBrainSlice.ts
│   │   │   ├── duckdbSlice.ts
│   │   │   ├── fileSystemSlice.ts
│   │   │   ├── profileSlice.ts
│   │   │   ├── querySlice.ts
│   │   │   ├── schemaSlice.ts
│   │   │   └── tabSlice.ts
│   │   └── types.ts
│   ├── types/
│   │   └── filesystem.d.ts
│   └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vitest.config.ts

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

================================================
FILE: .dockerignore
================================================
# Ignore node_modules (local dependencies)
node_modules

# Ignore build artifacts
dist

# Ignore configuration files that shouldn't be shared
.env
.vscode
.idea

# Ignore miscellaneous system files and logs
.DS_Store
npm-debug.log
yarn-debug.log
yarn-error.log
package-lock.json

# Ignore Git-specific files
.git
.gitignore

/docs

================================================
FILE: .github/ISSUE_TEMPLATE/bug.yml
================================================
name: 🐞 Bug Report
description: Found something that doesn't work as expected?
body:
  - type: dropdown
    id: Environment
    attributes:
      label: Environment
      description: How are you using Duck-UI?
      options:
        - NPM build
        - Docker
        - Other
    validations:
      required: true
  - type: textarea
    id: repro
    attributes:
      label: How did you encounter the bug?
      description: How can this bug be reproduced? Please provide steps to reproduce.
      placeholder: |-
        1. Run Docker container... 
        2. npm run build...
        3. Go to...
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: What did you expect?
      description: What it supposed to happen? What did you expect to see?
    validations:
      required: true
  - type: textarea
    id: actual
    attributes:
      label: Actual Result
      description: What was the accual result?
    validations:
      required: true
  - type: dropdown
    id: Browser
    attributes:
      label: Browser
      description: What browser are you using?
      options:
        - Chrome
        - Firefox
        - Edge
        - Safari
        - Brave
        - Other
    validations:
      required: true
  # browser version with instructions to check it 
  - type: textarea
    id: browser-version
    attributes:
      label: Browser Version
      description: What version of the browser are you using?
      placeholder: e.g. 125.0.0
    validations:
      required: true
  - type: textarea
    id: version
    attributes:
      label: Version
      description: What version of Duck-UI are you using?
      placeholder: e.g. 1.5.0
    validations:
      required: true
  - type: markdown
    attributes:
      value: |-
        ### All done, now, just submit the issue and I will do my best to take care of it!  🙏
    validations:
      required: false


================================================
FILE: .github/ISSUE_TEMPLATE/feature.yml
================================================
name: 💡 Feature Request
description: Tell us about something Duck-UI doesn't do yet, but should!
body:
  - type: textarea
    id: idea
    attributes:
      label: Idea Statement
      description: Which is the feature you would like to see implemented?
      placeholder: |-
        I want to be able to do anything I want, whenever I want. Because my ideas are the best.
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: Feature implementation brainstorm
      description: All your ideas are welcome, let's brainstorm together.
      placeholder: |-
        Create the next big feature that will all our problems.
    validations:
      required: false
  - type: markdown
    attributes:
      value: |-
        ## Thanks 🙏
    validations:
      required: false


================================================
FILE: .github/workflows/2-docker-build.yml
================================================
# .github/workflows/2-docker-build.yml
name: Build and Push Docker Image

on:
  push:
    branches:
      - main
    paths-ignore:
      - '**.md'
      - 'docs/**'
  pull_request:
    branches:
      - main
    paths-ignore:
      - '**.md'
      - 'docs/**'

jobs:
  build:
    runs-on: ubuntu-latest
    if: |
      (!contains(github.event.head_commit.message, 'docker-false') && !contains(github.event.head_commit.message, 'docs-only')) ||
      contains(github.event.head_commit.message, 'docker-only')
    permissions:
      contents: read
      packages: write
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GHCR_PAT }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
            type=sha,format=long
            type=ref,event=pr

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches: [main]
    paths-ignore:
      - "**.md"
      - "docs/**"
  pull_request:
    branches: [main]
    paths-ignore:
      - "**.md"
      - "docs/**"

jobs:
  lint:
    name: Lint & Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - run: bun install --frozen-lockfile
      - run: bun run lint
      - run: bun run format:check

  typecheck:
    name: Type Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - run: bun install --frozen-lockfile
      - run: bun run typecheck

  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - run: bun install --frozen-lockfile
      - run: bun run test

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: [lint, typecheck, test]
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - run: bun install --frozen-lockfile
      - run: bun run build
      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 7


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

package-lock.json
yarn.lock
pnpm-lock.yaml

# Editor directories and files
.vscode/*
/vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.env

post.md
CLAUDE.md

================================================
FILE: .husky/pre-commit
================================================
bunx lint-staged


================================================
FILE: .prettierignore
================================================
dist
node_modules
bun.lockb
*.wasm
public
docs/.vitepress/cache
docs/.vitepress/dist


================================================
FILE: .prettierrc
================================================
{
  "semi": true,
  "singleQuote": false,
  "trailingComma": "es5",
  "tabWidth": 2,
  "printWidth": 100,
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "lf"
}


================================================
FILE: Dockerfile
================================================
# Use an official Node runtime as a parent image with bun
FROM oven/bun:1-alpine AS build

ARG DUCK_UI_BASEPATH="/"

# Set the working directory
WORKDIR /app

# Copy package.json and bun.lockb (if exists)
COPY package.json bun.lockb* ./

# Install dependencies
RUN bun install --frozen-lockfile

# Bundle app source inside Docker image
COPY . .

# Build the app
RUN bun run build

# Use a second stage to reduce image size
FROM oven/bun:1-alpine

# Set the working directory for the second stage
WORKDIR /app

# Copy the build directory from the first stage to the second stage
COPY --from=build /app/dist /app

# Copy the injection script and serve config (COOP/COEP headers for OPFS)
COPY inject-env.js /app/
COPY serve.json /app/

# Install just serve for serving the built app
RUN bun add serve

# Expose port 5522
EXPOSE 5522

# Define environment variables
ENV DUCK_UI_EXTERNAL_CONNECTION_NAME=""
ENV DUCK_UI_EXTERNAL_HOST=""
ENV DUCK_UI_EXTERNAL_PORT=""
ENV DUCK_UI_EXTERNAL_USER=""
ENV DUCK_UI_EXTERNAL_PASS=""
ENV DUCK_UI_EXTERNAL_DATABASE_NAME=""

# Create user and change ownership
RUN addgroup -S duck-group -g 1001 && adduser -S duck-user -u 1001 -G duck-group
RUN chown -R duck-user:duck-group /app

USER duck-user

# Run the injection script then serve using bunx
CMD bun inject-env.js && bunx serve -s -l 5522 -c serve.json

================================================
FILE: LICENSE.md
================================================
# License

## DUCK UI - Apache License 2.0

Copyright 2025 Caio Ricciuti 

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

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

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

## What does this mean?

The Apache License 2.0 is a permissive open source license that provides users with extensive freedom to use, modify, and distribute the software, while also offering robust legal protection through its patent grant and clear contribution terms.

### You are free to:

- ✅ Use the software commercially
- ✅ Modify the software
- ✅ Distribute the software
- ✅ Use the software for private use
- ✅ Sublicense the software
- ✅ Use patents claims of contributors to the code

### Under the following conditions:

- ℹ️ Include the original copyright notice
- ℹ️ Include a copy of the license
- ℹ️ State significant changes made to the software
- ℹ️ Include the NOTICE file (if present) with attribution notes

### With the understanding that:

- ⚠️ The software is provided "as is", without warranty of any kind
- ⚠️ The authors cannot be held liable for damages
- ⚠️ Trademark use is not granted except as required for describing the origin of the work

## Key Benefits of Apache 2.0

### Patent Protection
Unlike simpler licenses like MIT, Apache 2.0 includes an express patent grant from contributors to users. This means that if a contributor has patents that cover their contribution, they automatically grant you a license to use those patents.

### Clear Contribution Terms
The license explicitly states that any contributions are assumed to be under the same Apache 2.0 license unless otherwise specified, providing clarity for collaborative development.

### Compatibility
Apache 2.0 is compatible with many other open source licenses and is widely accepted in both open source and commercial contexts.

## Third-Party Licenses

CH-UI is built on top of several open-source projects. We'd like to acknowledge and give credit to these projects:

- [ClickHouse](https://github.com/ClickHouse/ClickHouse) - Apache 2.0 License
- [React](https://github.com/facebook/react) - MIT License
- [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) - MIT License
- [Zustand](https://github.com/pmndrs/zustand) - MIT License
- [Monaco Editor](https://github.com/microsoft/monaco-editor) - MIT License
- [Lucide Icons](https://github.com/lucide-icons/lucide) - ISC License

For the full text of these licenses, please visit the respective project repositories.

================================================
FILE: README.md
================================================
# <img src="./public/logo.png" alt="Duck-UI Logo" title="Duck-UI Logo" width="50"> Duck-UI

Duck-UI is a web-based interface for interacting with DuckDB, a high-performance analytical database system. This project leverages DuckDB's WebAssembly (WASM) capabilities to provide a seamless and efficient user experience directly in the browser.

# [Official Docs](https://duckui.com?utm_source=github&utm_medium=readme) 🚀
#  [Demo](https://demo.duckui.com?utm_source=github&utm_medium=readme) 💻


## Features

- **SQL Editor**: Write and execute SQL queries with syntax highlighting and auto-completion.
- **Data Import**: Import data from CSV, JSON, Parquet, and Arrow files.
- **Data Explorer**: Browse and manage databases and tables.
- **Query History**: View and manage your recent SQL queries.

## Getting Started


### Docker (Recommended)

```bash
docker run -p 5522:5522 ghcr.io/caioricciuti/duck-ui:latest
```

Open your browser and navigate to `http://localhost:5522`.

### Environment Variables

You can customize Duck-UI behavior using environment variables:

```bash
# For external DuckDB connections
docker run -p 5522:5522 \
  -e DUCK_UI_EXTERNAL_CONNECTION_NAME="My DuckDB Server" \
  -e DUCK_UI_EXTERNAL_HOST="http://duckdb-server" \
  -e DUCK_UI_EXTERNAL_PORT="8000" \
  -e DUCK_UI_EXTERNAL_USER="username" \
  -e DUCK_UI_EXTERNAL_PASS="password" \
  -e DUCK_UI_EXTERNAL_DATABASE_NAME="my_database" \
  -e DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS="true" \
  ghcr.io/caioricciuti/duck-ui:latest
```

| Runtime Variable | Description | Default |
|----------|-------------|---------|
| `DUCK_UI_EXTERNAL_CONNECTION_NAME` | Name for the external connection | "" |
| `DUCK_UI_EXTERNAL_HOST` | Host URL for external DuckDB | "" |
| `DUCK_UI_EXTERNAL_PORT` | Port for external DuckDB | null |
| `DUCK_UI_EXTERNAL_USER` | Username for external connection | "" |
| `DUCK_UI_EXTERNAL_PASS` | Password for external connection | "" |
| `DUCK_UI_EXTERNAL_DATABASE_NAME` | Database name for external connection | "" |
| `DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS` | Allow unsigned extensions in DuckDB | false |
| `DUCK_UI_DUCKDB_WASM_USE_CDN` | Load DuckDB WASM from CDN (ignored when build-time `DUCK_UI_DUCKDB_WASM_CDN_ONLY=true`) | false |
| `DUCK_UI_DUCKDB_WASM_BASE_URL` | Custom CDN base URL (used when `DUCK_UI_DUCKDB_WASM_USE_CDN=true`) | auto jsDelivr |

| Build-time Variable | Description | Default |
|----------|-------------|---------|
| `DUCK_UI_DUCKDB_WASM_CDN_ONLY` | Build a CDN-only artifact (local DuckDB WASM assets are not bundled). | false |

When `DUCK_UI_DUCKDB_WASM_CDN_ONLY=true`, runtime `DUCK_UI_DUCKDB_WASM_USE_CDN=false` cannot switch back to local WASM.



### Prerequisites

- Node.js >= 20.x
- npm >= 10.x

### Installation

1. Clone the repository:

   ```bash
   git clone https://github.com/caioricciuti/duck-ui.git
   cd duck-ui
   ```

2. Install dependencies:

   ```bash
   npm install
   # or
   yarn install
   ```

### Running the Application

1. Start the development server:

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

2. Open your browser and navigate to `http://localhost:5173`.

### Building for Production

To create a production build, run:

```bash
npm run build
# or
yarn build
```

The output will be in the `dist` directory.

### Running with Docker

1. Build the Docker image:

   ```bash
   docker build -t duck-ui .
   ```

2. Run the Docker container:

   ```bash
   docker run -p 5522:5522 duck-ui
   ```

3. Open your browser and navigate to `http://localhost:5522`.

## Usage

### SQL Editor

- Write your SQL queries in the editor.
- Use `Cmd/Ctrl + Enter` to execute the query.
- View the results in the results pane.

### Data Import

- Click on the "Import Files" button to upload CSV, JSON, Parquet, or Arrow files.
- Configure the table name and import settings.
- For CSV files, you can customize import options:
  - Header row detection
  - Auto-detection of column types
  - Delimiter specification
  - Error handling (ignore errors, null padding for missing columns)
- View the imported data in the Data Explorer.

### Data Explorer

- Browse through the databases and tables.
- Preview table data and view table schemas.
- Delete tables if needed.

### Query History

- Access your recent queries from the Query History section.
- Copy queries to the clipboard or re-execute them.

### Theme Toggle

- Switch between light and dark themes using the theme toggle button.

### Keyboard Shortcuts

- `Cmd/Ctrl + B`: Expand/Shrink Sidebar
- `Cmd/Ctrl + K`: Open Search Bar
- `Cmd/Ctrl + Enter`: Run Query
- `Cmd/Ctrl + Shift + Enter`: Run highlighted query

## Contributing

Contributions are welcome! Please follow these steps to contribute:

1. Fork the repository.
2. Create a new branch (`git checkout -b feature/your-feature`).
3. Commit your changes (`git commit -m 'Add some feature'`).
4. Push to the branch (`git push origin feature/your-feature`).
5. Open a pull request.

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for details.

## Acknowledgements

- [DuckDB](https://duckdb.org/)
- [React](https://reactjs.org/)
- [Tailwind CSS](https://tailwindcss.com/)
- [Zustand](https://github.com/pmndrs/zustand)
- [Lucide Icons](https://lucide.dev/)

## Contact

For any inquiries or support, please contact [Caio Ricciuti](https://github.com/caioricciuti).

## Sponsors

This project is sponsored by:

### [Ibero Data](https://iberodata.es/) 
<img src="https://iberodata.es/logo.png" alt="Ibero Data Logo" title="Ibero Data Logo" width="100">

### [qxip](https://qxip.net/?utm_source=duck-ui&utm_medium=sponsorship) 

<img src="https://qxip.net/images/qxip.png" alt="qxip" title="qxip Logo" width="150">



<br/>

Want to be a sponsor? [Contact us](mailto:caio.ricciuti+sponsorship@outlook.com).


================================================
FILE: components.json
================================================
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "src/index.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide"
}

================================================
FILE: docker-compose.yml
================================================
services:
  duck-ui:
    image: ghcr.io/caioricciuti/duck-ui:latest
    restart: always
    ports:
      - "${DUCK_UI_PORT:-5522}:5522"
    environment:
      # External connection (optional)
      - DUCK_UI_EXTERNAL_CONNECTION_NAME=${DUCK_UI_EXTERNAL_CONNECTION_NAME:-}
      - DUCK_UI_EXTERNAL_HOST=${DUCK_UI_EXTERNAL_HOST:-}
      - DUCK_UI_EXTERNAL_PORT=${DUCK_UI_EXTERNAL_PORT:-}
      - DUCK_UI_EXTERNAL_USER=${DUCK_UI_EXTERNAL_USER:-}
      - DUCK_UI_EXTERNAL_PASS=${DUCK_UI_EXTERNAL_PASS:-}
      - DUCK_UI_EXTERNAL_DATABASE_NAME=${DUCK_UI_EXTERNAL_DATABASE_NAME:-}
      # DuckDB configuration
      - DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS=${DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS:-false}

================================================
FILE: eslint.config.js
================================================
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
  { ignores: ['dist', 'docs'] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
      '@typescript-eslint/no-unused-vars': [
        'error',
        { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
      ],
    },
  },
)


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/png" href="/logo-padding.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Duck UI</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: inject-env.js
================================================
const fs = require("fs");
const path = require("path");

const indexHtmlPath = path.join(__dirname, "index.html");
let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf8");

// Inject the environment variables
const envVars = {
  DUCK_UI_EXTERNAL_CONNECTION_NAME:
    process.env.DUCK_UI_EXTERNAL_CONNECTION_NAME || "",
  DUCK_UI_EXTERNAL_HOST: process.env.DUCK_UI_EXTERNAL_HOST || "",
  DUCK_UI_EXTERNAL_PORT: process.env.DUCK_UI_EXTERNAL_PORT || null,
  DUCK_UI_EXTERNAL_USER: process.env.DUCK_UI_EXTERNAL_USER || "",
  DUCK_UI_EXTERNAL_PASS: process.env.DUCK_UI_EXTERNAL_PASS || "",
  DUCK_UI_EXTERNAL_DATABASE_NAME:
    process.env.DUCK_UI_EXTERNAL_DATABASE_NAME || "",
  // Add new configuration for DuckDB settings
  DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS:
    process.env.DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS === "true" || false,
  DUCK_UI_DUCKDB_WASM_USE_CDN:
    process.env.DUCK_UI_DUCKDB_WASM_USE_CDN === "true" || false,
  DUCK_UI_DUCKDB_WASM_BASE_URL:
    process.env.DUCK_UI_DUCKDB_WASM_BASE_URL || ""
};

const scriptContent = `
<script>
  window.env = ${JSON.stringify(envVars)};
</script>
`;

// Insert the script just before the closing </head> tag
indexHtmlContent = indexHtmlContent.replace(
  "</head>",
  `${scriptContent}</head>`
);

fs.writeFileSync(indexHtmlPath, indexHtmlContent);

console.log("Environment variables injected successfully");


================================================
FILE: package.json
================================================
{
  "name": "duck-ui",
  "private": true,
  "version": "0.0.39",
  "release_date": "2026-04-13",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "format": "prettier --write \"src/**/*.{ts,tsx,css}\"",
    "format:check": "prettier --check \"src/**/*.{ts,tsx,css}\"",
    "typecheck": "tsc --noEmit",
    "preview": "vite preview",
    "test": "vitest run",
    "test:watch": "vitest",
    "prepare": "husky"
  },
  "dependencies": {
    "@dnd-kit/core": "^6.3.1",
    "@dnd-kit/sortable": "^10.0.0",
    "@duckdb/duckdb-wasm": "1.33.1-dev45.0",
    "@hookform/resolvers": "^5.2.2",
    "@mlc-ai/web-llm": "^0.2.82",
    "@radix-ui/react-accordion": "^1.2.12",
    "@radix-ui/react-alert-dialog": "^1.1.15",
    "@radix-ui/react-aspect-ratio": "^1.1.8",
    "@radix-ui/react-checkbox": "^1.3.3",
    "@radix-ui/react-collapsible": "^1.1.12",
    "@radix-ui/react-context-menu": "^2.2.16",
    "@radix-ui/react-dialog": "^1.1.15",
    "@radix-ui/react-dropdown-menu": "^2.1.16",
    "@radix-ui/react-hover-card": "^1.1.15",
    "@radix-ui/react-label": "^2.1.8",
    "@radix-ui/react-menubar": "^1.1.16",
    "@radix-ui/react-navigation-menu": "^1.2.14",
    "@radix-ui/react-popover": "^1.1.15",
    "@radix-ui/react-progress": "^1.1.8",
    "@radix-ui/react-radio-group": "^1.3.8",
    "@radix-ui/react-scroll-area": "^1.2.10",
    "@radix-ui/react-select": "^2.2.6",
    "@radix-ui/react-separator": "^1.1.8",
    "@radix-ui/react-slot": "^1.2.4",
    "@radix-ui/react-switch": "^1.2.6",
    "@radix-ui/react-tabs": "^1.1.13",
    "@radix-ui/react-tooltip": "^1.2.8",
    "@tailwindcss/typography": "^0.5.19",
    "@tailwindcss/vite": "^4.2.2",
    "@tanstack/react-table": "^8.21.3",
    "@tanstack/react-virtual": "^3.13.23",
    "@types/dompurify": "^3.2.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "date-fns": "^4.1.0",
    "dompurify": "^3.3.3",
    "exceljs": "^4.4.0",
    "framer-motion": "^12.38.0",
    "html2canvas": "^1.4.1",
    "lucide-react": "^0.546.0",
    "marked": "^17.0.6",
    "monaco-editor": "^0.55.1",
    "openai": "^6.34.0",
    "papaparse": "^5.5.3",
    "react": "^19.2.5",
    "react-dom": "^19.2.5",
    "react-error-boundary": "^6.1.1",
    "react-hook-form": "^7.72.1",
    "react-markdown": "^10.1.0",
    "react-resizable-panels": "^3.0.6",
    "react-router": "^7.14.0",
    "sonner": "^2.0.7",
    "sql-formatter": "^15.7.3",
    "tailwind-merge": "^3.5.0",
    "uplot": "^1.6.32",
    "vaul": "^1.1.2",
    "zod": "^4.3.6",
    "zustand": "^5.0.12"
  },
  "devDependencies": {
    "@eslint/js": "^9.39.4",
    "@types/node": "^24.12.2",
    "@types/papaparse": "^5.5.2",
    "@types/react": "^19.2.14",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^5.2.0",
    "baseline-browser-mapping": "^2.10.18",
    "esbuild": "^0.25.12",
    "eslint": "^9.39.4",
    "eslint-plugin-react-hooks": "^7.0.1",
    "eslint-plugin-react-refresh": "^0.4.26",
    "globals": "^16.5.0",
    "husky": "^9.1.7",
    "lint-staged": "^16.4.0",
    "prettier": "^3.8.2",
    "tailwindcss": "^4.2.2",
    "typescript": "~5.9.3",
    "typescript-eslint": "^8.58.2",
    "vite": "^7.3.2",
    "vitest": "^4.1.4"
  },
  "description": "Duck-UI is a web-based interface for interacting with DuckDB, a high-performance analytical database system. This project leverages DuckDB's WebAssembly (WASM) capabilities to provide a seamless and efficient user experience directly in the browser.",
  "main": "eslint.config.js",
  "directories": {
    "doc": "docs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/caioricciuti/duck-ui.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/caioricciuti/duck-ui/issues"
  },
  "homepage": "https://github.com/caioricciuti/duck-ui#readme",
  "lint-staged": {
    "src/**/*.{ts,tsx,css}": "prettier --write"
  },
  "overrides": {
    "brace-expansion": "^2.0.3",
    "picomatch": "^4.0.4",
    "flatted": "^3.4.2"
  }
}


================================================
FILE: public/databases/README.md
================================================
# Embedded Databases

This directory allows you to embed DuckDB database files (`.db`) that will be automatically loaded when DuckUI starts. This is perfect for:

- Deploying DuckUI with demo data
- Distributing pre-configured databases
- Creating self-contained analytical dashboards

## How to Add Your Database

1. **Place your `.db` file in this directory**
   ```
   public/databases/my-database.db
   ```

2. **Register it in `manifest.json`**
   ```json
   {
     "databases": [
       {
         "name": "My Database",
         "file": "my-database.db",
         "description": "Description of your database",
         "autoLoad": true
       }
     ]
   }
   ```

3. **Build and deploy**
   ```bash
   bun run build
   ```

## Manifest Format

Each database entry in `manifest.json` supports:

- **`name`** (required): Display name in the UI
- **`file`** (required): Filename of the `.db` file in this directory
- **`description`** (optional): Description shown in the UI
- **`autoLoad`** (optional, default: `true`): Whether to load on startup

## Example

```json
{
  "databases": [
    {
      "name": "Sales Demo",
      "file": "sales-demo.db",
      "description": "Sample sales data from 2023",
      "autoLoad": true
    },
    {
      "name": "Analytics",
      "file": "analytics.db",
      "description": "Web analytics data",
      "autoLoad": false
    }
  ]
}
```

## Notes

- Database files are fetched and loaded in the browser
- Large database files will increase initial load time
- All databases are loaded into memory (consider file sizes)
- You can attach/detach databases dynamically from the SQL editor


================================================
FILE: public/databases/manifest.json
================================================
{
  "databases": []
}


================================================
FILE: serve.json
================================================
{
  "headers": [
    {
      "source": "**/*",
      "headers": [
        { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
        { "key": "Cross-Origin-Embedder-Policy", "value": "credentialless" }
      ]
    }
  ]
}


================================================
FILE: src/components/charts/ChartVisualizationPro.tsx
================================================
/**
 * Professional Chart Visualization Component
 * Features: Auto-chart, live preview, multi-series, customization, export
 * Powered by uPlot (canvas-based, lightweight)
 */

import React, { useState, useRef, useMemo, useEffect, useCallback } from "react";
import uPlot from "uplot";
import { Button } from "@/components/ui/button";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { MultiSelect } from "@/components/ui/multi-select";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Switch } from "@/components/ui/switch";
import { Input } from "@/components/ui/input";
import {
  Download,
  BarChart3,
  LineChart,
  PieChart as PieChartIcon,
  ScatterChart,
  AreaChart,
  Settings2,
  RotateCcw,
  Layers,
  TrendingUp,
  CircleDot,
  ArrowUpDown,
} from "lucide-react";
import { toast } from "sonner";
import { useTheme } from "@/components/theme/theme-provider";
import { formatNumber, formatNumberWithSuffix, shortenLabel } from "@/lib/chartUtils";
import { transformData, isNumericColumn, suggestChartTypes } from "@/lib/chartDataTransform";
import { exportChartAsPNG } from "@/lib/chartExport";
import UPlotChart from "./UPlotChart";
import { tooltipPlugin } from "./tooltipPlugin";
import type { QueryResult, ChartConfig, ChartType, DataTransform } from "@/store";

interface ChartVisualizationProProps {
  result: QueryResult;
  chartConfig?: ChartConfig;
  onConfigChange: (config: ChartConfig | undefined) => void;
}

// Enhanced color palette
const DEFAULT_COLORS = [
  "#D99B43",
  "#8B5CF6",
  "#3B82F6",
  "#10B981",
  "#F59E0B",
  "#EF4444",
  "#EC4899",
  "#6366F1",
  "#14B8A6",
  "#F97316",
];

// Chart type display labels with icons
const CHART_TYPE_INFO: Record<string, { label: string; icon: React.ElementType }> = {
  bar: { label: "Bar", icon: BarChart3 },
  grouped_bar: { label: "Grouped Bar", icon: Layers },
  stacked_bar: { label: "Stacked Bar", icon: Layers },
  line: { label: "Line", icon: LineChart },
  area: { label: "Area", icon: AreaChart },
  stacked_area: { label: "Stacked Area", icon: TrendingUp },
  pie: { label: "Pie", icon: PieChartIcon },
  donut: { label: "Donut", icon: CircleDot },
  scatter: { label: "Scatter", icon: ScatterChart },
};

// ── SVG Pie/Donut Chart ──────────────────────────────────────────────────────

function PieChartDisplay({
  data,
  xKey,
  yKey,
  colors,
  isDonut,
  innerRadius = 0.45,
  theme,
}: {
  data: Record<string, unknown>[];
  xKey: string;
  yKey: string;
  colors: string[];
  isDonut: boolean;
  innerRadius?: number;
  theme: string;
}) {
  const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
  const total = data.reduce((sum, row) => sum + (Number(row[yKey]) || 0), 0);

  if (total === 0)
    return (
      <div className="flex items-center justify-center h-full text-muted-foreground">No data</div>
    );

  const slices: { label: string; value: number; pct: number; color: string }[] = [];
  let cumAngle = -Math.PI / 2;
  const arcs: {
    d: string;
    color: string;
    midAngle: number;
    pct: number;
    label: string;
    value: number;
  }[] = [];

  data.forEach((row, i) => {
    const value = Number(row[yKey]) || 0;
    const pct = value / total;
    const angle = pct * 2 * Math.PI;
    const startAngle = cumAngle;
    const endAngle = cumAngle + angle;
    const midAngle = startAngle + angle / 2;
    const color = colors[i % colors.length];

    slices.push({ label: String(row[xKey]), value, pct, color });

    const outerR = 1;
    const innerR = isDonut ? innerRadius : 0;
    const largeArc = angle > Math.PI ? 1 : 0;

    const x1 = Math.cos(startAngle) * outerR;
    const y1 = Math.sin(startAngle) * outerR;
    const x2 = Math.cos(endAngle) * outerR;
    const y2 = Math.sin(endAngle) * outerR;
    const ix1 = Math.cos(endAngle) * innerR;
    const iy1 = Math.sin(endAngle) * innerR;
    const ix2 = Math.cos(startAngle) * innerR;
    const iy2 = Math.sin(startAngle) * innerR;

    const d = isDonut
      ? `M ${x1} ${y1} A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2} L ${ix1} ${iy1} A ${innerR} ${innerR} 0 ${largeArc} 0 ${ix2} ${iy2} Z`
      : `M 0 0 L ${x1} ${y1} A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2} Z`;

    arcs.push({ d, color, midAngle, pct, label: String(row[xKey]), value });
    cumAngle = endAngle;
  });

  const strokeColor = theme === "dark" ? "#1a1a1a" : "#fff";

  return (
    <div className="flex items-center justify-center h-full gap-6">
      {/* Chart */}
      <div className="relative flex-shrink-0">
        <svg
          viewBox="-1.3 -1.3 2.6 2.6"
          className="w-full max-w-[360px] max-h-[360px]"
          style={{ minWidth: 200 }}
        >
          {arcs.map((arc, i) => {
            const isHovered = hoveredIdx === i;
            const tx = isHovered ? Math.cos(arc.midAngle) * 0.06 : 0;
            const ty = isHovered ? Math.sin(arc.midAngle) * 0.06 : 0;
            return (
              <g key={i}>
                <path
                  d={arc.d}
                  fill={arc.color}
                  stroke={strokeColor}
                  strokeWidth={0.02}
                  opacity={hoveredIdx !== null && !isHovered ? 0.4 : 1}
                  transform={`translate(${tx}, ${ty})`}
                  style={{ transition: "opacity 0.2s, transform 0.2s" }}
                  onMouseEnter={() => setHoveredIdx(i)}
                  onMouseLeave={() => setHoveredIdx(null)}
                />
                {arc.pct >= 0.05 && (
                  <text
                    x={Math.cos(arc.midAngle) * (isDonut ? 0.72 : 0.6) + tx}
                    y={Math.sin(arc.midAngle) * (isDonut ? 0.72 : 0.6) + ty}
                    textAnchor="middle"
                    dominantBaseline="central"
                    fill="white"
                    fontSize="0.11"
                    fontWeight="600"
                    pointerEvents="none"
                    style={{ transition: "all 0.2s" }}
                  >
                    {`${(arc.pct * 100).toFixed(0)}%`}
                  </text>
                )}
              </g>
            );
          })}
          {/* Donut center total */}
          {isDonut && (
            <g>
              <text
                x="0"
                y="-0.06"
                textAnchor="middle"
                dominantBaseline="central"
                fill={theme === "dark" ? "#999" : "#666"}
                fontSize="0.12"
              >
                Total
              </text>
              <text
                x="0"
                y="0.12"
                textAnchor="middle"
                dominantBaseline="central"
                fill={theme === "dark" ? "#e5e5e5" : "#1a1a1a"}
                fontSize="0.18"
                fontWeight="700"
              >
                {formatNumberWithSuffix(total)}
              </text>
            </g>
          )}
        </svg>
        {/* Hover tooltip */}
        {hoveredIdx !== null && (
          <div className="absolute top-2 left-1/2 -translate-x-1/2 bg-popover border rounded-lg shadow-lg px-3 py-2 text-xs pointer-events-none z-10">
            <div className="font-medium">{slices[hoveredIdx].label}</div>
            <div className="text-muted-foreground">
              {formatNumber(slices[hoveredIdx].value)} ({(slices[hoveredIdx].pct * 100).toFixed(1)}
              %)
            </div>
          </div>
        )}
      </div>
      {/* Legend */}
      <div className="flex flex-col gap-1.5 text-xs max-h-[320px] overflow-y-auto pr-2">
        {slices.map((s, i) => (
          <div
            key={i}
            className="flex items-center gap-2 cursor-default"
            onMouseEnter={() => setHoveredIdx(i)}
            onMouseLeave={() => setHoveredIdx(null)}
          >
            <span
              className="w-2.5 h-2.5 rounded-full flex-shrink-0"
              style={{ backgroundColor: s.color }}
            />
            <span className="text-muted-foreground truncate max-w-[140px]" title={s.label}>
              {s.label}
            </span>
            <span className="font-medium ml-auto tabular-nums pl-2">
              {formatNumberWithSuffix(s.value)}
            </span>
            <span className="text-muted-foreground tabular-nums w-[3.5em] text-right">
              {(s.pct * 100).toFixed(1)}%
            </span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ── XY Chart Legend ──────────────────────────────────────────────────────────

function ChartLegend({
  series,
  colors,
  onToggle,
}: {
  series: { label: string; show: boolean }[];
  colors: string[];
  onToggle: (idx: number) => void;
}) {
  if (series.length <= 1) return null;
  return (
    <div className="flex flex-wrap gap-x-4 gap-y-1 justify-center py-1 text-xs">
      {series.map((s, i) => (
        <button
          key={i}
          className="flex items-center gap-1.5 cursor-pointer hover:opacity-80 transition-opacity"
          style={{ opacity: s.show ? 1 : 0.35 }}
          onClick={() => onToggle(i)}
          type="button"
        >
          <span
            className="w-2.5 h-2.5 rounded-full flex-shrink-0"
            style={{ backgroundColor: colors[i % colors.length] }}
          />
          <span className="text-muted-foreground">{s.label}</span>
        </button>
      ))}
    </div>
  );
}

// ── Main Component ───────────────────────────────────────────────────────────

export const ChartVisualizationPro: React.FC<ChartVisualizationProProps> = ({
  result,
  chartConfig,
  onConfigChange,
}) => {
  const { theme } = useTheme();
  const chartRef = useRef<HTMLDivElement>(null);
  const uPlotRef = useRef<uPlot | null>(null);
  const [hiddenSeries, setHiddenSeries] = useState<Set<number>>(new Set());

  // Get numeric columns for Y-axis
  const numericColumns = useMemo(
    () => result.columns.filter((col) => isNumericColumn(result.data, col)),
    [result]
  );

  // Auto-chart: detect best config when no config exists
  const autoDetect = useCallback((): ChartConfig => {
    const xAxis =
      result.columns.find((col) => !isNumericColumn(result.data, col)) || result.columns[0] || "";
    const yCol = numericColumns[0] || result.columns[1] || "";
    const suggested = suggestChartTypes(result, xAxis, yCol);
    const type = (suggested[0] || "bar") as ChartType;
    return {
      type,
      xAxis,
      yAxis: yCol,
      colors: DEFAULT_COLORS,
      showGrid: true,
      showValues: false,
      smooth: false,
      legend: { show: true, position: "bottom" },
    };
  }, [result, numericColumns]);

  // On mount: auto-generate config if none provided
  useEffect(() => {
    if (!chartConfig && result.data.length > 0) {
      onConfigChange(autoDetect());
    }
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  // Live config update helper
  const updateConfig = useCallback(
    (updates: Partial<ChartConfig>) => {
      const base = chartConfig || autoDetect();
      onConfigChange({ ...base, ...updates });
    },
    [chartConfig, autoDetect, onConfigChange]
  );

  const updateTransform = useCallback(
    (updates: Partial<DataTransform>) => {
      const base = chartConfig || autoDetect();
      onConfigChange({ ...base, transform: { ...base.transform, ...updates } });
    },
    [chartConfig, autoDetect, onConfigChange]
  );

  // Current effective config
  const config = chartConfig || autoDetect();

  // Selected Y columns (from series or single yAxis)
  const selectedYColumns = useMemo(() => {
    if (config.series?.length) return config.series.map((s) => s.column);
    if (config.yAxis) return [config.yAxis];
    return [];
  }, [config]);

  const handleYColumnsChange = useCallback(
    (cols: string[]) => {
      const series = cols.map((col, idx) => ({
        column: col,
        label: col,
        color: DEFAULT_COLORS[idx % DEFAULT_COLORS.length],
      }));
      updateConfig({
        series: series.length > 1 ? series : undefined,
        yAxis: series.length === 1 ? cols[0] : undefined,
      });
    },
    [updateConfig]
  );

  // Transform data based on configuration
  const transformedData = useMemo(() => {
    return transformData(result, config.transform, config.xAxis, config.yAxis || config.series);
  }, [result, config.transform, config.xAxis, config.yAxis, config.series]);

  const handleExportPNG = async () => {
    if (!chartRef.current) {
      toast.error("No chart to export");
      return;
    }
    try {
      const fileName = `chart-${Date.now()}.png`;
      const bg = theme === "dark" ? "#1a1a1a" : "#ffffff";
      await exportChartAsPNG(chartRef.current, fileName, bg);
      toast.success("Chart exported as PNG");
    } catch (error) {
      toast.error(
        `Failed to export chart: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  };

  // ── Build uPlot options + data ──────────────────────────────────────────────

  const { uPlotOptions, uPlotData, seriesInfo } = useMemo(() => {
    if (!config.xAxis || (!config.yAxis && (!config.series || config.series.length === 0))) {
      return { uPlotOptions: null, uPlotData: null, seriesInfo: [] };
    }

    const chartData = transformedData.map((row) => {
      const newRow: Record<string, unknown> = { ...row };
      if (config.yAxis) newRow[config.yAxis] = Number(row[config.yAxis]) || 0;
      if (config.series) {
        config.series.forEach((s) => {
          newRow[s.column] = Number(row[s.column]) || 0;
        });
      }
      return newRow;
    });

    const yKeys: string[] =
      config.series?.map((s) => s.column) ?? (config.yAxis ? [config.yAxis] : []);
    const colors = config.colors || DEFAULT_COLORS;
    const isDark = theme === "dark";

    const xs = chartData.map((_, i) => i);
    const seriesData = yKeys.map((key) =>
      chartData.map((row) => (Number(row[key]) || 0) as number)
    );

    const isStacked = config.type === "stacked_bar" || config.type === "stacked_area";
    const stackedData = isStacked
      ? seriesData.reduce<number[][]>((acc, curr) => {
          if (acc.length === 0) return [curr];
          const prev = acc[acc.length - 1];
          acc.push(curr.map((v, i) => v + prev[i]));
          return acc;
        }, [])
      : seriesData;

    const finalSeriesData = isStacked ? stackedData : seriesData;

    const isBarType = ["bar", "stacked_bar", "grouped_bar"].includes(config.type);
    const isAreaType = ["area", "stacked_area"].includes(config.type);
    const isLineType = config.type === "line";
    const isScatter = config.type === "scatter";
    const useSmooth = config.smooth && (isLineType || isAreaType);

    const xLabels = chartData.map((row) => shortenLabel(String(row[config.xAxis])));

    const barsBuilder = isBarType
      ? uPlot.paths.bars!({
          size: [0.6, 100],
          radius: 0.2,
          gap: yKeys.length > 1 && config.type === "grouped_bar" ? 2 : 0,
        })
      : undefined;

    const splineBuilder = useSmooth ? uPlot.paths.spline!() : undefined;

    // Build series config
    const sInfo: { label: string; color: string }[] = [];
    const uSeries: uPlot.Series[] = [
      { label: config.xAxis },
      ...yKeys.map((key, i) => {
        const color = config.series?.[i]?.color || colors[i % colors.length];
        const seriesLabel = config.series?.[i]?.label || key;
        sInfo.push({ label: seriesLabel, color });

        const s: uPlot.Series = {
          label: seriesLabel,
          stroke: color,
          width: isBarType ? 0 : 2,
          fill: isBarType || isAreaType ? color + (isAreaType ? "66" : "cc") : undefined,
          points: { show: isScatter, size: isScatter ? 8 : 4 },
          paths: isBarType
            ? barsBuilder
            : useSmooth
              ? splineBuilder
              : isScatter
                ? () => null
                : undefined,
        };

        if (config.type === "grouped_bar" && yKeys.length > 1) {
          const groupBars = uPlot.paths.bars!({
            size: [0.6 / yKeys.length, 100],
            radius: 0.2,
            align: i === 0 ? -1 : i === yKeys.length - 1 ? 1 : 0,
          });
          s.paths = groupBars;
        }

        return s;
      }),
    ];

    const needsRotation = chartData.length > 10;
    const maxLabelLen = Math.max(...xLabels.map((l) => l.length), 1);
    const xAxisSize = needsRotation ? Math.min(120, 40 + maxLabelLen * 5) : 60;

    const uAxes: uPlot.Axis[] = [
      {
        stroke: isDark ? "#888" : "#666",
        grid: { stroke: isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)", width: 1 },
        ticks: { stroke: isDark ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.15)", width: 1 },
        values: (_u: uPlot, vals: number[]) => vals.map((v) => xLabels[v] ?? ""),
        gap: 8,
        size: xAxisSize,
        font: "12px system-ui, sans-serif",
        labelFont: "12px system-ui, sans-serif",
        rotate: needsRotation ? -45 : 0,
      },
      {
        stroke: isDark ? "#888" : "#666",
        grid: config.showGrid
          ? {
              stroke: isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)",
              width: 1,
              dash: [4, 4],
            }
          : { show: false },
        ticks: { stroke: isDark ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.15)", width: 1 },
        values: (_u: uPlot, vals: number[]) => vals.map((v) => formatNumberWithSuffix(v)),
        gap: 8,
        size: 70,
        font: "12px system-ui, sans-serif",
        labelFont: "12px system-ui, sans-serif",
      },
    ];

    // Data labels hook for bar charts
    const hooks: uPlot.Plugin["hooks"] = {};
    if (config.showValues && isBarType) {
      hooks.draw = [
        (u: uPlot) => {
          const ctx = u.ctx;
          ctx.save();
          ctx.font = "10px system-ui, sans-serif";
          ctx.fillStyle = isDark ? "#ccc" : "#333";
          ctx.textAlign = "center";
          ctx.textBaseline = "bottom";

          for (let si = 1; si < u.series.length; si++) {
            if (!u.series[si].show) continue;
            for (let di = 0; di < u.data[si].length; di++) {
              const val = u.data[si][di];
              if (val == null) continue;
              const cx = u.valToPos(u.data[0][di]!, "x", true);
              const cy = u.valToPos(val, "y", true);
              ctx.fillText(formatNumberWithSuffix(val as number), cx, cy - 4);
            }
          }
          ctx.restore();
        },
      ];
    }

    const opts: Omit<uPlot.Options, "width" | "height"> = {
      scales: { x: { time: false } },
      series: uSeries,
      axes: uAxes,
      cursor: {
        drag: { x: false, y: false },
        points: {
          size: 6,
          fill: (u: uPlot, i: number) =>
            (typeof u.series[i].stroke === "function"
              ? (u.series[i].stroke as (self: uPlot, seriesIdx: number) => string)(u, i)
              : u.series[i].stroke) as string,
          stroke: "transparent",
          width: 0,
        },
      },
      legend: { show: false },
      plugins: [tooltipPlugin(xLabels, { stacked: isStacked }), { hooks }],
      padding: [16, 16, 8, 0],
    };

    const data: uPlot.AlignedData = isStacked
      ? ([xs, ...finalSeriesData.reverse()] as uPlot.AlignedData)
      : ([xs, ...finalSeriesData] as uPlot.AlignedData);

    return { uPlotOptions: opts, uPlotData: data, seriesInfo: sInfo };
  }, [config, transformedData, theme]);

  const legendState = useMemo(
    () => seriesInfo.map((s, i) => ({ label: s.label, show: !hiddenSeries.has(i) })),
    [seriesInfo, hiddenSeries]
  );

  const handleLegendToggle = useCallback((idx: number) => {
    setHiddenSeries((prev) => {
      const next = new Set(prev);
      if (next.has(idx)) {
        next.delete(idx);
      } else {
        next.add(idx);
      }
      // Toggle series visibility on the uPlot instance
      if (uPlotRef.current) {
        uPlotRef.current.setSeries(idx + 1, { show: !next.has(idx) });
      }
      return next;
    });
  }, []);

  // ── Render ──────────────────────────────────────────────────────────────────

  const renderChart = () => {
    if (config.type === "pie" || config.type === "donut") {
      const chartData = transformedData.map((row) => ({
        ...row,
        [config.xAxis]: String(row[config.xAxis]),
        [config.yAxis || config.series?.[0]?.column || ""]:
          Number(row[config.yAxis || config.series?.[0]?.column || ""]) || 0,
      }));
      return (
        <PieChartDisplay
          data={chartData}
          xKey={config.xAxis}
          yKey={config.yAxis || config.series?.[0]?.column || ""}
          colors={config.colors || DEFAULT_COLORS}
          isDonut={config.type === "donut"}
          innerRadius={config.innerRadius ? config.innerRadius / 120 : 0.45}
          theme={theme}
        />
      );
    }

    if (!uPlotOptions || !uPlotData) return null;

    return (
      <div className="flex flex-col h-full">
        <div className="flex-1 min-h-0">
          <UPlotChart
            options={uPlotOptions}
            data={uPlotData}
            className="w-full h-full"
            onInit={(u) => {
              uPlotRef.current = u;
            }}
          />
        </div>
        <ChartLegend
          series={legendState}
          colors={seriesInfo.map((s) => s.color)}
          onToggle={handleLegendToggle}
        />
      </div>
    );
  };

  if (!result || !result.data || result.data.length === 0) {
    return (
      <div className="flex items-center justify-center h-full">
        <div className="text-center space-y-2">
          <div className="text-muted-foreground text-sm">No data available for visualization</div>
        </div>
      </div>
    );
  }

  const isLineOrArea = ["line", "area", "stacked_area"].includes(config.type);

  return (
    <div className="flex flex-col h-full">
      {/* Compact Toolbar */}
      <div className="flex flex-wrap items-center gap-2 px-4 py-2 border-b bg-muted/30">
        {/* Chart Type */}
        <Select
          value={config.type}
          onValueChange={(value) => updateConfig({ type: value as ChartType })}
        >
          <SelectTrigger className="w-[150px] shrink-0 h-8 text-xs">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            {Object.entries(CHART_TYPE_INFO).map(([value, info]) => {
              const Icon = info.icon;
              return (
                <SelectItem key={value} value={value}>
                  <span className="flex items-center gap-2">
                    <Icon className="h-3.5 w-3.5 opacity-60" />
                    {info.label}
                  </span>
                </SelectItem>
              );
            })}
          </SelectContent>
        </Select>

        {/* X-Axis */}
        <Select value={config.xAxis} onValueChange={(value) => updateConfig({ xAxis: value })}>
          <SelectTrigger className="w-[140px] shrink-0 h-8 text-xs">
            <span className="text-muted-foreground mr-1">X:</span>
            <SelectValue placeholder="Column" />
          </SelectTrigger>
          <SelectContent>
            {result.columns.map((col) => (
              <SelectItem key={col} value={col}>
                {col}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>

        {/* Y-Axis */}
        <div className="w-[200px] shrink-0">
          <MultiSelect
            options={numericColumns.map((col, idx) => ({
              label: col,
              value: col,
              color: selectedYColumns.includes(col)
                ? DEFAULT_COLORS[selectedYColumns.indexOf(col) % DEFAULT_COLORS.length]
                : DEFAULT_COLORS[idx % DEFAULT_COLORS.length],
            }))}
            selected={selectedYColumns}
            onChange={handleYColumnsChange}
            placeholder="Values..."
            className="h-8 text-xs"
          />
        </div>

        {/* Settings Popover */}
        <Popover>
          <PopoverTrigger asChild>
            <Button variant="ghost" size="icon" className="h-8 w-8">
              <Settings2 className="h-4 w-4" />
            </Button>
          </PopoverTrigger>
          <PopoverContent className="w-[260px]" align="end">
            <div className="space-y-4">
              <h4 className="font-medium text-sm">Chart Settings</h4>

              {/* Sort */}
              <div className="space-y-1.5">
                <label className="text-xs text-muted-foreground flex items-center gap-1">
                  <ArrowUpDown className="h-3 w-3" /> Sort by
                </label>
                <div className="flex gap-1.5">
                  <Select
                    value={config.transform?.sortBy || "__none__"}
                    onValueChange={(v) =>
                      updateTransform({ sortBy: v === "__none__" ? undefined : v })
                    }
                  >
                    <SelectTrigger className="h-8 text-xs flex-1">
                      <SelectValue placeholder="None" />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="__none__">None</SelectItem>
                      {result.columns.map((col) => (
                        <SelectItem key={col} value={col}>
                          {col}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                  {config.transform?.sortBy && (
                    <Select
                      value={config.transform?.sortOrder || "asc"}
                      onValueChange={(v) => updateTransform({ sortOrder: v as "asc" | "desc" })}
                    >
                      <SelectTrigger className="h-8 text-xs w-[80px]">
                        <SelectValue />
                      </SelectTrigger>
                      <SelectContent>
                        <SelectItem value="asc">Asc</SelectItem>
                        <SelectItem value="desc">Desc</SelectItem>
                      </SelectContent>
                    </Select>
                  )}
                </div>
              </div>

              {/* Limit */}
              <div className="space-y-1.5">
                <label className="text-xs text-muted-foreground">Limit rows</label>
                <Input
                  type="number"
                  min={0}
                  placeholder="No limit"
                  className="h-8 text-xs"
                  value={config.transform?.limit ?? ""}
                  onChange={(e) => {
                    const v = e.target.value ? parseInt(e.target.value, 10) : undefined;
                    updateTransform({ limit: v });
                  }}
                />
              </div>

              {/* Aggregation */}
              <div className="space-y-1.5">
                <label className="text-xs text-muted-foreground">Aggregation</label>
                <Select
                  value={config.transform?.aggregation || "none"}
                  onValueChange={(v) =>
                    updateTransform({
                      aggregation: v as "sum" | "avg" | "count" | "min" | "max" | "none",
                      groupBy: v !== "none" ? config.xAxis : undefined,
                    })
                  }
                >
                  <SelectTrigger className="h-8 text-xs">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="none">None</SelectItem>
                    <SelectItem value="sum">Sum</SelectItem>
                    <SelectItem value="avg">Average</SelectItem>
                    <SelectItem value="count">Count</SelectItem>
                    <SelectItem value="min">Min</SelectItem>
                    <SelectItem value="max">Max</SelectItem>
                  </SelectContent>
                </Select>
              </div>

              {/* Toggles */}
              <div className="space-y-3 pt-1 border-t">
                <div className="flex items-center justify-between">
                  <label className="text-xs">Show values</label>
                  <Switch
                    checked={config.showValues ?? false}
                    onCheckedChange={(v) => updateConfig({ showValues: v })}
                  />
                </div>
                <div className="flex items-center justify-between">
                  <label className="text-xs">Show grid</label>
                  <Switch
                    checked={config.showGrid ?? true}
                    onCheckedChange={(v) => updateConfig({ showGrid: v })}
                  />
                </div>
                {isLineOrArea && (
                  <div className="flex items-center justify-between">
                    <label className="text-xs">Smooth lines</label>
                    <Switch
                      checked={config.smooth ?? false}
                      onCheckedChange={(v) => updateConfig({ smooth: v })}
                    />
                  </div>
                )}
              </div>
            </div>
          </PopoverContent>
        </Popover>

        {/* Export */}
        <Button variant="ghost" size="icon" className="h-8 w-8" onClick={handleExportPNG}>
          <Download className="h-4 w-4" />
        </Button>

        {/* Clear / Reset */}
        <Button
          variant="ghost"
          size="icon"
          className="h-8 w-8"
          onClick={() => {
            onConfigChange(undefined);
            // Will auto-detect on next render
            setTimeout(() => onConfigChange(autoDetect()), 0);
          }}
          title="Reset chart"
        >
          <RotateCcw className="h-3.5 w-3.5" />
        </Button>
      </div>

      {/* Chart Display */}
      <div className="flex-1 min-h-0 p-2" ref={chartRef}>
        {renderChart()}
      </div>
    </div>
  );
};

export default React.memo(ChartVisualizationPro);


================================================
FILE: src/components/charts/UPlotChart.tsx
================================================
import { useRef, useEffect, useCallback } from "react";
import uPlot from "uplot";
import "uplot/dist/uPlot.min.css";

interface UPlotChartProps {
  options: Omit<uPlot.Options, "width" | "height">;
  data: uPlot.AlignedData;
  className?: string;
  onInit?: (chart: uPlot) => void;
}

export default function UPlotChart({ options, data, className, onInit }: UPlotChartProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const chartRef = useRef<uPlot | null>(null);

  const create = useCallback(() => {
    if (!containerRef.current) return;
    const el = containerRef.current;
    const { width, height } = el.getBoundingClientRect();
    if (width === 0 || height === 0) return;

    chartRef.current?.destroy();
    const chart = new uPlot({ ...options, width, height }, data, el);
    chartRef.current = chart;
    onInit?.(chart);
  }, [options, data, onInit]);

  useEffect(() => {
    create();

    const el = containerRef.current;
    if (!el) return;

    const ro = new ResizeObserver((entries) => {
      const { width: w, height: h } = entries[0].contentRect;
      if (w > 0 && h > 0) {
        chartRef.current?.setSize({ width: w, height: h });
      }
    });
    ro.observe(el);

    return () => {
      ro.disconnect();
      chartRef.current?.destroy();
      chartRef.current = null;
    };
  }, [create]);

  return <div ref={containerRef} className={className} style={{ minHeight: 0 }} />;
}


================================================
FILE: src/components/charts/tooltipPlugin.ts
================================================
import uPlot from "uplot";
import { formatNumberWithSuffix } from "@/lib/chartUtils";

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

export interface TooltipPluginOptions {
  stacked?: boolean;
}

export function tooltipPlugin(xLabels: string[], opts?: TooltipPluginOptions): uPlot.Plugin {
  let tooltip: HTMLDivElement;
  let over: HTMLElement;
  const isStacked = opts?.stacked ?? false;

  return {
    hooks: {
      init(u: uPlot) {
        over = u.over;

        tooltip = document.createElement("div");
        tooltip.className = "uplot-tooltip";
        tooltip.style.display = "none";
        tooltip.style.position = "absolute";
        tooltip.style.pointerEvents = "none";
        tooltip.style.zIndex = "100";
        over.appendChild(tooltip);

        over.addEventListener("mouseenter", () => {
          tooltip.style.display = "block";
        });
        over.addEventListener("mouseleave", () => {
          tooltip.style.display = "none";
        });
      },

      setSize() {
        // bounds recalculated in setCursor via over.clientWidth/Height
      },

      setCursor(u: uPlot) {
        const { idx, left, top } = u.cursor;
        if (idx == null || left == null || top == null) {
          tooltip.style.display = "none";
          return;
        }

        const xLabel = escapeHtml(xLabels[idx] ?? String(u.data[0][idx]));
        let html = `<div class="uplot-tooltip-title">${xLabel}</div>`;

        let total = 0;
        const rows: { label: string; color: string; val: number | null }[] = [];

        for (let i = 1; i < u.series.length; i++) {
          const s = u.series[i];
          if (!s.show) continue;
          const rawVal = u.data[i][idx];
          const val = rawVal != null ? (rawVal as number) : null;
          const color =
            typeof s.stroke === "function"
              ? (s.stroke as (self: uPlot, seriesIdx: number) => string)(u, i)
              : s.stroke;
          const safeLabel = typeof s.label === "string" ? escapeHtml(s.label) : "";
          const safeColor = typeof color === "string" ? escapeHtml(color) : "";

          if (val != null) total += val;
          rows.push({ label: safeLabel, color: safeColor, val });
        }

        for (const row of rows) {
          const formatted = row.val != null ? formatNumberWithSuffix(row.val) : "\u2014";
          const pct =
            isStacked && row.val != null && total > 0
              ? ` <span class="uplot-tooltip-pct">(${((row.val / total) * 100).toFixed(1)}%)</span>`
              : "";
          html += `<div class="uplot-tooltip-row">
            <span class="uplot-tooltip-dot" style="background:${row.color}"></span>
            <span class="uplot-tooltip-label">${row.label}</span>
            <span class="uplot-tooltip-value">${formatted}${pct}</span>
          </div>`;
        }

        if (isStacked && rows.length > 1) {
          html += `<div class="uplot-tooltip-total">
            <span class="uplot-tooltip-label">Total</span>
            <span class="uplot-tooltip-value">${formatNumberWithSuffix(total)}</span>
          </div>`;
        }

        tooltip.innerHTML = html;

        const tooltipW = tooltip.offsetWidth;
        const tooltipH = tooltip.offsetHeight;
        const overW = over.clientWidth;
        const overH = over.clientHeight;
        const pad = 12;

        let posLeft = left + pad;
        let posTop = top - tooltipH / 2;

        if (posLeft + tooltipW > overW) posLeft = left - tooltipW - pad;
        if (posTop < 0) posTop = 0;
        if (posTop + tooltipH > overH) posTop = overH - tooltipH;

        tooltip.style.left = posLeft + "px";
        tooltip.style.top = posTop + "px";
        tooltip.style.display = "block";
      },
    },
  };
}


================================================
FILE: src/components/cloud/CloudBrowser.tsx
================================================
/**
 * Cloud Browser Component
 * Displays cloud storage connections and allows browsing/importing files
 */

import { useState, useEffect } from "react";
import { useDuckStore } from "@/store";
import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import {
  Cloud,
  CloudOff,
  Plus,
  MoreVertical,
  Trash2,
  Link,
  Unlink,
  AlertCircle,
  Loader2,
} from "lucide-react";
import { CloudConnectionModal } from "./CloudConnectionModal";
import type { CloudConnection } from "@/store";

// Provider icons/colors
const PROVIDER_CONFIG = {
  s3: { label: "S3", color: "text-orange-500", bgColor: "bg-orange-500/10" },
  gcs: { label: "GCS", color: "text-blue-500", bgColor: "bg-blue-500/10" },
  azure: { label: "Azure", color: "text-cyan-500", bgColor: "bg-cyan-500/10" },
};

interface CloudConnectionItemProps {
  connection: CloudConnection;
  onConnect: () => void;
  onDisconnect: () => void;
  onRemove: () => void;
}

function CloudConnectionItem({
  connection,
  onConnect,
  onDisconnect,
  onRemove,
}: CloudConnectionItemProps) {
  const [isLoading, setIsLoading] = useState(false);
  const config = PROVIDER_CONFIG[connection.type];

  const handleConnect = async () => {
    setIsLoading(true);
    try {
      await onConnect();
    } finally {
      setIsLoading(false);
    }
  };

  const handleDisconnect = async () => {
    setIsLoading(true);
    try {
      await onDisconnect();
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="group flex items-center justify-between py-1.5 px-2 rounded hover:bg-muted/50">
      <div className="flex items-center gap-2 min-w-0 flex-1">
        <div className={`p-1 rounded ${config.bgColor}`}>
          <Cloud className={`h-3.5 w-3.5 ${config.color}`} />
        </div>
        <div className="min-w-0 flex-1">
          <div className="flex items-center gap-1.5">
            <span className="text-sm truncate">{connection.name}</span>
            <span className={`text-[10px] px-1 py-0.5 rounded ${config.bgColor} ${config.color}`}>
              {config.label}
            </span>
            {connection.isConnected && <span className="w-1.5 h-1.5 rounded-full bg-green-500" />}
          </div>
          {connection.lastError && (
            <TooltipProvider>
              <Tooltip>
                <TooltipTrigger asChild>
                  <div className="flex items-center gap-1 text-destructive">
                    <AlertCircle className="h-3 w-3" />
                    <span className="text-xs truncate">Error</span>
                  </div>
                </TooltipTrigger>
                <TooltipContent side="right" className="max-w-xs">
                  <p className="text-xs">{connection.lastError}</p>
                </TooltipContent>
              </Tooltip>
            </TooltipProvider>
          )}
        </div>
      </div>

      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          <Button variant="ghost" size="icon" className="h-6 w-6 opacity-0 group-hover:opacity-100">
            {isLoading ? (
              <Loader2 className="h-3.5 w-3.5 animate-spin" />
            ) : (
              <MoreVertical className="h-3.5 w-3.5" />
            )}
          </Button>
        </DropdownMenuTrigger>
        <DropdownMenuContent align="end">
          {connection.isConnected ? (
            <DropdownMenuItem onClick={handleDisconnect}>
              <Unlink className="h-4 w-4 mr-2" />
              Disconnect
            </DropdownMenuItem>
          ) : (
            <DropdownMenuItem onClick={handleConnect}>
              <Link className="h-4 w-4 mr-2" />
              Connect
            </DropdownMenuItem>
          )}
          <DropdownMenuSeparator />
          <DropdownMenuItem onClick={onRemove} className="text-destructive">
            <Trash2 className="h-4 w-4 mr-2" />
            Remove
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  );
}

export default function CloudBrowser() {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const cloudConnections = useDuckStore((s) => s.cloudConnections);
  const cloudSupportStatus = useDuckStore((s) => s.cloudSupportStatus);
  const isCloudStorageInitialized = useDuckStore((s) => s.isCloudStorageInitialized);
  const initCloudStorage = useDuckStore((s) => s.initCloudStorage);
  const connectCloudStorage = useDuckStore((s) => s.connectCloudStorage);
  const disconnectCloudStorage = useDuckStore((s) => s.disconnectCloudStorage);
  const removeCloudConnection = useDuckStore((s) => s.removeCloudConnection);

  // Initialize cloud storage on mount
  useEffect(() => {
    if (!isCloudStorageInitialized) {
      initCloudStorage();
    }
  }, [isCloudStorageInitialized, initCloudStorage]);

  const handleConnect = async (id: string) => {
    await connectCloudStorage(id);
  };

  const handleDisconnect = async (id: string) => {
    await disconnectCloudStorage(id);
  };

  const handleRemove = async (id: string) => {
    await removeCloudConnection(id);
  };

  // Show warning if cloud storage is not supported
  const showWarning = cloudSupportStatus && !cloudSupportStatus.httpfsAvailable;

  return (
    <div className="space-y-2">
      {/* Header */}
      <div className="flex items-center justify-between px-2">
        <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
          Cloud
        </span>
        <TooltipProvider>
          <Tooltip>
            <TooltipTrigger asChild>
              <Button
                variant="ghost"
                size="icon"
                className="h-5 w-5"
                onClick={() => setIsModalOpen(true)}
              >
                <Plus className="h-3.5 w-3.5" />
              </Button>
            </TooltipTrigger>
            <TooltipContent>Add Cloud Connection</TooltipContent>
          </Tooltip>
        </TooltipProvider>
      </div>

      {/* Warning Banner */}
      {showWarning && (
        <div className="mx-2 p-2 rounded bg-yellow-500/10 border border-yellow-500/20">
          <div className="flex items-start gap-2">
            <CloudOff className="h-4 w-4 text-yellow-500 mt-0.5 shrink-0" />
            <p className="text-xs text-yellow-600 dark:text-yellow-400">
              Cloud storage has limited support in browsers. Consider using HTTPS URLs instead.
            </p>
          </div>
        </div>
      )}

      {/* Connections List */}
      {cloudConnections.length === 0 ? (
        <div className="px-2 py-4 text-center">
          <Cloud className="h-8 w-8 mx-auto text-muted-foreground/30 mb-2" />
          <p className="text-xs text-muted-foreground">No cloud connections</p>
          <Button
            variant="link"
            size="sm"
            className="text-xs h-auto p-0 mt-1"
            onClick={() => setIsModalOpen(true)}
          >
            Add your first connection
          </Button>
        </div>
      ) : (
        <div className="space-y-0.5">
          {cloudConnections.map((conn) => (
            <CloudConnectionItem
              key={conn.id}
              connection={conn}
              onConnect={() => handleConnect(conn.id)}
              onDisconnect={() => handleDisconnect(conn.id)}
              onRemove={() => handleRemove(conn.id)}
            />
          ))}
        </div>
      )}

      {/* Add Connection Modal */}
      <CloudConnectionModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
    </div>
  );
}


================================================
FILE: src/components/cloud/CloudConnectionModal.tsx
================================================
/**
 * Cloud Connection Modal
 * Form for adding/editing S3, GCS, and Azure cloud storage connections
 */

import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Loader2, Cloud, AlertTriangle } from "lucide-react";
import { useDuckStore } from "@/store";
import type { CloudConnection } from "@/store";

interface CloudConnectionModalProps {
  isOpen: boolean;
  onClose: () => void;
  existingConnection?: CloudConnection;
}

// Form validation schema
const cloudConnectionSchema = z
  .object({
    name: z.string().min(1, "Name is required"),
    type: z.enum(["s3", "gcs", "azure"]),
    // S3 fields
    bucket: z.string().optional(),
    region: z.string().optional(),
    accessKeyId: z.string().optional(),
    secretAccessKey: z.string().optional(),
    endpoint: z.string().optional(),
    // GCS fields
    hmacKeyId: z.string().optional(),
    hmacSecret: z.string().optional(),
    // Azure fields
    accountName: z.string().optional(),
    accountKey: z.string().optional(),
    containerName: z.string().optional(),
  })
  .refine(
    (data) => {
      // Validate required fields based on type
      if (data.type === "s3") {
        return data.bucket && data.accessKeyId && data.secretAccessKey;
      }
      if (data.type === "gcs") {
        return data.bucket && data.hmacKeyId && data.hmacSecret;
      }
      if (data.type === "azure") {
        return data.containerName && data.accountName && data.accountKey;
      }
      return true;
    },
    {
      message: "Please fill in all required fields for the selected provider",
    }
  );

type CloudConnectionFormData = z.infer<typeof cloudConnectionSchema>;

export function CloudConnectionModal({
  isOpen,
  onClose,
  existingConnection,
}: CloudConnectionModalProps) {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [isTesting, setIsTesting] = useState(false);
  const [testResult, setTestResult] = useState<{ success: boolean; error?: string } | null>(null);

  const addCloudConnection = useDuckStore((s) => s.addCloudConnection);
  const cloudSupportStatus = useDuckStore((s) => s.cloudSupportStatus);

  const form = useForm<CloudConnectionFormData>({
    resolver: zodResolver(cloudConnectionSchema),
    defaultValues: {
      name: existingConnection?.name || "",
      type: existingConnection?.type || "s3",
      bucket: existingConnection?.bucket || "",
      region: existingConnection?.region || "us-east-1",
      accessKeyId: existingConnection?.accessKeyId || "",
      secretAccessKey: existingConnection?.secretAccessKey || "",
      endpoint: existingConnection?.endpoint || "",
      hmacKeyId: existingConnection?.hmacKeyId || "",
      hmacSecret: existingConnection?.hmacSecret || "",
      accountName: existingConnection?.accountName || "",
      accountKey: existingConnection?.accountKey || "",
      containerName: existingConnection?.containerName || "",
    },
  });

  const selectedType = form.watch("type");

  const onSubmit = async (data: CloudConnectionFormData) => {
    setIsSubmitting(true);
    try {
      await addCloudConnection({
        name: data.name,
        type: data.type,
        bucket: data.bucket,
        region: data.region,
        accessKeyId: data.accessKeyId,
        secretAccessKey: data.secretAccessKey,
        endpoint: data.endpoint,
        hmacKeyId: data.hmacKeyId,
        hmacSecret: data.hmacSecret,
        accountName: data.accountName,
        accountKey: data.accountKey,
        containerName: data.containerName,
      });
      onClose();
    } finally {
      setIsSubmitting(false);
    }
  };

  const handleTest = async () => {
    setIsTesting(true);
    setTestResult(null);

    // For testing, we'd need to add the connection first, test it, then remove if failed
    // For now, show a message about the support status
    setTimeout(() => {
      if (!cloudSupportStatus?.httpfsAvailable) {
        setTestResult({
          success: false,
          error:
            "Cloud storage (httpfs) is not available in this browser. Direct S3/GCS/Azure access may not work due to CORS restrictions.",
        });
      } else if (!cloudSupportStatus?.secretsSupported) {
        setTestResult({
          success: false,
          error: "DuckDB secrets are not supported. Cloud storage authentication may not work.",
        });
      } else {
        setTestResult({
          success: true,
        });
      }
      setIsTesting(false);
    }, 500);
  };

  return (
    <Dialog open={isOpen} onOpenChange={onClose}>
      <DialogContent className="sm:max-w-[500px]">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2">
            <Cloud className="h-5 w-5" />
            {existingConnection ? "Edit" : "Add"} Cloud Connection
          </DialogTitle>
          <DialogDescription>
            Connect to cloud storage (S3, Google Cloud Storage, or Azure Blob Storage)
          </DialogDescription>
        </DialogHeader>

        {/* Support Status Warning */}
        {cloudSupportStatus && !cloudSupportStatus.httpfsAvailable && (
          <Alert variant="destructive">
            <AlertTriangle className="h-4 w-4" />
            <AlertDescription>
              Cloud storage support is limited in this browser. The httpfs extension is not
              available. You may need to use HTTPS URLs directly instead of s3:// or gcs://
              protocols.
            </AlertDescription>
          </Alert>
        )}

        <Form {...form}>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
            {/* Connection Name */}
            <FormField
              control={form.control}
              name="name"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Connection Name</FormLabel>
                  <FormControl>
                    <Input placeholder="My S3 Bucket" {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />

            {/* Provider Type */}
            <FormField
              control={form.control}
              name="type"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Provider</FormLabel>
                  <Select onValueChange={field.onChange} defaultValue={field.value}>
                    <FormControl>
                      <SelectTrigger>
                        <SelectValue placeholder="Select provider" />
                      </SelectTrigger>
                    </FormControl>
                    <SelectContent>
                      <SelectItem value="s3">Amazon S3 / S3-Compatible</SelectItem>
                      <SelectItem value="gcs">Google Cloud Storage</SelectItem>
                      <SelectItem value="azure">Azure Blob Storage</SelectItem>
                    </SelectContent>
                  </Select>
                  <FormDescription>
                    S3-compatible includes MinIO, Cloudflare R2, DigitalOcean Spaces
                  </FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />

            {/* S3 Fields */}
            {selectedType === "s3" && (
              <>
                <FormField
                  control={form.control}
                  name="bucket"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Bucket Name *</FormLabel>
                      <FormControl>
                        <Input placeholder="my-bucket" {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="region"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Region</FormLabel>
                      <FormControl>
                        <Input placeholder="us-east-1" {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="accessKeyId"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Access Key ID *</FormLabel>
                      <FormControl>
                        <Input placeholder="AKIAIOSFODNN7EXAMPLE" {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="secretAccessKey"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Secret Access Key *</FormLabel>
                      <FormControl>
                        <Input type="password" placeholder="wJalrXUtn..." {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="endpoint"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Custom Endpoint (Optional)</FormLabel>
                      <FormControl>
                        <Input placeholder="https://minio.example.com" {...field} />
                      </FormControl>
                      <FormDescription>For S3-compatible services like MinIO or R2</FormDescription>
                      <FormMessage />
                    </FormItem>
                  )}
                />
              </>
            )}

            {/* GCS Fields */}
            {selectedType === "gcs" && (
              <>
                <FormField
                  control={form.control}
                  name="bucket"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Bucket Name *</FormLabel>
                      <FormControl>
                        <Input placeholder="my-gcs-bucket" {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="hmacKeyId"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>HMAC Key ID *</FormLabel>
                      <FormControl>
                        <Input placeholder="GOOGTS7C7FUP..." {...field} />
                      </FormControl>
                      <FormDescription>
                        Generate HMAC keys in GCP Console under Cloud Storage Settings
                      </FormDescription>
                      <FormMessage />
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="hmacSecret"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>HMAC Secret *</FormLabel>
                      <FormControl>
                        <Input type="password" placeholder="..." {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />
              </>
            )}

            {/* Azure Fields */}
            {selectedType === "azure" && (
              <>
                <FormField
                  control={form.control}
                  name="containerName"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Container Name *</FormLabel>
                      <FormControl>
                        <Input placeholder="my-container" {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="accountName"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Storage Account Name *</FormLabel>
                      <FormControl>
                        <Input placeholder="mystorageaccount" {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="accountKey"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Account Key *</FormLabel>
                      <FormControl>
                        <Input type="password" placeholder="..." {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />
              </>
            )}

            {/* Test Result */}
            {testResult && (
              <Alert variant={testResult.success ? "default" : "destructive"}>
                <AlertDescription>
                  {testResult.success ? "Cloud storage support is available!" : testResult.error}
                </AlertDescription>
              </Alert>
            )}

            <DialogFooter className="gap-2">
              <Button type="button" variant="outline" onClick={handleTest} disabled={isTesting}>
                {isTesting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
                Test Support
              </Button>
              <Button type="submit" disabled={isSubmitting}>
                {isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
                {existingConnection ? "Update" : "Add"} Connection
              </Button>
            </DialogFooter>
          </form>
        </Form>
      </DialogContent>
    </Dialog>
  );
}


================================================
FILE: src/components/command-palette/CommandPalette.tsx
================================================
import { useEffect, useState, useMemo } from "react";
import {
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "@/components/ui/command";
import { useDuckStore } from "@/store";
import { useTheme } from "@/components/theme/theme-provider";
import {
  Terminal,
  Home,
  Cable,
  Brain,
  Settings,
  Sun,
  Moon,
  Database,
  Table,
  Bookmark,
} from "lucide-react";
import type { EditorTabType } from "@/store";
import {
  getSavedQueries,
  type SavedQuery,
} from "@/services/persistence/repositories/savedQueryRepository";

export default function CommandPalette() {
  const [open, setOpen] = useState(false);
  const { theme, setTheme } = useTheme();

  const tabs = useDuckStore((s) => s.tabs);
  const activeTabId = useDuckStore((s) => s.activeTabId);
  const createTab = useDuckStore((s) => s.createTab);
  const setActiveTab = useDuckStore((s) => s.setActiveTab);
  const toggleBrainPanel = useDuckStore((s) => s.toggleBrainPanel);
  const databases = useDuckStore((s) => s.databases);
  const connectionList = useDuckStore((s) => s.connectionList);
  const currentConnection = useDuckStore((s) => s.currentConnection);
  const setCurrentConnection = useDuckStore((s) => s.setCurrentConnection);
  const currentProfileId = useDuckStore((s) => s.currentProfileId);
  const savedQueriesVersion = useDuckStore((s) => s.savedQueriesVersion);

  const [savedQueries, setSavedQueries] = useState<SavedQuery[]>([]);

  // Load saved queries when palette opens
  useEffect(() => {
    if (open && currentProfileId) {
      getSavedQueries(currentProfileId)
        .then(setSavedQueries)
        .catch(() => {});
    }
  }, [open, currentProfileId, savedQueriesVersion]);

  // Keyboard shortcut
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
        e.preventDefault();
        setOpen((o) => !o);
      }
    };
    document.addEventListener("keydown", handleKeyDown);
    return () => document.removeEventListener("keydown", handleKeyDown);
  }, []);

  const openOrFocusTab = (type: EditorTabType, title: string) => {
    const existing = tabs.find((t) => t.type === type);
    if (existing) {
      setActiveTab(existing.id);
    } else {
      createTab(type, "", title);
    }
    setOpen(false);
  };

  const openTabs = useMemo(() => tabs.filter((t) => t.id !== activeTabId), [tabs, activeTabId]);

  const tableEntries = useMemo(() => {
    const entries: { database: string; table: string }[] = [];
    for (const db of databases) {
      for (const table of db.tables) {
        entries.push({ database: db.name, table: table.name });
      }
    }
    return entries;
  }, [databases]);

  return (
    <CommandDialog open={open} onOpenChange={setOpen}>
      <CommandInput placeholder="Search commands, tabs, tables..." />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>

        {/* Quick Actions */}
        <CommandGroup heading="Quick Actions">
          <CommandItem
            onSelect={() => {
              createTab("sql", "");
              setOpen(false);
            }}
          >
            <Terminal className="mr-2 h-4 w-4" />
            New SQL Tab
          </CommandItem>
          <CommandItem onSelect={() => openOrFocusTab("home", "Home")}>
            <Home className="mr-2 h-4 w-4" />
            Home
          </CommandItem>
          <CommandItem onSelect={() => openOrFocusTab("connections", "Connections")}>
            <Cable className="mr-2 h-4 w-4" />
            Connections
          </CommandItem>
          <CommandItem onSelect={() => openOrFocusTab("brain", "Duck Brain")}>
            <Brain className="mr-2 h-4 w-4" />
            Duck Brain
          </CommandItem>
          <CommandItem onSelect={() => openOrFocusTab("settings", "Settings")}>
            <Settings className="mr-2 h-4 w-4" />
            Settings
          </CommandItem>
          <CommandItem
            onSelect={() => {
              toggleBrainPanel();
              setOpen(false);
            }}
          >
            <Brain className="mr-2 h-4 w-4" />
            Toggle AI Panel
          </CommandItem>
          <CommandItem
            onSelect={() => {
              setTheme(theme === "dark" ? "light" : "dark");
              setOpen(false);
            }}
          >
            {theme === "dark" ? (
              <Sun className="mr-2 h-4 w-4" />
            ) : (
              <Moon className="mr-2 h-4 w-4" />
            )}
            {theme === "dark" ? "Light Theme" : "Dark Theme"}
          </CommandItem>
        </CommandGroup>

        {/* Open Tabs */}
        {openTabs.length > 0 && (
          <>
            <CommandSeparator />
            <CommandGroup heading="Open Tabs">
              {openTabs.map((tab) => (
                <CommandItem
                  key={tab.id}
                  onSelect={() => {
                    setActiveTab(tab.id);
                    setOpen(false);
                  }}
                >
                  <Terminal className="mr-2 h-4 w-4" />
                  {tab.title || tab.type}
                  <CommandShortcut>{tab.type}</CommandShortcut>
                </CommandItem>
              ))}
            </CommandGroup>
          </>
        )}

        {/* Connections */}
        {connectionList.connections.length > 1 && (
          <>
            <CommandSeparator />
            <CommandGroup heading="Connections">
              {connectionList.connections
                .filter((c) => c.id !== currentConnection?.id)
                .map((conn) => (
                  <CommandItem
                    key={conn.id}
                    onSelect={async () => {
                      await setCurrentConnection(conn.id);
                      setOpen(false);
                    }}
                  >
                    <Database className="mr-2 h-4 w-4" />
                    Switch to {conn.name}
                    <CommandShortcut>{conn.scope}</CommandShortcut>
                  </CommandItem>
                ))}
            </CommandGroup>
          </>
        )}

        {/* Saved Queries */}
        {savedQueries.length > 0 && (
          <>
            <CommandSeparator />
            <CommandGroup heading="Saved Queries">
              {savedQueries.map((query) => (
                <CommandItem
                  key={query.id}
                  onSelect={() => {
                    createTab("sql", query.sql_text, query.name);
                    setOpen(false);
                  }}
                >
                  <Bookmark className="mr-2 h-4 w-4" />
                  {query.name}
                </CommandItem>
              ))}
            </CommandGroup>
          </>
        )}

        {/* Tables */}
        {tableEntries.length > 0 && (
          <>
            <CommandSeparator />
            <CommandGroup heading="Tables">
              {tableEntries.map(({ database, table }) => (
                <CommandItem
                  key={`${database}.${table}`}
                  onSelect={() => {
                    const query = `SELECT * FROM "${database}"."${table}" LIMIT 100`;
                    createTab("sql", query, table);
                    setOpen(false);
                  }}
                >
                  <Table className="mr-2 h-4 w-4" />
                  {database}.{table}
                  <CommandShortcut>SELECT</CommandShortcut>
                </CommandItem>
              ))}
            </CommandGroup>
          </>
        )}
      </CommandList>
    </CommandDialog>
  );
}


================================================
FILE: src/components/common/FloatingActionButton.tsx
================================================
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { LucideIcon } from "lucide-react";

interface FloatingActionButtonProps {
  onClick: () => void;
  icon: LucideIcon;
  label: string;
  disabled?: boolean;
  className?: string;
  variant?: "default" | "outline" | "secondary";
}

export const FloatingActionButton: React.FC<FloatingActionButtonProps> = ({
  onClick,
  icon: Icon,
  label,
  disabled = false,
  className,
  variant = "default",
}) => {
  return (
    <Button
      onClick={onClick}
      disabled={disabled}
      variant={variant}
      size="lg"
      className={cn(
        // Base styles
        "fixed bottom-6 right-6 z-50",
        "h-14 px-6 rounded-full shadow-lg",
        "flex items-center gap-2",
        // Mobile-first (show by default)
        "md:hidden",
        // Animation
        "transition-all duration-200",
        "hover:scale-105 active:scale-95",
        // Shadow
        "shadow-[0_8px_16px_rgba(0,0,0,0.15)]",
        "dark:shadow-[0_8px_16px_rgba(0,0,0,0.3)]",
        className
      )}
      aria-label={label}
    >
      <Icon className="h-5 w-5" />
      <span className="font-medium">{label}</span>
    </Button>
  );
};

export default FloatingActionButton;


================================================
FILE: src/components/common/ImportOptionsPopover.tsx
================================================
import React, { useState } from "react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Import, Table, Link2 } from "lucide-react";
import { cn } from "@/lib/utils";

export interface ImportOptions {
  tableName: string;
  importMode: "table" | "view";
}

interface ImportOptionsPopoverProps {
  fileName: string;
  onImport: (options: ImportOptions) => void;
  children: React.ReactNode;
  disabled?: boolean;
}

/**
 * Generate a valid table name from a filename
 */
function generateTableName(fileName: string): string {
  return fileName
    .replace(/\.[^.]+$/, "") // Remove extension
    .replace(/[^a-zA-Z0-9_]/g, "_") // Replace special chars with underscore
    .replace(/^[0-9]/, "_$&") // Ensure doesn't start with number
    .replace(/_+/g, "_") // Collapse multiple underscores
    .replace(/^_|_$/g, ""); // Trim leading/trailing underscores
}

const ImportOptionsPopover: React.FC<ImportOptionsPopoverProps> = ({
  fileName,
  onImport,
  children,
  disabled = false,
}) => {
  const [open, setOpen] = useState(false);
  const [tableName, setTableName] = useState(generateTableName(fileName));
  const [importMode, setImportMode] = useState<"table" | "view">("table");

  const handleImport = () => {
    if (!tableName.trim()) return;
    onImport({ tableName: tableName.trim(), importMode });
    setOpen(false);
  };

  const handleOpenChange = (isOpen: boolean) => {
    if (isOpen) {
      // Reset to defaults when opening
      setTableName(generateTableName(fileName));
      setImportMode("table");
    }
    setOpen(isOpen);
  };

  return (
    <Popover open={open} onOpenChange={handleOpenChange}>
      <PopoverTrigger asChild disabled={disabled}>
        {children}
      </PopoverTrigger>
      <PopoverContent className="w-72" align="start" side="right">
        <div className="space-y-4">
          <div className="space-y-2">
            <h4 className="font-medium text-sm">Import Options</h4>
            <p className="text-xs text-muted-foreground truncate" title={fileName}>
              {fileName}
            </p>
          </div>

          <div className="space-y-2">
            <Label htmlFor="tableName" className="text-xs">
              Name
            </Label>
            <Input
              id="tableName"
              value={tableName}
              onChange={(e) => setTableName(e.target.value)}
              placeholder="table_name"
              className="h-8 text-sm"
            />
          </div>

          <div className="space-y-2">
            <Label className="text-xs">Mode</Label>
            <div className="flex gap-1">
              <Button
                type="button"
                variant={importMode === "table" ? "default" : "outline"}
                size="sm"
                className={cn("flex-1 gap-1.5 text-xs", importMode === "table" && "bg-primary")}
                onClick={() => setImportMode("table")}
              >
                <Table className="h-3.5 w-3.5" />
                Table
              </Button>
              <Button
                type="button"
                variant={importMode === "view" ? "default" : "outline"}
                size="sm"
                className={cn("flex-1 gap-1.5 text-xs", importMode === "view" && "bg-primary")}
                onClick={() => setImportMode("view")}
              >
                <Link2 className="h-3.5 w-3.5" />
                View
              </Button>
            </div>
            <p className="text-[10px] text-muted-foreground">
              {importMode === "table"
                ? "Copies data into DuckDB (faster queries)"
                : "Links to file (fresh data, less memory)"}
            </p>
          </div>

          <Button
            onClick={handleImport}
            disabled={!tableName.trim()}
            size="sm"
            className="w-full gap-1.5"
          >
            <Import className="h-3.5 w-3.5" />
            {importMode === "table" ? "Import" : "Link"}
          </Button>
        </div>
      </PopoverContent>
    </Popover>
  );
};

export default ImportOptionsPopover;


================================================
FILE: src/components/connection/ConnectionsModal.tsx
================================================
// ConnectionManager.tsx
import React from "react";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetFooter,
} from "@/components/ui/sheet";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { useDuckStore } from "@/store";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Info } from "lucide-react";

const scopeEnum = z.enum(["External", "OPFS"]);
const nameSchema = z
  .string()
  .min(2, {
    message: "Connection name must be at least 2 characters.",
  })
  .max(30, {
    message: "Connection name must not exceed 30 characters.",
  });

const opfsSchema = z.object({
  name: nameSchema,
  scope: z.literal(scopeEnum.enum.OPFS),
  path: z.string().min(1, {
    message: "Path is required.",
  }),
});

const externalSchema = z.object({
  name: nameSchema,
  scope: z.literal(scopeEnum.enum.External),
  host: z.string().url({
    message: "Host must be a valid URL.",
  }),
  port: z
    .string()
    .refine((val) => !isNaN(parseInt(val, 10)) || val === "", {
      message: "Port must be a number.",
    })
    .optional(),
  database: z.string().optional(),
  user: z.string().optional(),
  password: z.string().optional(),
  authMode: z.enum(["none", "password", "api_key"]).optional(),
  apiKey: z.string().optional(),
});

const connectionSchema = z.discriminatedUnion("scope", [opfsSchema, externalSchema]);

type ConnectionFormValues = z.infer<typeof connectionSchema>;

interface ConnectionManagerProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onSubmit: (values: ConnectionFormValues) => Promise<void>;
  initialValues?: ConnectionFormValues;
  isEditMode?: boolean;
}

const ConnectionManager: React.FC<ConnectionManagerProps> = ({
  open,
  onOpenChange,
  onSubmit,
  initialValues,
  isEditMode = false,
}) => {
  const form = useForm<ConnectionFormValues>({
    resolver: zodResolver(connectionSchema),
    defaultValues: initialValues || {
      name: "Local DuckDB",
      scope: "External" as const,
      host: "http://localhost:9999",
      port: "",
      database: "",
      user: "",
      password: "",
      authMode: "none" as const,
      apiKey: "",
    },
    mode: "onChange",
  });

  const currentScope = form.watch("scope");
  const isLoadingExternalConnection = useDuckStore((s) => s.isLoadingExternalConnection);

  const handleSubmit = async (values: ConnectionFormValues) => {
    await onSubmit(values);
    form.reset();
    onOpenChange(false);
  };

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent side="right" className="w-full sm:w-[450px] overflow-y-auto">
        <SheetHeader>
          <SheetTitle>{isEditMode ? "Edit Connection" : "Add New Connection"}</SheetTitle>
          <SheetDescription>
            {isEditMode
              ? "Modify existing connection details."
              : "Connect to a DuckDB instance or browser storage."}
          </SheetDescription>
        </SheetHeader>

        <div className="mt-6">
          <Form {...form}>
            <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
              <FormField
                control={form.control}
                name="name"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Connection Name</FormLabel>
                    <FormControl>
                      <Input placeholder="My Database" {...field} />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />

              <FormField
                control={form.control}
                name="scope"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Connection Type</FormLabel>
                    <Select onValueChange={field.onChange} defaultValue={field.value}>
                      <FormControl>
                        <SelectTrigger>
                          <SelectValue placeholder="Select type" />
                        </SelectTrigger>
                      </FormControl>
                      <SelectContent>
                        <SelectItem value="External">DuckDB HTTP Server</SelectItem>
                        <SelectItem value="OPFS">Browser Storage (OPFS)</SelectItem>
                      </SelectContent>
                    </Select>
                    <FormMessage />
                  </FormItem>
                )}
              />

              {/* External Connection Fields */}
              {currentScope === "External" && (
                <>
                  <Alert className="bg-muted/50">
                    <Info className="h-4 w-4" />
                    <AlertDescription className="text-xs space-y-1">
                      <p>Start HTTP server in DuckDB:</p>
                      <pre className="bg-background px-2 py-1 rounded text-[10px] leading-relaxed">
                        {`INSTALL httpserver FROM community;
LOAD httpserver;
SELECT httpserve_start('0.0.0.0', 9999, '');`}
                      </pre>
                    </AlertDescription>
                  </Alert>

                  <FormField
                    control={form.control}
                    name="host"
                    render={({ field }) => (
                      <FormItem>
                        <FormLabel>Host URL</FormLabel>
                        <FormControl>
                          <Input
                            placeholder="http://localhost:9999"
                            {...field}
                            value={field.value ?? ""}
                          />
                        </FormControl>
                        <FormDescription>Full URL including protocol (http/https)</FormDescription>
                        <FormMessage />
                      </FormItem>
                    )}
                  />

                  <FormField
                    control={form.control}
                    name="database"
                    render={({ field }) => (
                      <FormItem>
                        <FormLabel>Database (optional)</FormLabel>
                        <FormControl>
                          <Input placeholder="my_database" {...field} value={field.value ?? ""} />
                        </FormControl>
                        <FormMessage />
                      </FormItem>
                    )}
                  />

                  <FormField
                    control={form.control}
                    name="authMode"
                    render={({ field }) => (
                      <FormItem>
                        <FormLabel>Authentication</FormLabel>
                        <Select onValueChange={field.onChange} defaultValue={field.value}>
                          <FormControl>
                            <SelectTrigger>
                              <SelectValue placeholder="Select auth mode" />
                            </SelectTrigger>
                          </FormControl>
                          <SelectContent>
                            <SelectItem value="none">None</SelectItem>
                            <SelectItem value="password">Username/Password</SelectItem>
                            <SelectItem value="api_key">API Key</SelectItem>
                          </SelectContent>
                        </Select>
                        <FormMessage />
                      </FormItem>
                    )}
                  />

                  {form.watch("authMode") === "password" && (
                    <div className="grid grid-cols-2 gap-3">
                      <FormField
                        control={form.control}
                        name="user"
                        render={({ field }) => (
                          <FormItem>
                            <FormLabel>Username</FormLabel>
                            <FormControl>
                              <Input placeholder="user" {...field} value={field.value ?? ""} />
                            </FormControl>
                            <FormMessage />
                          </FormItem>
                        )}
                      />
                      <FormField
                        control={form.control}
                        name="password"
                        render={({ field }) => (
                          <FormItem>
                            <FormLabel>Password</FormLabel>
                            <FormControl>
                              <Input
                                type="password"
                                placeholder="********"
                                {...field}
                                value={field.value ?? ""}
                              />
                            </FormControl>
                            <FormMessage />
                          </FormItem>
                        )}
                      />
                    </div>
                  )}

                  {form.watch("authMode") === "api_key" && (
                    <FormField
                      control={form.control}
                      name="apiKey"
                      render={({ field }) => (
                        <FormItem>
                          <FormLabel>API Key</FormLabel>
                          <FormControl>
                            <Input
                              type="password"
                              placeholder="Enter API key"
                              {...field}
                              value={field.value ?? ""}
                            />
                          </FormControl>
                          <FormMessage />
                        </FormItem>
                      )}
                    />
                  )}
                </>
              )}

              {/* OPFS Fields */}
              {currentScope === "OPFS" && (
                <>
                  <Alert className="bg-muted/50">
                    <Info className="h-4 w-4" />
                    <AlertDescription className="text-xs">
                      Data persists in your browser across sessions.
                    </AlertDescription>
                  </Alert>

                  <FormField
                    control={form.control}
                    name="path"
                    render={({ field }) => (
                      <FormItem>
                        <FormLabel>Database File</FormLabel>
                        <FormControl>
                          <Input placeholder="my_data.db" {...field} value={field.value ?? ""} />
                        </FormControl>
                        <FormDescription>
                          Filename for your database (e.g., data.db)
                        </FormDescription>
                        <FormMessage />
                      </FormItem>
                    )}
                  />
                </>
              )}

              <SheetFooter className="pt-4">
                <Button
                  type="button"
                  variant="outline"
                  onClick={() => onOpenChange(false)}
                  disabled={isLoadingExternalConnection}
                >
                  Cancel
                </Button>
                <Button type="submit" disabled={isLoadingExternalConnection}>
                  {isLoadingExternalConnection
                    ? "Connecting..."
                    : isEditMode
                      ? "Update"
                      : "Connect"}
                </Button>
              </SheetFooter>
            </form>
          </Form>
        </div>
      </SheetContent>
    </Sheet>
  );
};

export default ConnectionManager;


================================================
FILE: src/components/duck-brain/DuckBrainCodeBlock.tsx
================================================
import React from "react";
import { Copy, Play, FileInput, Check, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import type { QueryResultArtifact } from "@/store";
import ResultsArtifact from "./ResultsArtifact";

interface DuckBrainCodeBlockProps {
  sql: string;
  messageId: string;
  queryResult?: QueryResultArtifact;
  onExecute?: (messageId: string, sql: string) => void;
  onInsert?: (sql: string) => void;
  className?: string;
}

const DuckBrainCodeBlock: React.FC<DuckBrainCodeBlockProps> = ({
  sql,
  messageId,
  queryResult,
  onExecute,
  onInsert,
  className,
}) => {
  const [copied, setCopied] = React.useState(false);
  const isRunning = queryResult?.status === "running";

  const handleCopy = async () => {
    try {
      await navigator.clipboard.writeText(sql);
      setCopied(true);
      toast.success("SQL copied to clipboard");
      setTimeout(() => setCopied(false), 2000);
    } catch {
      toast.error("Failed to copy");
    }
  };

  const handleExecute = () => {
    if (onExecute && !isRunning) {
      onExecute(messageId, sql);
    }
  };

  const handleRetry = () => {
    handleExecute();
  };

  return (
    <div className={cn("space-y-2", className)}>
      {/* SQL Code Block */}
      <div className="rounded-lg overflow-hidden border bg-muted/30">
        {/* SQL Code */}
        <pre className="p-3 text-xs overflow-x-auto">
          <code className="text-foreground font-mono whitespace-pre-wrap break-all">{sql}</code>
        </pre>

        {/* Actions */}
        <div className="flex items-center gap-1 p-2 border-t bg-muted/50">
          <Button variant="ghost" size="sm" onClick={handleCopy} className="h-7 text-xs gap-1">
            {copied ? (
              <>
                <Check className="h-3 w-3" />
                Copied
              </>
            ) : (
              <>
                <Copy className="h-3 w-3" />
                Copy
              </>
            )}
          </Button>

          {onInsert && (
            <Button
              variant="ghost"
              size="sm"
              onClick={() => onInsert(sql)}
              className="h-7 text-xs gap-1"
            >
              <FileInput className="h-3 w-3" />
              Insert
            </Button>
          )}

          {onExecute && (
            <Button
              variant="ghost"
              size="sm"
              onClick={handleExecute}
              disabled={isRunning}
              className="h-7 text-xs gap-1 text-primary"
            >
              {isRunning ? (
                <>
                  <Loader2 className="h-3 w-3 animate-spin" />
                  Running...
                </>
              ) : (
                <>
                  <Play className="h-3 w-3" />
                  Run
                </>
              )}
            </Button>
          )}
        </div>
      </div>

      {/* Results Artifact */}
      {queryResult && queryResult.status !== "pending" && (
        <ResultsArtifact queryResult={queryResult} onRetry={handleRetry} />
      )}
    </div>
  );
};

export default DuckBrainCodeBlock;


================================================
FILE: src/components/duck-brain/DuckBrainInput.tsx
================================================
import React, { useState, useRef, useEffect, useCallback } from "react";
import { Send, Square, Loader2, Table2, Columns } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import type { DatabaseInfo } from "@/store";
import SchemaAutocomplete, {
  buildSchemaSuggestions,
  type SchemaSuggestion,
} from "./SchemaAutocomplete";

// Render text with styled @ mentions
const renderWithMentions = (text: string) => {
  const mentionRegex = /@([\w.]+)/g;
  const parts: React.ReactNode[] = [];
  let lastIndex = 0;
  let match;

  while ((match = mentionRegex.exec(text)) !== null) {
    // Add text before the mention
    if (match.index > lastIndex) {
      parts.push(
        <span key={`text-${lastIndex}`} className="whitespace-pre-wrap">
          {text.slice(lastIndex, match.index)}
        </span>
      );
    }

    // Add the mention as a styled pill
    const mention = match[1];
    const isColumn = mention.includes(".");
    parts.push(
      <span
        key={`mention-${match.index}`}
        className="inline-flex items-center gap-0.5 px-1 py-px rounded bg-primary/20 text-primary text-xs font-medium align-baseline"
      >
        {isColumn ? <Columns className="h-3 w-3" /> : <Table2 className="h-3 w-3" />}
        {mention}
      </span>
    );

    lastIndex = match.index + match[0].length;
  }

  // Add remaining text
  if (lastIndex < text.length) {
    parts.push(
      <span key={`text-${lastIndex}`} className="whitespace-pre-wrap">
        {text.slice(lastIndex)}
      </span>
    );
  }

  return parts.length > 0 ? parts : text;
};

interface DuckBrainInputProps {
  onSend: (message: string) => void;
  onAbort?: () => void;
  isGenerating: boolean;
  disabled?: boolean;
  placeholder?: string;
  databases?: DatabaseInfo[];
  className?: string;
}

const DuckBrainInput: React.FC<DuckBrainInputProps> = ({
  onSend,
  onAbort,
  isGenerating,
  disabled = false,
  placeholder = "Ask Duck Brain to write SQL... (@ for tables)",
  databases = [],
  className,
}) => {
  const [input, setInput] = useState("");
  const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false);
  const [suggestions, setSuggestions] = useState<SchemaSuggestion[]>([]);
  const [activeIndex, setActiveIndex] = useState(0);
  const [mentionStart, setMentionStart] = useState<number | null>(null);
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const containerRef = useRef<HTMLFormElement>(null);

  // Auto-resize textarea
  useEffect(() => {
    const textarea = textareaRef.current;
    if (textarea) {
      textarea.style.height = "auto";
      textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
    }
  }, [input]);

  // Detect @ mentions and filter suggestions
  const handleInputChange = useCallback(
    (e: React.ChangeEvent<HTMLTextAreaElement>) => {
      const value = e.target.value;
      const cursorPos = e.target.selectionStart || 0;
      setInput(value);

      // Find the @ before cursor
      const textBeforeCursor = value.slice(0, cursorPos);
      const lastAtIndex = textBeforeCursor.lastIndexOf("@");

      if (lastAtIndex !== -1) {
        // Check if there's a space between @ and cursor (mention ended)
        const textAfterAt = textBeforeCursor.slice(lastAtIndex + 1);
        const hasSpace = /\s/.test(textAfterAt);

        if (!hasSpace) {
          // Active mention - show suggestions
          setMentionStart(lastAtIndex);
          const filter = textAfterAt;
          const filtered = buildSchemaSuggestions(databases, filter);
          setSuggestions(filtered);
          setIsAutocompleteOpen(filtered.length > 0);
          setActiveIndex(0);
          return;
        }
      }

      // No active mention
      setIsAutocompleteOpen(false);
      setMentionStart(null);
    },
    [databases]
  );

  // Insert selected suggestion
  const insertSuggestion = useCallback(
    (suggestion: SchemaSuggestion) => {
      if (mentionStart === null) return;

      const before = input.slice(0, mentionStart);
      const cursorPos = textareaRef.current?.selectionStart || input.length;
      const after = input.slice(cursorPos);

      // Insert with @ prefix so it renders as a pill
      const insertText = `@${suggestion.fullPath}`;
      const newInput = `${before}${insertText} ${after}`;

      setInput(newInput);
      setIsAutocompleteOpen(false);
      setMentionStart(null);

      // Focus and set cursor position
      setTimeout(() => {
        const newCursorPos = before.length + insertText.length + 1;
        textareaRef.current?.focus();
        textareaRef.current?.setSelectionRange(newCursorPos, newCursorPos);
      }, 0);
    },
    [input, mentionStart]
  );

  const handleKeyDown = (e: React.KeyboardEvent) => {
    // Handle autocomplete navigation
    if (isAutocompleteOpen && suggestions.length > 0) {
      switch (e.key) {
        case "ArrowDown":
          e.preventDefault();
          setActiveIndex((prev) => (prev + 1) % suggestions.length);
          return;
        case "ArrowUp":
          e.preventDefault();
          setActiveIndex((prev) => (prev === 0 ? suggestions.length - 1 : prev - 1));
          return;
        case "Tab":
        case "Enter":
          if (suggestions[activeIndex]) {
            e.preventDefault();
            insertSuggestion(suggestions[activeIndex]);
            return;
          }
          break;
        case "Escape":
          e.preventDefault();
          setIsAutocompleteOpen(false);
          return;
      }
    }

    // Regular Enter to send (if not in autocomplete)
    if (e.key === "Enter" && !e.shiftKey && !isAutocompleteOpen) {
      e.preventDefault();
      handleSubmit();
    }
  };

  const handleSubmit = (e?: React.FormEvent) => {
    e?.preventDefault();
    if (!input.trim() || isGenerating || disabled) return;

    onSend(input.trim());
    setInput("");
    setIsAutocompleteOpen(false);
  };

  // Close autocomplete when clicking outside
  useEffect(() => {
    const handleClickOutside = (e: MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
        setIsAutocompleteOpen(false);
      }
    };

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

  // Check if input has any @ mentions to show overlay
  const hasMentions = /@[\w.]+/.test(input);

  return (
    <form ref={containerRef} onSubmit={handleSubmit} className={cn("relative", className)}>
      {/* Visual overlay for styled mentions */}
      {hasMentions && (
        <div
          className="absolute inset-0 pointer-events-none px-3 py-[9px] text-sm overflow-hidden"
          aria-hidden="true"
        >
          <div className="whitespace-pre-wrap break-words leading-normal">
            {renderWithMentions(input)}
          </div>
        </div>
      )}
      <Textarea
        ref={textareaRef}
        value={input}
        onChange={handleInputChange}
        onKeyDown={handleKeyDown}
        placeholder={placeholder}
        disabled={disabled || isGenerating}
        className={cn(
          "min-h-[44px] max-h-[120px] resize-none pr-12",
          "text-sm placeholder:text-muted-foreground/60",
          // Make text transparent when we have mentions to show overlay
          hasMentions && "text-transparent caret-foreground"
        )}
        rows={1}
      />

      {/* Schema Autocomplete Popover */}
      <SchemaAutocomplete
        isOpen={isAutocompleteOpen}
        suggestions={suggestions}
        activeIndex={activeIndex}
        onSelect={insertSuggestion}
        position={{ top: 50, left: 0 }}
      />

      <div className="absolute right-2 bottom-2">
        {isGenerating ? (
          <Button
            type="button"
            variant="ghost"
            size="icon"
            onClick={onAbort}
            className="h-8 w-8 text-destructive hover:text-destructive"
          >
            <Square className="h-4 w-4" />
          </Button>
        ) : (
          <Button
            type="submit"
            variant="ghost"
            size="icon"
            disabled={!input.trim() || disabled}
            className="h-8 w-8"
          >
            {disabled ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
          </Button>
        )}
      </div>
    </form>
  );
};

export default DuckBrainInput;


================================================
FILE: src/components/duck-brain/DuckBrainMessages.tsx
================================================
import React, { useRef, useEffect } from "react";
import { User, Bot, Table2, Columns } from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import type { DuckBrainMessage } from "@/store";
import DuckBrainCodeBlock from "./DuckBrainCodeBlock";
import MarkdownContent from "./MarkdownContent";

// Render @ mentions as styled pills
const renderMentions = (content: string) => {
  // Match @table_name or @table.column patterns
  const mentionRegex = /@([\w.]+)/g;
  const parts: (string | React.ReactNode)[] = [];
  let lastIndex = 0;
  let match;

  while ((match = mentionRegex.exec(content)) !== null) {
    // Add text before the mention
    if (match.index > lastIndex) {
      parts.push(content.slice(lastIndex, match.index));
    }

    // Add the mention as a pill
    const mention = match[1];
    const isColumn = mention.includes(".");
    parts.push(
      <span
        key={`${match.index}-${mention}`}
        className="inline-flex items-center gap-1 px-1.5 py-0.5 mx-0.5 rounded-md bg-primary/20 text-primary-foreground text-xs font-medium"
      >
        {isColumn ? <Columns className="h-3 w-3" /> : <Table2 className="h-3 w-3" />}
        {mention}
      </span>
    );

    lastIndex = match.index + match[0].length;
  }

  // Add remaining text
  if (lastIndex < content.length) {
    parts.push(content.slice(lastIndex));
  }

  return parts.length > 0 ? parts : content;
};

interface DuckBrainMessagesProps {
  messages: DuckBrainMessage[];
  streamingContent: string;
  isGenerating: boolean;
  onExecuteSQL?: (messageId: string, sql: string) => void;
  onInsertSQL?: (sql: string) => void;
  className?: string;
}

const DuckBrainMessages: React.FC<DuckBrainMessagesProps> = ({
  messages,
  streamingContent,
  isGenerating,
  onExecuteSQL,
  onInsertSQL,
  className,
}) => {
  const scrollRef = useRef<HTMLDivElement>(null);
  const bottomRef = useRef<HTMLDivElement>(null);

  // Auto-scroll to bottom on new messages
  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages, streamingContent]);

  const formatTime = (date: Date) => {
    return new Date(date).toLocaleTimeString([], {
      hour: "2-digit",
      minute: "2-digit",
    });
  };

  if (messages.length === 0 && !isGenerating) {
    return (
      <div className={cn("flex-1 flex items-center justify-center p-4", className)}>
        <div className="text-center text-muted-foreground">
          <Bot className="h-12 w-12 mx-auto mb-3 opacity-50" />
          <p className="text-sm font-medium">Hi! I'm Duck Brain</p>
          <p className="text-xs mt-1">Ask me to write SQL queries for your data</p>
        </div>
      </div>
    );
  }

  return (
    <ScrollArea className={cn("flex-1", className)} ref={scrollRef}>
      <div className="p-4 space-y-4">
        {messages.map((message) => (
          <div
            key={message.id}
            className={cn("flex gap-3", message.role === "user" ? "justify-end" : "justify-start")}
          >
            {message.role === "assistant" && (
              <div className="flex-shrink-0 w-7 h-7 rounded-full bg-primary/10 flex items-center justify-center">
                <Bot className="h-4 w-4 text-primary" />
              </div>
            )}

            <div
              className={cn(
                "max-w-[85%] space-y-2",
                message.role === "user" ? "items-end" : "items-start"
              )}
            >
              <div
                className={cn(
                  "rounded-2xl px-4 py-2",
                  message.role === "user"
                    ? "bg-primary text-primary-foreground rounded-br-sm"
                    : "bg-muted rounded-bl-sm"
                )}
              >
                {message.role === "user" ? (
                  <p className="text-sm whitespace-pre-wrap">{renderMentions(message.content)}</p>
                ) : (
                  <MarkdownContent content={message.content} skipCodeBlocks={!!message.sql} />
                )}
              </div>

              {/* SQL Code Block for assistant messages with extracted SQL */}
              {message.role === "assistant" && message.sql && (
                <DuckBrainCodeBlock
                  sql={message.sql}
                  messageId={message.id}
                  queryResult={message.queryResult}
                  onExecute={onExecuteSQL}
                  onInsert={onInsertSQL}
                />
              )}

              <span className="text-[10px] text-muted-foreground px-1">
                {formatTime(message.timestamp)}
              </span>
            </div>

            {message.role === "user" && (
              <div className="flex-shrink-0 w-7 h-7 rounded-full bg-muted flex items-center justify-center">
                <User className="h-4 w-4" />
              </div>
            )}
          </div>
        ))}

        {/* Streaming response */}
        {isGenerating && streamingContent && (
          <div className="flex gap-3 justify-start">
            <div className="flex-shrink-0 w-7 h-7 rounded-full bg-primary/10 flex items-center justify-center">
              <Bot className="h-4 w-4 text-primary animate-pulse" />
            </div>
            <div className="max-w-[85%]">
              <div className="rounded-2xl rounded-bl-sm bg-muted px-4 py-2">
                <p className="text-sm whitespace-pre-wrap">
                  {streamingContent}
                  <span className="inline-block w-1 h-4 bg-primary ml-1 animate-pulse" />
                </p>
              </div>
            </div>
          </div>
        )}

        {/* Loading indicator */}
        {isGenerating && !streamingContent && (
          <div className="flex gap-3 justify-start">
            <div className="flex-shrink-0 w-7 h-7 rounded-full bg-primary/10 flex items-center justify-center">
              <Bot className="h-4 w-4 text-primary" />
            </div>
            <div className="rounded-2xl rounded-bl-sm bg-muted px-4 py-2">
              <div className="flex gap-1">
                <span className="w-2 h-2 bg-muted-foreground/50 rounded-full animate-bounce" />
                <span className="w-2 h-2 bg-muted-foreground/50 rounded-full animate-bounce [animation-delay:150ms]" />
                <span className="w-2 h-2 bg-muted-foreground/50 rounded-full animate-bounce [animation-delay:300ms]" />
              </div>
            </div>
          </div>
        )}

        <div ref={bottomRef} />
      </div>
    </ScrollArea>
  );
};

export default DuckBrainMessages;


================================================
FILE: src/components/duck-brain/DuckBrainPanel.tsx
================================================
import React, { useCallback, useMemo } from "react";
import { Brain, X, Loader2, AlertCircle, Download, Trash2, RefreshCw, Cloud } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useDuckStore, type AIProviderType } from "@/store";
import { AVAILABLE_MODELS, DEFAULT_MODEL } from "@/lib/duckBrain";
import { OPENAI_MODELS, ANTHROPIC_MODELS } from "@/lib/duckBrain/providers/types";
import DuckBrainMessages from "./DuckBrainMessages";
import DuckBrainInput from "./DuckBrainInput";
import { toast } from "sonner";

interface DuckBrainPanelProps {
  tabId: string;
}

const DuckBrainPanel: React.FC<DuckBrainPanelProps> = React.memo(({ tabId }) => {
  const duckBrain = useDuckStore((s) => s.duckBrain);
  const databases = useDuckStore((s) => s.databases);
  const toggleBrainPanel = useDuckStore((s) => s.toggleBrainPanel);
  const initializeDuckBrain = useDuckStore((s) => s.initializeDuckBrain);
  const generateSQL = useDuckStore((s) => s.generateSQL);
  const abortGeneration = useDuckStore((s) => s.abortGeneration);
  const clearBrainMessages = useDuckStore((s) => s.clearBrainMessages);
  const executeQueryInChat = useDuckStore((s) => s.executeQueryInChat);
  const updateTabQuery = useDuckStore((s) => s.updateTabQuery);
  const setAIProvider = useDuckStore((s) => s.setAIProvider);

  const {
    modelStatus,
    downloadProgress,
    downloadStatus,
    isWebGPUSupported,
    error,
    messages,
    isGenerating,
    streamingContent,
    aiProvider = "webllm",
    providerConfigs = {},
  } = duckBrain;

  // Get the display name for the current provider/model
  const providerDisplayInfo = useMemo(() => {
    if (aiProvider === "openai") {
      const config = providerConfigs.openai;
      if (config?.apiKey) {
        const model = OPENAI_MODELS.find((m) => m.id === config.modelId);
        return { name: model?.name || "GPT-4o Mini", isCloud: true };
      }
    } else if (aiProvider === "anthropic") {
      const config = providerConfigs.anthropic;
      if (config?.apiKey) {
        const model = ANTHROPIC_MODELS.find((m) => m.id === config.modelId);
        return { name: model?.name || "Claude Sonnet 4", isCloud: true };
      }
    } else if (aiProvider === "openai-compatible") {
      const config = providerConfigs["openai-compatible"];
      if (config?.baseUrl && config?.modelId) {
        return { name: config.modelId, isCloud: true };
      }
    }
    // Default to local model
    const localModel = AVAILABLE_MODELS.find((m) => m.id === duckBrain.currentModel);
    return { name: localModel?.displayName || "Local Model", isCloud: false };
  }, [aiProvider, providerConfigs, duckBrain.currentModel]);

  // Build list of available providers for selector
  const availableProviders = useMemo(() => {
    const providers: { value: AIProviderType; label: string }[] = [];

    // Add WebLLM if model is loaded or loading
    if (modelStatus === "ready" || modelStatus === "downloading" || modelStatus === "loading") {
      const localModel = AVAILABLE_MODELS.find((m) => m.id === duckBrain.currentModel);
      providers.push({
        value: "webllm",
        label: localModel?.displayName || "Local Model",
      });
    }

    // Add OpenAI if configured
    if (providerConfigs.openai?.apiKey) {
      const model = OPENAI_MODELS.find((m) => m.id === providerConfigs.openai?.modelId);
      providers.push({
        value: "openai",
        label: model?.name || "GPT-4o Mini",
      });
    }

    // Add Anthropic if configured
    if (providerConfigs.anthropic?.apiKey) {
      const model = ANTHROPIC_MODELS.find((m) => m.id === providerConfigs.anthropic?.modelId);
      providers.push({
        value: "anthropic",
        label: model?.name || "Claude Sonnet 4",
      });
    }

    // Add OpenAI-Compatible if configured
    if (
      providerConfigs["openai-compatible"]?.baseUrl &&
      providerConfigs["openai-compatible"]?.modelId
    ) {
      providers.push({
        value: "openai-compatible",
        label: providerConfigs["openai-compatible"].modelId,
      });
    }

    return providers;
  }, [modelStatus, duckBrain.currentModel, providerConfigs]);

  const handleSend = useCallback(
    async (message: string) => {
      await generateSQL(message);
    },
    [generateSQL]
  );

  const handleExecuteSQL = useCallback(
    async (messageId: string, sql: string) => {
      try {
        const result = await executeQueryInChat(messageId, sql);
        if (result) {
          toast.success(`Query returned ${result.rowCount} rows`);
        }
      } catch {
        // Error is already handled in the store and shown in ResultsArtifact
      }
    },
    [executeQueryInChat]
  );

  const handleInsertSQL = useCallback(
    (sql: string) => {
      updateTabQuery(tabId, sql);
      toast.success("SQL inserted into editor");
    },
    [updateTabQuery, tabId]
  );

  const handleInitialize = useCallback(async () => {
    await initializeDuckBrain(DEFAULT_MODEL.id);
  }, [initializeDuckBrain]);

  // Render WebGPU not supported state
  if (isWebGPUSupported === false) {
    return (
      <div className="flex flex-col h-full border-l bg-background">
        <Header onClose={toggleBrainPanel} />
        <div className="flex-1 flex items-center justify-center p-4">
          <Alert variant="destructive" className="max-w-sm">
            <AlertCircle className="h-4 w-4" />
            <AlertDescription className="mt-2">
              <p className="font-medium">WebGPU Not Supported</p>
              <p className="text-xs mt-1">
                Duck Brain requires WebGPU for local AI processing. Please use Chrome 113+ or Edge
                113+.
              </p>
            </AlertDescription>
          </Alert>
        </div>
      </div>
    );
  }

  // Check if external provider is configured
  const hasExternalProvider =
    (aiProvider === "openai" && providerConfigs.openai?.apiKey) ||
    (aiProvider === "anthropic" && providerConfigs.anthropic?.apiKey) ||
    (aiProvider === "openai-compatible" &&
      providerConfigs["openai-compatible"]?.baseUrl &&
      providerConfigs["openai-compatible"]?.modelId);

  // Render idle state (not initialized) - only for WebLLM without external provider
  if ((modelStatus === "idle" || modelStatus === "checking") && !hasExternalProvider) {
    return (
      <div className="flex flex-col h-full border-l bg-background">
        <Header onClose={toggleBrainPanel} />
        <div className="flex-1 flex items-center justify-center p-4">
          <div className="text-center max-w-sm">
            <Brain className="h-12 w-12 mx-auto mb-4 text-primary" />
            <h3 className="font-semibold mb-2">Initialize Duck Brain</h3>
            <p className="text-sm text-muted-foreground mb-4">
              Download an AI model to enable natural language to SQL conversion. This runs 100%
              locally in your browser.
            </p>
            <div className="space-y-2 text-xs text-muted-foreground mb-4">
              <p>
                <strong>Model:</strong> {DEFAULT_MODEL.displayName}
              </p>
              <p>
                <strong>Size:</strong> {DEFAULT_MODEL.size}
              </p>
              <p>First load downloads the model. Future loads use cache.</p>
            </div>
            <Button onClick={handleInitialize} className="gap-2">
              <Download className="h-4 w-4" />
              Load AI Model
            </Button>
          </div>
        </div>
      </div>
    );
  }

  // Render downloading/loading state
  if (modelStatus === "downloading" || modelStatus === "loading") {
    return (
      <div className="flex flex-col h-full border-l bg-background">
        <Header onClose={toggleBrainPanel} />
        <div className="flex-1 flex items-center justify-center p-4">
          <div className="text-center max-w-sm w-full">
            <Loader2 className="h-8 w-8 mx-auto mb-4 animate-spin text-primary" />
            <h3 className="font-semibold mb-2">
              {modelStatus === "downloading" ? "Downloading Model..." : "Loading Model..."}
            </h3>
            <Progress value={downloadProgress} className="mb-2" />
            <p className="text-xs text-muted-foreground">
              {downloadStatus || `${downloadProgress}%`}
            </p>
          </div>
        </div>
      </div>
    );
  }

  // Render error state
  if (modelStatus === "error") {
    return (
      <div className="flex flex-col h-full border-l bg-background">
        <Header onClose={toggleBrainPanel} />
        <div className="flex-1 flex items-center justify-center p-4">
          <div className="text-center max-w-sm">
            <AlertCircle className="h-12 w-12 mx-auto mb-4 text-destructive" />
            <h3 className="font-semibold mb-2">Failed to Load Model</h3>
            <p className="text-sm text-muted-foreground mb-4">
              {error || "An unknown error occurred"}
            </p>
            <Button onClick={handleInitialize} variant="outline" className="gap-2">
              <RefreshCw className="h-4 w-4" />
              Try Again
            </Button>
          </div>
        </div>
      </div>
    );
  }

  // Render ready state with chat interface
  return (
    <div className="flex flex-col h-full border-l bg-background">
      <Header
        onClose={toggleBrainPanel}
        onClear={clearBrainMessages}
        showClear={messages.length > 0}
      />

      {/* Status Badge & Provider Selector */}
      <div className="px-3 py-2 border-b">
        <div className="flex items-center gap-2">
          <Badge variant="secondary" className="bg-green-500/10 text-green-600 text-xs">
            <span className="w-1.5 h-1.5 rounded-full bg-green-500 mr-1.5" />
            Ready
          </Badge>
          {/* Provider selector - show when multiple providers available */}
          {availableProviders.length > 1 ? (
            <Select
              value={aiProvider}
              onValueChange={(value) => setAIProvider(value as AIProviderType)}
            >
              <SelectTrigger className="h-6 w-auto gap-1 px-2 text-xs border-0 bg-transparent">
                <div className="flex items-center gap-1">
                  {providerDisplayInfo.isCloud && <Cloud className="h-3 w-3" />}
                  <SelectValue />
                </div>
              </SelectTrigger>
              <SelectContent>
                {availableProviders.map((p) => (
                  <SelectItem key={p.value} value={p.value} className="text-xs">
                    {p.label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          ) : (
            <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
              {providerDisplayInfo.isCloud && <Cloud className="h-3 w-3" />}
              <span>{providerDisplayInfo.name}</span>
            </div>
          )}
        </div>
      </div>

      {/* Messages */}
      <DuckBrainMessages
        messages={messages}
        streamingContent={streamingContent}
        isGenerating={isGenerating}
        onExecuteSQL={handleExecuteSQL}
        onInsertSQL={handleInsertSQL}
        className="flex-1"
      />

      {/* Input */}
      <div className="p-3 border-t">
        <DuckBrainInput
          onSend={handleSend}
          onAbort={abortGeneration}
          isGenerating={isGenerating}
          disabled={modelStatus !== "ready" && !hasExternalProvider}
          databases={databases}
          placeholder="Ask Duck Brain... (@ for tables)"
        />
      </div>
    </div>
  );
});

// Header component
interface HeaderProps {
  onClose: () => void;
  onClear?: () => void;
  showClear?: boolean;
}

const Header: React.FC<HeaderProps> = ({ onClose, onClear, showClear }) => (
  <div className="flex items-center justify-between px-3 py-2 border-b">
    <div className="flex items-center gap-2">
      <Brain className="h-5 w-5 text-primary" />
      <span className="font-semibold text-sm">Duck Brain</span>
    </div>
    <div className="flex items-center gap-1">
      {showClear && onClear && (
        <Button variant="ghost" size="icon" onClick={onClear} className="h-7 w-7">
          <Trash2 className="h-4 w-4" />
        </Button>
      )}
      <Button variant="ghost" size="icon" onClick={onClose} className="h-7 w-7">
        <X className="h-4 w-4" />
      </Button>
    </div>
  </div>
);

export default DuckBrainPanel;


================================================
FILE: src/components/duck-brain/MarkdownContent.tsx
================================================
import React from "react";
import ReactMarkdown, { Components } from "react-markdown";
import { cn } from "@/lib/utils";

interface MarkdownContentProps {
  content: string;
  skipCodeBlocks?: boolean;
  className?: string;
}

/**
 * Renders markdown content with custom styling.
 * When skipCodeBlocks is true, removes ```sql...``` blocks since they're handled separately.
 */
const MarkdownContent: React.FC<MarkdownContentProps> = ({
  content,
  skipCodeBlocks = false,
  className,
}) => {
  // Remove ALL code blocks if they're handled separately by DuckBrainCodeBlock
  // Uses two passes to catch all variations:
  // 1. Fenced blocks with language identifier and newline: ```sql\n...\n```
  // 2. Fallback for any remaining code blocks: ```...```
  const processedContent = skipCodeBlocks
    ? content
        .replace(/```\w*\n[\s\S]*?```/g, "") // Code blocks with newline after lang
        .replace(/```[\s\S]*?```/g, "") // Any remaining code blocks
        .replace(/\n{3,}/g, "\n\n") // Collapse multiple blank lines
        .trim()
    : content;

  // If nothing left after removing code blocks, don't render
  if (!processedContent) {
    return null;
  }

  const components: Components = {
    // Inline code styling
    code: ({ className: codeClassName, children, ...props }) => {
      // Check if this is a code block (has language class) vs inline code
      const isCodeBlock = codeClassName?.includes("language-");

      if (isCodeBlock) {
        return (
          <pre className="bg-muted p-3 rounded-md overflow-x-auto my-2">
            <code className={cn("text-sm font-mono", codeClassName)} {...props}>
              {children}
            </code>
          </pre>
        );
      }

      // Inline code
      return (
        <code className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono" {...props}>
          {children}
        </code>
      );
    },
    // Paragraph styling
    p: ({ children }) => <p className="mb-2 last:mb-0 leading-relaxed">{children}</p>,
    // Strong/bold
    strong: ({ children }) => <strong className="font-semibold">{children}</strong>,
    // Emphasis/italic
    em: ({ children }) => <em className="italic">{children}</em>,
    // Unordered list
    ul: ({ children }) => <ul className="list-disc pl-4 mb-2 space-y-1">{children}</ul>,
    // Ordered list
    ol: ({ children }) => <ol className="list-decimal pl-4 mb-2 space-y-1">{children}</ol>,
    // List item
    li: ({ children }) => <li className="leading-relaxed">{children}</li>,
    // Links
    a: ({ href, children }) => (
      <a
        href={href}
        target="_blank"
        rel="noopener noreferrer"
        className="text-primary underline hover:no-underline"
      >
        {children}
      </a>
    ),
    // Blockquote
    blockquote: ({ children }) => (
      <blockquote className="border-l-2 border-muted-foreground/30 pl-3 italic my-2">
        {children}
      </blockquote>
    ),
    // Headings (rarely used in chat but good to have)
    h1: ({ children }) => <h1 className="text-lg font-bold mt-3 mb-2">{children}</h1>,
    h2: ({ children }) => <h2 className="text-base font-bold mt-3 mb-2">{children}</h2>,
    h3: ({ children }) => <h3 className="text-sm font-bold mt-2 mb-1">{children}</h3>,
    // Horizontal rule
    hr: () => <hr className="my-3 border-muted-foreground/20" />,
  };

  return (
    <div className={cn("text-sm prose-sm max-w-none", className)}>
      <ReactMarkdown components={components}>{processedContent}</ReactMarkdown>
    </div>
  );
};

export default MarkdownContent;


================================================
FILE: src/components/duck-brain/ResultsArtifact.tsx
================================================
import React, { useState } from "react";
import { Loader2, AlertCircle, ChevronDown, ChevronUp, Table2, RotateCcw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Collapsible, CollapsibleTrigger } from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import type { QueryResultArtifact } from "@/store";

interface ResultsArtifactProps {
  queryResult: QueryResultArtifact;
  onRetry?: () => void;
  className?: string;
}

const MAX_INLINE_ROWS = 5;
const MAX_INLINE_COLUMNS = 6;

const ResultsArtifact: React.FC<ResultsArtifactProps> = ({ queryResult, onRetry, className }) => {
  const [isExpanded, setIsExpanded] = useState(false);
  const { status, data, error } = queryResult;

  // Running state
  if (status === "running") {
    return (
      <div
        className={cn(
          "flex items-center gap-2 p-3 rounded-lg border border-border bg-muted/30",
          className
        )}
      >
        <Loader2 className="h-4 w-4 animate-spin text-primary" />
        <span className="text-sm text-muted-foreground">Executing query...</span>
      </div>
    );
  }

  // Error state
  if (status === "error") {
    return (
      <div
        className={cn("p-3 rounded-lg border border-destructive/50 bg-destructive/5", className)}
      >
        <div className="flex items-start gap-2">
          <AlertCircle className="h-4 w-4 text-destructive mt-0.5 flex-shrink-0" />
          <div className="flex-1 min-w-0">
            <p className="text-sm font-medium text-destructive">Query failed</p>
            <p className="text-xs text-muted-foreground mt-1 break-words">
              {error || "Unknown error occurred"}
            </p>
          </div>
          {onRetry && (
            <Button variant="ghost" size="sm" onClick={onRetry} className="flex-shrink-0 h-7 px-2">
              <RotateCcw className="h-3 w-3 mr-1" />
              Retry
            </Button>
          )}
        </div>
      </div>
    );
  }

  // Pending state - shouldn't normally show, but handle it
  if (status === "pending" || !data) {
    return null;
  }

  // Success state - show results
  const { columns, columnTypes, data: rows, rowCount } = data;
  const hasMoreRows = rowCount > MAX_INLINE_ROWS;
  const hasMoreColumns = columns.length > MAX_INLINE_COLUMNS;
  const displayRows = isExpanded ? rows.slice(0, 50) : rows.slice(0, MAX_INLINE_ROWS);
  const displayColumns = columns.slice(0, MAX_INLINE_COLUMNS);

  return (
    <div className={cn("rounded-lg border border-border overflow-hidden bg-card", className)}>
      {/* Header */}
      <div className="flex items-center justify-between px-3 py-2 bg-muted/50 border-b">
        <div className="flex items-center gap-2">
          <Table2 className="h-3.5 w-3.5 text-muted-foreground" />
          <span className="text-xs font-medium">Results</span>
          <Badge variant="secondary" className="text-[10px] px-1.5 py-0">
            {rowCount.toLocaleString()} row{rowCount !== 1 ? "s" : ""}
          </Badge>
        </div>
      </div>

      {/* Table */}
      <div className="overflow-x-auto">
        <table className="w-full text-xs">
          <thead>
            <tr className="border-b bg-muted/30">
              {displayColumns.map((col, i) => (
                <th
                  key={col}
                  className="px-3 py-1.5 text-left font-medium text-muted-foreground whitespace-nowrap"
                >
                  <div className="flex flex-col">
                    <span>{col}</span>
                    <span className="text-[10px] font-normal opacity-60">{columnTypes[i]}</span>
                  </div>
                </th>
              ))}
              {hasMoreColumns && (
                <th className="px-3 py-1.5 text-left font-medium text-muted-foreground">
                  <span className="text-[10px]">+{columns.length - MAX_INLINE_COLUMNS} more</span>
                </th>
              )}
            </tr>
          </thead>
          <tbody>
            {displayRows.map((row, rowIdx) => (
              <tr key={rowIdx} className="border-b last:border-0 hover:bg-muted/20">
                {displayColumns.map((col) => (
                  <td
                    key={col}
                    className="px-3 py-1.5 whitespace-nowrap max-w-[200px] truncate"
                    title={String(row[col] ?? "")}
                  >
                    {formatCellValue(row[col])}
                  </td>
                ))}
                {hasMoreColumns && <td className="px-3 py-1.5 text-muted-foreground">...</td>}
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Expand/Collapse */}
      {hasMoreRows && (
        <Collapsible open={isExpanded} onOpenChange={setIsExpanded}>
          <CollapsibleTrigger asChild>
            <button className="w-full px-3 py-1.5 text-xs text-center text-muted-foreground hover:text-foreground hover:bg-muted/50 flex items-center justify-center gap-1 border-t">
              {isExpanded ? (
                <>
                  <ChevronUp className="h-3 w-3" />
                  Show less
                </>
              ) : (
                <>
                  <ChevronDown className="h-3 w-3" />
                  Show more ({Math.min(50, rowCount) - MAX_INLINE_ROWS} more rows)
                </>
              )}
            </button>
          </CollapsibleTrigger>
        </Collapsible>
      )}
    </div>
  );
};

// Helper to format cell values for display
function formatCellValue(value: unknown): string {
  if (value === null || value === undefined) {
    return "NULL";
  }
  if (typeof value === "boolean") {
    return value ? "true" : "false";
  }
  if (typeof value === "object") {
    return JSON.stringify(value);
  }
  if (typeof value === "number") {
    // Format numbers nicely
    if (Number.isInteger(value)) {
      return value.toLocaleString();
    }
    return value.toLocaleString(undefined, { maximumFractionDigits: 4 });
  }
  return String(value);
}

export default ResultsArtifact;


================================================
FILE: src/components/duck-brain/SchemaAutocomplete.tsx
================================================
import React, { useEffect, useRef } from "react";
import { Table2, Columns3 } from "lucide-react";
import { cn } from "@/lib/utils";
import type { DatabaseInfo } from "@/store";

export interface SchemaSuggestion {
  type: "table" | "column";
  name: string;
  fullPath: string;
  tableName?: string;
  columnType?: string;
  rowCount?: number;
}

interface SchemaAutocompleteProps {
  isOpen: boolean;
  suggestions: SchemaSuggestion[];
  activeIndex: number;
  onSelect: (suggestion: SchemaSuggestion) => void;
  position: { top: number; left: number };
  className?: string;
}

/**
 * Builds suggestions from database schema
 */
export function buildSchemaSuggestions(
  databases: DatabaseInfo[],
  filter: string = ""
): SchemaSuggestion[] {
  const suggestions: SchemaSuggestion[] = [];
  const lowerFilter = filter.toLowerCase();

  // Check if filter contains a dot (table.column)
  const dotIndex = filter.indexOf(".");
  const tableFilter = dotIndex > 0 ? filter.slice(0, dotIndex).toLowerCase() : null;
  const columnFilter = dotIndex > 0 ? filter.slice(dotIndex + 1).toLowerCase() : null;

  for (const db of databases) {
    for (const table of db.tables) {
      const tableName = db.name === "memory" ? table.name : `${db.name}.${table.name}`;

      // If filtering for columns of a specific table
      if (tableFilter) {
        if (table.name.toLowerCase() === tableFilter || tableName.toLowerCase() === tableFilter) {
          // Show columns for this table
          for (const col of table.columns) {
            if (!columnFilter || col.name.toLowerCase().startsWith(columnFilter)) {
              suggestions.push({
                type: "column",
                name: col.name,
                fullPath: `${table.name}.${col.name}`,
                tableName: table.name,
                columnType: col.type,
              });
            }
          }
        }
      } else {
        // Show tables matching filter
        if (!lowerFilter || table.name.toLowerCase().startsWith(lowerFilter)) {
          suggestions.push({
            type: "table",
            name: table.name,
            fullPath: tableName,
            rowCount: table.rowCount,
          });
        }
      }
    }
  }

  // Sort: tables first, then columns, alphabetically
  return suggestions
    .sort((a, b) => {
      if (a.type !== b.type) return a.type === "table" ? -1 : 1;
      return a.name.localeCompare(b.name);
    })
    .slice(0, 10); // Limit to 10 suggestions
}

const SchemaAutocomplete: React.FC<SchemaAutocompleteProps> = ({
  isOpen,
  suggestions,
  activeIndex,
  onSelect,
  position,
  className,
}) => {
  const listRef = useRef<HTMLDivElement>(null);

  // Scroll active item into view
  useEffect(() => {
    if (listRef.current && activeIndex >= 0) {
      const activeItem = listRef.current.children[activeIndex] as HTMLElement;
      activeItem?.scrollIntoView({ block: "nearest" });
    }
  }, [activeIndex]);

  if (!isOpen || suggestions.length === 0) {
    return null;
  }

  return (
    <div
      className={cn(
        "absolute z-50 w-64 max-h-48 overflow-auto",
        "bg-popover border rounded-md shadow-lg",
        className
      )}
      style={{ bottom: position.top, left: position.left }}
    >
      <div ref={listRef} className="py-1">
        {suggestions.map((suggestion, index) => (
          <button
            key={`${suggestion.type}-${suggestion.fullPath}`}
            type="button"
            onClick={() => onSelect(suggestion)}
            className={cn(
              "w-full px-3 py-1.5 text-left text-sm flex items-center gap-2",
              "hover:bg-accent",
              index === activeIndex && "bg-accent"
            )}
          >
            {suggestion.type === "table" ? (
              <Table2 className="h-3.5 w-3.5 text-primary flex-shrink-0" />
            ) : (
              <Columns3 className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
            )}
            <div className="flex-1 min-w-0">
              <span className="font-medium truncate block">{suggestion.name}</span>
              {suggestion.type === "table" && (
                <span className="text-[10px] text-muted-foreground">
                  {suggestion.rowCount ? `${suggestion.rowCount.toLocaleString()} rows` : "table"}
                </span>
              )}
              {suggestion.type === "column" && suggestion.columnType && (
                <span className="text-[10px] text-muted-foreground">{suggestion.columnType}</span>
              )}
            </div>
          </button>
        ))}
      </div>
      <div className="px-3 py-1.5 text-[10px] text-muted-foreground border-t bg-muted/30">
        <kbd className="px-1 rounded bg-muted">↑↓</kbd> navigate
        <span className="mx-1.5">·</span>
        <kbd className="px-1 rounded bg-muted">Tab</kbd> select
        <span className="mx-1.5">·</span>
        <kbd className="px-1 rounded bg-muted">Esc</kbd> close
      </div>
    </div>
  );
};

export default SchemaAutocomplete;


================================================
FILE: src/components/editor/SqlEditor.tsx
================================================
import React, { useRef, useEffect, useState, useCallback } from "react";
import {
  Play,
  Loader2,
  Lightbulb,
  Command,
  Edit,
  Share2,
  Brain,
  Bookmark,
  ListTree,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { useDuckStore } from "@/store";
import { useTheme } from "../theme/theme-provider";
import { cn } from "@/lib/utils";
import { createEditor, useMonacoConfig, type EditorInstance } from "./monacoConfig";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { Input } from "@/components/ui/input";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import FloatingActionButton from "@/components/common/FloatingActionButton";
import { copyQueryURL } from "@/hooks/useQueryFromURL";
import SaveQueryDialog from "@/components/saved-queries/SaveQueryDialog";
import { ExplainPlanViewer } from "@/components/workspace/ExplainPlanViewer";

interface SqlEditorProps {
  tabId: string;
  title: string;
  className?: string;
}

const SqlEditor: React.FC<SqlEditorProps> = ({ tabId, title, className }) => {
  const editorRef = useRef<HTMLDivElement>(null);
  const editorInstanceRef = useRef<EditorInstance | null>(null);
  const { theme } = useTheme();
  const tabs = useDuckStore((s) => s.tabs);
  const executeQuery = useDuckStore((s) => s.executeQuery);
  const isExecuting = useDuckStore((s) => !!s.executingTabs[tabId]);
  const updateTabTitle = useDuckStore((s) => s.updateTabTitle);
  const toggleBrainPanel = useDuckStore((s) => s.toggleBrainPanel);
  const duckBrain = useDuckStore((s) => s.duckBrain);
  const currentProfileId = useDuckStore((s) => s.currentProfileId);
  const monacoConfig = useMonacoConfig(theme);

  const currentTab = tabs.find((tab) => tab.id === tabId);
  const currentContent =
    currentTab?.type === "sql" && typeof currentTab.content === "string" ? currentTab.content : "";

  const [isEditingTitle, setIsEditingTitle] = useState(false);
  const [currentTitle, setCurrentTitle] = useState(title);
  const [saveDialogOpen, setSaveDialogOpen] = useState(false);
  const [explainOpen, setExplainOpen] = useState(false);
  const [explainText, setExplainText] = useState("");

  // Stable callback for query execution
  const stableExecuteCallback = useCallback(
    async (query: string, queryTabId: string) => {
      await executeQuery(query, queryTabId);
    },
    [executeQuery] // Add executeQuery as a dependency
  );

  // Editor initialization effect
  useEffect(() => {
    if (!editorRef.current) return;

    // Initialize editor with stable configuration
    editorInstanceRef.current = createEditor(
      editorRef.current,
      monacoConfig,
      currentContent,
      tabId,
      stableExecuteCallback
    );

    // Cleanup function
    return () => {
      if (editorInstanceRef.current) {
        editorInstanceRef.current.dispose();
        editorInstanceRef.current = null;
      }
    };
  }, [tabId, monacoConfig, stableExecuteCallback]); // Keep stableExecuteCallback

  // Content sync effect
  useEffect(() => {
    const editor = editorInstanceRef.current?.editor;
    if (editor && currentContent !== editor.getValue()) {
      const position = editor.getPosition();
      editor.setValue(currentContent);
      if (position) {
        editor.setPosition(position);
      }
    }
  }, [currentContent]); // Only depend on currentContent

  const handleExecuteQuery = async () => {
    const editor = editorInstanceRef.current?.editor;
    if (!editor || isExecuting) return;

    const query = editor.getValue().trim();
    if (!query) return;

    try {
      await executeQuery(query, tabId);
    } catch (error) {
      console.error("Query execution failed:", error);
      toast.error("Query execution failed");
    }
  };

  const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setCurrentTitle(e.target.value);
  };

  const handleTitleSubmit = () => {
    if (currentTitle.trim()) {
      updateTabTitle(tabId, currentTitle);
      setIsEditingTitle(false);
      toast.success(`Tab title updated to ${currentTitle}`);
    } else {
      setCurrentTitle(title);
      setIsEditingTitle(false);
      toast.error("Title cannot be empty");
    }
  };

  const handleTitleEdit = () => {
    setIsEditingTitle(true);
  };

  const handleExplainQuery = async () => {
    const editor = editorInstanceRef.current?.editor;
    if (!editor || isExecuting) return;

    const query = editor.getValue().trim();
    if (!query) return;

    try {
      // Run without tabId so the result is returned without overwriting the tab's data
      const result = await executeQuery(`EXPLAIN ANALYZE ${query}`);
      if (result && result.data?.length > 0) {
        // DuckDB returns rows with explain_key / explain_value — the analyzed_plan row has the full plan
        const planRow = result.data.find((row) => row["explain_key"] === "analyzed_plan");
        const planText = planRow
          ? String(planRow["explain_value"])
          : result.data.map((row) => String(row["explain_value"] ?? "")).join("\n");
        setExplainText(planText);
        setExplainOpen(true);
      }
    } catch (error) {
      console.error("Explain failed:", error);
      toast.error("Explain query failed");
    }
  };

  const handleShareQuery = async () => {
    const editor = editorInstanceRef.current?.editor;
    if (!editor) return;

    const query = editor.getValue().trim();
    if (!query) {
      toast.error("No query to share");
      return;
    }

    const success = await copyQueryURL(query, false);
    if (success) {
      toast.success("Query URL copied to clipboard");
    } else {
      toast.error("Failed to copy URL");
    }
  };

  return (
    <div className={cn("flex flex-col h-full relative", className)}>
      {/* Header */}
      <div className="flex items-center justify-between px-4 py-2 border-b">
        {/* Title (always visible) */}
        <div className="flex items-center gap-2 flex-1 min-w-0">
          {isEditingTitle ? (
            <Input
              className="text-sm font-medium truncate max-w-[200px]"
              value={currentTitle}
              onChange={handleTitleChange}
              onBlur={handleTitleSubmit}
              onKeyDown={(e) => {
                if (e.key === "Enter") {
                  handleTitleSubmit();
                } else if (e.key === "Escape") {
                  setCurrentTitle(title);
                  setIsEditingTitle(false);
                }
              }}
              autoFocus
            />
          ) : (
            <div className="flex items-center gap-2">
              <span className="text-lg font-medium truncate text-sm">{currentTitle}</span>
              <Button
                variant="ghost"
                size="icon"
                onClick={handleTitleEdit}
                className="group-hover:opacity-100 transition-opacity hidden md:flex"
                aria-label="Edit tab title"
              >
                <Edit className="h-4 w-4" />
              </Button>
            </div>
          )}
        </div>

        {/* Desktop Actions */}
        <div className="hidden md:flex items-center gap-4">
          <div className="flex gap-2 text-sm text-muted-foreground">
            <TooltipProvider>
              <Tooltip delayDuration={200}>
                <TooltipTrigger className="hover:bg-muted/50 p-2 rounded-md transition-colors">
                  <Lightbulb className="h-5 w-5 text-yellow-500/70 hover:text-yellow-500 transition-colors" />
                </TooltipTrigger>
                <TooltipContent side="bottom" className="w-72 p-0" sideOffset={5}>
                  <div className="bg-card px-3 py-2 rounded-t-sm border-b">
                    <h4 className="font-medium flex items-center gap-2">
                      <Command className="h-4 w-4" />
                      SQL Editor Shortcuts
                    </h4>
                  </div>
                  <div className="p-3 space-y-3">
                    <div className="flex items-center justify-between">
                      <span className="text-sm">Run Query</span>
                      <Badge variant="secondary" className="font-mono text-xs">
                        Ctrl + Enter
                      </Badge>
                    </div>
                    <div className="flex items-center justify-between">
                      <span className="text-sm">Run Selected</span>
                      <Badge variant="secondary" className="font-mono text-xs">
                        Ctrl + Shift + Enter
                      </Badge>
                    </div>
                  </div>
                </TooltipContent>
              </Tooltip>
            </TooltipProvider>
          </div>
          <TooltipProvider>
            <Tooltip delayDuration={200}>
              <TooltipTrigger asChild>
                <Button onClick={handleShareQuery} variant="ghost" size="icon" className="h-9 w-9">
                  <Share2 className="h-4 w-4" />
                </Button>
              </TooltipTrigger>
              <TooltipContent side="bottom">
                <p>Copy shareable URL</p>
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip delayDuration={200}>
              <TooltipTrigger asChild>
                <Button
                  onClick={() => setSaveDialogOpen(true)}
                  variant="ghost"
                  size="icon"
                  className="h-9 w-9"
                  disabled={!currentContent.trim() || !currentProfileId}
                >
                  <Bookmark className="h-4 w-4" />
                </Button>
              </TooltipTrigger>
              <TooltipContent side="bottom">
                <p>Save Query</p>
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip delayDuration={200}>
              <TooltipTrigger asChild>
                <Button
                  onClick={toggleBrainPanel}
                  variant={duckBrain.isPanelOpen ? "secondary" : "ghost"}
                  size="icon"
                  className="h-9 w-9"
                >
                  <Brain className={cn("h-4 w-4", duckBrain.isPanelOpen && "text-primary")} />
                </Button>
              </TooltipTrigger>
              <TooltipContent side="bottom">
                <p>{duckBrain.isPanelOpen ? "Close Duck Brain" : "Open Duck Brain"}</p>
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip delayDuration={200}>
              <TooltipTrigger asChild>
                <Button
                  onClick={handleExplainQuery}
                  disabled={isExecuting}
                  variant="ghost"
                  size="icon"
                  className="h-9 w-9"
                >
                  <ListTree className="h-4 w-4" />
                </Button>
              </TooltipTrigger>
              <TooltipContent side="bottom">
                <p>Explain Plan</p>
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <Button
            onClick={handleExecuteQuery}
            disabled={isExecuting}
            variant="outline"
            className="flex items-center gap-2 min-w-[100px]"
          >
            {isExecuting ? (
              <Loader2 className="h-4 w-4 animate-spin" />
            ) : (
              <Play className="h-4 w-4" />
            )}
            {isExecuting ? "Running..." : "Run Query"}
          </Button>
        </div>
      </div>

      {/* Editor */}
      <div className="flex-1 relative">
        <div ref={editorRef} className="h-full w-full absolute inset-0" />
      </div>

      {/* Mobile FAB */}
      <FloatingActionButton
        onClick={handleExecuteQuery}
        icon={isExecuting ? Loader2 : Play}
        label={isExecuting ? "Running..." : "Run"}
        disabled={isExecuting}
        className={isExecuting ? "animate-pulse" : ""}
      />

      <SaveQueryDialog
        open={saveDialogOpen}
        onOpenChange={setSaveDialogOpen}
        defaultName={currentTitle}
        sqlText={currentContent}
      />

      <ExplainPlanViewer
        open={explainOpen}
        onOpenChange={setExplainOpen}
        explainText={explainText}
      />
    </div>
  );
};

export default SqlEditor;


================================================
FILE: src/components/editor/monacoConfig.ts
================================================
// monacoConfig.ts
import * as monaco from "monaco-editor";
import { useDuckStore } from "@/store";
import { useMemo } from "react";
import type { editor } from "monaco-editor";
import { toast } from "sonner";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import { format } from "sql-formatter";
import { sqlEscapeString } from "@/lib/sqlSanitize";

// Types
export interface EditorInstance {
  editor: editor.IStandaloneCodeEditor;
  dispose: () => void;
}

interface EditorConfig {
  language: string;
  theme: string;
  automaticLayout: boolean;
  tabSize: number;
  minimap: { enabled: boolean };
  padding: { top: number };
  suggestOnTriggerCharacters: boolean;
  quickSuggestions: boolean;
  wordBasedSuggestions: boolean;
  fontSize: number;
  lineNumbers: "on" | "off" | "relative";
  scrollBeyondLastLine: boolean;
  cursorBlinking: "blink" | "smooth" | "phase" | "expand" | "solid";
  matchBrackets: "always" | "never" | "near";
  rulers: number[];
}

// Worker configuration
self.MonacoEnvironment = {
  getWorker(_workerId: string) {
    return new editorWorker();
  },
};

// Create editor instance
export const createEditor = (
  container: HTMLElement,
  config: EditorConfig,
  initialContent: string,
  tabId: string,
  executeQueryFn: (query: string, tabId: string) => Promise<void>
): EditorInstance => {
  const editor = monaco.editor.create(container, {
    ...config,
    value: initialContent,
    wordBasedSuggestions: config.wordBasedSuggestions ? "allDocuments" : "off",
    bracketPairColorization: { enabled: true },
    guides: { bracketPairs: true, indentation: true },
    renderWhitespace: "selection",
    smoothScrolling: true,
    cursorSmoothCaretAnimation: "on",
    formatOnPaste: true,
    formatOnType: true,
    snippetSuggestions: "inline",
    suggest: {
      preview: true,
      showMethods: true,
      showFunctions: true,
      showVariables: true,
      showWords: true,
      showColors: true,
    },
  });

  // Add commands
  editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, async () => {
    const query = editor.getValue().trim();
    if (!query) {
      toast.error("Please enter a query to execute");
      return;
    }
    try {
      await executeQueryFn(query, tabId);
    } catch (err) {
      toast.error(
        `Query execution failed: ${err instanceof Error ? err.message : "Unknown error"}`
      );
    }
  });

  editor.addCommand(monaco.KeyMod.Alt | monaco.KeyCode.KeyF, () => {
    const formatAction = editor.getAction("editor.action.formatDocument");
    formatAction?.run();
  });

  // Add context menu actions
  editor.addAction({
    id: "execute-selection",
    label: "Execute Selected Query",
    keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter],
    contextMenuGroupId: "navigation",
    run: async (ed) => {
      const selection = ed.getSelection();
      const selectedText = selection ? ed.getModel()?.getValueInRange(selection) : "";

      if (selectedText?.trim()) {
        try {
          await executeQueryFn(selectedText.trim(), tabId);
        } catch (err) {
          toast.error(
            `Query execution failed: ${err instanceof Error ? err.message : "Unknown error"}`
          );
        }
      }
    },
  });

  editor.addAction({
    id: "format-sql",
    label: "Format SQL",
    keybindings: [monaco.KeyMod.Alt | monaco.KeyCode.KeyF],
    contextMenuGroupId: "modification",
    run: (ed) => {
      const text = ed.getValue();
      try {
        const formatted = format(text, {
          language: "sql",
          keywordCase: "upper",
          indentStyle: "standard",
          linesBetweenQueries: 2,
        });
        ed.setValue(formatted);
      } catch {
        toast.error("Failed to format SQL");
      }
    },
  });

  // Setup content change listener with debounce
  let timeoutId: number;
  const disposable = editor.onDidChangeModelContent(() => {
    clearTimeout(timeoutId);
    timeoutId = window.setTimeout(() => {
      const newValue = editor.getValue();
      useDuckStore.getState().updateTabQuery(tabId, newValue);
    }, 300);
  });

  return {
    editor,
    dispose: () => {
      clearTimeout(timeoutId);
      disposable.dispose();
      editor.dispose();
    },
  };
};

// Create a lightweight editor for notebook cells (no auto-save to tab query)
export const createCellEditor = (
  container: HTMLElement,
  config: EditorConfig,
  initialContent: string,
  executeQueryFn: () => Promise<void>,
  onContentChange: (value: string) => void
): EditorInstance => {
  const editor = monaco.editor.create(container, {
    ...config,
    value: initialContent,
    wordBasedSuggestions: config.wordBasedSuggestions ? "allDocuments" : "off",
    bracketPairColorization: { enabled: true },
    guides: { bracketPairs: true, indentation: true },
    renderWhitespace: "selection",
    smoothScrolling: true,
    cursorSmoothCaretAnimation: "on",
    formatOnPaste: true,
    formatOnType: true,
    snippetSuggestions: "inline",
    suggest: {
      preview: true,
      showMethods: true,
      showFunctions: true,
      showVariables: true,
      showWords: true,
      showColors: true,
    },
  });

  // Ctrl/Cmd+Enter to run cell
  editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, async () => {
    const query = editor.getValue().trim();
    if (!query) return;
    try {
      await executeQueryFn();
    } catch (err) {
      toast.error(
        `Query execution failed: ${err instanceof Error ? err.message : "Unknown error"}`
      );
    }
  });

  // Shift+Enter to run cell (Jupyter-style)
  editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, async () => {
    const query = editor.getValue().trim();
    if (!query) return;
    try {
      await executeQueryFn();
    } catch (err) {
      toast.error(
        `Query execution failed: ${err instanceof Error ? err.message : "Unknown error"}`
      );
    }
  });

  editor.addCommand(monaco.KeyMod.Alt | monaco.KeyCode.KeyF, () => {
    const formatAction = editor.getAction("editor.action.formatDocument");
    formatAction?.run();
  });

  editor.addAction({
    id: "format-sql",
    label: "Format SQL",
    keybindings: [monaco.KeyMod.Alt | monaco.KeyCode.KeyF],
    contextMenuGroupId: "modification",
    run: (ed) => {
      const text = ed.getValue();
      try {
        const formatted = format(text, {
          language: "sql",
          keywordCase: "upper",
          indentStyle: "standard",
          linesBetweenQueries: 2,
        });
        ed.setValue(formatted);
      } catch {
        toast.error("Failed to format SQL");
      }
    },
  });

  // Content change listener — calls provided callback instead of updateTabQuery
  let timeoutId: number;
  const disposable = editor.onDidChangeModelContent(() => {
    clearTimeout(timeoutId);
    timeoutId = window.setTimeout(() => {
      onContentChange(editor.getValue());
    }, 300);
  });

  return {
    editor,
    dispose: () => {
      clearTimeout(timeoutId);
      disposable.dispose();
      editor.dispose();
    },
  };
};

// Enhanced config hook with better defaults
export const useMonacoConfig = (theme: string): EditorConfig => {
  return useMemo(
    () => ({
      language: "sql",
      theme: theme === "dark" ? "vs-dark" : "vs",
      automaticLayout: true,
      tabSize: 2,
      minimap: { enabled: false },
      padding: { top: 10 },
      suggestOnTriggerCharacters: true,
      quickSuggestions: true,
      wordBasedSuggestions: false,
      fontSize: 12,
      lineNumbers: "on",
      scrollBeyondLastLine: false,
      cursorBlinking: "blink",
      matchBrackets: "always",
      rulers: [],
    }),
    [theme]
  );
};

// Register SQL formatting provider
monaco.languages.registerDocumentFormattingEditProvider("sql", {
  provideDocumentFormattingEdits: (model) => {
    try {
      const formatted = format(model.getValue(), {
        language: "sql",
        keywordCase: "upper",
        indentStyle: "standard",
        linesBetweenQueries: 2,
      });

      return [
        {
          range: model.getFullModelRange(),
          text: formatted,
        },
      ];
    } catch (err) {
      console.error("SQL formatting failed:", err);
      return [];
    }
  },
});

// Adapt to use the WASM autocompletion
interface AutocompleteItem {
  suggestion: string;
}
const queryNative = async <T>(
  connection: { query: (sql: string) => Promise<{ toArray: () => unknown[] }> },
  query: string
): Promise<T[]> => {
  const results = await connection.query(query);
  return results.toArray().map((row: unknown) => row as T);
};

monaco.languages.registerCompletionItemProvider("sql", {
  triggerCharacters: [" ", ".", "(", ","],
  async provideCompletionItems(model, position) {
    const word = model.getWordUntilPosition(position);
    const range = {
      startLineNumber: position.lineNumber,
      endLineNumber: position.lineNumber,
      startColumn: word.startColumn,
      endColumn: word.endColumn,
    };
    const textInRange = model.getValueInRange({
      startColumn: 0,
      endColumn: position.column,
      startLineNumber: position.lineNumber,
      endLineNumber: position.lineNumber,
    });

    // Get the connection and ensure it's valid
    const { connection } = useDuckStore.getState();
    if (!connection) {
      console.warn("No database connection available for autocompletion.");
      return { suggestions: [] };
    }
    try {
      const escapedText = sqlEscapeString(textInRange);
      const query = `select suggestion from sql_auto_complete('${escapedText}')`;
      const items: AutocompleteItem[] = await queryNative<AutocompleteItem>(connection, query);

      const suggestions = items.map((item) => {
        return {
          label: String(item.suggestion),
          kind: monaco.languages.CompletionItemKind.Field,
          insertText: String(item.suggestion),
          range,
        };
      });

      return { suggestions };
    } catch (error) {
      console.error("Autocompletion query failed:", error);
      return { suggestions: [] };
    }
  },
});

// Export everything needed
export default {
  createEditor,
  useMonacoConfig,
};


================================================
FILE: src/components/explorer/ColumnNode.tsx
================================================
import React, { useState } from "react";
import { ChevronRight, ChevronDown, Hash, Type, Calendar, ToggleLeft } from "lucide-react";
import { type ColumnStats } from "@/store";

interface ColumnNodeProps {
  stats: ColumnStats;
}

const getTypeIcon = (type: string) => {
  const upperType = type.toUpperCase();
  if (
    upperType.includes("INT") ||
    upperType.includes("DOUBLE") ||
    upperType.includes("FLOAT") ||
    upperType.includes("DECIMAL")
  ) {
    return <Hash className="w-3 h-3" />;
  } else if (upperType.includes("DATE") || upperType.includes("TIME")) {
    return <Calendar className="w-3 h-3" />;
  } else if (upperType.includes("BOOL")) {
    return <ToggleLeft className="w-3 h-3" />;
  }
  return <Type className="w-3 h-3" />;
};

const getTypeColor = (type: string) => {
  const upperType = type.toUpperCase();
  if (
    upperType.includes("INT") ||
    upperType.includes("DOUBLE") ||
    upperType.includes("FLOAT") ||
    upperType.includes("DECIMAL")
  ) {
    return "text-purple-500 bg-purple-500/10";
  } else if (upperType.includes("DATE") || upperType.includes("TIME")) {
    return "text-green-500 bg-green-500/10";
  } else if (upperType.includes("BOOL")) {
    return "text-yellow-500 bg-yellow-500/10";
  }
  return "text-blue-500 bg-blue-500/10";
};

const getFillColor = (percentage: number) => {
  if (percentage >= 90) return "bg-green-500";
  if (percentage >= 50) return "bg-yellow-500";
  return "bg-red-500";
};

export const ColumnNode: React.FC<ColumnNodeProps> = ({ stats }) => {
  const [isExpanded, setIsExpanded] = useState(false);

  // Safe parsing function that handles both string and number types
  const parseValue = (value: string | number): number => {
    if (typeof value === "number") return value;
    if (typeof value === "string") {
      // Remove quotes if present
      const cleaned = value.replace(/"/g, "");
      return parseFloat(cleaned) || 0;
    }
    return 0;
  };

  const nullPercentage = parseValue(stats.null_percentage);
  const fillPercentage = 100 - nullPercentage;
  const uniqueCount = stats.approx_unique ? parseValue(stats.approx_unique) : 0;
  const totalCount = parseValue(stats.count);

  const isNumeric =
    stats.column_type.toUpperCase().includes("INT") ||
    stats.column_type.toUpperCase().includes("DOUBLE") ||
    stats.column_type.toUpperCase().includes("FLOAT") ||
    stats.column_type.toUpperCase().includes("DECIMAL");

  return (
    <div className="ml-8">
      <div
        className="flex items-center py-1.5 px-2 hover:bg-secondary/50 rounded-md cursor-pointer group"
        onClick={() => setIsExpanded(!isExpanded)}
      >
        <div className="flex-1 flex items-center gap-2 min-w-0">
          {isExpanded ? (
            <ChevronDown className="w-3 h-3 flex-shrink-0 text-muted-foreground" />
          ) : (
            <ChevronRight className="w-3 h-3 flex-shrink-0 text-muted-foreground" />
          )}

          <div className={`flex-shrink-0 p-1 rounded ${getTypeColor(stats.column_type)}`}>
            {getTypeIcon(stats.column_type)}
          </div>

          <div className="flex-1 min-w-0">
            <div className="flex items-center gap-2">
              <span className="text-xs font-medium truncate">{stats.column_name}</span>
              <span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground font-mono">
                {stats.column_type}
              </span>
            </div>

            {!isExpanded && (
              <div className="flex items-center gap-2 mt-1">
                <div className="flex-1 max-w-[120px]">
                  <div className="flex items-center gap-1">
                    <div className="flex-1 h-1.5 bg-muted rounded-full overflow-hidden">
                      <div
                        className={`h-full ${getFillColor(fillPercentage)} transition-all`}
                        style={{ width: `${fillPercentage}%` }}
                      />
                    </div>
                    <span className="text-[9px] text-muted-foreground font-mono">
                      {fillPercentage.toFixed(0)}%
                    </span>
                  </div>
                </div>
                <span className="text-[10px] text-muted-foreground">
                  {uniqueCount.toLocaleString()} unique
                </span>
              </div>
            )}
          </div>
        </div>
      </div>

      {isExpanded && (
        <div className="ml-6 mt-1 mb-2 p-2 bg-muted/30 rounded-md space-y-2">
          {/* Fill Percentage */}
          <div className="space-y-1">
            <div className="flex items-center justify-between text-[10px]">
              <span className="text-muted-foreground">Data Fill</span>
              <span className="font-medium">{fillPercentage.toFixed(1)}%</span>
            </div>
            <div className="h-2 bg-background rounded-full overflow-hidden">
              <div
                className={`h-full ${getFillColor(fillPercentage)} transition-all`}
                style={{ width: `${fillPercentage}%` }}
              />
            </div>
          </div>

          {/* Basic Stats */}
          <div className="grid grid-cols-2 gap-x-3 gap-y-1 text-[10px]">
            <div className="flex justify-between">
              <span className="text-muted-foreground">Total:</span>
              <span className="font-mono">{totalCount.toLocaleString()}</span>
            </div>
            <div className="flex justify-between">
              <span className="text-muted-foreground">Unique:</span>
              <span className="font-mono">{uniqueCount.toLocaleString()}</span>
            </div>
            <div className="flex justify-between">
              <span className="text-muted-foreground">Nulls:</span>
              <span className="font-mono">{((nullPercentage / 100) * totalCount).toFixed(0)}</span>
            </div>
            <div className="flex justify-between">
              <span className="text-muted-foreground">Cardinality:</span>
              <span className="font-mono">
                {isNaN((uniqueCount / totalCount) * 100)
                  ? "0.0"
                  : ((uniqueCount / totalCount) * 100).toFixed(1)}
                %
              </span>
            </div>
          </div>

          {/* Numeric Stats */}
          {isNumeric && stats.avg && (
            <>
              <div className="border-t border-border/50 my-1" />
              <div className="space-y-1 text-[10px]">
                <div className="flex justify-between">
                  <span className="text-muted-foreground">Min:</span>
                  <span className="font-mono">{parseValue(stats.min!).toLocaleString()}</span>
                </div>
                <div className="flex justify-between">
                  <span className="text-muted-foreground">Max:</span>
                  <span className="font-mono">{parseValue(stats.max!).toLocaleString()}</span>
                </div>
                <div className="flex justify-between">
                  <span className="text-muted-foreground">Avg:</span>
                  <span className="font-mono">
                    {parseValue(stats.avg).toLocaleString(undefined, {
                      maximumFractionDigits: 2,
                    })}
                  </span>
                </div>
                {stats.std && (
                  <div className="flex justify-between">
                    <span className="text-muted-foreground">Std Dev:</span>
                    <span className="font-mono">
                      {parseValue(stats.std).toLocaleString(undefined, {
                        maximumFractionDigits: 2,
                      })}
                    </span>
                  </div>
                )}
              </div>

              {stats.q25 && stats.q50 && stats.q75 && (
                <>
                  <div className="border-t border-border/50 my-1" />
                  <div className="space-y-1 text-[10px]">
                    <div className="text-[9px] text-muted-foreground font-medium mb-1">
                      Quartiles
                    </div>
                    <div className="flex justify-between">
                      <span className="text-muted-foreground">Q1 (25%):</span>
                      <span className="font-mono text-[9px]">
                        {parseValue(stats.q25).toLocaleString()}
                      </span>
                    </div>
                    <div className="flex justify-between">
                      <span className="text-muted-foreground">Q2 (50%):</span>
                      <span className="font-mono text-[9px]">
                        {parseValue(stats.q50).toLocaleString()}
                      </span>
                    </div>
                    <div className="flex justify-between">
                      <span className="text-muted-foreground">Q3 (75%):</span>
                      <span className="font-mono text-[9px]">
                        {parseValue(stats.q75).toLocaleString()}
                      </span>
                    </div>
                  </div>
                </>
              )}
            </>
          )}

          {/* String Stats */}
          {!isNumeric && stats.min && stats.max && (
            <>
              <div className="border-t border-border/50 my-1" />
              <div className="space-y-1 text-[10px]">
                <div className="flex justify-between gap-2">
                  <span className="text-muted-foreground flex-shrink-0">Min:</span>
                  <span className="font-mono truncate text-right" title={stats.min}>
                    {stats.min}
                  </span>
                </div>
                <div className="flex justify-between gap-2">
                  <span className="text-muted-foreground flex-shrink-0">Max:</span>
                  <span className="font-mono truncate text-right" title={stats.max}>
                    {stats.max}
                  </span>
                </div>
              </div>
            </>
          )}
        </div>
      )}
    </div>
  );
};

export default ColumnNode;


================================================
FILE: src/components/explorer/DataExplorer.tsx
================================================
import React, { useState, useCallback, lazy, Suspense } from "react";
import { useDuckStore } from "@/store";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Database, EllipsisVertical, FileUp, Plus, RefreshCw, Server } from "lucide-react";

const FileImporter = lazy(() => import("./FileImporter"));
import TreeNode, { TreeNodeData } from "./TreeNode";
import { Input } from "@/components/ui/input";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuPortal,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import FolderBrowser from "@/components/folders/FolderBrowser";
import CloudBrowser from "@/components/cloud/CloudBrowser";
import { type FileEntry, fileSystemService } from "@/lib/fileSystem";
import { type ImportOptions } from "@/components/common/ImportOptionsPopover";
import { toast } from "sonner";

export default function DataExplorer() {
  const [isSheetOpen, setIsSheetOpen] = useState(false);
  const [isDraggingOver, setIsDraggingOver] = useState(false);
  const databases = useDuckStore((s) => s.databases);
  const isLoading = useDuckStore((s) => s.isLoading);
  const currentConnection = useDuckStore((s) => s.currentConnection);
  const importFile = useDuckStore((s) => s.importFile);
  const fetchDatabasesAndTablesInfo = useDuckStore((s) => s.fetchDatabasesAndTablesInfo);
  const isFileSystemSupported = useDuckStore((s) => s.isFileSystemSupported);
  const schemaFetchError = useDuckStore((s) => s.schemaFetchError);
  const isLoadingDbTablesFetch = useDuckStore((s) => s.isLoadingDbTablesFetch);
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSearchTerm(e.target.value);
  };

  // Handle file import from mounted folder
  const handleFolderFileImport = useCallback(
    async (folderId: string, file: FileEntry, options: ImportOptions) => {
      const { tableName, importMode } = options;
      const modeLabel = importMode === "view" ? "Linking" : "Importing";
      const resultLabel = importMode === "view" ? "view" : "table";

      try {
        toast.loading(`${modeLabel} ${file.name}...`, { id: "folder-import" });

        // Read file from folder
        const fileData = await fileSystemService.readFile(folderId, file.path);
        const buffer = await fileData.arrayBuffer();

        // Determine file type from extension
        const ext = file.extension.replace(".", "").toLowerCase();
        let fileType = ext;
        if (ext === "jsonl" || ext === "ndjson") fileType = "json";

        await importFile(file.name, buffer, tableName, fileType, undefined, { importMode });
        await fetchDatabasesAndTablesInfo();

        toast.success(`Created ${resultLabel} "${tableName}" from "${file.name}"`, {
          id: "folder-import",
        });
      } catch (error) {
        console.error("Failed to import file:", error);
        toast.error(
          `Failed to import: ${error instanceof Error ? error.message : "Unknown error"}`,
          { id: "folder-import" }
        );
      }
    },
    [importFile, fetchDatabasesAndTablesInfo]
  );
  const buildTreeData = () => {
    const treeData: TreeNodeData[] = databases.map((db) => ({
      name: db.name,
      type: "database",
      children: db.tables.map((table) => ({
        name: table.name,
        type: "table",
      })),
    }));
    return treeData;
  };
  const treeData = buildTreeData();
  const isExternal = currentConnection?.scope === "External";

  const handleDragOver = useCallback(
    (e: React.DragEvent) => {
      e.preventDefault();
      e.stopPropagation();
      if (e.dataTransfer.types.includes("Files") && !isExternal) {
        setIsDraggingOver(true);
      }
    },
    [isExternal]
  );

  const handleDragLeave = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    // Only set false if we're leaving the container (not entering a child)
    if (e.currentTarget === e.target || !e.currentTarget.contains(e.relatedTarget as Node)) {
      setIsDraggingOver(false);
    }
  }, []);

  const handleDrop = useCallback(
    (e: React.DragEvent) => {
      e.preventDefault();
      e.stopPropagation();
      setIsDraggingOver(false);
      if (isExternal || !e.dataTransfer.files.length) return;
      setIsSheetOpen(true);
    },
    [isExternal]
  );

  return (
    <Card
      className="h-full overflow-hidden border-none relative"
      onDragOver={handleDragOver}
      onDragLeave={handleDragLeave}
      onDrop={handleDrop}
    >
      {isDraggingOver && (
        <div className="absolute inset-0 z-50 flex items-center justify-center bg-primary/10 border-2 border-dashed border-primary rounded-lg pointer-events-none">
          <div className="text-center">
            <FileUp className="h-10 w-10 text-primary mx-auto mb-2" />
            <p className="text-sm font-medium text-primary">Drop files to import</p>
          </div>
        </div>
      )}
      {isLoading && (
        <div className="flex items-center justify-center h-full">
          <p className="text-muted-foreground">Loading databases...</p>
        </div>
      )}

      <CardHeader className="p-2 border-b">
        <div className="flex items-center justify-between">
          
Download .txt
gitextract_fb4zvf6o/

├── .dockerignore
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   └── feature.yml
│   └── workflows/
│       ├── 2-docker-build.yml
│       └── ci.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── .prettierrc
├── Dockerfile
├── LICENSE.md
├── README.md
├── components.json
├── docker-compose.yml
├── eslint.config.js
├── index.html
├── inject-env.js
├── package.json
├── public/
│   └── databases/
│       ├── README.md
│       └── manifest.json
├── serve.json
├── src/
│   ├── components/
│   │   ├── charts/
│   │   │   ├── ChartVisualizationPro.tsx
│   │   │   ├── UPlotChart.tsx
│   │   │   └── tooltipPlugin.ts
│   │   ├── cloud/
│   │   │   ├── CloudBrowser.tsx
│   │   │   └── CloudConnectionModal.tsx
│   │   ├── command-palette/
│   │   │   └── CommandPalette.tsx
│   │   ├── common/
│   │   │   ├── FloatingActionButton.tsx
│   │   │   └── ImportOptionsPopover.tsx
│   │   ├── connection/
│   │   │   └── ConnectionsModal.tsx
│   │   ├── duck-brain/
│   │   │   ├── DuckBrainCodeBlock.tsx
│   │   │   ├── DuckBrainInput.tsx
│   │   │   ├── DuckBrainMessages.tsx
│   │   │   ├── DuckBrainPanel.tsx
│   │   │   ├── MarkdownContent.tsx
│   │   │   ├── ResultsArtifact.tsx
│   │   │   └── SchemaAutocomplete.tsx
│   │   ├── editor/
│   │   │   ├── SqlEditor.tsx
│   │   │   └── monacoConfig.ts
│   │   ├── explorer/
│   │   │   ├── ColumnNode.tsx
│   │   │   ├── DataExplorer.tsx
│   │   │   ├── FileImporter.tsx
│   │   │   └── TreeNode.tsx
│   │   ├── folders/
│   │   │   └── FolderBrowser.tsx
│   │   ├── layout/
│   │   │   ├── ConnectionSwitcher.tsx
│   │   │   ├── MobileNavDrawer.tsx
│   │   │   └── Sidebar.tsx
│   │   ├── notebook/
│   │   │   ├── MarkdownRenderer.tsx
│   │   │   ├── NotebookCell.tsx
│   │   │   ├── NotebookTab.tsx
│   │   │   └── NotebookToolbar.tsx
│   │   ├── profile/
│   │   │   ├── PasswordDialog.tsx
│   │   │   ├── ProfileAvatar.tsx
│   │   │   ├── ProfileEditor.tsx
│   │   │   └── ProfilePicker.tsx
│   │   ├── saved-queries/
│   │   │   ├── SaveQueryDialog.tsx
│   │   │   └── SavedQueriesPanel.tsx
│   │   ├── table/
│   │   │   ├── CellValueViewer.tsx
│   │   │   ├── ColumnStatsPanel.tsx
│   │   │   └── DuckUItable.tsx
│   │   ├── theme/
│   │   │   ├── mode-toggle.tsx
│   │   │   └── theme-provider.tsx
│   │   ├── ui/
│   │   │   ├── accordion.tsx
│   │   │   ├── alert-dialog.tsx
│   │   │   ├── alert.tsx
│   │   │   ├── aspect-ratio.tsx
│   │   │   ├── badge.tsx
│   │   │   ├── breadcrumb.tsx
│   │   │   ├── button.tsx
│   │   │   ├── card.tsx
│   │   │   ├── checkbox.tsx
│   │   │   ├── collapsible.tsx
│   │   │   ├── command.tsx
│   │   │   ├── context-menu.tsx
│   │   │   ├── dialog.tsx
│   │   │   ├── drawer.tsx
│   │   │   ├── dropdown-menu.tsx
│   │   │   ├── form.tsx
│   │   │   ├── hover-card.tsx
│   │   │   ├── input.tsx
│   │   │   ├── label.tsx
│   │   │   ├── menubar.tsx
│   │   │   ├── multi-select.tsx
│   │   │   ├── navigation-menu.tsx
│   │   │   ├── pagination.tsx
│   │   │   ├── popover.tsx
│   │   │   ├── progress.tsx
│   │   │   ├── radio-group.tsx
│   │   │   ├── resizable.tsx
│   │   │   ├── scroll-area.tsx
│   │   │   ├── select.tsx
│   │   │   ├── separator.tsx
│   │   │   ├── sheet.tsx
│   │   │   ├── skeleton.tsx
│   │   │   ├── sonner.tsx
│   │   │   ├── switch.tsx
│   │   │   ├── table.tsx
│   │   │   ├── tabs.tsx
│   │   │   ├── textarea.tsx
│   │   │   └── tooltip.tsx
│   │   └── workspace/
│   │       ├── BrainTab.tsx
│   │       ├── ConnectionsTab.tsx
│   │       ├── ExplainPlanViewer.tsx
│   │       ├── HomeTab.tsx
│   │       ├── QueryHistory.tsx
│   │       ├── SettingsTab.tsx
│   │       ├── SortableTab.tsx
│   │       ├── SqlTab.tsx
│   │       └── WorkspaceTabs.tsx
│   ├── hooks/
│   │   └── useQueryFromURL.ts
│   ├── index.css
│   ├── lib/
│   │   ├── chartDataTransform.ts
│   │   ├── chartExport.ts
│   │   ├── chartUtils.ts
│   │   ├── cloudStorage/
│   │   │   ├── index.ts
│   │   │   └── testHttpfs.ts
│   │   ├── duckBrain/
│   │   │   ├── index.ts
│   │   │   ├── models.config.ts
│   │   │   ├── prompts/
│   │   │   │   └── text-to-sql.ts
│   │   │   ├── providers/
│   │   │   │   ├── anthropic.provider.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── openai.provider.ts
│   │   │   │   └── types.ts
│   │   │   ├── schemaFormatter.ts
│   │   │   ├── sqlParser.ts
│   │   │   ├── webllm.service.ts
│   │   │   └── webllm.worker.ts
│   │   ├── fileSystem/
│   │   │   └── index.ts
│   │   ├── sqlSanitize.ts
│   │   └── utils.ts
│   ├── main.tsx
│   ├── pages/
│   │   └── Home.tsx
│   ├── services/
│   │   ├── duckdb/
│   │   │   ├── __tests__/
│   │   │   │   ├── resultParser.test.ts
│   │   │   │   └── utils.test.ts
│   │   │   ├── externalConnection.ts
│   │   │   ├── index.ts
│   │   │   ├── opfsConnection.ts
│   │   │   ├── resultParser.ts
│   │   │   ├── schemaFetcher.ts
│   │   │   ├── utils.ts
│   │   │   └── wasmConnection.ts
│   │   └── persistence/
│   │       ├── __tests__/
│   │       │   ├── crypto.test.ts
│   │       │   └── migrations.test.ts
│   │       ├── crypto.ts
│   │       ├── fallback.ts
│   │       ├── index.ts
│   │       ├── migrations.ts
│   │       ├── repositories/
│   │       │   ├── aiConfigRepository.ts
│   │       │   ├── connectionRepository.ts
│   │       │   ├── index.ts
│   │       │   ├── profileRepository.ts
│   │       │   ├── queryHistoryRepository.ts
│   │       │   ├── savedQueryRepository.ts
│   │       │   ├── settingsRepository.ts
│   │       │   └── workspaceRepository.ts
│   │       └── systemDb.ts
│   ├── store/
│   │   ├── index.ts
│   │   ├── slices/
│   │   │   ├── connectionSlice.ts
│   │   │   ├── duckBrainSlice.ts
│   │   │   ├── duckdbSlice.ts
│   │   │   ├── fileSystemSlice.ts
│   │   │   ├── profileSlice.ts
│   │   │   ├── querySlice.ts
│   │   │   ├── schemaSlice.ts
│   │   │   └── tabSlice.ts
│   │   └── types.ts
│   ├── types/
│   │   └── filesystem.d.ts
│   └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vitest.config.ts
Download .txt
SYMBOL INDEX (409 symbols across 92 files)

FILE: src/components/charts/ChartVisualizationPro.tsx
  type ChartVisualizationProProps (line 44) | interface ChartVisualizationProProps {
  constant DEFAULT_COLORS (line 51) | const DEFAULT_COLORS = [
  constant CHART_TYPE_INFO (line 65) | const CHART_TYPE_INFO: Record<string, { label: string; icon: React.Eleme...
  function PieChartDisplay (line 79) | function PieChartDisplay({
  function ChartLegend (line 262) | function ChartLegend({

FILE: src/components/charts/UPlotChart.tsx
  type UPlotChartProps (line 5) | interface UPlotChartProps {
  function UPlotChart (line 12) | function UPlotChart({ options, data, className, onInit }: UPlotChartProp...

FILE: src/components/charts/tooltipPlugin.ts
  function escapeHtml (line 4) | function escapeHtml(str: string): string {
  type TooltipPluginOptions (line 13) | interface TooltipPluginOptions {
  function tooltipPlugin (line 17) | function tooltipPlugin(xLabels: string[], opts?: TooltipPluginOptions): ...

FILE: src/components/cloud/CloudBrowser.tsx
  constant PROVIDER_CONFIG (line 32) | const PROVIDER_CONFIG = {
  type CloudConnectionItemProps (line 38) | interface CloudConnectionItemProps {
  function CloudConnectionItem (line 45) | function CloudConnectionItem({
  function CloudBrowser (line 137) | function CloudBrowser() {

FILE: src/components/cloud/CloudConnectionModal.tsx
  type CloudConnectionModalProps (line 41) | interface CloudConnectionModalProps {
  type CloudConnectionFormData (line 85) | type CloudConnectionFormData = z.infer<typeof cloudConnectionSchema>;
  function CloudConnectionModal (line 87) | function CloudConnectionModal({

FILE: src/components/command-palette/CommandPalette.tsx
  function CommandPalette (line 32) | function CommandPalette() {

FILE: src/components/common/FloatingActionButton.tsx
  type FloatingActionButtonProps (line 5) | interface FloatingActionButtonProps {

FILE: src/components/common/ImportOptionsPopover.tsx
  type ImportOptions (line 9) | interface ImportOptions {
  type ImportOptionsPopoverProps (line 14) | interface ImportOptionsPopoverProps {
  function generateTableName (line 24) | function generateTableName(fileName: string): string {

FILE: src/components/connection/ConnectionsModal.tsx
  type ConnectionFormValues (line 75) | type ConnectionFormValues = z.infer<typeof connectionSchema>;
  type ConnectionManagerProps (line 77) | interface ConnectionManagerProps {

FILE: src/components/duck-brain/DuckBrainCodeBlock.tsx
  type DuckBrainCodeBlockProps (line 9) | interface DuckBrainCodeBlockProps {

FILE: src/components/duck-brain/DuckBrainInput.tsx
  type DuckBrainInputProps (line 57) | interface DuckBrainInputProps {

FILE: src/components/duck-brain/DuckBrainMessages.tsx
  type DuckBrainMessagesProps (line 47) | interface DuckBrainMessagesProps {

FILE: src/components/duck-brain/DuckBrainPanel.tsx
  type DuckBrainPanelProps (line 21) | interface DuckBrainPanelProps {
  type HeaderProps (line 328) | interface HeaderProps {

FILE: src/components/duck-brain/MarkdownContent.tsx
  type MarkdownContentProps (line 5) | interface MarkdownContentProps {

FILE: src/components/duck-brain/ResultsArtifact.tsx
  type ResultsArtifactProps (line 9) | interface ResultsArtifactProps {
  constant MAX_INLINE_ROWS (line 15) | const MAX_INLINE_ROWS = 5;
  constant MAX_INLINE_COLUMNS (line 16) | const MAX_INLINE_COLUMNS = 6;
  function formatCellValue (line 154) | function formatCellValue(value: unknown): string {

FILE: src/components/duck-brain/SchemaAutocomplete.tsx
  type SchemaSuggestion (line 6) | interface SchemaSuggestion {
  type SchemaAutocompleteProps (line 15) | interface SchemaAutocompleteProps {
  function buildSchemaSuggestions (line 27) | function buildSchemaSuggestions(

FILE: src/components/editor/SqlEditor.tsx
  type SqlEditorProps (line 27) | interface SqlEditorProps {

FILE: src/components/editor/monacoConfig.ts
  type EditorInstance (line 12) | interface EditorInstance {
  type EditorConfig (line 17) | interface EditorConfig {
  method getWorker (line 37) | getWorker(_workerId: string) {
  type AutocompleteItem (line 306) | interface AutocompleteItem {
  method provideCompletionItems (line 319) | async provideCompletionItems(model, position) {

FILE: src/components/explorer/ColumnNode.tsx
  type ColumnNodeProps (line 5) | interface ColumnNodeProps {

FILE: src/components/explorer/DataExplorer.tsx
  function DataExplorer (line 24) | function DataExplorer() {

FILE: src/components/explorer/FileImporter.tsx
  constant ACCEPTED_FILE_TYPES (line 50) | const ACCEPTED_FILE_TYPES = {
  constant MAX_FILE_SIZE (line 58) | const MAX_FILE_SIZE = 3 * 1024 * 1024 * 1024;
  constant SUPPORTED_FILE_EXTENSIONS (line 59) | const SUPPORTED_FILE_EXTENSIONS = [
  constant MAX_CONCURRENT_UPLOADS (line 69) | const MAX_CONCURRENT_UPLOADS = 3;
  constant PREVIEW_ROW_LIMIT (line 70) | const PREVIEW_ROW_LIMIT = 20;
  constant DUCKDB_TYPES (line 73) | const DUCKDB_TYPES = [
  type FileExtension (line 87) | type FileExtension = (typeof SUPPORTED_FILE_EXTENSIONS)[number];
  type FileWithPreview (line 89) | interface FileWithPreview extends File {
  type UploadError (line 93) | interface UploadError {
  type FileImportState (line 100) | interface FileImportState {
  type CsvImportOptions (line 108) | interface CsvImportOptions {
  type SchemaColumn (line 126) | interface SchemaColumn {
  type FileImporterProps (line 133) | interface FileImporterProps {
  type FileDetailsProps (line 139) | interface FileDetailsProps {

FILE: src/components/explorer/TreeNode.tsx
  type TreeNodeData (line 31) | interface TreeNodeData {
  type TreeNodeProps (line 38) | interface TreeNodeProps {

FILE: src/components/folders/FolderBrowser.tsx
  type FolderBrowserProps (line 36) | interface FolderBrowserProps {
  type FileNodeProps (line 76) | interface FileNodeProps {
  type MountedFolderNodeProps (line 265) | interface MountedFolderNodeProps {

FILE: src/components/layout/ConnectionSwitcher.tsx
  type ConnectionSwitcherProps (line 16) | interface ConnectionSwitcherProps {
  function ConnectionSwitcher (line 20) | function ConnectionSwitcher({ className }: ConnectionSwitcherProps) {

FILE: src/components/layout/Sidebar.tsx
  type SidebarProps (line 43) | interface SidebarProps {
  function Sidebar (line 48) | function Sidebar({ isExplorerOpen, onToggleExplorer }: SidebarProps) {

FILE: src/components/notebook/MarkdownRenderer.tsx
  type MarkdownRendererProps (line 5) | interface MarkdownRendererProps {
  function MarkdownRenderer (line 15) | function MarkdownRenderer({ content, className }: MarkdownRendererProps) {

FILE: src/components/notebook/NotebookCell.tsx
  type NotebookCellProps (line 49) | interface NotebookCellProps {
  function NotebookCellComponent (line 59) | function NotebookCellComponent({
  function CellResults (line 371) | function CellResults({

FILE: src/components/notebook/NotebookTab.tsx
  type NotebookTabProps (line 8) | interface NotebookTabProps {
  function NotebookTab (line 12) | function NotebookTab({ tabId }: NotebookTabProps) {

FILE: src/components/notebook/NotebookToolbar.tsx
  type NotebookToolbarProps (line 10) | interface NotebookToolbarProps {
  function NotebookToolbar (line 17) | function NotebookToolbar({

FILE: src/components/profile/PasswordDialog.tsx
  type PasswordDialogProps (line 18) | interface PasswordDialogProps {
  function PasswordDialog (line 25) | function PasswordDialog({

FILE: src/components/profile/ProfileAvatar.tsx
  type ProfileAvatarProps (line 13) | interface ProfileAvatarProps {
  function ProfileAvatar (line 19) | function ProfileAvatar({ avatarEmoji, size = "md", className }: ProfileA...

FILE: src/components/profile/ProfileEditor.tsx
  constant AVATAR_OPTIONS (line 9) | const AVATAR_OPTIONS = [
  type ProfileEditorProps (line 33) | interface ProfileEditorProps {
  function ProfileEditor (line 40) | function ProfileEditor({

FILE: src/components/profile/ProfilePicker.tsx
  type ProfilePickerProps (line 22) | interface ProfilePickerProps {
  function ProfilePicker (line 28) | function ProfilePicker({

FILE: src/components/saved-queries/SaveQueryDialog.tsx
  type SaveQueryDialogProps (line 19) | interface SaveQueryDialogProps {
  function SaveQueryDialog (line 26) | function SaveQueryDialog({

FILE: src/components/saved-queries/SavedQueriesPanel.tsx
  type SavedQueriesPanelProps (line 25) | interface SavedQueriesPanelProps {
  function SavedQueriesPanel (line 29) | function SavedQueriesPanel({ onClose }: SavedQueriesPanelProps) {

FILE: src/components/table/CellValueViewer.tsx
  type CellValueViewerProps (line 7) | interface CellValueViewerProps {

FILE: src/components/table/ColumnStatsPanel.tsx
  type DataRow (line 7) | type DataRow = Record<string, unknown>;
  type ColumnStats (line 9) | interface ColumnStats {
  type ColumnStatsPanelProps (line 21) | interface ColumnStatsPanelProps {

FILE: src/components/table/DuckUItable.tsx
  type DataRow (line 62) | type DataRow = Record<string, any>;
  type CellPosition (line 65) | type CellPosition = { row: number; col: string };
  type ContextMenuPosition (line 66) | type ContextMenuPosition = { x: number; y: number };
  type DuckTableProps (line 69) | interface DuckTableProps {
  constant DEFAULT_COLUMN_WIDTH (line 86) | const DEFAULT_COLUMN_WIDTH = 150;
  constant MIN_COLUMN_WIDTH (line 87) | const MIN_COLUMN_WIDTH = 50;
  constant MAX_COLUMN_WIDTH (line 88) | const MAX_COLUMN_WIDTH = 1000;
  constant DEFAULT_MAX_AUTO_WIDTH (line 89) | const DEFAULT_MAX_AUTO_WIDTH = 250;
  constant DEFAULT_MIN_AUTO_WIDTH (line 90) | const DEFAULT_MIN_AUTO_WIDTH = 80;
  constant DEFAULT_SAMPLE_SIZE (line 91) | const DEFAULT_SAMPLE_SIZE = 100;

FILE: src/components/theme/mode-toggle.tsx
  function ModeToggle (line 12) | function ModeToggle() {

FILE: src/components/theme/theme-provider.tsx
  type Theme (line 3) | type Theme = "dark" | "light" | "system";
  type ThemeProviderProps (line 5) | type ThemeProviderProps = {
  type ThemeProviderState (line 11) | type ThemeProviderState = {
  function ThemeProvider (line 23) | function ThemeProvider({

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

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

FILE: src/components/ui/form.tsx
  type FormFieldContextValue (line 18) | type FormFieldContextValue<
  type FormItemContextValue (line 63) | type FormItemContextValue = {

FILE: src/components/ui/multi-select.tsx
  type MultiSelectOption (line 16) | interface MultiSelectOption {
  type MultiSelectProps (line 22) | interface MultiSelectProps {
  function MultiSelect (line 31) | function MultiSelect({

FILE: src/components/ui/pagination.tsx
  type PaginationLinkProps (line 29) | type PaginationLinkProps = {

FILE: src/components/ui/sheet.tsx
  type SheetContentProps (line 52) | interface SheetContentProps

FILE: src/components/ui/skeleton.tsx
  function Skeleton (line 3) | function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivE...

FILE: src/components/ui/sonner.tsx
  type ToasterProps (line 4) | type ToasterProps = React.ComponentProps<typeof Sonner>;

FILE: src/components/workspace/ConnectionsTab.tsx
  type ConnectionFormValues (line 63) | type ConnectionFormValues = z.infer<typeof connectionSchema>;

FILE: src/components/workspace/ExplainPlanViewer.tsx
  type ExplainPlanViewerProps (line 11) | interface ExplainPlanViewerProps {
  constant OPERATOR_COLORS (line 17) | const OPERATOR_COLORS: Record<string, string> = {
  function colorizeOperators (line 37) | function colorizeOperators(text: string): React.ReactNode[] {
  function ExplainPlanViewer (line 56) | function ExplainPlanViewer({ open, onOpenChange, explainText }: ExplainP...

FILE: src/components/workspace/QueryHistory.tsx
  type QueryHistoryProps (line 39) | interface QueryHistoryProps {

FILE: src/components/workspace/SettingsTab.tsx
  function SettingsTab (line 38) | function SettingsTab() {

FILE: src/components/workspace/SortableTab.tsx
  type Tab (line 9) | interface Tab {
  type SortableTabProps (line 16) | interface SortableTabProps {

FILE: src/components/workspace/SqlTab.tsx
  type SqlTabProps (line 33) | interface SqlTabProps {

FILE: src/components/workspace/WorkspaceTabs.tsx
  function WorkspaceTabs (line 72) | function WorkspaceTabs() {

FILE: src/hooks/useQueryFromURL.ts
  function useQueryFromURL (line 10) | function useQueryFromURL() {
  function generateQueryURL (line 60) | function generateQueryURL(query: string, autoExecute = false): string {
  function copyQueryURL (line 73) | async function copyQueryURL(query: string, autoExecute = false): Promise...

FILE: src/lib/chartDataTransform.ts
  type TransformedData (line 9) | type TransformedData = Record<string, unknown>[];
  function adjustColorBrightness (line 260) | function adjustColorBrightness(hex: string, percent: number): string {

FILE: src/lib/cloudStorage/index.ts
  type CloudProviderType (line 11) | type CloudProviderType = "s3" | "gcs" | "azure";
  type CloudConnection (line 14) | interface CloudConnection {
  type CloudFile (line 43) | interface CloudFile {
  type CloudSupportStatus (line 53) | interface CloudSupportStatus {
  constant DB_NAME (line 62) | const DB_NAME = "duck-ui-cloud";
  constant STORE_NAME (line 63) | const STORE_NAME = "cloud-connections";
  constant DB_VERSION (line 64) | const DB_VERSION = 1;
  function openDatabase (line 66) | async function openDatabase(): Promise<IDBDatabase> {
  class CloudStorageService (line 85) | class CloudStorageService {
    method init (line 94) | async init(): Promise<void> {
    method checkCloudSupport (line 114) | async checkCloudSupport(): Promise<CloudSupportStatus> {
    method getSupportStatus (line 176) | getSupportStatus(): CloudSupportStatus | null {
    method loadPersistedConnections (line 183) | private async loadPersistedConnections(): Promise<void> {
    method persistConnection (line 207) | private async persistConnection(conn: CloudConnection): Promise<void> {
    method removePersistedConnection (line 233) | private async removePersistedConnection(id: string): Promise<void> {
    method addConnection (line 249) | async addConnection(
    method updateConnection (line 270) | async updateConnection(
    method removeConnection (line 287) | async removeConnection(id: string): Promise<void> {
    method getConnections (line 301) | getConnections(): CloudConnection[] {
    method getConnection (line 308) | getConnection(id: string): CloudConnection | undefined {
    method connect (line 315) | async connect(id: string): Promise<boolean> {
    method disconnect (line 384) | async disconnect(id: string): Promise<void> {
    method testConnection (line 404) | async testConnection(id: string): Promise<{ success: boolean; error?: ...
    method getUriPrefix (line 446) | getUriPrefix(id: string): string | null {

FILE: src/lib/cloudStorage/testHttpfs.ts
  type HttpfsTestResult (line 11) | interface HttpfsTestResult {
  function testHttpfsSupport (line 18) | async function testHttpfsSupport(): Promise<HttpfsTestResult[]> {

FILE: src/lib/duckBrain/models.config.ts
  type ModelConfig (line 2) | interface ModelConfig {
  constant AVAILABLE_MODELS (line 10) | const AVAILABLE_MODELS: ModelConfig[] = [
  constant DEFAULT_MODEL (line 34) | const DEFAULT_MODEL = AVAILABLE_MODELS[0];

FILE: src/lib/duckBrain/prompts/text-to-sql.ts
  constant TEXT_TO_SQL_SYSTEM_PROMPT (line 4) | const TEXT_TO_SQL_SYSTEM_PROMPT = `You are Duck Brain, a DuckDB SQL quer...
  function buildResultsContext (line 28) | function buildResultsContext(messages: DuckBrainMessage[]): string {
  constant DUCKDB_FEW_SHOT_EXAMPLES (line 59) | const DUCKDB_FEW_SHOT_EXAMPLES: ChatCompletionMessageParam[] = [
  function buildTextToSQLMessages (line 102) | function buildTextToSQLMessages(

FILE: src/lib/duckBrain/providers/anthropic.provider.ts
  class AnthropicProvider (line 14) | class AnthropicProvider implements AIProvider {
    method initialize (line 24) | async initialize(config: ProviderConfig): Promise<void> {
    method generateStreaming (line 48) | async generateStreaming(
    method generateText (line 152) | async generateText(
    method abort (line 192) | abort(): void {
    method cleanup (line 196) | async cleanup(): Promise<void> {
    method getStatus (line 202) | getStatus(): ProviderStatus {
    method isReady (line 211) | isReady(): boolean {

FILE: src/lib/duckBrain/providers/index.ts
  function createProvider (line 12) | function createProvider(type: AIProviderType): AIProvider {
  function testProviderConnection (line 32) | async function testProviderConnection(

FILE: src/lib/duckBrain/providers/openai.provider.ts
  class OpenAIProvider (line 14) | class OpenAIProvider implements AIProvider {
    method initialize (line 25) | async initialize(config: ProviderConfig): Promise<void> {
    method generateStreaming (line 86) | async generateStreaming(
    method generateText (line 175) | async generateText(
    method abort (line 215) | abort(): void {
    method cleanup (line 219) | async cleanup(): Promise<void> {
    method getStatus (line 225) | getStatus(): ProviderStatus {
    method isReady (line 234) | isReady(): boolean {

FILE: src/lib/duckBrain/providers/types.ts
  type AIProviderType (line 3) | type AIProviderType = "webllm" | "openai" | "anthropic" | "gemini" | "op...
  type ProviderConfig (line 5) | interface ProviderConfig {
  type StreamCallbacks (line 11) | interface StreamCallbacks {
  type GenerationOptions (line 17) | interface GenerationOptions {
  type ProviderStatus (line 23) | interface ProviderStatus {
  type AIProvider (line 34) | interface AIProvider {
  type ModelOption (line 83) | interface ModelOption {
  constant OPENAI_MODELS (line 90) | const OPENAI_MODELS: ModelOption[] = [
  constant ANTHROPIC_MODELS (line 117) | const ANTHROPIC_MODELS: ModelOption[] = [

FILE: src/lib/duckBrain/schemaFormatter.ts
  type SchemaContext (line 3) | interface SchemaContext {
  function formatSchemaForContext (line 13) | function formatSchemaForContext(
  function getSchemaSummary (line 86) | function getSchemaSummary(databases: DatabaseInfo[]): string {

FILE: src/lib/duckBrain/sqlParser.ts
  type ParsedSQLResult (line 1) | interface ParsedSQLResult {
  constant SQL_KEYWORDS (line 7) | const SQL_KEYWORDS = [
  function extractSQLFromResponse (line 29) | function extractSQLFromResponse(response: string): ParsedSQLResult {
  function formatSQLForDisplay (line 150) | function formatSQLForDisplay(sql: string): string {

FILE: src/lib/duckBrain/webllm.service.ts
  type GPUAdapter (line 10) | interface GPUAdapter {
  type GPUInterface (line 14) | interface GPUInterface {
  type Navigator (line 19) | interface Navigator {
  type ModelStatus (line 24) | type ModelStatus = "idle" | "checking" | "downloading" | "loading" | "re...
  type DuckBrainServiceState (line 26) | interface DuckBrainServiceState {
  type StreamCallbacks (line 35) | interface StreamCallbacks {
  type StateListener (line 41) | type StateListener = (state: DuckBrainServiceState) => void;
  class DuckBrainService (line 47) | class DuckBrainService {
    method constructor (line 62) | constructor() {
    method checkWebGPUSupport (line 70) | async checkWebGPUSupport(): Promise<boolean> {
    method updateState (line 106) | private updateState(partial: Partial<DuckBrainServiceState>) {
    method subscribe (line 114) | subscribe(listener: StateListener): () => void {
    method getState (line 124) | getState(): DuckBrainServiceState {
    method initialize (line 145) | async initialize(modelId: string = DEFAULT_MODEL.id): Promise<void> {
    method generateStreaming (line 204) | async generateStreaming(
    method generate (line 249) | async generate(
    method abort (line 269) | abort(): void {
    method cleanup (line 277) | async cleanup(): Promise<void> {
    method isReady (line 305) | isReady(): boolean {

FILE: src/lib/fileSystem/index.ts
  type FileEntry (line 8) | interface FileEntry {
  type FolderEntry (line 18) | interface FolderEntry {
  type FSEntry (line 26) | type FSEntry = FileEntry | FolderEntry;
  type MountedFolder (line 28) | interface MountedFolder {
  constant SUPPORTED_EXTENSIONS (line 37) | const SUPPORTED_EXTENSIONS = [
  function isFileSystemAccessSupported (line 54) | function isFileSystemAccessSupported(): boolean {
  constant DB_NAME (line 59) | const DB_NAME = "duck-ui-filesystem";
  constant STORE_NAME (line 60) | const STORE_NAME = "folder-handles";
  constant DB_VERSION (line 61) | const DB_VERSION = 1;
  function openDatabase (line 66) | async function openDatabase(): Promise<IDBDatabase> {
  function verifyPermission (line 85) | async function verifyPermission(
  function getExtension (line 107) | function getExtension(filename: string): string {
  class FileSystemService (line 115) | class FileSystemService {
    method init (line 123) | async init(): Promise<void> {
    method loadPersistedFolders (line 145) | private async loadPersistedFolders(): Promise<void> {
    method persistFolder (line 170) | private async persistFolder(folder: MountedFolder): Promise<void> {
    method removePersistedFolder (line 186) | private async removePersistedFolder(id: string): Promise<void> {
    method mountFolder (line 202) | async mountFolder(): Promise<MountedFolder> {
    method unmountFolder (line 233) | async unmountFolder(id: string): Promise<void> {
    method getMountedFolders (line 241) | getMountedFolders(): MountedFolder[] {
    method getFolder (line 248) | getFolder(id: string): MountedFolder | undefined {
    method requestPermission (line 255) | async requestPermission(id: string): Promise<boolean> {
    method checkAllPermissions (line 267) | async checkAllPermissions(): Promise<Map<string, boolean>> {
    method listFiles (line 288) | async listFiles(
    method readDirectory (line 315) | private async readDirectory(
    method readFile (line 383) | async readFile(folderId: string, filePath: string): Promise<File> {
    method readFileBuffer (line 415) | async readFileBuffer(folderId: string, filePath: string): Promise<Arra...
    method requestWritePermission (line 423) | async requestWritePermission(id: string): Promise<boolean> {
    method saveFile (line 435) | async saveFile(
    method getFolderHandle (line 483) | getFolderHandle(id: string): FileSystemDirectoryHandle | undefined {

FILE: src/lib/sqlSanitize.ts
  function sqlEscapeString (line 2) | function sqlEscapeString(value: string): string {
  function sqlEscapeIdentifier (line 7) | function sqlEscapeIdentifier(name: string): string {

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

FILE: src/main.tsx
  type LoadingScreenProps (line 24) | interface LoadingScreenProps {
  type AppInitializerProps (line 28) | interface AppInitializerProps {
  function boot (line 79) | async function boot() {
  function migrateFromLocalStorage (line 148) | async function migrateFromLocalStorage(profileId: string): Promise<void> {

FILE: src/pages/Home.tsx
  function Home (line 11) | function Home() {

FILE: src/services/duckdb/opfsConnection.ts
  function normalizeOpfsPath (line 10) | function normalizeOpfsPath(path: string): string {

FILE: src/services/persistence/__tests__/migrations.test.ts
  function createMockConnection (line 5) | function createMockConnection() {

FILE: src/services/persistence/crypto.ts
  constant KEY_DB_NAME (line 9) | const KEY_DB_NAME = "duck-ui-keys";
  constant KEY_STORE_NAME (line 10) | const KEY_STORE_NAME = "encryption-keys";
  constant KEY_DB_VERSION (line 11) | const KEY_DB_VERSION = 1;
  constant ALGORITHM (line 13) | const ALGORITHM = "AES-GCM";
  constant KEY_LENGTH (line 14) | const KEY_LENGTH = 256;
  constant IV_LENGTH (line 15) | const IV_LENGTH = 12;
  constant SALT_LENGTH (line 16) | const SALT_LENGTH = 16;
  constant PBKDF2_ITERATIONS (line 17) | const PBKDF2_ITERATIONS = 100_000;
  function uint8ArrayToBase64 (line 21) | function uint8ArrayToBase64(bytes: Uint8Array): string {
  function base64ToUint8Array (line 29) | function base64ToUint8Array(base64: string): Uint8Array {
  function openKeyDatabase (line 40) | function openKeyDatabase(): Promise<IDBDatabase> {
  function storeKeyForProfile (line 54) | async function storeKeyForProfile(
  function loadKeyForProfile (line 84) | async function loadKeyForProfile(
  function deleteKeyForProfile (line 117) | async function deleteKeyForProfile(profileId: string): Promise<void> {
  function generateEncryptionKey (line 140) | async function generateEncryptionKey(): Promise<CryptoKey> {
  function generateSalt (line 147) | function generateSalt(): Uint8Array {
  function deriveKeyFromPassword (line 151) | async function deriveKeyFromPassword(
  function encrypt (line 179) | async function encrypt(plaintext: string, key: CryptoKey): Promise<strin...
  function decrypt (line 194) | async function decrypt(encoded: string, key: CryptoKey): Promise<string> {
  function exportKey (line 204) | async function exportKey(key: CryptoKey): Promise<string> {
  function importKey (line 209) | async function importKey(exported: string): Promise<CryptoKey> {

FILE: src/services/persistence/fallback.ts
  constant FALLBACK_DB_NAME (line 7) | const FALLBACK_DB_NAME = "duck-ui-persistence";
  constant FALLBACK_DB_VERSION (line 8) | const FALLBACK_DB_VERSION = 1;
  constant STORES (line 10) | const STORES = [
  function openFallbackDb (line 23) | function openFallbackDb(): Promise<IDBDatabase> {
  type StoreName (line 52) | type StoreName = (typeof STORES)[number];
  function fallbackPut (line 54) | async function fallbackPut(
  function fallbackGet (line 68) | async function fallbackGet(store: StoreName, key: IDBValidKey): Promise<...
  function fallbackGetAll (line 79) | async function fallbackGetAll(store: StoreName): Promise<unknown[]> {
  function fallbackDelete (line 90) | async function fallbackDelete(store: StoreName, key: IDBValidKey): Promi...
  function fallbackClear (line 101) | async function fallbackClear(store: StoreName): Promise<void> {

FILE: src/services/persistence/migrations.ts
  type Migration (line 8) | interface Migration {
  constant MIGRATIONS (line 14) | const MIGRATIONS: Migration[] = [
  function runMigrations (line 95) | async function runMigrations(conn: duckdb.AsyncDuckDBConnection): Promis...

FILE: src/services/persistence/repositories/aiConfigRepository.ts
  type AIProviderConfig (line 6) | interface AIProviderConfig {
  type AIConversation (line 13) | interface AIConversation {
  function saveProviderConfig (line 23) | async function saveProviderConfig(
  function getProviderConfigs (line 53) | async function getProviderConfigs(
  function saveConversation (line 97) | async function saveConversation(
  function getConversations (line 137) | async function getConversations(profileId: string): Promise<AIConversati...

FILE: src/services/persistence/repositories/connectionRepository.ts
  type SavedConnection (line 6) | interface SavedConnection {
  type ConnectionInput (line 17) | interface ConnectionInput {
  function saveConnection (line 25) | async function saveConnection(
  function getConnections (line 70) | async function getConnections(
  function deleteConnection (line 132) | async function deleteConnection(id: string): Promise<void> {

FILE: src/services/persistence/repositories/profileRepository.ts
  type Profile (line 5) | interface Profile {
  function createProfile (line 15) | async function createProfile(
  function getProfile (line 53) | async function getProfile(id: string): Promise<Profile | null> {
  function listProfiles (line 75) | async function listProfiles(): Promise<Profile[]> {
  function updateProfile (line 99) | async function updateProfile(
  function deleteProfile (line 128) | async function deleteProfile(id: string): Promise<void> {
  function updateLastActive (line 155) | async function updateLastActive(id: string): Promise<void> {

FILE: src/services/persistence/repositories/queryHistoryRepository.ts
  type HistoryEntry (line 5) | interface HistoryEntry {
  function addHistoryEntry (line 16) | async function addHistoryEntry(
  function getHistory (line 53) | async function getHistory(
  function clearHistory (line 85) | async function clearHistory(profileId: string): Promise<void> {
  function getHistoryCount (line 99) | async function getHistoryCount(profileId: string): Promise<number> {

FILE: src/services/persistence/repositories/savedQueryRepository.ts
  type SavedQuery (line 5) | interface SavedQuery {
  function saveQuery (line 17) | async function saveQuery(
  function getSavedQueries (line 50) | async function getSavedQueries(profileId: string, folder?: string): Prom...
  function updateSavedQuery (line 79) | async function updateSavedQuery(
  function deleteSavedQuery (line 110) | async function deleteSavedQuery(id: string): Promise<void> {

FILE: src/services/persistence/repositories/settingsRepository.ts
  type SettingRecord (line 4) | interface SettingRecord {
  function getSetting (line 11) | async function getSetting(
  function setSetting (line 33) | async function setSetting(
  function getSettingsByCategory (line 50) | async function getSettingsByCategory(
  function deleteSetting (line 77) | async function deleteSetting(

FILE: src/services/persistence/repositories/workspaceRepository.ts
  type WorkspaceState (line 4) | interface WorkspaceState {
  function saveWorkspace (line 13) | async function saveWorkspace(
  function loadWorkspace (line 42) | async function loadWorkspace(profileId: string): Promise<WorkspaceState ...

FILE: src/services/persistence/systemDb.ts
  function sqlEscape (line 15) | function sqlEscape(value: string): string {
  function sqlQuote (line 20) | function sqlQuote(value: string): string {
  function sqlIdentifier (line 25) | function sqlIdentifier(name: string): string {
  function isOpfsAvailable (line 37) | async function isOpfsAvailable(): Promise<boolean> {
  function initializeSystemDb (line 54) | function initializeSystemDb(): Promise<void> {
  function doInitialize (line 61) | async function doInitialize(): Promise<void> {
  function getSystemConnection (line 70) | function getSystemConnection(): AsyncDuckDBConnection {
  function isUsingOpfs (line 78) | function isUsingOpfs(): boolean {
  function isSystemDbInitialized (line 85) | function isSystemDbInitialized(): boolean {
  function closeSystemDb (line 92) | async function closeSystemDb(): Promise<void> {

FILE: src/store/index.ts
  function persistWorkspaceState (line 47) | async function persistWorkspaceState(state: DuckStoreState): Promise<voi...
  function startAutoSave (line 117) | function startAutoSave(): void {

FILE: src/store/slices/duckdbSlice.ts
  constant DEFAULT_DUCKDB_MEMORY_LIMIT_MB (line 7) | const DEFAULT_DUCKDB_MEMORY_LIMIT_MB = 4096;

FILE: src/store/slices/profileSlice.ts
  constant VERIFY_TOKEN_PLAINTEXT (line 38) | const VERIFY_TOKEN_PLAINTEXT = "duck-ui-profile-verify";
  function mapProfile (line 280) | function mapProfile(p: {

FILE: src/store/slices/tabSlice.ts
  function parseNotebookCells (line 6) | function parseNotebookCells(tab: EditorTab): NotebookCell[] {
  function serializeCells (line 15) | function serializeCells(cells: NotebookCell[]): string {
  function createDefaultCell (line 21) | function createDefaultCell(type: "sql" | "markdown" = "sql"): NotebookCe...
  function updateNotebookContent (line 25) | function updateNotebookContent(

FILE: src/store/types.ts
  type Window (line 9) | interface Window {
  type CurrentConnection (line 28) | interface CurrentConnection {
  type ConnectionProvider (line 43) | interface ConnectionProvider {
  type ConnectionList (line 58) | interface ConnectionList {
  type ColumnInfo (line 66) | interface ColumnInfo {
  type ColumnStats (line 72) | interface ColumnStats {
  type TableInfo (line 87) | interface TableInfo {
  type DatabaseInfo (line 96) | interface DatabaseInfo {
  type QueryResult (line 105) | interface QueryResult {
  type QueryHistoryItem (line 113) | interface QueryHistoryItem {
  type QueryResultArtifact (line 120) | interface QueryResultArtifact {
  type ExternalQueryResponse (line 127) | interface ExternalQueryResponse {
  type AIProviderType (line 137) | type AIProviderType = "webllm" | "openai" | "anthropic" | "openai-compat...
  type ProviderConfigs (line 139) | interface ProviderConfigs {
  type DuckBrainMessage (line 145) | interface DuckBrainMessage {
  type MountedFolderInfo (line 158) | interface MountedFolderInfo {
  type EditorTabType (line 169) | type EditorTabType = "sql" | "notebook" | "home" | "brain" | "connection...
  type NotebookCell (line 171) | interface NotebookCell {
  type ChartType (line 180) | type ChartType =
  type AggregationType (line 198) | type AggregationType = "sum" | "avg" | "count" | "min" | "max" | "none";
  type SortOrder (line 199) | type SortOrder = "asc" | "desc" | "none";
  type AxisScale (line 200) | type AxisScale = "linear" | "log";
  type SeriesConfig (line 202) | interface SeriesConfig {
  type AxisConfig (line 211) | interface AxisConfig {
  type LegendConfig (line 221) | interface LegendConfig {
  type AnnotationConfig (line 227) | interface AnnotationConfig {
  type DataTransform (line 237) | interface DataTransform {
  type ChartConfig (line 246) | interface ChartConfig {
  type EditorTab (line 269) | interface EditorTab {
  type DuckdbSlice (line 282) | interface DuckdbSlice {
  type ConnectionSlice (line 298) | interface ConnectionSlice {
  type QuerySlice (line 310) | interface QuerySlice {
  type SchemaSlice (line 320) | interface SchemaSlice {
  type TabSlice (line 339) | interface TabSlice {
  type DuckBrainSlice (line 368) | interface DuckBrainSlice {
  type FileSystemSlice (line 402) | interface FileSystemSlice {
  type Profile (line 427) | interface Profile {
  type ProfileSlice (line 436) | interface ProfileSlice {
  type DuckStoreState (line 457) | type DuckStoreState = DuckdbSlice &

FILE: src/types/filesystem.d.ts
  type FileSystemHandlePermissionDescriptor (line 6) | interface FileSystemHandlePermissionDescriptor {
  type FileSystemHandle (line 10) | interface FileSystemHandle {
  type FileSystemFileHandle (line 18) | interface FileSystemFileHandle extends FileSystemHandle {
  type FileSystemDirectoryHandle (line 24) | interface FileSystemDirectoryHandle extends FileSystemHandle {
  type FileSystemGetDirectoryOptions (line 41) | interface FileSystemGetDirectoryOptions {
  type FileSystemGetFileOptions (line 45) | interface FileSystemGetFileOptions {
  type FileSystemRemoveOptions (line 49) | interface FileSystemRemoveOptions {
  type FileSystemCreateWritableOptions (line 53) | interface FileSystemCreateWritableOptions {
  type FileSystemWritableFileStream (line 57) | interface FileSystemWritableFileStream extends WritableStream {
  type FileSystemWriteChunkType (line 63) | type FileSystemWriteChunkType = ArrayBuffer | ArrayBufferView | Blob | s...
  type WriteParams (line 65) | interface WriteParams {
  type OpenFilePickerOptions (line 72) | interface OpenFilePickerOptions {
  type SaveFilePickerOptions (line 79) | interface SaveFilePickerOptions {
  type DirectoryPickerOptions (line 86) | interface DirectoryPickerOptions {
  type FilePickerAcceptType (line 92) | interface FilePickerAcceptType {
  type WellKnownDirectory (line 97) | type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music...
  type Window (line 99) | interface Window {
Condensed preview — 173 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,002K chars).
[
  {
    "path": ".dockerignore",
    "chars": 330,
    "preview": "# Ignore node_modules (local dependencies)\nnode_modules\n\n# Ignore build artifacts\ndist\n\n# Ignore configuration files tha"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "chars": 1925,
    "preview": "name: 🐞 Bug Report\ndescription: Found something that doesn't work as expected?\nbody:\n  - type: dropdown\n    id: Environm"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "chars": 819,
    "preview": "name: 💡 Feature Request\ndescription: Tell us about something Duck-UI doesn't do yet, but should!\nbody:\n  - type: textare"
  },
  {
    "path": ".github/workflows/2-docker-build.yml",
    "chars": 1764,
    "preview": "# .github/workflows/2-docker-build.yml\nname: Build and Push Docker Image\n\non:\n  push:\n    branches:\n      - main\n    pat"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1426,
    "preview": "name: CI\n\non:\n  push:\n    branches: [main]\n    paths-ignore:\n      - \"**.md\"\n      - \"docs/**\"\n  pull_request:\n    branc"
  },
  {
    "path": ".gitignore",
    "chars": 330,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 17,
    "preview": "bunx lint-staged\n"
  },
  {
    "path": ".prettierignore",
    "chars": 85,
    "preview": "dist\nnode_modules\nbun.lockb\n*.wasm\npublic\ndocs/.vitepress/cache\ndocs/.vitepress/dist\n"
  },
  {
    "path": ".prettierrc",
    "chars": 181,
    "preview": "{\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"trailingComma\": \"es5\",\n  \"tabWidth\": 2,\n  \"printWidth\": 100,\n  \"bracketSpac"
  },
  {
    "path": "Dockerfile",
    "chars": 1339,
    "preview": "# Use an official Node runtime as a parent image with bun\nFROM oven/bun:1-alpine AS build\n\nARG DUCK_UI_BASEPATH=\"/\"\n\n# S"
  },
  {
    "path": "LICENSE.md",
    "chars": 2882,
    "preview": "# License\n\n## DUCK UI - Apache License 2.0\n\nCopyright 2025 Caio Ricciuti \n\nLicensed under the Apache License, Version 2."
  },
  {
    "path": "README.md",
    "chars": 5819,
    "preview": "# <img src=\"./public/logo.png\" alt=\"Duck-UI Logo\" title=\"Duck-UI Logo\" width=\"50\"> Duck-UI\n\nDuck-UI is a web-based inter"
  },
  {
    "path": "components.json",
    "chars": 442,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": {"
  },
  {
    "path": "docker-compose.yml",
    "chars": 688,
    "preview": "services:\n  duck-ui:\n    image: ghcr.io/caioricciuti/duck-ui:latest\n    restart: always\n    ports:\n      - \"${DUCK_UI_PO"
  },
  {
    "path": "eslint.config.js",
    "chars": 883,
    "preview": "import js from '@eslint/js'\nimport globals from 'globals'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport reac"
  },
  {
    "path": "index.html",
    "chars": 360,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/png\" href=\"/logo"
  },
  {
    "path": "inject-env.js",
    "chars": 1366,
    "preview": "const fs = require(\"fs\");\nconst path = require(\"path\");\n\nconst indexHtmlPath = path.join(__dirname, \"index.html\");\nlet i"
  },
  {
    "path": "package.json",
    "chars": 4088,
    "preview": "{\n  \"name\": \"duck-ui\",\n  \"private\": true,\n  \"version\": \"0.0.39\",\n  \"release_date\": \"2026-04-13\",\n  \"type\": \"module\",\n  \""
  },
  {
    "path": "public/databases/README.md",
    "chars": 1635,
    "preview": "# Embedded Databases\n\nThis directory allows you to embed DuckDB database files (`.db`) that will be automatically loaded"
  },
  {
    "path": "public/databases/manifest.json",
    "chars": 22,
    "preview": "{\n  \"databases\": []\n}\n"
  },
  {
    "path": "serve.json",
    "chars": 236,
    "preview": "{\n  \"headers\": [\n    {\n      \"source\": \"**/*\",\n      \"headers\": [\n        { \"key\": \"Cross-Origin-Opener-Policy\", \"value\""
  },
  {
    "path": "src/components/charts/ChartVisualizationPro.tsx",
    "chars": 30104,
    "preview": "/**\n * Professional Chart Visualization Component\n * Features: Auto-chart, live preview, multi-series, customization, ex"
  },
  {
    "path": "src/components/charts/UPlotChart.tsx",
    "chars": 1430,
    "preview": "import { useRef, useEffect, useCallback } from \"react\";\nimport uPlot from \"uplot\";\nimport \"uplot/dist/uPlot.min.css\";\n\ni"
  },
  {
    "path": "src/components/charts/tooltipPlugin.ts",
    "chars": 3907,
    "preview": "import uPlot from \"uplot\";\nimport { formatNumberWithSuffix } from \"@/lib/chartUtils\";\n\nfunction escapeHtml(str: string):"
  },
  {
    "path": "src/components/cloud/CloudBrowser.tsx",
    "chars": 7761,
    "preview": "/**\n * Cloud Browser Component\n * Displays cloud storage connections and allows browsing/importing files\n */\n\nimport { u"
  },
  {
    "path": "src/components/cloud/CloudConnectionModal.tsx",
    "chars": 14927,
    "preview": "/**\n * Cloud Connection Modal\n * Form for adding/editing S3, GCS, and Azure cloud storage connections\n */\n\nimport { useS"
  },
  {
    "path": "src/components/command-palette/CommandPalette.tsx",
    "chars": 7674,
    "preview": "import { useEffect, useState, useMemo } from \"react\";\nimport {\n  CommandDialog,\n  CommandEmpty,\n  CommandGroup,\n  Comman"
  },
  {
    "path": "src/components/common/FloatingActionButton.tsx",
    "chars": 1258,
    "preview": "import { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { LucideIcon } from \"lucide-re"
  },
  {
    "path": "src/components/common/ImportOptionsPopover.tsx",
    "chars": 4275,
    "preview": "import React, { useState } from \"react\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popove"
  },
  {
    "path": "src/components/connection/ConnectionsModal.tsx",
    "chars": 12180,
    "preview": "// ConnectionManager.tsx\nimport React from \"react\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,"
  },
  {
    "path": "src/components/duck-brain/DuckBrainCodeBlock.tsx",
    "chars": 3218,
    "preview": "import React from \"react\";\nimport { Copy, Play, FileInput, Check, Loader2 } from \"lucide-react\";\nimport { Button } from "
  },
  {
    "path": "src/components/duck-brain/DuckBrainInput.tsx",
    "chars": 8538,
    "preview": "import React, { useState, useRef, useEffect, useCallback } from \"react\";\nimport { Send, Square, Loader2, Table2, Columns"
  },
  {
    "path": "src/components/duck-brain/DuckBrainMessages.tsx",
    "chars": 6625,
    "preview": "import React, { useRef, useEffect } from \"react\";\nimport { User, Bot, Table2, Columns } from \"lucide-react\";\nimport { Sc"
  },
  {
    "path": "src/components/duck-brain/DuckBrainPanel.tsx",
    "chars": 12777,
    "preview": "import React, { useCallback, useMemo } from \"react\";\nimport { Brain, X, Loader2, AlertCircle, Download, Trash2, RefreshC"
  },
  {
    "path": "src/components/duck-brain/MarkdownContent.tsx",
    "chars": 3568,
    "preview": "import React from \"react\";\nimport ReactMarkdown, { Components } from \"react-markdown\";\nimport { cn } from \"@/lib/utils\";"
  },
  {
    "path": "src/components/duck-brain/ResultsArtifact.tsx",
    "chars": 6127,
    "preview": "import React, { useState } from \"react\";\nimport { Loader2, AlertCircle, ChevronDown, ChevronUp, Table2, RotateCcw } from"
  },
  {
    "path": "src/components/duck-brain/SchemaAutocomplete.tsx",
    "chars": 5033,
    "preview": "import React, { useEffect, useRef } from \"react\";\nimport { Table2, Columns3 } from \"lucide-react\";\nimport { cn } from \"@"
  },
  {
    "path": "src/components/editor/SqlEditor.tsx",
    "chars": 12536,
    "preview": "import React, { useRef, useEffect, useState, useCallback } from \"react\";\nimport {\n  Play,\n  Loader2,\n  Lightbulb,\n  Comm"
  },
  {
    "path": "src/components/editor/monacoConfig.ts",
    "chars": 10229,
    "preview": "// monacoConfig.ts\nimport * as monaco from \"monaco-editor\";\nimport { useDuckStore } from \"@/store\";\nimport { useMemo } f"
  },
  {
    "path": "src/components/explorer/ColumnNode.tsx",
    "chars": 10190,
    "preview": "import React, { useState } from \"react\";\nimport { ChevronRight, ChevronDown, Hash, Type, Calendar, ToggleLeft } from \"lu"
  },
  {
    "path": "src/components/explorer/DataExplorer.tsx",
    "chars": 10911,
    "preview": "import React, { useState, useCallback, lazy, Suspense } from \"react\";\nimport { useDuckStore } from \"@/store\";\nimport { C"
  },
  {
    "path": "src/components/explorer/FileImporter.tsx",
    "chars": 73041,
    "preview": "import React, { useCallback, useState, useMemo, useRef, useEffect } from \"react\";\nimport { format } from \"date-fns\";\nimp"
  },
  {
    "path": "src/components/explorer/TreeNode.tsx",
    "chars": 10140,
    "preview": "// TreeNode.tsx\nimport React, { useState, useCallback, useMemo, useEffect } from \"react\";\nimport {\n  ChevronRight,\n  Che"
  },
  {
    "path": "src/components/folders/FolderBrowser.tsx",
    "chars": 15344,
    "preview": "import React, { useEffect, useState, useCallback } from \"react\";\nimport {\n  FolderOpen,\n  FolderPlus,\n  ChevronRight,\n  "
  },
  {
    "path": "src/components/layout/ConnectionSwitcher.tsx",
    "chars": 4625,
    "preview": "import * as React from \"react\";\nimport { ChevronDown, Circle, Loader2, Cable } from \"lucide-react\";\nimport {\n  DropdownM"
  },
  {
    "path": "src/components/layout/MobileNavDrawer.tsx",
    "chars": 4701,
    "preview": "import { useState } from \"react\";\nimport { Menu, Github, BookText, Cable, Sun, Moon, Brain } from \"lucide-react\";\nimport"
  },
  {
    "path": "src/components/layout/Sidebar.tsx",
    "chars": 16563,
    "preview": "/**\n * Sidebar Component\n * Minimalist icon-only sidebar with tooltips\n */\n\nimport { useState } from \"react\";\nimport {\n "
  },
  {
    "path": "src/components/notebook/MarkdownRenderer.tsx",
    "chars": 758,
    "preview": "import { useMemo } from \"react\";\nimport { marked } from \"marked\";\nimport DOMPurify from \"dompurify\";\n\ninterface Markdown"
  },
  {
    "path": "src/components/notebook/NotebookCell.tsx",
    "chars": 13461,
    "preview": "import React, { useRef, useEffect, useState, useCallback, useMemo } from \"react\";\nimport {\n  Play,\n  Loader2,\n  Trash2,\n"
  },
  {
    "path": "src/components/notebook/NotebookTab.tsx",
    "chars": 3931,
    "preview": "import { useState, useCallback, useRef, useMemo } from \"react\";\nimport { useDuckStore } from \"@/store\";\nimport type { No"
  },
  {
    "path": "src/components/notebook/NotebookToolbar.tsx",
    "chars": 1725,
    "preview": "import { Play, Loader2, Plus, Code, Type } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport "
  },
  {
    "path": "src/components/profile/PasswordDialog.tsx",
    "chars": 3061,
    "preview": "import { useState } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeade"
  },
  {
    "path": "src/components/profile/ProfileAvatar.tsx",
    "chars": 936,
    "preview": "import { useTheme } from \"@/components/theme/theme-provider\";\nimport { cn } from \"@/lib/utils\";\nimport Logo from \"/logo."
  },
  {
    "path": "src/components/profile/ProfileEditor.tsx",
    "chars": 5369,
    "preview": "import { useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/u"
  },
  {
    "path": "src/components/profile/ProfilePicker.tsx",
    "chars": 5330,
    "preview": "import { useState } from \"react\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { Lock, UserPlus, Loader2 } fro"
  },
  {
    "path": "src/components/saved-queries/SaveQueryDialog.tsx",
    "chars": 3369,
    "preview": "import { useState, useEffect } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  "
  },
  {
    "path": "src/components/saved-queries/SavedQueriesPanel.tsx",
    "chars": 7567,
    "preview": "import { useState, useEffect, useRef } from \"react\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { MoreVertic"
  },
  {
    "path": "src/components/table/CellValueViewer.tsx",
    "chars": 5561,
    "preview": "import React, { useState, useMemo } from \"react\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components"
  },
  {
    "path": "src/components/table/ColumnStatsPanel.tsx",
    "chars": 9400,
    "preview": "import React, { useMemo } from \"react\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";"
  },
  {
    "path": "src/components/table/DuckUItable.tsx",
    "chars": 65277,
    "preview": "import React, { useState, useMemo, useEffect, useRef } from \"react\";\nimport {\n  flexRender,\n  getCoreRowModel,\n  getFilt"
  },
  {
    "path": "src/components/theme/mode-toggle.tsx",
    "chars": 1159,
    "preview": "import { Moon, Sun } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  D"
  },
  {
    "path": "src/components/theme/theme-provider.tsx",
    "chars": 1592,
    "preview": "import { createContext, useContext, useEffect, useState } from \"react\";\n\ntype Theme = \"dark\" | \"light\" | \"system\";\n\ntype"
  },
  {
    "path": "src/components/ui/accordion.tsx",
    "chars": 1975,
    "preview": "import * as React from \"react\";\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\";\nimport { ChevronDown } "
  },
  {
    "path": "src/components/ui/alert-dialog.tsx",
    "chars": 4350,
    "preview": "import * as React from \"react\";\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\";\n\nimport { cn } fro"
  },
  {
    "path": "src/components/ui/alert.tsx",
    "chars": 1573,
    "preview": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \""
  },
  {
    "path": "src/components/ui/aspect-ratio.tsx",
    "chars": 158,
    "preview": "\"use client\";\n\nimport * as AspectRatioPrimitive from \"@radix-ui/react-aspect-ratio\";\n\nconst AspectRatio = AspectRatioPri"
  },
  {
    "path": "src/components/ui/badge.tsx",
    "chars": 1110,
    "preview": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \""
  },
  {
    "path": "src/components/ui/breadcrumb.tsx",
    "chars": 2745,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { ChevronRight, MoreHorizontal } fro"
  },
  {
    "path": "src/components/ui/button.tsx",
    "chars": 1848,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"cla"
  },
  {
    "path": "src/components/ui/card.tsx",
    "chars": 1823,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Card = React.forwardRef<HTMLDivElement, React."
  },
  {
    "path": "src/components/ui/checkbox.tsx",
    "chars": 1067,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport { C"
  },
  {
    "path": "src/components/ui/collapsible.tsx",
    "chars": 320,
    "preview": "import * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\";\n\nconst Collapsible = CollapsiblePrimitive.Root;\n\nco"
  },
  {
    "path": "src/components/ui/command.tsx",
    "chars": 4898,
    "preview": "import * as React from \"react\";\nimport { type DialogProps } from \"@radix-ui/react-dialog\";\nimport { Command as CommandPr"
  },
  {
    "path": "src/components/ui/context-menu.tsx",
    "chars": 7240,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as ContextMenuPrimitive from \"@radix-ui/react-context-menu\";\nimp"
  },
  {
    "path": "src/components/ui/dialog.tsx",
    "chars": 3799,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { X } f"
  },
  {
    "path": "src/components/ui/drawer.tsx",
    "chars": 2972,
    "preview": "import * as React from \"react\";\nimport { Drawer as DrawerPrimitive } from \"vaul\";\n\nimport { cn } from \"@/lib/utils\";\n\nco"
  },
  {
    "path": "src/components/ui/dropdown-menu.tsx",
    "chars": 7417,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\ni"
  },
  {
    "path": "src/components/ui/form.tsx",
    "chars": 4101,
    "preview": "import * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { Slot } from \"@radix-ui"
  },
  {
    "path": "src/components/ui/hover-card.tsx",
    "chars": 1192,
    "preview": "import * as React from \"react\";\nimport * as HoverCardPrimitive from \"@radix-ui/react-hover-card\";\n\nimport { cn } from \"@"
  },
  {
    "path": "src/components/ui/input.tsx",
    "chars": 797,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Input = React.forwardRef<HTMLInputElement, Rea"
  },
  {
    "path": "src/components/ui/label.tsx",
    "chars": 715,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { cva, ty"
  },
  {
    "path": "src/components/ui/menubar.tsx",
    "chars": 7914,
    "preview": "import * as React from \"react\";\nimport * as MenubarPrimitive from \"@radix-ui/react-menubar\";\nimport { Check, ChevronRigh"
  },
  {
    "path": "src/components/ui/multi-select.tsx",
    "chars": 4902,
    "preview": "import * as React from \"react\";\nimport { X, Check, ChevronsUpDown } from \"lucide-react\";\nimport { Badge } from \"@/compon"
  },
  {
    "path": "src/components/ui/navigation-menu.tsx",
    "chars": 5027,
    "preview": "import * as React from \"react\";\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\";\nimport { cva"
  },
  {
    "path": "src/components/ui/pagination.tsx",
    "chars": 2736,
    "preview": "import * as React from \"react\";\nimport { ChevronLeft, ChevronRight, MoreHorizontal } from \"lucide-react\";\n\nimport { cn }"
  },
  {
    "path": "src/components/ui/popover.tsx",
    "chars": 1238,
    "preview": "import * as React from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\n\nimport { cn } from \"@/lib/"
  },
  {
    "path": "src/components/ui/progress.tsx",
    "chars": 780,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\n\nimport { "
  },
  {
    "path": "src/components/ui/radio-group.tsx",
    "chars": 1446,
    "preview": "import * as React from \"react\";\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport { Circle } fr"
  },
  {
    "path": "src/components/ui/resizable.tsx",
    "chars": 1714,
    "preview": "\"use client\";\n\nimport { GripVertical } from \"lucide-react\";\nimport * as ResizablePrimitive from \"react-resizable-panels\""
  },
  {
    "path": "src/components/ui/scroll-area.tsx",
    "chars": 1634,
    "preview": "import * as React from \"react\";\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\";\n\nimport { cn } from "
  },
  {
    "path": "src/components/ui/select.tsx",
    "chars": 5614,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { Check"
  },
  {
    "path": "src/components/ui/separator.tsx",
    "chars": 722,
    "preview": "import * as React from \"react\";\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\";\n\nimport { cn } from \"@/"
  },
  {
    "path": "src/components/ui/sheet.tsx",
    "chars": 4232,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { cva, t"
  },
  {
    "path": "src/components/ui/skeleton.tsx",
    "chars": 234,
    "preview": "import { cn } from \"@/lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {\n  "
  },
  {
    "path": "src/components/ui/sonner.tsx",
    "chars": 885,
    "preview": "import { useTheme } from \"@/components/theme/theme-provider\";\nimport { Toaster as Sonner } from \"sonner\";\n\ntype ToasterP"
  },
  {
    "path": "src/components/ui/switch.tsx",
    "chars": 1160,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\";\n\nimport { cn "
  },
  {
    "path": "src/components/ui/table.tsx",
    "chars": 2721,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Table = React.forwardRef<HTMLTableElement, Rea"
  },
  {
    "path": "src/components/ui/tabs.tsx",
    "chars": 1894,
    "preview": "import * as React from \"react\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\n\nimport { cn } from \"@/lib/utils\""
  },
  {
    "path": "src/components/ui/textarea.tsx",
    "chars": 715,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Textarea = React.forwardRef<HTMLTextAreaElemen"
  },
  {
    "path": "src/components/ui/tooltip.tsx",
    "chars": 1169,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\n\nimport { cn"
  },
  {
    "path": "src/components/workspace/BrainTab.tsx",
    "chars": 25652,
    "preview": "import { useState, useEffect } from \"react\";\nimport { useDuckStore, type AIProviderType } from \"@/store\";\nimport { Butto"
  },
  {
    "path": "src/components/workspace/ConnectionsTab.tsx",
    "chars": 12444,
    "preview": "import { useState } from \"react\";\nimport { useDuckStore, ConnectionProvider } from \"@/store\";\nimport { generateUUID } fr"
  },
  {
    "path": "src/components/workspace/ExplainPlanViewer.tsx",
    "chars": 2266,
    "preview": "import { useMemo } from \"react\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport {\n  Sheet,\n  SheetCont"
  },
  {
    "path": "src/components/workspace/HomeTab.tsx",
    "chars": 17659,
    "preview": "import { useState, useEffect } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardHeader,"
  },
  {
    "path": "src/components/workspace/QueryHistory.tsx",
    "chars": 6849,
    "preview": "import React, { useState } from \"react\";\nimport { useDuckStore, QueryHistoryItem } from \"@/store\";\nimport { Button } fro"
  },
  {
    "path": "src/components/workspace/SettingsTab.tsx",
    "chars": 13691,
    "preview": "import { useEffect, useState } from \"react\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/t"
  },
  {
    "path": "src/components/workspace/SortableTab.tsx",
    "chars": 3980,
    "preview": "import React from \"react\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { useSortable } from \"@dnd-kit/sortable\";\nim"
  },
  {
    "path": "src/components/workspace/SqlTab.tsx",
    "chars": 6177,
    "preview": "// src/components/workspace/SqlTab.tsx\nimport React from \"react\";\nimport { useDuckStore } from \"@/store\";\nimport SqlEdit"
  },
  {
    "path": "src/components/workspace/WorkspaceTabs.tsx",
    "chars": 8149,
    "preview": "// src/components/workspace/WorkspaceTabs.tsx\nimport { useMemo, useEffect, lazy, Suspense } from \"react\";\nimport { Tabs,"
  },
  {
    "path": "src/hooks/useQueryFromURL.ts",
    "chars": 2552,
    "preview": "import { useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useDuckStore } from "
  },
  {
    "path": "src/index.css",
    "chars": 8275,
    "preview": "@import \"tailwindcss\";\n@plugin \"@tailwindcss/typography\";\n\n:root {\n  --background: oklch(1 0 0);\n  --foreground: oklch(0"
  },
  {
    "path": "src/lib/chartDataTransform.ts",
    "chars": 9267,
    "preview": "/**\n * Advanced data transformation utilities for charting\n * Handles aggregations, grouping, sorting, and filtering\n */"
  },
  {
    "path": "src/lib/chartExport.ts",
    "chars": 8443,
    "preview": "/**\n * Chart export utilities for PNG, SVG formats\n * Uses html2canvas and SVG manipulation for high-quality exports\n */"
  },
  {
    "path": "src/lib/chartUtils.ts",
    "chars": 3200,
    "preview": "/**\n * Format a number with thousand separators\n * @param value - The number to format\n * @returns Formatted string (e.g"
  },
  {
    "path": "src/lib/cloudStorage/index.ts",
    "chars": 12431,
    "preview": "/**\n * Cloud Storage Service\n * Manages connections to S3, Google Cloud Storage, and Azure Blob Storage\n */\n\nimport { us"
  },
  {
    "path": "src/lib/cloudStorage/testHttpfs.ts",
    "chars": 6260,
    "preview": "/**\n * HTTPFS Feasibility Test Utility\n * Run these tests in browser console to check what works in DuckDB-WASM\n *\n * Us"
  },
  {
    "path": "src/lib/duckBrain/index.ts",
    "chars": 692,
    "preview": "// Duck Brain - Local AI Data Analyst for DuckUI\n// Powered by WebLLM (in-browser LLM inference)\n\nexport { duckBrainServ"
  },
  {
    "path": "src/lib/duckBrain/models.config.ts",
    "chars": 866,
    "preview": "// Model configurations for Duck Brain\nexport interface ModelConfig {\n  id: string;\n  displayName: string;\n  size: strin"
  },
  {
    "path": "src/lib/duckBrain/prompts/text-to-sql.ts",
    "chars": 4218,
    "preview": "import type { ChatCompletionMessageParam } from \"@mlc-ai/web-llm\";\nimport type { DuckBrainMessage } from \"@/store\";\n\nexp"
  },
  {
    "path": "src/lib/duckBrain/providers/anthropic.provider.ts",
    "chars": 6431,
    "preview": "import type { ChatCompletionMessageParam } from \"@mlc-ai/web-llm\";\nimport type {\n  AIProvider,\n  ProviderConfig,\n  Strea"
  },
  {
    "path": "src/lib/duckBrain/providers/index.ts",
    "chars": 1510,
    "preview": "export * from \"./types\";\nexport { OpenAIProvider } from \"./openai.provider\";\nexport { AnthropicProvider } from \"./anthro"
  },
  {
    "path": "src/lib/duckBrain/providers/openai.provider.ts",
    "chars": 6839,
    "preview": "import type { ChatCompletionMessageParam } from \"@mlc-ai/web-llm\";\nimport type {\n  AIProvider,\n  ProviderConfig,\n  Strea"
  },
  {
    "path": "src/lib/duckBrain/providers/types.ts",
    "chars": 2844,
    "preview": "import type { ChatCompletionMessageParam } from \"@mlc-ai/web-llm\";\n\nexport type AIProviderType = \"webllm\" | \"openai\" | \""
  },
  {
    "path": "src/lib/duckBrain/schemaFormatter.ts",
    "chars": 2644,
    "preview": "import type { DatabaseInfo } from \"@/store\";\n\nexport interface SchemaContext {\n  formatted: string;\n  tableCount: number"
  },
  {
    "path": "src/lib/duckBrain/sqlParser.ts",
    "chars": 5300,
    "preview": "export interface ParsedSQLResult {\n  sql: string | null;\n  confidence: number;\n  issues: string[];\n}\n\nconst SQL_KEYWORDS"
  },
  {
    "path": "src/lib/duckBrain/webllm.service.ts",
    "chars": 7903,
    "preview": "import {\n  CreateWebWorkerMLCEngine,\n  WebWorkerMLCEngine,\n  InitProgressReport,\n  ChatCompletionMessageParam,\n} from \"@"
  },
  {
    "path": "src/lib/duckBrain/webllm.worker.ts",
    "chars": 233,
    "preview": "import { WebWorkerMLCEngineHandler } from \"@mlc-ai/web-llm\";\n\n// Create handler for WebLLM engine in Web Worker\nconst ha"
  },
  {
    "path": "src/lib/fileSystem/index.ts",
    "chars": 12525,
    "preview": "/**\n * File System Access API Service\n * Provides persistent folder access across browser sessions\n */\nimport { generate"
  },
  {
    "path": "src/lib/sqlSanitize.ts",
    "chars": 364,
    "preview": "/** Escape a string value for safe use in SQL single-quoted literals */\nexport function sqlEscapeString(value: string): "
  },
  {
    "path": "src/lib/utils.ts",
    "chars": 1985,
    "preview": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: C"
  },
  {
    "path": "src/main.tsx",
    "chars": 11195,
    "preview": "import \"./index.css\";\nimport { StrictMode, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react"
  },
  {
    "path": "src/pages/Home.tsx",
    "chars": 3161,
    "preview": "import { useState } from \"react\";\nimport { Menu } from \"lucide-react\";\nimport DataExplorer from \"@/components/explorer/D"
  },
  {
    "path": "src/services/duckdb/__tests__/resultParser.test.ts",
    "chars": 4278,
    "preview": "import { describe, it, expect } from \"vitest\";\nimport { rawResultToJSON } from \"../resultParser\";\n\ndescribe(\"rawResultTo"
  },
  {
    "path": "src/services/duckdb/__tests__/utils.test.ts",
    "chars": 3422,
    "preview": "import { describe, it, expect, vi } from \"vitest\";\nimport { retryWithBackoff, updateHistory } from \"../utils\";\n\n// Mock "
  },
  {
    "path": "src/services/duckdb/externalConnection.ts",
    "chars": 6937,
    "preview": "import { rawResultToJSON } from \"./resultParser\";\nimport { sqlEscapeIdentifier, sqlEscapeString } from \"@/lib/sqlSanitiz"
  },
  {
    "path": "src/services/duckdb/index.ts",
    "chars": 617,
    "preview": "// DuckDB Service Layer\n// Extracted from the monolithic store for testability and modularity.\n\nexport { rawResultToJSON"
  },
  {
    "path": "src/services/duckdb/opfsConnection.ts",
    "chars": 5213,
    "preview": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport { retryWithBackoff, validateConnection } from \"./utils\";\nimport { "
  },
  {
    "path": "src/services/duckdb/resultParser.ts",
    "chars": 8500,
    "preview": "import type { QueryResult, ExternalQueryResponse } from \"@/store/types\";\n\n/**\n * Converts a raw result (from an external"
  },
  {
    "path": "src/services/duckdb/schemaFetcher.ts",
    "chars": 2112,
    "preview": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport { sqlEscapeIdentifier, sqlEscapeString } from \"@/lib/sqlSanitize\";"
  },
  {
    "path": "src/services/duckdb/utils.ts",
    "chars": 1708,
    "preview": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport { generateUUID } from \"@/lib/utils\";\nimport type { QueryHistoryIte"
  },
  {
    "path": "src/services/duckdb/wasmConnection.ts",
    "chars": 6716,
    "preview": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport { sqlEscapeString, sqlEscapeIdentifier } from \"@/lib/sqlSanitize\";"
  },
  {
    "path": "src/services/persistence/__tests__/crypto.test.ts",
    "chars": 6775,
    "preview": "import { describe, it, expect } from \"vitest\";\nimport {\n  generateEncryptionKey,\n  generateSalt,\n  deriveKeyFromPassword"
  },
  {
    "path": "src/services/persistence/__tests__/migrations.test.ts",
    "chars": 5139,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { describe, it, expect, vi } from \"vitest\";\nimport { runM"
  },
  {
    "path": "src/services/persistence/crypto.ts",
    "chars": 6411,
    "preview": "/**\n * Web Crypto API helpers for encrypting sensitive data at rest.\n * Uses AES-256-GCM for encryption and PBKDF2 for p"
  },
  {
    "path": "src/services/persistence/fallback.ts",
    "chars": 3686,
    "preview": "/**\n * IndexedDB fallback for browsers without OPFS support (e.g., Firefox).\n * Implements the same data access patterns"
  },
  {
    "path": "src/services/persistence/index.ts",
    "chars": 440,
    "preview": "export {\n  initializeSystemDb,\n  getSystemConnection,\n  closeSystemDb,\n  isOpfsAvailable,\n  isUsingOpfs,\n  isSystemDbIni"
  },
  {
    "path": "src/services/persistence/migrations.ts",
    "chars": 4088,
    "preview": "/**\n * Schema migration runner for the system database.\n * Tracks applied versions in `schema_version` table and runs pe"
  },
  {
    "path": "src/services/persistence/repositories/aiConfigRepository.ts",
    "chars": 5395,
    "preview": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nim"
  },
  {
    "path": "src/services/persistence/repositories/connectionRepository.ts",
    "chars": 4089,
    "preview": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nim"
  },
  {
    "path": "src/services/persistence/repositories/index.ts",
    "chars": 274,
    "preview": "export * from \"./profileRepository\";\nexport * from \"./settingsRepository\";\nexport * from \"./connectionRepository\";\nexpor"
  },
  {
    "path": "src/services/persistence/repositories/profileRepository.ts",
    "chars": 5812,
    "preview": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nim"
  },
  {
    "path": "src/services/persistence/repositories/queryHistoryRepository.ts",
    "chars": 3793,
    "preview": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nim"
  },
  {
    "path": "src/services/persistence/repositories/savedQueryRepository.ts",
    "chars": 4297,
    "preview": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nim"
  },
  {
    "path": "src/services/persistence/repositories/settingsRepository.ts",
    "chars": 2709,
    "preview": "import { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nimport { fallbackPut, fallbackGet, fallbackGet"
  },
  {
    "path": "src/services/persistence/repositories/workspaceRepository.ts",
    "chars": 2259,
    "preview": "import { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nimport { fallbackPut, fallbackGet } from \"../f"
  },
  {
    "path": "src/services/persistence/systemDb.ts",
    "chars": 2745,
    "preview": "/**\n * System Database Service\n *\n * Manages persistence for profiles, settings, connections, query history,\n * and othe"
  },
  {
    "path": "src/store/index.ts",
    "chars": 5035,
    "preview": "import { create, type StateCreator } from \"zustand\";\nimport { devtools } from \"zustand/middleware\";\nimport { createDuckd"
  },
  {
    "path": "src/store/slices/connectionSlice.ts",
    "chars": 8034,
    "preview": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport {\n  testExternalConnection,\n  testOP"
  },
  {
    "path": "src/store/slices/duckBrainSlice.ts",
    "chars": 13552,
    "preview": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport { generateUUID } from \"@/lib/utils\";"
  },
  {
    "path": "src/store/slices/duckdbSlice.ts",
    "chars": 4871,
    "preview": "import type { StateCreator } from \"zustand\";\nimport { initializeWasmConnection } from \"@/services/duckdb\";\nimport type {"
  },
  {
    "path": "src/store/slices/fileSystemSlice.ts",
    "chars": 6245,
    "preview": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport { cloudStorageService } from \"@/lib/"
  },
  {
    "path": "src/store/slices/profileSlice.ts",
    "chars": 9213,
    "preview": "import { StateCreator } from \"zustand\";\nimport type { DuckStoreState, ProfileSlice, Profile } from \"../types\";\nimport {\n"
  },
  {
    "path": "src/store/slices/querySlice.ts",
    "chars": 4520,
    "preview": "import type { StateCreator } from \"zustand\";\nimport {\n  executeExternalQuery,\n  resultToJSON,\n  validateConnection,\n  up"
  },
  {
    "path": "src/store/slices/schemaSlice.ts",
    "chars": 7323,
    "preview": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport {\n  executeExternalQuery,\n  resultTo"
  },
  {
    "path": "src/store/slices/tabSlice.ts",
    "chars": 6656,
    "preview": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport { generateUUID } from \"@/lib/utils\";"
  },
  {
    "path": "src/store/types.ts",
    "chars": 11920,
    "preview": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport type { CloudConnection, CloudSupportStatus } from \"@/lib/cloudStor"
  },
  {
    "path": "src/types/filesystem.d.ts",
    "chars": 3282,
    "preview": "/**\n * File System Access API type declarations\n * These APIs are available in Chrome 86+ and Edge 86+\n */\n\ninterface Fi"
  },
  {
    "path": "src/vite-env.d.ts",
    "chars": 188,
    "preview": "/// <reference types=\"vite/client\" />\n\ndeclare const __DUCK_UI_VERSION__: string;\ndeclare const __DUCK_UI_RELEASE_DATE__"
  },
  {
    "path": "tsconfig.app.json",
    "chars": 772,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    },\n\n    \"tsBuildInfoFile\": \"./n"
  },
  {
    "path": "tsconfig.json",
    "chars": 213,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"files\": [],\n  \"refere"
  },
  {
    "path": "tsconfig.node.json",
    "chars": 593,
    "preview": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"target\": \"ES2022\","
  },
  {
    "path": "vite.config.ts",
    "chars": 1733,
    "preview": "import { defineConfig, loadEnv } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport path from 'path';\nimport "
  },
  {
    "path": "vitest.config.ts",
    "chars": 283,
    "preview": "import { defineConfig } from \"vitest/config\";\nimport path from \"path\";\n\nexport default defineConfig({\n  resolve: {\n    a"
  }
]

About this extraction

This page contains the full source code of the caioricciuti/duck-ui GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 173 files (925.3 KB), approximately 226.1k tokens, and a symbol index with 409 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!