Fix ESLint issues

This commit is contained in:
Stijnus 2025-01-28 11:39:12 +01:00
parent 58d3853cd6
commit c4c73622f5
44 changed files with 4193 additions and 3291 deletions

View File

@ -8,6 +8,8 @@ bolt.diy (previously oTToDev) is an open-source AI-powered full-stack web develo
- Focus on best practices and clean code - Focus on best practices and clean code
- Provide clear explanations for code changes - Provide clear explanations for code changes
- Maintain consistent code style with the existing codebase - Maintain consistent code style with the existing codebase
- Always write comments that are relevant to the code they describe
- Always write a changelog what you did and save it in a file called changelog.md in the root of the project
# Techstack # Techstack

View File

@ -1,4 +1,4 @@
name: "Bug report" name: 'Bug report'
description: Create a report to help us improve description: Create a report to help us improve
body: body:
- type: markdown - type: markdown

View File

@ -19,5 +19,5 @@ Usual values: Software Developers using the IDE | Contributors -->
# Capabilities # Capabilities
<!-- which existing capabilities or future features can be imagined that belong to this epic? This list serves as illustration to sketch the boundaries of this epic. <!-- which existing capabilities or future features can be imagined that belong to this epic? This list serves as illustration to sketch the boundaries of this epic.
Once features are actually being planned / described in detail, they can be linked here. --> Once features are actually being planned / described in detail, they can be linked here. -->

View File

@ -13,13 +13,13 @@ assignees: ''
# Scope # Scope
<!-- This is kind-of the definition-of-done for a feature. <!-- This is kind-of the definition-of-done for a feature.
Try to keep the scope as small as possible and prefer creating multiple, small features which each solve a single problem / make something better Try to keep the scope as small as possible and prefer creating multiple, small features which each solve a single problem / make something better
--> -->
# Options # Options
<!-- If you already have an idea how this can be implemented, please describe it here. <!-- If you already have an idea how this can be implemented, please describe it here.
This allows potential other contributors to join forces and provide meaningful feedback prio to even starting work on it. This allows potential other contributors to join forces and provide meaningful feedback prio to even starting work on it.
--> -->

View File

@ -8,7 +8,7 @@ on:
- main - main
tags: tags:
- v* - v*
- "*" - '*'
permissions: permissions:
packages: write packages: write
@ -57,7 +57,7 @@ jobs:
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} # ${{ secrets.DOCKER_USERNAME }} username: ${{ github.actor }} # ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.GITHUB_TOKEN }} # ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.GITHUB_TOKEN }} # ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6

View File

@ -5,7 +5,7 @@ on:
branches: branches:
- main - main
paths: paths:
- 'docs/**' # This will only trigger the workflow when files in docs directory change - 'docs/**' # This will only trigger the workflow when files in docs directory change
permissions: permissions:
contents: write contents: write
jobs: jobs:
@ -23,7 +23,7 @@ jobs:
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: 3.x python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v4 - uses: actions/cache@v4
with: with:
key: mkdocs-material-${{ env.cache_id }} key: mkdocs-material-${{ env.cache_id }}
@ -32,4 +32,4 @@ jobs:
mkdocs-material- mkdocs-material-
- run: pip install mkdocs-material - run: pip install mkdocs-material
- run: mkdocs gh-deploy --force - run: mkdocs gh-deploy --force

View File

@ -9,10 +9,10 @@ on:
jobs: jobs:
validate: validate:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Validate PR Labels - name: Validate PR Labels
run: | run: |
if [[ "${{ contains(github.event.pull_request.labels.*.name, 'stable-release') }}" == "true" ]]; then if [[ "${{ contains(github.event.pull_request.labels.*.name, 'stable-release') }}" == "true" ]]; then
@ -28,4 +28,4 @@ jobs:
fi fi
else else
echo "This PR doesn't have the stable-release label. No release will be created." echo "This PR doesn't have the stable-release label. No release will be created."
fi fi

View File

@ -29,4 +29,4 @@ jobs:
docs docs
refactor refactor
revert revert
test test

View File

@ -2,8 +2,8 @@ name: Mark Stale Issues and Pull Requests
on: on:
schedule: schedule:
- cron: '0 2 * * *' # Runs daily at 2:00 AM UTC - cron: '0 2 * * *' # Runs daily at 2:00 AM UTC
workflow_dispatch: # Allows manual triggering of the workflow workflow_dispatch: # Allows manual triggering of the workflow
jobs: jobs:
stale: stale:
@ -14,12 +14,12 @@ jobs:
uses: actions/stale@v8 uses: actions/stale@v8
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue has been marked as stale due to inactivity. If no further activity occurs, it will be closed in 7 days." stale-issue-message: 'This issue has been marked as stale due to inactivity. If no further activity occurs, it will be closed in 7 days.'
stale-pr-message: "This pull request has been marked as stale due to inactivity. If no further activity occurs, it will be closed in 7 days." stale-pr-message: 'This pull request has been marked as stale due to inactivity. If no further activity occurs, it will be closed in 7 days.'
days-before-stale: 10 # Number of days before marking an issue or PR as stale days-before-stale: 10 # Number of days before marking an issue or PR as stale
days-before-close: 4 # Number of days after being marked stale before closing days-before-close: 4 # Number of days after being marked stale before closing
stale-issue-label: "stale" # Label to apply to stale issues stale-issue-label: 'stale' # Label to apply to stale issues
stale-pr-label: "stale" # Label to apply to stale pull requests stale-pr-label: 'stale' # Label to apply to stale pull requests
exempt-issue-labels: "pinned,important" # Issues with these labels won't be marked stale exempt-issue-labels: 'pinned,important' # Issues with these labels won't be marked stale
exempt-pr-labels: "pinned,important" # PRs with these labels won't be marked stale exempt-pr-labels: 'pinned,important' # PRs with these labels won't be marked stale
operations-per-run: 75 # Limits the number of actions per run to avoid API rate limits operations-per-run: 75 # Limits the number of actions per run to avoid API rate limits

View File

@ -7,12 +7,12 @@ on:
permissions: permissions:
contents: write contents: write
jobs: jobs:
prepare-release: prepare-release:
if: contains(github.event.head_commit.message, '#release') if: contains(github.event.head_commit.message, '#release')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -80,7 +80,6 @@ jobs:
NEW_VERSION=${{ steps.bump_version.outputs.new_version }} NEW_VERSION=${{ steps.bump_version.outputs.new_version }}
pnpm version $NEW_VERSION --no-git-tag-version --allow-same-version pnpm version $NEW_VERSION --no-git-tag-version --allow-same-version
- name: Prepare changelog script - name: Prepare changelog script
run: chmod +x .github/scripts/generate-changelog.sh run: chmod +x .github/scripts/generate-changelog.sh
@ -89,14 +88,14 @@ jobs:
env: env:
NEW_VERSION: ${{ steps.bump_version.outputs.new_version }} NEW_VERSION: ${{ steps.bump_version.outputs.new_version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: .github/scripts/generate-changelog.sh run: .github/scripts/generate-changelog.sh
- name: Get the latest commit hash and version tag - name: Get the latest commit hash and version tag
run: | run: |
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV
echo "NEW_VERSION=${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_ENV echo "NEW_VERSION=${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_ENV
- name: Commit and Tag Release - name: Commit and Tag Release
run: | run: |
git pull git pull
@ -123,4 +122,4 @@ jobs:
gh release create "$VERSION" \ gh release create "$VERSION" \
--title "Release $VERSION" \ --title "Release $VERSION" \
--notes "${{ steps.changelog.outputs.content }}" \ --notes "${{ steps.changelog.outputs.content }}" \
--target stable --target stable

View File

@ -6,15 +6,15 @@ Welcome! This guide provides all the details you need to contribute effectively
## 📋 Table of Contents ## 📋 Table of Contents
1. [Code of Conduct](#code-of-conduct) 1. [Code of Conduct](#code-of-conduct)
2. [How Can I Contribute?](#how-can-i-contribute) 2. [How Can I Contribute?](#how-can-i-contribute)
3. [Pull Request Guidelines](#pull-request-guidelines) 3. [Pull Request Guidelines](#pull-request-guidelines)
4. [Coding Standards](#coding-standards) 4. [Coding Standards](#coding-standards)
5. [Development Setup](#development-setup) 5. [Development Setup](#development-setup)
6. [Testing](#testing) 6. [Testing](#testing)
7. [Deployment](#deployment) 7. [Deployment](#deployment)
8. [Docker Deployment](#docker-deployment) 8. [Docker Deployment](#docker-deployment)
9. [VS Code Dev Containers Integration](#vs-code-dev-containers-integration) 9. [VS Code Dev Containers Integration](#vs-code-dev-containers-integration)
--- ---
@ -27,60 +27,67 @@ This project is governed by our **Code of Conduct**. By participating, you agree
## 🛠️ How Can I Contribute? ## 🛠️ How Can I Contribute?
### 1⃣ Reporting Bugs or Feature Requests ### 1⃣ Reporting Bugs or Feature Requests
- Check the [issue tracker](#) to avoid duplicates. - Check the [issue tracker](#) to avoid duplicates.
- Use issue templates (if available). - Use issue templates (if available).
- Provide detailed, relevant information and steps to reproduce bugs. - Provide detailed, relevant information and steps to reproduce bugs.
### 2⃣ Code Contributions ### 2⃣ Code Contributions
1. Fork the repository.
2. Create a feature or fix branch. 1. Fork the repository.
3. Write and test your code. 2. Create a feature or fix branch.
3. Write and test your code.
4. Submit a pull request (PR). 4. Submit a pull request (PR).
### 3⃣ Join as a Core Contributor ### 3⃣ Join as a Core Contributor
Interested in maintaining and growing the project? Fill out our [Contributor Application Form](https://forms.gle/TBSteXSDCtBDwr5m7). Interested in maintaining and growing the project? Fill out our [Contributor Application Form](https://forms.gle/TBSteXSDCtBDwr5m7).
--- ---
## ✅ Pull Request Guidelines ## ✅ Pull Request Guidelines
### PR Checklist ### PR Checklist
- Branch from the **main** branch.
- Update documentation, if needed.
- Test all functionality manually.
- Focus on one feature/bug per PR.
### Review Process - Branch from the **main** branch.
1. Manual testing by reviewers. - Update documentation, if needed.
2. At least one maintainer review required. - Test all functionality manually.
3. Address review comments. - Focus on one feature/bug per PR.
### Review Process
1. Manual testing by reviewers.
2. At least one maintainer review required.
3. Address review comments.
4. Maintain a clean commit history. 4. Maintain a clean commit history.
--- ---
## 📏 Coding Standards ## 📏 Coding Standards
### General Guidelines ### General Guidelines
- Follow existing code style.
- Comment complex logic. - Follow existing code style.
- Keep functions small and focused. - Comment complex logic.
- Keep functions small and focused.
- Use meaningful variable names. - Use meaningful variable names.
--- ---
## 🖥️ Development Setup ## 🖥️ Development Setup
### 1⃣ Initial Setup ### 1⃣ Initial Setup
- Clone the repository:
- Clone the repository:
```bash ```bash
git clone https://github.com/stackblitz-labs/bolt.diy.git git clone https://github.com/stackblitz-labs/bolt.diy.git
``` ```
- Install dependencies: - Install dependencies:
```bash ```bash
pnpm install pnpm install
``` ```
- Set up environment variables: - Set up environment variables:
1. Rename `.env.example` to `.env.local`. 1. Rename `.env.example` to `.env.local`.
2. Add your API keys: 2. Add your API keys:
```bash ```bash
GROQ_API_KEY=XXX GROQ_API_KEY=XXX
@ -88,23 +95,26 @@ Interested in maintaining and growing the project? Fill out our [Contributor App
OPENAI_API_KEY=XXX OPENAI_API_KEY=XXX
... ...
``` ```
3. Optionally set: 3. Optionally set:
- Debug level: `VITE_LOG_LEVEL=debug` - Debug level: `VITE_LOG_LEVEL=debug`
- Context size: `DEFAULT_NUM_CTX=32768` - Context size: `DEFAULT_NUM_CTX=32768`
**Note**: Never commit your `.env.local` file to version control. Its already in `.gitignore`. **Note**: Never commit your `.env.local` file to version control. Its already in `.gitignore`.
### 2⃣ Run Development Server ### 2⃣ Run Development Server
```bash ```bash
pnpm run dev pnpm run dev
``` ```
**Tip**: Use **Google Chrome Canary** for local testing. **Tip**: Use **Google Chrome Canary** for local testing.
--- ---
## 🧪 Testing ## 🧪 Testing
Run the test suite with: Run the test suite with:
```bash ```bash
pnpm test pnpm test
``` ```
@ -113,10 +123,12 @@ pnpm test
## 🚀 Deployment ## 🚀 Deployment
### Deploy to Cloudflare Pages ### Deploy to Cloudflare Pages
```bash ```bash
pnpm run deploy pnpm run deploy
``` ```
Ensure you have required permissions and that Wrangler is configured. Ensure you have required permissions and that Wrangler is configured.
--- ---
@ -127,67 +139,76 @@ This section outlines the methods for deploying the application using Docker. Th
--- ---
### 🧑‍💻 Development Environment ### 🧑‍💻 Development Environment
#### Build Options #### Build Options
**Option 1: Helper Scripts**
**Option 1: Helper Scripts**
```bash ```bash
# Development build # Development build
npm run dockerbuild npm run dockerbuild
``` ```
**Option 2: Direct Docker Build Command** **Option 2: Direct Docker Build Command**
```bash ```bash
docker build . --target bolt-ai-development docker build . --target bolt-ai-development
``` ```
**Option 3: Docker Compose Profile** **Option 3: Docker Compose Profile**
```bash ```bash
docker compose --profile development up docker compose --profile development up
``` ```
#### Running the Development Container #### Running the Development Container
```bash ```bash
docker run -p 5173:5173 --env-file .env.local bolt-ai:development docker run -p 5173:5173 --env-file .env.local bolt-ai:development
``` ```
--- ---
### 🏭 Production Environment ### 🏭 Production Environment
#### Build Options #### Build Options
**Option 1: Helper Scripts**
**Option 1: Helper Scripts**
```bash ```bash
# Production build # Production build
npm run dockerbuild:prod npm run dockerbuild:prod
``` ```
**Option 2: Direct Docker Build Command** **Option 2: Direct Docker Build Command**
```bash ```bash
docker build . --target bolt-ai-production docker build . --target bolt-ai-production
``` ```
**Option 3: Docker Compose Profile** **Option 3: Docker Compose Profile**
```bash ```bash
docker compose --profile production up docker compose --profile production up
``` ```
#### Running the Production Container #### Running the Production Container
```bash ```bash
docker run -p 5173:5173 --env-file .env.local bolt-ai:production docker run -p 5173:5173 --env-file .env.local bolt-ai:production
``` ```
--- ---
### Coolify Deployment ### Coolify Deployment
For an easy deployment process, use [Coolify](https://github.com/coollabsio/coolify): For an easy deployment process, use [Coolify](https://github.com/coollabsio/coolify):
1. Import your Git repository into Coolify.
2. Choose **Docker Compose** as the build pack. 1. Import your Git repository into Coolify.
3. Configure environment variables (e.g., API keys). 2. Choose **Docker Compose** as the build pack.
4. Set the start command: 3. Configure environment variables (e.g., API keys).
4. Set the start command:
```bash ```bash
docker compose --profile production up docker compose --profile production up
``` ```
@ -200,20 +221,22 @@ The `docker-compose.yaml` configuration is compatible with **VS Code Dev Contain
### Steps to Use Dev Containers ### Steps to Use Dev Containers
1. Open the command palette in VS Code (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS). 1. Open the command palette in VS Code (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS).
2. Select **Dev Containers: Reopen in Container**. 2. Select **Dev Containers: Reopen in Container**.
3. Choose the **development** profile when prompted. 3. Choose the **development** profile when prompted.
4. VS Code will rebuild the container and open it with the pre-configured environment. 4. VS Code will rebuild the container and open it with the pre-configured environment.
--- ---
## 🔑 Environment Variables ## 🔑 Environment Variables
Ensure `.env.local` is configured correctly with: Ensure `.env.local` is configured correctly with:
- API keys.
- Context-specific configurations. - API keys.
- Context-specific configurations.
Example for the `DEFAULT_NUM_CTX` variable:
Example for the `DEFAULT_NUM_CTX` variable:
```bash ```bash
DEFAULT_NUM_CTX=24576 # Uses 32GB VRAM DEFAULT_NUM_CTX=24576 # Uses 32GB VRAM
``` ```

24
FAQ.md
View File

@ -12,6 +12,7 @@ For the best experience with bolt.diy, we recommend using the following models:
- **Qwen 2.5 Coder 32b**: Best model for self-hosting with reasonable hardware requirements - **Qwen 2.5 Coder 32b**: Best model for self-hosting with reasonable hardware requirements
**Note**: Models with less than 7b parameters typically lack the capability to properly interact with bolt! **Note**: Models with less than 7b parameters typically lack the capability to properly interact with bolt!
</details> </details>
<details> <details>
@ -21,20 +22,21 @@ For the best experience with bolt.diy, we recommend using the following models:
Mention the frameworks or libraries you want to use (e.g., Astro, Tailwind, ShadCN) in your initial prompt. This ensures that bolt.diy scaffolds the project according to your preferences. Mention the frameworks or libraries you want to use (e.g., Astro, Tailwind, ShadCN) in your initial prompt. This ensures that bolt.diy scaffolds the project according to your preferences.
- **Use the enhance prompt icon**: - **Use the enhance prompt icon**:
Before sending your prompt, click the *enhance* icon to let the AI refine your prompt. You can edit the suggested improvements before submitting. Before sending your prompt, click the _enhance_ icon to let the AI refine your prompt. You can edit the suggested improvements before submitting.
- **Scaffold the basics first, then add features**: - **Scaffold the basics first, then add features**:
Ensure the foundational structure of your application is in place before introducing advanced functionality. This helps bolt.diy establish a solid base to build on. Ensure the foundational structure of your application is in place before introducing advanced functionality. This helps bolt.diy establish a solid base to build on.
- **Batch simple instructions**: - **Batch simple instructions**:
Combine simple tasks into a single prompt to save time and reduce API credit consumption. For example: Combine simple tasks into a single prompt to save time and reduce API credit consumption. For example:
*"Change the color scheme, add mobile responsiveness, and restart the dev server."* _"Change the color scheme, add mobile responsiveness, and restart the dev server."_
</details> </details>
<details> <details>
<summary><strong>How do I contribute to bolt.diy?</strong></summary> <summary><strong>How do I contribute to bolt.diy?</strong></summary>
Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to get involved! Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to get involved!
</details> </details>
<details> <details>
@ -42,48 +44,60 @@ Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to g
Visit our [Roadmap](https://roadmap.sh/r/ottodev-roadmap-2ovzo) for the latest updates. Visit our [Roadmap](https://roadmap.sh/r/ottodev-roadmap-2ovzo) for the latest updates.
New features and improvements are on the way! New features and improvements are on the way!
</details> </details>
<details> <details>
<summary><strong>Why are there so many open issues/pull requests?</strong></summary> <summary><strong>Why are there so many open issues/pull requests?</strong></summary>
bolt.diy began as a small showcase project on @ColeMedin's YouTube channel to explore editing open-source projects with local LLMs. However, it quickly grew into a massive community effort! bolt.diy began as a small showcase project on @ColeMedin's YouTube channel to explore editing open-source projects with local LLMs. However, it quickly grew into a massive community effort!
We're forming a team of maintainers to manage demand and streamline issue resolution. The maintainers are rockstars, and we're also exploring partnerships to help the project thrive. We're forming a team of maintainers to manage demand and streamline issue resolution. The maintainers are rockstars, and we're also exploring partnerships to help the project thrive.
</details> </details>
<details> <details>
<summary><strong>How do local LLMs compare to larger models like Claude 3.5 Sonnet for bolt.diy?</strong></summary> <summary><strong>How do local LLMs compare to larger models like Claude 3.5 Sonnet for bolt.diy?</strong></summary>
While local LLMs are improving rapidly, larger models like GPT-4o, Claude 3.5 Sonnet, and DeepSeek Coder V2 236b still offer the best results for complex applications. Our ongoing focus is to improve prompts, agents, and the platform to better support smaller local LLMs. While local LLMs are improving rapidly, larger models like GPT-4o, Claude 3.5 Sonnet, and DeepSeek Coder V2 236b still offer the best results for complex applications. Our ongoing focus is to improve prompts, agents, and the platform to better support smaller local LLMs.
</details> </details>
<details> <details>
<summary><strong>Common Errors and Troubleshooting</strong></summary> <summary><strong>Common Errors and Troubleshooting</strong></summary>
### **"There was an error processing this request"** ### **"There was an error processing this request"**
This generic error message means something went wrong. Check both: This generic error message means something went wrong. Check both:
- The terminal (if you started the app with Docker or `pnpm`). - The terminal (if you started the app with Docker or `pnpm`).
- The developer console in your browser (press `F12` or right-click > *Inspect*, then go to the *Console* tab). - The developer console in your browser (press `F12` or right-click > _Inspect_, then go to the _Console_ tab).
### **"x-api-key header missing"** ### **"x-api-key header missing"**
This error is sometimes resolved by restarting the Docker container. This error is sometimes resolved by restarting the Docker container.
If that doesn't work, try switching from Docker to `pnpm` or vice versa. We're actively investigating this issue. If that doesn't work, try switching from Docker to `pnpm` or vice versa. We're actively investigating this issue.
### **Blank preview when running the app** ### **Blank preview when running the app**
A blank preview often occurs due to hallucinated bad code or incorrect commands. A blank preview often occurs due to hallucinated bad code or incorrect commands.
To troubleshoot: To troubleshoot:
- Check the developer console for errors. - Check the developer console for errors.
- Remember, previews are core functionality, so the app isn't broken! We're working on making these errors more transparent. - Remember, previews are core functionality, so the app isn't broken! We're working on making these errors more transparent.
### **"Everything works, but the results are bad"** ### **"Everything works, but the results are bad"**
Local LLMs like Qwen-2.5-Coder are powerful for small applications but still experimental for larger projects. For better results, consider using larger models like GPT-4o, Claude 3.5 Sonnet, or DeepSeek Coder V2 236b. Local LLMs like Qwen-2.5-Coder are powerful for small applications but still experimental for larger projects. For better results, consider using larger models like GPT-4o, Claude 3.5 Sonnet, or DeepSeek Coder V2 236b.
### **"Received structured exception #0xc0000005: access violation"** ### **"Received structured exception #0xc0000005: access violation"**
If you are getting this, you are probably on Windows. The fix is generally to update the [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170) If you are getting this, you are probably on Windows. The fix is generally to update the [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170)
### **"Miniflare or Wrangler errors in Windows"** ### **"Miniflare or Wrangler errors in Windows"**
You will need to make sure you have the latest version of Visual Studio C++ installed (14.40.33816), more information here https://github.com/stackblitz-labs/bolt.diy/issues/19. You will need to make sure you have the latest version of Visual Studio C++ installed (14.40.33816), more information here https://github.com/stackblitz-labs/bolt.diy/issues/19.
</details> </details>
--- ---

View File

@ -31,7 +31,7 @@ and this way communicate where the focus currently is.
2. Grouping of features 2. Grouping of features
By linking features with epics, we can keep them together and document *why* we invest work into a particular thing. By linking features with epics, we can keep them together and document _why_ we invest work into a particular thing.
## Features (mid-term) ## Features (mid-term)
@ -41,13 +41,13 @@ function, you name it).
However, we intentionally describe features in a more vague manner. Why? Everybody loves crisp, well-defined However, we intentionally describe features in a more vague manner. Why? Everybody loves crisp, well-defined
acceptance-criteria, no? Well, every product owner loves it. because he knows what hell get once its done. acceptance-criteria, no? Well, every product owner loves it. because he knows what hell get once its done.
But: **here is no owner of this product**. Therefore, we grant *maximum flexibility to the developer contributing a feature* so that he can bring in his ideas and have most fun implementing it. But: **here is no owner of this product**. Therefore, we grant _maximum flexibility to the developer contributing a feature_ so that he can bring in his ideas and have most fun implementing it.
The feature therefore tries to describe *what* should be improved but not in detail *how*. The feature therefore tries to describe _what_ should be improved but not in detail _how_.
## PRs as materialized features (short-term) ## PRs as materialized features (short-term)
Once a developer starts working on a feature, a draft-PR *can* be opened asap to share, describe and discuss, how the feature shall be implemented. But: this is not a must. It just helps to get early feedback and get other developers involved. Sometimes, the developer just wants to get started and then open a PR later. Once a developer starts working on a feature, a draft-PR _can_ be opened asap to share, describe and discuss, how the feature shall be implemented. But: this is not a must. It just helps to get early feedback and get other developers involved. Sometimes, the developer just wants to get started and then open a PR later.
In a loosely organized project, it may as well happen that multiple PRs are opened for the same feature. This is no real issue: Usually, peoply being passionate about a solution are willing to join forces and get it done together. And if a second developer was just faster getting the same feature realized: Be happy that it's been done, close the PR and look out for the next feature to implement 🤓 In a loosely organized project, it may as well happen that multiple PRs are opened for the same feature. This is no real issue: Usually, peoply being passionate about a solution are willing to join forces and get it done together. And if a second developer was just faster getting the same feature realized: Be happy that it's been done, close the PR and look out for the next feature to implement 🤓

View File

@ -4,10 +4,12 @@
Welcome to bolt.diy, the official open source version of Bolt.new (previously known as oTToDev and bolt.new ANY LLM), which allows you to choose the LLM that you use for each prompt! Currently, you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, LMStudio, Mistral, xAI, HuggingFace, DeepSeek, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See the instructions below for running this locally and extending it to include more models. Welcome to bolt.diy, the official open source version of Bolt.new (previously known as oTToDev and bolt.new ANY LLM), which allows you to choose the LLM that you use for each prompt! Currently, you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, LMStudio, Mistral, xAI, HuggingFace, DeepSeek, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See the instructions below for running this locally and extending it to include more models.
----- ---
Check the [bolt.diy Docs](https://stackblitz-labs.github.io/bolt.diy/) for more offical installation instructions and more informations. Check the [bolt.diy Docs](https://stackblitz-labs.github.io/bolt.diy/) for more offical installation instructions and more informations.
----- ---
Also [this pinned post in our community](https://thinktank.ottomator.ai/t/videos-tutorial-helpful-content/3243) has a bunch of incredible resources for running and deploying bolt.diy yourself! Also [this pinned post in our community](https://thinktank.ottomator.ai/t/videos-tutorial-helpful-content/3243) has a bunch of incredible resources for running and deploying bolt.diy yourself!
We have also launched an experimental agent called the "bolt.diy Expert" that can answer common questions about bolt.diy. Find it here on the [oTTomator Live Agent Studio](https://studio.ottomator.ai/). We have also launched an experimental agent called the "bolt.diy Expert" that can answer common questions about bolt.diy. Find it here on the [oTTomator Live Agent Studio](https://studio.ottomator.ai/).

View File

@ -572,8 +572,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
<div className="flex flex-col justify-center gap-5"> <div className="flex flex-col justify-center gap-5">
{!chatStarted && ( {!chatStarted && (
<div className="flex justify-center gap-2"> <div className="flex justify-center gap-2">
{ImportButtons(importChat)} <div className="flex items-center gap-2">
<GitCloneButton importChat={importChat} /> {ImportButtons(importChat)}
<GitCloneButton importChat={importChat} className="min-w-[120px]" />
</div>
</div> </div>
)} )}
{!chatStarted && {!chatStarted &&

View File

@ -6,22 +6,21 @@ import { generateId } from '~/utils/fileUtils';
import { useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { LoadingOverlay } from '~/components/ui/LoadingOverlay'; import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
import type { IChatMetadata } from '~/lib/persistence'; import { RepositorySelectionDialog } from '~/components/settings/connections/components/RepositorySelectionDialog';
import { cn } from '~/lib/utils';
import { Button } from '~/components/ui/Button';
import type { IChatMetadata } from '~/lib/persistence/db';
const IGNORE_PATTERNS = [ const IGNORE_PATTERNS = [
'node_modules/**', 'node_modules/**',
'.git/**', '.git/**',
'.github/**', '.github/**',
'.vscode/**', '.vscode/**',
'**/*.jpg',
'**/*.jpeg',
'**/*.png',
'dist/**', 'dist/**',
'build/**', 'build/**',
'.next/**', '.next/**',
'coverage/**', 'coverage/**',
'.cache/**', '.cache/**',
'.vscode/**',
'.idea/**', '.idea/**',
'**/*.log', '**/*.log',
'**/.DS_Store', '**/.DS_Store',
@ -34,51 +33,94 @@ const IGNORE_PATTERNS = [
const ig = ignore().add(IGNORE_PATTERNS); const ig = ignore().add(IGNORE_PATTERNS);
const MAX_FILE_SIZE = 100 * 1024; // 100KB limit per file
const MAX_TOTAL_SIZE = 500 * 1024; // 500KB total limit
interface GitCloneButtonProps { interface GitCloneButtonProps {
className?: string; className?: string;
importChat?: (description: string, messages: Message[], metadata?: IChatMetadata) => Promise<void>; importChat?: (description: string, messages: Message[], metadata?: IChatMetadata) => Promise<void>;
} }
export default function GitCloneButton({ importChat }: GitCloneButtonProps) { export default function GitCloneButton({ importChat, className }: GitCloneButtonProps) {
const { ready, gitClone } = useGit(); const { ready, gitClone } = useGit();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const onClick = async (_e: any) => { const handleClone = async (repoUrl: string) => {
if (!ready) { if (!ready) {
return; return;
} }
const repoUrl = prompt('Enter the Git url'); setLoading(true);
if (repoUrl) { try {
setLoading(true); const { workdir, data } = await gitClone(repoUrl);
try { if (importChat) {
const { workdir, data } = await gitClone(repoUrl); const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
const textDecoder = new TextDecoder('utf-8');
if (importChat) { let totalSize = 0;
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath)); const skippedFiles: string[] = [];
console.log(filePaths); const fileContents = [];
const textDecoder = new TextDecoder('utf-8'); for (const filePath of filePaths) {
const { data: content, encoding } = data[filePath];
const fileContents = filePaths // Skip binary files
.map((filePath) => { if (
const { data: content, encoding } = data[filePath]; content instanceof Uint8Array &&
return { !filePath.match(/\.(txt|md|js|jsx|ts|tsx|json|html|css|scss|less|yml|yaml|xml|svg)$/i)
path: filePath, ) {
content: skippedFiles.push(filePath);
encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '', continue;
}; }
})
.filter((f) => f.content);
const commands = await detectProjectCommands(fileContents); try {
const commandsMessage = createCommandsMessage(commands); const textContent =
encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '';
if (!textContent) {
continue;
}
// Check file size
const fileSize = new TextEncoder().encode(textContent).length;
if (fileSize > MAX_FILE_SIZE) {
skippedFiles.push(`${filePath} (too large: ${Math.round(fileSize / 1024)}KB)`);
continue;
}
// Check total size
if (totalSize + fileSize > MAX_TOTAL_SIZE) {
skippedFiles.push(`${filePath} (would exceed total size limit)`);
continue;
}
totalSize += fileSize;
fileContents.push({
path: filePath,
content: textContent,
});
} catch (e: any) {
skippedFiles.push(`${filePath} (error: ${e.message})`);
}
}
const commands = await detectProjectCommands(fileContents);
const commandsMessage = createCommandsMessage(commands);
const filesMessage: Message = {
role: 'assistant',
content: `Cloning the repo ${repoUrl} into ${workdir}
${
skippedFiles.length > 0
? `\nSkipped files (${skippedFiles.length}):
${skippedFiles.map((f) => `- ${f}`).join('\n')}`
: ''
}
const filesMessage: Message = {
role: 'assistant',
content: `Cloning the repo ${repoUrl} into ${workdir}
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled"> <boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">
${fileContents ${fileContents
.map( .map(
@ -89,37 +131,50 @@ ${escapeBoltTags(file.content)}
) )
.join('\n')} .join('\n')}
</boltArtifact>`, </boltArtifact>`,
id: generateId(), id: generateId(),
createdAt: new Date(), createdAt: new Date(),
}; };
const messages = [filesMessage]; const messages = [filesMessage];
if (commandsMessage) { if (commandsMessage) {
messages.push(commandsMessage); messages.push(commandsMessage);
}
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages, { gitUrl: repoUrl });
} }
} catch (error) {
console.error('Error during import:', error); await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
toast.error('Failed to import repository');
} finally {
setLoading(false);
} }
} catch (error) {
console.error('Error during import:', error);
toast.error('Failed to import repository');
} finally {
setLoading(false);
} }
}; };
return ( return (
<> <>
<button <Button
onClick={onClick} onClick={() => setIsDialogOpen(true)}
title="Clone a Git Repo" title="Clone a Git Repo"
className="px-4 py-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 transition-all flex items-center gap-2" variant="outline"
size="lg"
className={cn(
'gap-2 bg-[#F5F5F5] dark:bg-[#252525]',
'text-bolt-elements-textPrimary dark:text-white',
'hover:bg-[#E5E5E5] dark:hover:bg-[#333333]',
'border-[#E5E5E5] dark:border-[#333333]',
'h-10 px-4 py-2 min-w-[120px] justify-center',
'transition-all duration-200 ease-in-out',
className,
)}
disabled={!ready || loading}
> >
<span className="i-ph:git-branch" /> <span className="i-ph:git-branch w-4 h-4" />
Clone a Git Repo Clone a Git Repo
</button> </Button>
<RepositorySelectionDialog isOpen={isDialogOpen} onClose={() => setIsDialogOpen(false)} onSelect={handleClone} />
{loading && <LoadingOverlay message="Please wait while we clone the repository..." />} {loading && <LoadingOverlay message="Please wait while we clone the repository..." />}
</> </>
); );

View File

@ -4,6 +4,8 @@ import { toast } from 'react-toastify';
import { MAX_FILES, isBinaryFile, shouldIncludeFile } from '~/utils/fileUtils'; import { MAX_FILES, isBinaryFile, shouldIncludeFile } from '~/utils/fileUtils';
import { createChatFromFolder } from '~/utils/folderImport'; import { createChatFromFolder } from '~/utils/folderImport';
import { logStore } from '~/lib/stores/logs'; // Assuming logStore is imported from this location import { logStore } from '~/lib/stores/logs'; // Assuming logStore is imported from this location
import { Button } from '~/components/ui/Button';
import { cn } from '~/lib/utils';
interface ImportFolderButtonProps { interface ImportFolderButtonProps {
className?: string; className?: string;
@ -112,17 +114,27 @@ export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ classNam
onChange={handleFileChange} onChange={handleFileChange}
{...({} as any)} {...({} as any)}
/> />
<button <Button
onClick={() => { onClick={() => {
const input = document.getElementById('folder-import'); const input = document.getElementById('folder-import');
input?.click(); input?.click();
}} }}
className={className} variant="outline"
size="lg"
className={cn(
'gap-2 bg-[#F5F5F5] dark:bg-[#252525]',
'text-bolt-elements-textPrimary dark:text-white',
'hover:bg-[#E5E5E5] dark:hover:bg-[#333333]',
'border-[#E5E5E5] dark:border-[#333333]',
'h-10 px-4 py-2 min-w-[120px] justify-center',
'transition-all duration-200 ease-in-out',
className,
)}
disabled={isLoading} disabled={isLoading}
> >
<div className="i-ph:upload-simple" /> <span className="i-ph:upload-simple w-4 h-4" />
{isLoading ? 'Importing...' : 'Import Folder'} {isLoading ? 'Importing...' : 'Import Folder'}
</button> </Button>
</> </>
); );
}; };

View File

@ -1,6 +1,8 @@
import type { Message } from 'ai'; import type { Message } from 'ai';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { ImportFolderButton } from '~/components/chat/ImportFolderButton'; import { ImportFolderButton } from '~/components/chat/ImportFolderButton';
import { Button } from '~/components/ui/Button';
import { cn } from '~/lib/utils';
type ChatData = { type ChatData = {
messages?: Message[]; // Standard Bolt format messages?: Message[]; // Standard Bolt format
@ -57,19 +59,35 @@ export function ImportButtons(importChat: ((description: string, messages: Messa
/> />
<div className="flex flex-col items-center gap-4 max-w-2xl text-center"> <div className="flex flex-col items-center gap-4 max-w-2xl text-center">
<div className="flex gap-2"> <div className="flex gap-2">
<button <Button
onClick={() => { onClick={() => {
const input = document.getElementById('chat-import'); const input = document.getElementById('chat-import');
input?.click(); input?.click();
}} }}
className="px-4 py-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 transition-all flex items-center gap-2" variant="outline"
size="lg"
className={cn(
'gap-2 bg-[#F5F5F5] dark:bg-[#252525]',
'text-bolt-elements-textPrimary dark:text-white',
'hover:bg-[#E5E5E5] dark:hover:bg-[#333333]',
'border-[#E5E5E5] dark:border-[#333333]',
'h-10 px-4 py-2 min-w-[120px] justify-center',
'transition-all duration-200 ease-in-out',
)}
> >
<div className="i-ph:upload-simple" /> <span className="i-ph:upload-simple w-4 h-4" />
Import Chat Import Chat
</button> </Button>
<ImportFolderButton <ImportFolderButton
importChat={importChat} importChat={importChat}
className="px-4 py-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 transition-all flex items-center gap-2" className={cn(
'gap-2 bg-[#F5F5F5] dark:bg-[#252525]',
'text-bolt-elements-textPrimary dark:text-white',
'hover:bg-[#E5E5E5] dark:hover:bg-[#333333]',
'border border-[#E5E5E5] dark:border-[#333333]',
'h-10 px-4 py-2 min-w-[120px] justify-center',
'transition-all duration-200 ease-in-out rounded-lg',
)}
/> />
</div> </div>
</div> </div>

View File

@ -0,0 +1,459 @@
import * as Dialog from '@radix-ui/react-dialog';
import { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import { motion } from 'framer-motion';
import { getLocalStorage } from '~/utils/localStorage';
import { classNames } from '~/utils/classNames';
import type { GitHubUserResponse } from '~/types/GitHub';
import { logStore } from '~/lib/stores/logs';
interface PushToGitHubDialogProps {
isOpen: boolean;
onClose: () => void;
onPush: (repoName: string, username?: string, token?: string) => Promise<void>;
}
interface GitHubRepo {
name: string;
full_name: string;
html_url: string;
description: string;
stargazers_count: number;
forks_count: number;
default_branch: string;
updated_at: string;
language: string;
private: boolean;
}
export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDialogProps) {
const [repoName, setRepoName] = useState('');
const [isPrivate, setIsPrivate] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState<GitHubUserResponse | null>(null);
const [recentRepos, setRecentRepos] = useState<GitHubRepo[]>([]);
const [isFetchingRepos, setIsFetchingRepos] = useState(false);
const [showSuccessDialog, setShowSuccessDialog] = useState(false);
const [createdRepoUrl, setCreatedRepoUrl] = useState('');
// Load GitHub connection on mount
useEffect(() => {
if (isOpen) {
const connection = getLocalStorage('github_connection');
if (connection?.user && connection?.token) {
setUser(connection.user);
// Only fetch if we have both user and token
if (connection.token.trim()) {
fetchRecentRepos(connection.token);
}
}
}
}, [isOpen]);
const fetchRecentRepos = async (token: string) => {
if (!token) {
logStore.logError('No GitHub token available');
toast.error('GitHub authentication required');
return;
}
try {
setIsFetchingRepos(true);
const response = await fetch(
'https://api.github.com/user/repos?sort=updated&per_page=5&type=all&affiliation=owner,organization_member',
{
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${token.trim()}`,
},
},
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
if (response.status === 401) {
toast.error('GitHub token expired. Please reconnect your account.');
// Clear invalid token
const connection = getLocalStorage('github_connection');
if (connection) {
localStorage.removeItem('github_connection');
setUser(null);
}
} else {
logStore.logError('Failed to fetch GitHub repositories', {
status: response.status,
statusText: response.statusText,
error: errorData,
});
toast.error(`Failed to fetch repositories: ${response.statusText}`);
}
return;
}
const repos = (await response.json()) as GitHubRepo[];
setRecentRepos(repos);
} catch (error) {
logStore.logError('Failed to fetch GitHub repositories', { error });
toast.error('Failed to fetch recent repositories');
} finally {
setIsFetchingRepos(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const connection = getLocalStorage('github_connection');
if (!connection?.token || !connection?.user) {
toast.error('Please connect your GitHub account in Settings > Connections first');
return;
}
if (!repoName.trim()) {
toast.error('Repository name is required');
return;
}
setIsLoading(true);
try {
await onPush(repoName, connection.user.login, connection.token);
const repoUrl = `https://github.com/${connection.user.login}/${repoName}`;
setCreatedRepoUrl(repoUrl);
setShowSuccessDialog(true);
} catch (error) {
console.error('Error pushing to GitHub:', error);
toast.error('Failed to push to GitHub. Please check your repository name and try again.');
} finally {
setIsLoading(false);
}
};
const handleClose = () => {
setRepoName('');
setIsPrivate(false);
setShowSuccessDialog(false);
setCreatedRepoUrl('');
onClose();
};
// Success Dialog
if (showSuccessDialog) {
return (
<Dialog.Root open={isOpen} onOpenChange={(open) => !open && handleClose()}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[9999]" />
<div className="fixed inset-0 flex items-center justify-center z-[9999]">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
className="w-[90vw] md:w-[500px]"
>
<Dialog.Content className="bg-white dark:bg-[#0A0A0A] rounded-lg p-6 border border-[#E5E5E5] dark:border-[#1A1A1A] shadow-xl">
<div className="text-center space-y-4">
<motion.div
initial={{ scale: 0.8 }}
animate={{ scale: 1 }}
transition={{ delay: 0.1 }}
className="mx-auto w-12 h-12 rounded-xl bg-green-500/10 flex items-center justify-center text-green-500"
>
<div className="i-ph:check-circle-bold w-6 h-6" />
</motion.div>
<div className="space-y-2">
<h3 className="text-lg font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark">
Repository Created Successfully!
</h3>
<p className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark">
Your code has been pushed to GitHub and is ready to use.
</p>
</div>
<div className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg p-3 text-left">
<p className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark mb-2">
Repository URL
</p>
<div className="flex items-center gap-2">
<code className="flex-1 text-sm bg-bolt-elements-background dark:bg-bolt-elements-background-dark px-3 py-2 rounded border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark font-mono">
{createdRepoUrl}
</code>
<motion.button
onClick={() => {
navigator.clipboard.writeText(createdRepoUrl);
toast.success('URL copied to clipboard');
}}
className="p-2 text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary dark:text-bolt-elements-textSecondary-dark dark:hover:text-bolt-elements-textPrimary-dark"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<div className="i-ph:copy w-4 h-4" />
</motion.button>
</div>
</div>
<div className="flex gap-2 pt-2">
<motion.button
onClick={handleClose}
className="flex-1 px-4 py-2 text-sm bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
Close
</motion.button>
<motion.a
href={createdRepoUrl}
target="_blank"
rel="noopener noreferrer"
className="flex-1 px-4 py-2 text-sm bg-purple-500 text-white rounded-lg hover:bg-purple-600 inline-flex items-center justify-center gap-2"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<div className="i-ph:github-logo w-4 h-4" />
Open Repository
</motion.a>
</div>
</div>
</Dialog.Content>
</motion.div>
</div>
</Dialog.Portal>
</Dialog.Root>
);
}
if (!user) {
return (
<Dialog.Root open={isOpen} onOpenChange={(open) => !open && handleClose()}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[9999]" />
<div className="fixed inset-0 flex items-center justify-center z-[9999]">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
className="w-[90vw] md:w-[500px]"
>
<Dialog.Content className="bg-white dark:bg-[#0A0A0A] rounded-lg p-6 border border-[#E5E5E5] dark:border-[#1A1A1A] shadow-xl">
<div className="text-center space-y-4">
<motion.div
initial={{ scale: 0.8 }}
animate={{ scale: 1 }}
transition={{ delay: 0.1 }}
className="mx-auto w-12 h-12 rounded-xl bg-bolt-elements-background-depth-3 flex items-center justify-center text-purple-500"
>
<div className="i-ph:github-logo w-6 h-6" />
</motion.div>
<h3 className="text-lg font-medium text-gray-900 dark:text-white">GitHub Connection Required</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Please connect your GitHub account in Settings {'>'} Connections to push your code to GitHub.
</p>
<motion.button
className="px-4 py-2 rounded-lg bg-purple-500 text-white text-sm hover:bg-purple-600 inline-flex items-center gap-2"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={handleClose}
>
<div className="i-ph:x-circle" />
Close
</motion.button>
</div>
</Dialog.Content>
</motion.div>
</div>
</Dialog.Portal>
</Dialog.Root>
);
}
return (
<Dialog.Root open={isOpen} onOpenChange={(open) => !open && handleClose()}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[9999]" />
<div className="fixed inset-0 flex items-center justify-center z-[9999]">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
className="w-[90vw] md:w-[500px]"
>
<Dialog.Content className="bg-white dark:bg-[#0A0A0A] rounded-lg border border-[#E5E5E5] dark:border-[#1A1A1A] shadow-xl">
<div className="p-6">
<div className="flex items-center gap-4 mb-6">
<motion.div
initial={{ scale: 0.8 }}
animate={{ scale: 1 }}
transition={{ delay: 0.1 }}
className="w-10 h-10 rounded-xl bg-bolt-elements-background-depth-3 flex items-center justify-center text-purple-500"
>
<div className="i-ph:git-branch w-5 h-5" />
</motion.div>
<div>
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">
Push to GitHub
</Dialog.Title>
<p className="text-sm text-gray-600 dark:text-gray-400">
Push your code to a new or existing GitHub repository
</p>
</div>
<Dialog.Close
className="ml-auto p-2 text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400"
onClick={handleClose}
>
<div className="i-ph:x w-5 h-5" />
</Dialog.Close>
</div>
<div className="flex items-center gap-3 mb-6 p-3 bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg">
<img src={user.avatar_url} alt={user.login} className="w-10 h-10 rounded-full" />
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">{user.name || user.login}</p>
<p className="text-sm text-gray-500 dark:text-gray-400">@{user.login}</p>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<label htmlFor="repoName" className="text-sm text-gray-600 dark:text-gray-400">
Repository Name
</label>
<input
id="repoName"
type="text"
value={repoName}
onChange={(e) => setRepoName(e.target.value)}
placeholder="my-awesome-project"
className="w-full px-4 py-2 rounded-lg bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 border border-[#E5E5E5] dark:border-[#1A1A1A] text-gray-900 dark:text-white placeholder-gray-400"
required
/>
</div>
{recentRepos.length > 0 && (
<div className="space-y-2">
<label className="text-sm text-gray-600 dark:text-gray-400">Recent Repositories</label>
<div className="space-y-2">
{recentRepos.map((repo) => (
<motion.button
key={repo.full_name}
type="button"
onClick={() => setRepoName(repo.name)}
className="w-full p-3 text-left rounded-lg bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 hover:bg-bolt-elements-background-depth-3 dark:hover:bg-bolt-elements-background-depth-4 transition-colors group"
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="i-ph:git-repository w-4 h-4 text-purple-500" />
<span className="text-sm font-medium text-gray-900 dark:text-white group-hover:text-purple-500">
{repo.name}
</span>
</div>
{repo.private && (
<span className="text-xs px-2 py-1 rounded-full bg-purple-500/10 text-purple-500">
Private
</span>
)}
</div>
{repo.description && (
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400 line-clamp-2">
{repo.description}
</p>
)}
<div className="mt-2 flex items-center gap-3 text-xs text-gray-400 dark:text-gray-500">
{repo.language && (
<span className="flex items-center gap-1">
<div className="i-ph:code w-3 h-3" />
{repo.language}
</span>
)}
<span className="flex items-center gap-1">
<div className="i-ph:star w-3 h-3" />
{repo.stargazers_count.toLocaleString()}
</span>
<span className="flex items-center gap-1">
<div className="i-ph:git-fork w-3 h-3" />
{repo.forks_count.toLocaleString()}
</span>
<span className="flex items-center gap-1">
<div className="i-ph:clock w-3 h-3" />
{new Date(repo.updated_at).toLocaleDateString()}
</span>
</div>
</motion.button>
))}
</div>
</div>
)}
{isFetchingRepos && (
<div className="flex items-center justify-center py-4 text-gray-500 dark:text-gray-400">
<div className="i-ph:spinner-gap-bold animate-spin w-4 h-4 mr-2" />
Loading repositories...
</div>
)}
<div className="flex items-center gap-2">
<input
type="checkbox"
id="private"
checked={isPrivate}
onChange={(e) => setIsPrivate(e.target.checked)}
className="rounded border-[#E5E5E5] dark:border-[#1A1A1A] text-purple-500 focus:ring-purple-500 dark:bg-[#0A0A0A]"
/>
<label htmlFor="private" className="text-sm text-gray-600 dark:text-gray-400">
Make repository private
</label>
</div>
<div className="pt-4 flex gap-2">
<motion.button
type="button"
onClick={handleClose}
className="px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#1A1A1A] text-gray-600 dark:text-gray-400 hover:bg-[#E5E5E5] dark:hover:bg-[#252525] text-sm"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
Cancel
</motion.button>
<motion.button
type="submit"
disabled={isLoading}
className={classNames(
'flex-1 px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 text-sm inline-flex items-center justify-center gap-2',
isLoading ? 'opacity-50 cursor-not-allowed' : '',
)}
whileHover={!isLoading ? { scale: 1.02 } : {}}
whileTap={!isLoading ? { scale: 0.98 } : {}}
>
{isLoading ? (
<>
<div className="i-ph:spinner-gap-bold animate-spin w-4 h-4" />
Pushing...
</>
) : (
<>
<div className="i-ph:git-branch w-4 h-4" />
Push to GitHub
</>
)}
</motion.button>
</div>
</form>
</div>
</Dialog.Content>
</motion.div>
</div>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@ -0,0 +1,692 @@
import type { GitHubRepoInfo, GitHubContent, RepositoryStats } from '~/types/GitHub';
import { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import * as Dialog from '@radix-ui/react-dialog';
import { cn } from '~/lib/utils';
import { getLocalStorage } from '~/utils/localStorage';
import { classNames as utilsClassNames } from '~/utils/classNames';
import { motion } from 'framer-motion';
import { formatSize } from '~/utils/formatSize';
interface GitHubTreeResponse {
tree: Array<{
path: string;
type: string;
size?: number;
}>;
}
interface RepositorySelectionDialogProps {
isOpen: boolean;
onClose: () => void;
onSelect: (url: string) => void;
}
interface SearchFilters {
language?: string;
stars?: number;
forks?: number;
}
interface StatsDialogProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
stats: RepositoryStats;
isLargeRepo?: boolean;
}
function StatsDialog({ isOpen, onClose, onConfirm, stats, isLargeRepo }: StatsDialogProps) {
return (
<Dialog.Root open={isOpen} onOpenChange={(open) => !open && onClose()}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[9999]" />
<div className="fixed inset-0 flex items-center justify-center z-[9999]">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
className="w-[90vw] md:w-[500px]"
>
<Dialog.Content className="bg-white dark:bg-[#1E1E1E] rounded-lg border border-[#E5E5E5] dark:border-[#333333] shadow-xl">
<div className="p-6 space-y-4">
<div>
<h3 className="text-lg font-medium text-[#111111] dark:text-white">Repository Overview</h3>
<div className="mt-4 space-y-2">
<p className="text-sm text-[#666666] dark:text-[#999999]">Repository Statistics:</p>
<div className="space-y-2 text-sm text-[#111111] dark:text-white">
<div className="flex items-center gap-2">
<span className="i-ph:files text-purple-500 w-4 h-4" />
<span>Total Files: {stats.totalFiles}</span>
</div>
<div className="flex items-center gap-2">
<span className="i-ph:database text-purple-500 w-4 h-4" />
<span>Total Size: {formatSize(stats.totalSize)}</span>
</div>
<div className="flex items-center gap-2">
<span className="i-ph:code text-purple-500 w-4 h-4" />
<span>
Languages:{' '}
{Object.entries(stats.languages)
.sort(([, a], [, b]) => b - a)
.slice(0, 3)
.map(([lang, size]) => `${lang} (${formatSize(size)})`)
.join(', ')}
</span>
</div>
{stats.hasPackageJson && (
<div className="flex items-center gap-2">
<span className="i-ph:package text-purple-500 w-4 h-4" />
<span>Has package.json</span>
</div>
)}
{stats.hasDependencies && (
<div className="flex items-center gap-2">
<span className="i-ph:tree-structure text-purple-500 w-4 h-4" />
<span>Has dependencies</span>
</div>
)}
</div>
</div>
{isLargeRepo && (
<div className="mt-4 p-3 bg-yellow-50 dark:bg-yellow-500/10 rounded-lg text-sm flex items-start gap-2">
<span className="i-ph:warning text-yellow-600 dark:text-yellow-500 w-4 h-4 flex-shrink-0 mt-0.5" />
<div className="text-yellow-800 dark:text-yellow-500">
This repository is quite large ({formatSize(stats.totalSize)}). Importing it might take a while
and could impact performance.
</div>
</div>
)}
</div>
</div>
<div className="border-t border-[#E5E5E5] dark:border-[#333333] p-4 flex justify-end gap-3 bg-[#F9F9F9] dark:bg-[#252525] rounded-b-lg">
<button
onClick={onClose}
className="px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#333333] text-[#666666] hover:text-[#111111] dark:text-[#999999] dark:hover:text-white transition-colors"
>
Cancel
</button>
<button
onClick={onConfirm}
className="px-4 py-2 rounded-lg bg-purple-500 text-white hover:bg-purple-600 transition-colors"
>
OK
</button>
</div>
</Dialog.Content>
</motion.div>
</div>
</Dialog.Portal>
</Dialog.Root>
);
}
export function RepositorySelectionDialog({ isOpen, onClose, onSelect }: RepositorySelectionDialogProps) {
const [selectedRepository, setSelectedRepository] = useState<GitHubRepoInfo | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [repositories, setRepositories] = useState<GitHubRepoInfo[]>([]);
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState<GitHubRepoInfo[]>([]);
const [activeTab, setActiveTab] = useState<'my-repos' | 'search' | 'url'>('my-repos');
const [customUrl, setCustomUrl] = useState('');
const [branches, setBranches] = useState<{ name: string; default?: boolean }[]>([]);
const [selectedBranch, setSelectedBranch] = useState('');
const [filters, setFilters] = useState<SearchFilters>({});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [stats, setStats] = useState<RepositoryStats | null>(null);
const [showStatsDialog, setShowStatsDialog] = useState(false);
const [currentStats, setCurrentStats] = useState<RepositoryStats | null>(null);
const [pendingGitUrl, setPendingGitUrl] = useState<string>('');
// Fetch user's repositories when dialog opens
useEffect(() => {
if (isOpen && activeTab === 'my-repos') {
fetchUserRepos();
}
}, [isOpen, activeTab]);
const fetchUserRepos = async () => {
const connection = getLocalStorage('github_connection');
if (!connection?.token) {
toast.error('Please connect your GitHub account first');
return;
}
setIsLoading(true);
try {
const response = await fetch('https://api.github.com/user/repos?sort=updated&per_page=100&type=all', {
headers: {
Authorization: `Bearer ${connection.token}`,
},
});
if (!response.ok) {
throw new Error('Failed to fetch repositories');
}
const data = await response.json();
// Add type assertion and validation
if (
Array.isArray(data) &&
data.every((item) => typeof item === 'object' && item !== null && 'full_name' in item)
) {
setRepositories(data as GitHubRepoInfo[]);
} else {
throw new Error('Invalid repository data format');
}
} catch (error) {
console.error('Error fetching repos:', error);
toast.error('Failed to fetch your repositories');
} finally {
setIsLoading(false);
}
};
const handleSearch = async (query: string) => {
setIsLoading(true);
setSearchResults([]);
try {
let searchQuery = query;
if (filters.language) {
searchQuery += ` language:${filters.language}`;
}
if (filters.stars) {
searchQuery += ` stars:>${filters.stars}`;
}
if (filters.forks) {
searchQuery += ` forks:>${filters.forks}`;
}
const response = await fetch(
`https://api.github.com/search/repositories?q=${encodeURIComponent(searchQuery)}&sort=stars&order=desc`,
{
headers: {
Accept: 'application/vnd.github.v3+json',
},
},
);
if (!response.ok) {
throw new Error('Failed to search repositories');
}
const data = await response.json();
// Add type assertion and validation
if (typeof data === 'object' && data !== null && 'items' in data && Array.isArray(data.items)) {
setSearchResults(data.items as GitHubRepoInfo[]);
} else {
throw new Error('Invalid search results format');
}
} catch (error) {
console.error('Error searching repos:', error);
toast.error('Failed to search repositories');
} finally {
setIsLoading(false);
}
};
const fetchBranches = async (repo: GitHubRepoInfo) => {
setIsLoading(true);
try {
const response = await fetch(`https://api.github.com/repos/${repo.full_name}/branches`, {
headers: {
Authorization: `Bearer ${getLocalStorage('github_connection')?.token}`,
},
});
if (!response.ok) {
throw new Error('Failed to fetch branches');
}
const data = await response.json();
// Add type assertion and validation
if (Array.isArray(data) && data.every((item) => typeof item === 'object' && item !== null && 'name' in item)) {
setBranches(
data.map((branch) => ({
name: branch.name,
default: branch.name === repo.default_branch,
})),
);
} else {
throw new Error('Invalid branch data format');
}
} catch (error) {
console.error('Error fetching branches:', error);
toast.error('Failed to fetch branches');
} finally {
setIsLoading(false);
}
};
const handleRepoSelect = async (repo: GitHubRepoInfo) => {
setSelectedRepository(repo);
await fetchBranches(repo);
};
const formatGitUrl = (url: string): string => {
// Remove any tree references and ensure .git extension
const baseUrl = url
.replace(/\/tree\/[^/]+/, '') // Remove /tree/branch-name
.replace(/\/$/, '') // Remove trailing slash
.replace(/\.git$/, ''); // Remove .git if present
return `${baseUrl}.git`;
};
const verifyRepository = async (repoUrl: string): Promise<RepositoryStats | null> => {
try {
const [owner, repo] = repoUrl
.replace(/\.git$/, '')
.split('/')
.slice(-2);
const connection = getLocalStorage('github_connection');
const headers: HeadersInit = connection?.token ? { Authorization: `Bearer ${connection.token}` } : {};
// Fetch repository tree
const treeResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/main?recursive=1`, {
headers,
});
if (!treeResponse.ok) {
throw new Error('Failed to fetch repository structure');
}
const treeData = (await treeResponse.json()) as GitHubTreeResponse;
// Calculate repository stats
let totalSize = 0;
let totalFiles = 0;
const languages: { [key: string]: number } = {};
let hasPackageJson = false;
let hasDependencies = false;
for (const file of treeData.tree) {
if (file.type === 'blob') {
totalFiles++;
if (file.size) {
totalSize += file.size;
}
// Check for package.json
if (file.path === 'package.json') {
hasPackageJson = true;
// Fetch package.json content to check dependencies
const contentResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/package.json`, {
headers,
});
if (contentResponse.ok) {
const content = (await contentResponse.json()) as GitHubContent;
const packageJson = JSON.parse(Buffer.from(content.content, 'base64').toString());
hasDependencies = !!(
packageJson.dependencies ||
packageJson.devDependencies ||
packageJson.peerDependencies
);
}
}
// Detect language based on file extension
const ext = file.path.split('.').pop()?.toLowerCase();
if (ext) {
languages[ext] = (languages[ext] || 0) + (file.size || 0);
}
}
}
const stats: RepositoryStats = {
totalFiles,
totalSize,
languages,
hasPackageJson,
hasDependencies,
};
setStats(stats);
return stats;
} catch (error) {
console.error('Error verifying repository:', error);
toast.error('Failed to verify repository');
return null;
}
};
const handleImport = async () => {
try {
let gitUrl: string;
if (activeTab === 'url' && customUrl) {
gitUrl = formatGitUrl(customUrl);
} else if (selectedRepository) {
gitUrl = formatGitUrl(selectedRepository.html_url);
if (selectedBranch) {
gitUrl = `${gitUrl}#${selectedBranch}`;
}
} else {
return;
}
// Verify repository before importing
const stats = await verifyRepository(gitUrl);
if (!stats) {
return;
}
setCurrentStats(stats);
setPendingGitUrl(gitUrl);
setShowStatsDialog(true);
} catch (error) {
console.error('Error preparing repository:', error);
toast.error('Failed to prepare repository. Please try again.');
}
};
const handleStatsConfirm = () => {
setShowStatsDialog(false);
if (pendingGitUrl) {
onSelect(pendingGitUrl);
onClose();
}
};
const handleFilterChange = (key: keyof SearchFilters, value: string) => {
let parsedValue: string | number | undefined = value;
if (key === 'stars' || key === 'forks') {
parsedValue = value ? parseInt(value, 10) : undefined;
}
setFilters((prev) => ({ ...prev, [key]: parsedValue }));
handleSearch(searchQuery);
};
// Handle dialog close properly
const handleClose = () => {
setIsLoading(false); // Reset loading state
setSearchQuery(''); // Reset search
setSearchResults([]); // Reset results
onClose();
};
return (
<Dialog.Root
open={isOpen}
onOpenChange={(open) => {
if (!open) {
handleClose();
}
}}
>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50" />
<Dialog.Content className="fixed top-[50%] left-[50%] -translate-x-1/2 -translate-y-1/2 w-[90vw] md:w-[600px] max-h-[85vh] overflow-hidden bg-white dark:bg-[#1A1A1A] rounded-xl shadow-xl z-[51] border border-[#E5E5E5] dark:border-[#333333]">
<div className="p-4 border-b border-[#E5E5E5] dark:border-[#333333] flex items-center justify-between">
<Dialog.Title className="text-lg font-semibold text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark">
Import GitHub Repository
</Dialog.Title>
<Dialog.Close
onClick={handleClose}
className={cn(
'p-2 rounded-lg transition-all duration-200 ease-in-out',
'text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary',
'dark:text-bolt-elements-textTertiary-dark dark:hover:text-bolt-elements-textPrimary-dark',
'hover:bg-bolt-elements-background-depth-2 dark:hover:bg-bolt-elements-background-depth-3',
'focus:outline-none focus:ring-2 focus:ring-bolt-elements-borderColor dark:focus:ring-bolt-elements-borderColor-dark',
)}
>
<span className="i-ph:x block w-5 h-5" aria-hidden="true" />
<span className="sr-only">Close dialog</span>
</Dialog.Close>
</div>
<div className="p-4">
<div className="flex gap-2 mb-4">
<TabButton active={activeTab === 'my-repos'} onClick={() => setActiveTab('my-repos')}>
<span className="i-ph:book-bookmark" />
My Repos
</TabButton>
<TabButton active={activeTab === 'search'} onClick={() => setActiveTab('search')}>
<span className="i-ph:magnifying-glass" />
Search
</TabButton>
<TabButton active={activeTab === 'url'} onClick={() => setActiveTab('url')}>
<span className="i-ph:link" />
URL
</TabButton>
</div>
{activeTab === 'url' ? (
<div className="space-y-4">
<input
type="text"
placeholder="Enter GitHub repository URL..."
value={customUrl}
onChange={(e) => setCustomUrl(e.target.value)}
className="w-full px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333] text-bolt-elements-textPrimary"
/>
<button
onClick={handleImport}
disabled={!customUrl}
className="w-full h-10 px-4 py-2 rounded-lg bg-purple-500 text-white hover:bg-purple-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 flex items-center gap-2 justify-center"
>
Import Repository
</button>
</div>
) : (
<>
{activeTab === 'search' && (
<div className="space-y-4 mb-4">
<div className="flex gap-2">
<input
type="text"
placeholder="Search repositories..."
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
handleSearch(e.target.value);
}}
className="flex-1 px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333] text-bolt-elements-textPrimary"
/>
<button
onClick={() => setFilters({})}
className="px-3 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#252525] text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary"
>
<span className="i-ph:funnel-simple" />
</button>
</div>
<div className="grid grid-cols-2 gap-2">
<input
type="text"
placeholder="Filter by language..."
value={filters.language || ''}
onChange={(e) => {
setFilters({ ...filters, language: e.target.value });
handleSearch(searchQuery);
}}
className="px-3 py-1.5 text-sm rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333]"
/>
<input
type="number"
placeholder="Min stars..."
value={filters.stars || ''}
onChange={(e) => handleFilterChange('stars', e.target.value)}
className="px-3 py-1.5 text-sm rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333]"
/>
</div>
<input
type="number"
placeholder="Min forks..."
value={filters.forks || ''}
onChange={(e) => handleFilterChange('forks', e.target.value)}
className="px-3 py-1.5 text-sm rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333]"
/>
</div>
)}
<div className="space-y-3 max-h-[400px] overflow-y-auto pr-2 custom-scrollbar">
{selectedRepository ? (
<div className="space-y-4">
<div className="flex items-center gap-2">
<button
onClick={() => setSelectedRepository(null)}
className="p-1.5 rounded-lg hover:bg-[#F5F5F5] dark:hover:bg-[#252525]"
>
<span className="i-ph:arrow-left w-4 h-4" />
</button>
<h3 className="font-medium">{selectedRepository.full_name}</h3>
</div>
<div className="space-y-2">
<label className="text-sm text-bolt-elements-textSecondary">Select Branch</label>
<select
value={selectedBranch}
onChange={(e) => setSelectedBranch(e.target.value)}
className="w-full px-3 py-2 rounded-lg bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark focus:outline-none focus:ring-2 focus:ring-bolt-elements-borderColor dark:focus:ring-bolt-elements-borderColor-dark"
>
{branches.map((branch) => (
<option
key={branch.name}
value={branch.name}
className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark"
>
{branch.name} {branch.default ? '(default)' : ''}
</option>
))}
</select>
<button
onClick={handleImport}
className="w-full h-10 px-4 py-2 rounded-lg bg-purple-500 text-white hover:bg-purple-600 transition-all duration-200 flex items-center gap-2 justify-center"
>
Import Selected Branch
</button>
</div>
</div>
) : (
<RepositoryList
repos={activeTab === 'my-repos' ? repositories : searchResults}
isLoading={isLoading}
onSelect={handleRepoSelect}
activeTab={activeTab}
/>
)}
</div>
</>
)}
</div>
</Dialog.Content>
</Dialog.Portal>
{currentStats && (
<StatsDialog
isOpen={showStatsDialog}
onClose={handleStatsConfirm}
onConfirm={handleStatsConfirm}
stats={currentStats}
isLargeRepo={currentStats.totalSize > 50 * 1024 * 1024}
/>
)}
</Dialog.Root>
);
}
function TabButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {
return (
<button
onClick={onClick}
className={utilsClassNames(
'px-4 py-2 h-10 rounded-lg transition-all duration-200 flex items-center gap-2 min-w-[120px] justify-center',
active
? 'bg-purple-500 text-white hover:bg-purple-600'
: 'bg-[#F5F5F5] dark:bg-[#252525] text-bolt-elements-textPrimary dark:text-white hover:bg-[#E5E5E5] dark:hover:bg-[#333333] border border-[#E5E5E5] dark:border-[#333333]',
)}
>
{children}
</button>
);
}
function RepositoryList({
repos,
isLoading,
onSelect,
activeTab,
}: {
repos: GitHubRepoInfo[];
isLoading: boolean;
onSelect: (repo: GitHubRepoInfo) => void;
activeTab: string;
}) {
if (isLoading) {
return (
<div className="flex items-center justify-center py-8 text-bolt-elements-textSecondary">
<span className="i-ph:spinner animate-spin mr-2" />
Loading repositories...
</div>
);
}
if (repos.length === 0) {
return (
<div className="flex flex-col items-center justify-center py-8 text-bolt-elements-textSecondary">
<span className="i-ph:folder-simple-dashed w-12 h-12 mb-2 opacity-50" />
<p>{activeTab === 'my-repos' ? 'No repositories found' : 'Search for repositories'}</p>
</div>
);
}
return repos.map((repo) => <RepositoryCard key={repo.full_name} repo={repo} onSelect={() => onSelect(repo)} />);
}
function RepositoryCard({ repo, onSelect }: { repo: GitHubRepoInfo; onSelect: () => void }) {
return (
<div className="p-4 rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333] hover:border-purple-500/50 transition-colors">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="i-ph:git-repository text-bolt-elements-textTertiary" />
<h3 className="font-medium text-bolt-elements-textPrimary dark:text-white">{repo.name}</h3>
</div>
<button
onClick={onSelect}
className="px-4 py-2 h-10 rounded-lg bg-purple-500 text-white hover:bg-purple-600 transition-all duration-200 flex items-center gap-2 min-w-[120px] justify-center"
>
<span className="i-ph:download-simple w-4 h-4" />
Import
</button>
</div>
{repo.description && <p className="text-sm text-bolt-elements-textSecondary mb-3">{repo.description}</p>}
<div className="flex items-center gap-4 text-sm text-bolt-elements-textTertiary">
{repo.language && (
<span className="flex items-center gap-1">
<span className="i-ph:code" />
{repo.language}
</span>
)}
<span className="flex items-center gap-1">
<span className="i-ph:star" />
{repo.stargazers_count.toLocaleString()}
</span>
<span className="flex items-center gap-1">
<span className="i-ph:clock" />
{new Date(repo.updated_at).toLocaleDateString()}
</span>
</div>
</div>
);
}

View File

@ -370,14 +370,25 @@ export const DeveloperWindow = ({ open, onClose }: DeveloperWindowProps) => {
} }
}; };
// Trap focus when window is open
useEffect(() => {
if (open) {
// Prevent background scrolling
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [open]);
return ( return (
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<RadixDialog.Root open={open}> <RadixDialog.Root open={open}>
<RadixDialog.Portal> <RadixDialog.Portal>
<div <div className="fixed inset-0 flex items-center justify-center z-[100]">
className="fixed inset-0 flex items-center justify-center z-[60]"
style={{ opacity: developerMode ? 1 : 0, transition: 'opacity 0.2s ease-in-out' }}
>
<RadixDialog.Overlay className="fixed inset-0"> <RadixDialog.Overlay className="fixed inset-0">
<motion.div <motion.div
className="absolute inset-0 bg-black/50 backdrop-blur-sm" className="absolute inset-0 bg-black/50 backdrop-blur-sm"
@ -388,7 +399,12 @@ export const DeveloperWindow = ({ open, onClose }: DeveloperWindowProps) => {
/> />
</RadixDialog.Overlay> </RadixDialog.Overlay>
<RadixDialog.Content aria-describedby={undefined} className="relative z-[61]"> <RadixDialog.Content
aria-describedby={undefined}
onEscapeKeyDown={onClose}
onPointerDownOutside={onClose}
className="relative z-[101]"
>
<motion.div <motion.div
className={classNames( className={classNames(
'w-[1200px] h-[90vh]', 'w-[1200px] h-[90vh]',

View File

@ -515,13 +515,27 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
</div> </div>
); );
// Trap focus when window is open
useEffect(() => {
if (open) {
// Prevent background scrolling
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [open]);
return ( return (
<> <>
<DeveloperWindow open={showDeveloperWindow} onClose={handleDeveloperWindowClose} /> <DeveloperWindow open={showDeveloperWindow} onClose={handleDeveloperWindowClose} />
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<RadixDialog.Root open={open && !showDeveloperWindow}> <RadixDialog.Root open={open}>
<RadixDialog.Portal> <RadixDialog.Portal>
<div className="fixed inset-0 flex items-center justify-center z-[50]"> <div className="fixed inset-0 flex items-center justify-center z-[100]">
<RadixDialog.Overlay asChild> <RadixDialog.Overlay asChild>
<motion.div <motion.div
className="absolute inset-0 bg-black/50 backdrop-blur-sm" className="absolute inset-0 bg-black/50 backdrop-blur-sm"
@ -531,7 +545,12 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
/> />
</RadixDialog.Overlay> </RadixDialog.Overlay>
<RadixDialog.Content aria-describedby={undefined} asChild> <RadixDialog.Content
aria-describedby={undefined}
onEscapeKeyDown={onClose}
onPointerDownOutside={onClose}
className="relative z-[101]"
>
<motion.div <motion.div
className={classNames( className={classNames(
'relative', 'relative',

View File

@ -24,47 +24,45 @@ export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: History
syncWithGlobalStore: isActiveChat, syncWithGlobalStore: isActiveChat,
}); });
const renderDescriptionForm = (
<form onSubmit={handleSubmit} className="flex-1 flex items-center">
<input
type="text"
className="flex-1 bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 mr-2"
autoFocus
value={currentDescription}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
/>
<button
type="submit"
className="i-ph:check scale-110 hover:text-bolt-elements-item-contentAccent"
onMouseDown={handleSubmit}
/>
</form>
);
return ( return (
<div <div
className={classNames( className={classNames(
'group rounded-md text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 overflow-hidden flex justify-between items-center px-2 py-1', 'group rounded-lg text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50/80 dark:hover:bg-gray-800/30 overflow-hidden flex justify-between items-center px-3 py-2 transition-colors',
{ '[&&]:text-bolt-elements-textPrimary bg-bolt-elements-background-depth-3': isActiveChat }, { 'text-gray-900 dark:text-white bg-gray-50/80 dark:bg-gray-800/30': isActiveChat },
)} )}
> >
{editing ? ( {editing ? (
renderDescriptionForm <form onSubmit={handleSubmit} className="flex-1 flex items-center gap-2">
<input
type="text"
className="flex-1 bg-white dark:bg-gray-900 text-gray-900 dark:text-white rounded-md px-3 py-1.5 text-sm border border-gray-200 dark:border-gray-800 focus:outline-none focus:ring-1 focus:ring-purple-500/50"
autoFocus
value={currentDescription}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
/>
<button
type="submit"
className="i-ph:check h-4 w-4 text-gray-500 hover:text-purple-500 transition-colors"
onMouseDown={handleSubmit}
/>
</form>
) : ( ) : (
<a href={`/chat/${item.urlId}`} className="flex w-full relative truncate block"> <a href={`/chat/${item.urlId}`} className="flex w-full relative truncate block">
{currentDescription} <WithTooltip tooltip={currentDescription}>
<span className="truncate pr-24">{currentDescription}</span>
</WithTooltip>
<div <div
className={classNames( className={classNames(
'absolute right-0 z-1 top-0 bottom-0 bg-gradient-to-l from-bolt-elements-background-depth-2 group-hover:from-bolt-elements-background-depth-3 box-content pl-3 to-transparent w-10 flex justify-end group-hover:w-22 group-hover:from-99%', 'absolute right-0 top-0 bottom-0 flex items-center bg-white dark:bg-gray-950 group-hover:bg-gray-50/80 dark:group-hover:bg-gray-800/30 px-2',
{ 'from-bolt-elements-background-depth-3 w-10 ': isActiveChat }, { 'bg-gray-50/80 dark:bg-gray-800/30': isActiveChat },
)} )}
> >
<div className="flex items-center p-1 text-bolt-elements-textSecondary opacity-0 group-hover:opacity-100 transition-opacity"> <div className="flex items-center gap-2.5 text-gray-400 dark:text-gray-500 opacity-0 group-hover:opacity-100 transition-opacity">
<ChatActionButton <ChatActionButton
toolTipContent="Export chat" toolTipContent="Export"
icon="i-ph:download-simple" icon="i-ph:download-simple h-4 w-4"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
exportChat(item.id); exportChat(item.id);
@ -72,14 +70,14 @@ export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: History
/> />
{onDuplicate && ( {onDuplicate && (
<ChatActionButton <ChatActionButton
toolTipContent="Duplicate chat" toolTipContent="Duplicate"
icon="i-ph:copy" icon="i-ph:copy h-4 w-4"
onClick={() => onDuplicate?.(item.id)} onClick={() => onDuplicate?.(item.id)}
/> />
)} )}
<ChatActionButton <ChatActionButton
toolTipContent="Rename chat" toolTipContent="Rename"
icon="i-ph:pencil-fill" icon="i-ph:pencil-fill h-4 w-4"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
toggleEditMode(); toggleEditMode();
@ -87,9 +85,9 @@ export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: History
/> />
<Dialog.Trigger asChild> <Dialog.Trigger asChild>
<ChatActionButton <ChatActionButton
toolTipContent="Delete chat" toolTipContent="Delete"
icon="i-ph:trash" icon="i-ph:trash h-4 w-4"
className="[&&]:hover:text-bolt-elements-button-danger-text" className="hover:text-red-500"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
onDelete?.(event); onDelete?.(event);
@ -121,11 +119,11 @@ const ChatActionButton = forwardRef(
ref: ForwardedRef<HTMLButtonElement>, ref: ForwardedRef<HTMLButtonElement>,
) => { ) => {
return ( return (
<WithTooltip tooltip={toolTipContent}> <WithTooltip tooltip={toolTipContent} position="bottom" sideOffset={4}>
<button <button
ref={ref} ref={ref}
type="button" type="button"
className={`scale-110 mr-2 hover:text-bolt-elements-item-contentAccent ${icon} ${className ? className : ''}`} className={`text-gray-400 dark:text-gray-500 hover:text-purple-500 dark:hover:text-purple-400 transition-colors ${icon} ${className ? className : ''}`}
onClick={onClick} onClick={onClick}
/> />
</WithTooltip> </WithTooltip>

View File

@ -11,12 +11,13 @@ import { logger } from '~/utils/logger';
import { HistoryItem } from './HistoryItem'; import { HistoryItem } from './HistoryItem';
import { binDates } from './date-binning'; import { binDates } from './date-binning';
import { useSearchFilter } from '~/lib/hooks/useSearchFilter'; import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
import { classNames } from '~/utils/classNames';
const menuVariants = { const menuVariants = {
closed: { closed: {
opacity: 0, opacity: 0,
visibility: 'hidden', visibility: 'hidden',
left: '-150px', left: '-340px',
transition: { transition: {
duration: 0.2, duration: 0.2,
ease: cubicEasingFn, ease: cubicEasingFn,
@ -41,15 +42,18 @@ function CurrentDateTime() {
useEffect(() => { useEffect(() => {
const timer = setInterval(() => { const timer = setInterval(() => {
setDateTime(new Date()); setDateTime(new Date());
}, 60000); // Update every minute }, 60000);
return () => clearInterval(timer); return () => clearInterval(timer);
}, []); }, []);
return ( return (
<div className="flex items-center gap-2 px-4 py-3 font-bold text-gray-700 dark:text-gray-300 border-b border-bolt-elements-borderColor"> <div className="flex items-center gap-2 px-4 py-2 text-sm text-gray-600 dark:text-gray-400 border-b border-gray-100 dark:border-gray-800/50">
<div className="h-4 w-4 i-ph:clock-thin" /> <div className="h-4 w-4 i-lucide:clock opacity-80" />
{dateTime.toLocaleDateString()} {dateTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} <div className="flex gap-2">
<span>{dateTime.toLocaleDateString()}</span>
<span>{dateTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
</div>
</div> </div>
); );
} }
@ -111,6 +115,10 @@ export const Menu = () => {
const exitThreshold = 40; const exitThreshold = 40;
function onMouseMove(event: MouseEvent) { function onMouseMove(event: MouseEvent) {
if (isSettingsOpen) {
return;
}
if (event.pageX < enterThreshold) { if (event.pageX < enterThreshold) {
setOpen(true); setOpen(true);
} }
@ -125,7 +133,7 @@ export const Menu = () => {
return () => { return () => {
window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mousemove', onMouseMove);
}; };
}, []); }, [isSettingsOpen]);
const handleDeleteClick = (event: React.UIEvent, item: ChatHistoryItem) => { const handleDeleteClick = (event: React.UIEvent, item: ChatHistoryItem) => {
event.preventDefault(); event.preventDefault();
@ -137,96 +145,122 @@ export const Menu = () => {
loadEntries(); // Reload the list after duplication loadEntries(); // Reload the list after duplication
}; };
const handleSettingsClick = () => {
setIsSettingsOpen(true);
setOpen(false);
};
const handleSettingsClose = () => {
setIsSettingsOpen(false);
};
return ( return (
<motion.div <>
ref={menuRef} <motion.div
initial="closed" ref={menuRef}
animate={open ? 'open' : 'closed'} initial="closed"
variants={menuVariants} animate={open ? 'open' : 'closed'}
className="flex selection-accent flex-col side-menu fixed top-0 w-[350px] h-full bg-bolt-elements-background-depth-2 border-r rounded-r-3xl border-bolt-elements-borderColor z-sidebar shadow-xl shadow-bolt-elements-sidebar-dropdownShadow text-sm" variants={menuVariants}
> style={{ width: '340px' }}
<div className="h-[60px]" /> {/* Spacer for top margin */} className={classNames(
<CurrentDateTime /> 'flex selection-accent flex-col side-menu fixed top-0 h-full',
<div className="flex-1 flex flex-col h-full w-full overflow-hidden"> 'bg-white dark:bg-gray-950 border-r border-gray-100 dark:border-gray-800/50',
<div className="p-4 select-none"> 'shadow-sm text-sm',
<a isSettingsOpen ? 'z-40' : 'z-sidebar',
href="/" )}
className="flex gap-2 items-center bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme mb-4" >
> <div className="h-12 flex items-center px-4 border-b border-gray-100 dark:border-gray-800/50 bg-gray-50/50 dark:bg-gray-900/50"></div>
<span className="inline-block i-bolt:chat scale-110" /> <CurrentDateTime />
Start new chat <div className="flex-1 flex flex-col h-full w-full overflow-hidden">
</a> <div className="p-4 space-y-3">
<div className="relative w-full"> <a
<input href="/"
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor" className="flex gap-2 items-center bg-purple-50 dark:bg-purple-500/10 text-purple-700 dark:text-purple-300 hover:bg-purple-100 dark:hover:bg-purple-500/20 rounded-lg px-4 py-2 transition-colors"
type="search" >
placeholder="Search" <span className="inline-block i-lucide:message-square h-4 w-4" />
onChange={handleSearchChange} <span className="text-sm font-medium">Start new chat</span>
aria-label="Search chats" </a>
/> <div className="relative w-full">
<div className="absolute left-3 top-1/2 -translate-y-1/2">
<span className="i-lucide:search h-4 w-4 text-gray-400 dark:text-gray-500" />
</div>
<input
className="w-full bg-gray-50 dark:bg-gray-900 relative pl-9 pr-3 py-2 rounded-lg focus:outline-none focus:ring-1 focus:ring-purple-500/50 text-sm text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-500 border border-gray-200 dark:border-gray-800"
type="search"
placeholder="Search chats..."
onChange={handleSearchChange}
aria-label="Search chats"
/>
</div>
</div>
<div className="text-gray-600 dark:text-gray-400 text-sm font-medium px-4 py-2">Your Chats</div>
<div className="flex-1 overflow-auto px-3 pb-3">
{filteredList.length === 0 && (
<div className="px-4 text-gray-500 dark:text-gray-400 text-sm">
{list.length === 0 ? 'No previous conversations' : 'No matches found'}
</div>
)}
<DialogRoot open={dialogContent !== null}>
{binDates(filteredList).map(({ category, items }) => (
<div key={category} className="mt-2 first:mt-0 space-y-1">
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 sticky top-0 z-1 bg-white dark:bg-gray-950 px-4 py-1">
{category}
</div>
<div className="space-y-0.5 pr-1">
{items.map((item) => (
<HistoryItem
key={item.id}
item={item}
exportChat={exportChat}
onDelete={(event) => handleDeleteClick(event, item)}
onDuplicate={() => handleDuplicate(item.id)}
/>
))}
</div>
</div>
))}
<Dialog onBackdrop={closeDialog} onClose={closeDialog}>
{dialogContent?.type === 'delete' && (
<>
<div className="p-6 bg-white dark:bg-gray-950">
<DialogTitle className="text-gray-900 dark:text-white">Delete Chat?</DialogTitle>
<DialogDescription className="mt-2 text-gray-600 dark:text-gray-400">
<p>
You are about to delete{' '}
<span className="font-medium text-gray-900 dark:text-white">
{dialogContent.item.description}
</span>
</p>
<p className="mt-2">Are you sure you want to delete this chat?</p>
</DialogDescription>
</div>
<div className="flex justify-end gap-3 px-6 py-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-100 dark:border-gray-800">
<DialogButton type="secondary" onClick={closeDialog}>
Cancel
</DialogButton>
<DialogButton
type="danger"
onClick={(event) => {
deleteItem(event, dialogContent.item);
closeDialog();
}}
>
Delete
</DialogButton>
</div>
</>
)}
</Dialog>
</DialogRoot>
</div>
<div className="flex items-center justify-between border-t border-gray-200 dark:border-gray-800 px-4 py-3">
<SettingsButton onClick={handleSettingsClick} />
<ThemeSwitch />
</div> </div>
</div> </div>
<div className="text-bolt-elements-textPrimary font-medium pl-6 pr-5 my-2">Your Chats</div> </motion.div>
<div className="flex-1 overflow-auto pl-4 pr-5 pb-5">
{filteredList.length === 0 && ( <UsersWindow open={isSettingsOpen} onClose={handleSettingsClose} />
<div className="pl-2 text-bolt-elements-textTertiary"> </>
{list.length === 0 ? 'No previous conversations' : 'No matches found'}
</div>
)}
<DialogRoot open={dialogContent !== null}>
{binDates(filteredList).map(({ category, items }) => (
<div key={category} className="mt-4 first:mt-0 space-y-1">
<div className="text-bolt-elements-textTertiary sticky top-0 z-1 bg-bolt-elements-background-depth-2 pl-2 pt-2 pb-1">
{category}
</div>
{items.map((item) => (
<HistoryItem
key={item.id}
item={item}
exportChat={exportChat}
onDelete={(event) => handleDeleteClick(event, item)}
onDuplicate={() => handleDuplicate(item.id)}
/>
))}
</div>
))}
<Dialog onBackdrop={closeDialog} onClose={closeDialog}>
{dialogContent?.type === 'delete' && (
<>
<DialogTitle>Delete Chat?</DialogTitle>
<DialogDescription asChild>
<div>
<p>
You are about to delete <strong>{dialogContent.item.description}</strong>.
</p>
<p className="mt-1">Are you sure you want to delete this chat?</p>
</div>
</DialogDescription>
<div className="px-5 pb-4 bg-bolt-elements-background-depth-2 flex gap-2 justify-end">
<DialogButton type="secondary" onClick={closeDialog}>
Cancel
</DialogButton>
<DialogButton
type="danger"
onClick={(event) => {
deleteItem(event, dialogContent.item);
closeDialog();
}}
>
Delete
</DialogButton>
</div>
</>
)}
</Dialog>
</DialogRoot>
</div>
<div className="flex items-center justify-between border-t border-bolt-elements-borderColor p-4">
<SettingsButton onClick={() => setIsSettingsOpen(true)} />
<ThemeSwitch />
</div>
</div>
<UsersWindow open={isSettingsOpen} onClose={() => setIsSettingsOpen(false)} />
</motion.div>
); );
}; };

View File

@ -39,21 +39,21 @@ function dateCategory(date: Date) {
} }
if (isThisWeek(date)) { if (isThisWeek(date)) {
// e.g., "Monday" // e.g., "Mon" instead of "Monday"
return format(date, 'eeee'); return format(date, 'EEE');
} }
const thirtyDaysAgo = subDays(new Date(), 30); const thirtyDaysAgo = subDays(new Date(), 30);
if (isAfter(date, thirtyDaysAgo)) { if (isAfter(date, thirtyDaysAgo)) {
return 'Last 30 Days'; return 'Past 30 Days';
} }
if (isThisYear(date)) { if (isThisYear(date)) {
// e.g., "July" // e.g., "Jan" instead of "January"
return format(date, 'MMMM'); return format(date, 'LLL');
} }
// e.g., "July 2023" // e.g., "Jan 2023" instead of "January 2023"
return format(date, 'MMMM yyyy'); return format(date, 'LLL yyyy');
} }

View File

@ -0,0 +1,46 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '~/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-bolt-elements-borderColor disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-bolt-elements-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-2',
destructive: 'bg-red-500 text-white hover:bg-red-600',
outline:
'border border-input bg-transparent hover:bg-bolt-elements-background-depth-2 hover:text-bolt-elements-textPrimary',
secondary:
'bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-2',
ghost: 'hover:bg-bolt-elements-background-depth-1 hover:text-bolt-elements-textPrimary',
link: 'text-bolt-elements-textPrimary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
_asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, _asChild = false, ...props }, ref) => {
return <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
},
);
Button.displayName = 'Button';
export { Button, buttonVariants };

View File

@ -17,10 +17,11 @@ interface DialogButtonProps {
export const DialogButton = memo(({ type, children, onClick, disabled }: DialogButtonProps) => { export const DialogButton = memo(({ type, children, onClick, disabled }: DialogButtonProps) => {
return ( return (
<button <button
className={classNames('inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm', { className={classNames('inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors', {
'bg-purple-500 text-white hover:bg-purple-600': type === 'primary', 'bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-500 dark:hover:bg-purple-600': type === 'primary',
'text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary': type === 'secondary', 'bg-transparent text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100':
'text-red-500 hover:bg-red-50 dark:hover:bg-red-500/10': type === 'danger', type === 'secondary',
'bg-transparent text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-500/10': type === 'danger',
})} })}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}

View File

@ -17,8 +17,7 @@ import { renderLogger } from '~/utils/logger';
import { EditorPanel } from './EditorPanel'; import { EditorPanel } from './EditorPanel';
import { Preview } from './Preview'; import { Preview } from './Preview';
import useViewport from '~/lib/hooks'; import useViewport from '~/lib/hooks';
import Cookies from 'js-cookie'; import { PushToGitHubDialog } from '~/components/settings/connections/components/PushToGitHubDialog';
import { chatMetadata, useChatHistory } from '~/lib/persistence';
interface WorkspaceProps { interface WorkspaceProps {
chatStarted?: boolean; chatStarted?: boolean;
@ -59,6 +58,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
renderLogger.trace('Workbench'); renderLogger.trace('Workbench');
const [isSyncing, setIsSyncing] = useState(false); const [isSyncing, setIsSyncing] = useState(false);
const [isPushDialogOpen, setIsPushDialogOpen] = useState(false);
const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0)); const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
const showWorkbench = useStore(workbenchStore.showWorkbench); const showWorkbench = useStore(workbenchStore.showWorkbench);
@ -67,8 +67,6 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
const unsavedFiles = useStore(workbenchStore.unsavedFiles); const unsavedFiles = useStore(workbenchStore.unsavedFiles);
const files = useStore(workbenchStore.files); const files = useStore(workbenchStore.files);
const selectedView = useStore(workbenchStore.currentView); const selectedView = useStore(workbenchStore.currentView);
const metadata = useStore(chatMetadata);
const { updateChatMestaData } = useChatHistory();
const isSmallViewport = useViewport(1024); const isSmallViewport = useViewport(1024);
@ -171,65 +169,8 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
<div className="i-ph:terminal" /> <div className="i-ph:terminal" />
Toggle Terminal Toggle Terminal
</PanelHeaderButton> </PanelHeaderButton>
<PanelHeaderButton <PanelHeaderButton className="mr-1 text-sm" onClick={() => setIsPushDialogOpen(true)}>
className="mr-1 text-sm" <div className="i-ph:git-branch" />
onClick={() => {
let repoName = metadata?.gitUrl?.split('/').slice(-1)[0]?.replace('.git', '') || null;
let repoConfirmed: boolean = true;
if (repoName) {
repoConfirmed = confirm(`Do you want to push to the repository ${repoName}?`);
}
if (!repoName || !repoConfirmed) {
repoName = prompt(
'Please enter a name for your new GitHub repository:',
'bolt-generated-project',
);
} else {
}
if (!repoName) {
alert('Repository name is required. Push to GitHub cancelled.');
return;
}
let githubUsername = Cookies.get('githubUsername');
let githubToken = Cookies.get('githubToken');
if (!githubUsername || !githubToken) {
const usernameInput = prompt('Please enter your GitHub username:');
const tokenInput = prompt('Please enter your GitHub personal access token:');
if (!usernameInput || !tokenInput) {
alert('GitHub username and token are required. Push to GitHub cancelled.');
return;
}
githubUsername = usernameInput;
githubToken = tokenInput;
Cookies.set('githubUsername', usernameInput);
Cookies.set('githubToken', tokenInput);
Cookies.set(
'git:github.com',
JSON.stringify({ username: tokenInput, password: 'x-oauth-basic' }),
);
}
const commitMessage =
prompt('Please enter a commit message:', 'Initial commit') || 'Initial commit';
workbenchStore.pushToGitHub(repoName, commitMessage, githubUsername, githubToken);
if (!metadata?.gitUrl) {
updateChatMestaData({
...(metadata || {}),
gitUrl: `https://github.com/${githubUsername}/${repoName}.git`,
});
}
}}
>
<div className="i-ph:github-logo" />
Push to GitHub Push to GitHub
</PanelHeaderButton> </PanelHeaderButton>
</div> </div>
@ -271,10 +212,26 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
</div> </div>
</div> </div>
</div> </div>
<PushToGitHubDialog
isOpen={isPushDialogOpen}
onClose={() => setIsPushDialogOpen(false)}
onPush={async (repoName, username, token) => {
try {
await workbenchStore.pushToGitHub(repoName, undefined, username, token);
// Success dialog will be shown by PushToGitHubDialog component
} catch (error) {
console.error('Error pushing to GitHub:', error);
toast.error('Failed to push to GitHub');
throw error; // Rethrow to let PushToGitHubDialog handle the error state
}
}}
/>
</motion.div> </motion.div>
) )
); );
}); });
interface ViewProps extends HTMLMotionProps<'div'> { interface ViewProps extends HTMLMotionProps<'div'> {
children: JSX.Element; children: JSX.Element;
} }

View File

@ -19,7 +19,12 @@ export default class GroqProvider extends BaseProvider {
{ name: 'llama-3.2-3b-preview', label: 'Llama 3.2 3b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 }, { name: 'llama-3.2-3b-preview', label: 'Llama 3.2 3b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
{ name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 }, { name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
{ name: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 }, { name: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
{ name: 'deepseek-r1-distill-llama-70b', label: 'Deepseek R1 Distill Llama 70b (Groq)', provider: 'Groq', maxTokenAllowed: 131072 }, {
name: 'deepseek-r1-distill-llama-70b',
label: 'Deepseek R1 Distill Llama 70b (Groq)',
provider: 'Groq',
maxTokenAllowed: 131072,
},
]; ];
getModelInstance(options: { getModelInstance(options: {

View File

@ -536,7 +536,7 @@ export class WorkbenchStore {
sha: newCommit.sha, sha: newCommit.sha,
}); });
alert(`Repository created and code pushed: ${repo.html_url}`); return repo.html_url; // Return the URL instead of showing alert
} catch (error) { } catch (error) {
console.error('Error pushing to GitHub:', error); console.error('Error pushing to GitHub:', error);
throw error; // Rethrow the error for further handling throw error; // Rethrow the error for further handling

133
app/types/GitHub.ts Normal file
View File

@ -0,0 +1,133 @@
export interface GitHubUserResponse {
login: string;
avatar_url: string;
html_url: string;
name: string;
bio: string;
public_repos: number;
followers: number;
following: number;
public_gists: number;
created_at: string;
updated_at: string;
}
export interface GitHubRepoInfo {
name: string;
full_name: string;
html_url: string;
description: string;
stargazers_count: number;
forks_count: number;
default_branch: string;
updated_at: string;
language: string;
languages_url: string;
}
export interface GitHubContent {
name: string;
path: string;
sha: string;
size: number;
url: string;
html_url: string;
git_url: string;
download_url: string;
type: string;
content: string;
encoding: string;
}
export interface GitHubBranch {
name: string;
commit: {
sha: string;
url: string;
};
}
export interface GitHubBlobResponse {
content: string;
encoding: string;
sha: string;
size: number;
url: string;
}
export interface GitHubOrganization {
login: string;
avatar_url: string;
description: string;
html_url: string;
}
export interface GitHubEvent {
id: string;
type: string;
created_at: string;
repo: {
name: string;
url: string;
};
payload: {
action?: string;
ref?: string;
ref_type?: string;
description?: string;
};
}
export interface GitHubLanguageStats {
[key: string]: number;
}
export interface GitHubStats {
repos: GitHubRepoInfo[];
totalStars: number;
totalForks: number;
organizations: GitHubOrganization[];
recentActivity: GitHubEvent[];
languages: GitHubLanguageStats;
totalGists: number;
}
export interface GitHubConnection {
user: GitHubUserResponse | null;
token: string;
tokenType: 'classic' | 'fine-grained';
stats?: GitHubStats;
}
export interface GitHubTokenInfo {
token: string;
scope: string[];
avatar_url: string;
name: string | null;
created_at: string;
followers: number;
}
export interface GitHubRateLimits {
limit: number;
remaining: number;
reset: Date;
used: number;
}
export interface GitHubAuthState {
username: string;
tokenInfo: GitHubTokenInfo | null;
isConnected: boolean;
isVerifying: boolean;
isLoadingRepos: boolean;
rateLimits?: GitHubRateLimits;
}
export interface RepositoryStats {
totalFiles: number;
totalSize: number;
languages: Record<string, number>;
hasPackageJson: boolean;
hasDependencies: boolean;
}

12
app/utils/formatSize.ts Normal file
View File

@ -0,0 +1,12 @@
export function formatSize(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}

View File

@ -6,19 +6,16 @@ Release v0.0.6
### ✨ Features ### ✨ Features
* implement Claude 3, Claude3.5, Nova Pro, Nova Lite and Mistral model integration with AWS Bedrock ([#974](https://github.com/stackblitz-labs/bolt.diy/pull/974)) by @kunjabijukchhe - implement Claude 3, Claude3.5, Nova Pro, Nova Lite and Mistral model integration with AWS Bedrock ([#974](https://github.com/stackblitz-labs/bolt.diy/pull/974)) by @kunjabijukchhe
* enhance chat import with multi-format support ([#936](https://github.com/stackblitz-labs/bolt.diy/pull/936)) by @sidbetatester - enhance chat import with multi-format support ([#936](https://github.com/stackblitz-labs/bolt.diy/pull/936)) by @sidbetatester
* added Github provider ([#1109](https://github.com/stackblitz-labs/bolt.diy/pull/1109)) by @newnol - added Github provider ([#1109](https://github.com/stackblitz-labs/bolt.diy/pull/1109)) by @newnol
* added the "Open Preview in a New Tab" ([#1101](https://github.com/stackblitz-labs/bolt.diy/pull/1101)) by @Stijnus - added the "Open Preview in a New Tab" ([#1101](https://github.com/stackblitz-labs/bolt.diy/pull/1101)) by @Stijnus
* configure dynamic providers via .env ([#1108](https://github.com/stackblitz-labs/bolt.diy/pull/1108)) by @mrsimpson - configure dynamic providers via .env ([#1108](https://github.com/stackblitz-labs/bolt.diy/pull/1108)) by @mrsimpson
* added deepseek reasoner model in deepseek provider ([#1151](https://github.com/stackblitz-labs/bolt.diy/pull/1151)) by @thecodacus - added deepseek reasoner model in deepseek provider ([#1151](https://github.com/stackblitz-labs/bolt.diy/pull/1151)) by @thecodacus
* enhance context handling by adding code context selection and implementing summary generation ([#1091](https://github.com/stackblitz-labs/bolt.diy/pull/1091)) by @thecodacus - enhance context handling by adding code context selection and implementing summary generation ([#1091](https://github.com/stackblitz-labs/bolt.diy/pull/1091)) by @thecodacus
### 🐛 Bug Fixes ### 🐛 Bug Fixes
## 📈 Stats ## 📈 Stats
**Full Changelog**: [`v0.0.5..v0.0.6`](https://github.com/stackblitz-labs/bolt.diy/compare/v0.0.5...v0.0.6) **Full Changelog**: [`v0.0.5..v0.0.6`](https://github.com/stackblitz-labs/bolt.diy/compare/v0.0.5...v0.0.6)

View File

@ -6,8 +6,8 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
target: bolt-ai-production target: bolt-ai-production
ports: ports:
- "5173:5173" - '5173:5173'
env_file: ".env.local" env_file: '.env.local'
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- COMPOSE_PROFILES=production - COMPOSE_PROFILES=production
@ -28,7 +28,7 @@ services:
- DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768} - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
- RUNNING_IN_DOCKER=true - RUNNING_IN_DOCKER=true
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - 'host.docker.internal:host-gateway'
command: pnpm run dockerstart command: pnpm run dockerstart
profiles: profiles:
- production - production
@ -37,7 +37,7 @@ services:
image: bolt-ai:development image: bolt-ai:development
build: build:
target: bolt-ai-development target: bolt-ai-development
env_file: ".env.local" env_file: '.env.local'
environment: environment:
- NODE_ENV=development - NODE_ENV=development
- VITE_HMR_PROTOCOL=ws - VITE_HMR_PROTOCOL=ws
@ -61,7 +61,7 @@ services:
- DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768} - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
- RUNNING_IN_DOCKER=true - RUNNING_IN_DOCKER=true
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - 'host.docker.internal:host-gateway'
volumes: volumes:
- type: bind - type: bind
source: . source: .
@ -69,14 +69,14 @@ services:
consistency: cached consistency: cached
- /app/node_modules - /app/node_modules
ports: ports:
- "5173:5173" - '5173:5173'
command: pnpm run dev --host 0.0.0.0 command: pnpm run dev --host 0.0.0.0
profiles: ["development", "default"] profiles: ['development', 'default']
app-prebuild: app-prebuild:
image: ghcr.io/stackblitz-labs/bolt.diy:latest image: ghcr.io/stackblitz-labs/bolt.diy:latest
ports: ports:
- "5173:5173" - '5173:5173'
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- COMPOSE_PROFILES=production - COMPOSE_PROFILES=production
@ -86,7 +86,7 @@ services:
- DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768} - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
- RUNNING_IN_DOCKER=true - RUNNING_IN_DOCKER=true
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - 'host.docker.internal:host-gateway'
command: pnpm run dockerstart command: pnpm run dockerstart
profiles: profiles:
- prebuilt - prebuilt

View File

@ -6,15 +6,15 @@ Welcome! This guide provides all the details you need to contribute effectively
## 📋 Table of Contents ## 📋 Table of Contents
1. [Code of Conduct](#code-of-conduct) 1. [Code of Conduct](#code-of-conduct)
2. [How Can I Contribute?](#how-can-i-contribute) 2. [How Can I Contribute?](#how-can-i-contribute)
3. [Pull Request Guidelines](#pull-request-guidelines) 3. [Pull Request Guidelines](#pull-request-guidelines)
4. [Coding Standards](#coding-standards) 4. [Coding Standards](#coding-standards)
5. [Development Setup](#development-setup) 5. [Development Setup](#development-setup)
6. [Testing](#testing) 6. [Testing](#testing)
7. [Deployment](#deployment) 7. [Deployment](#deployment)
8. [Docker Deployment](#docker-deployment) 8. [Docker Deployment](#docker-deployment)
9. [VS Code Dev Containers Integration](#vs-code-dev-containers-integration) 9. [VS Code Dev Containers Integration](#vs-code-dev-containers-integration)
--- ---
@ -27,60 +27,67 @@ This project is governed by our **Code of Conduct**. By participating, you agree
## 🛠️ How Can I Contribute? ## 🛠️ How Can I Contribute?
### 1⃣ Reporting Bugs or Feature Requests ### 1⃣ Reporting Bugs or Feature Requests
- Check the [issue tracker](#) to avoid duplicates. - Check the [issue tracker](#) to avoid duplicates.
- Use issue templates (if available). - Use issue templates (if available).
- Provide detailed, relevant information and steps to reproduce bugs. - Provide detailed, relevant information and steps to reproduce bugs.
### 2⃣ Code Contributions ### 2⃣ Code Contributions
1. Fork the repository.
2. Create a feature or fix branch. 1. Fork the repository.
3. Write and test your code. 2. Create a feature or fix branch.
3. Write and test your code.
4. Submit a pull request (PR). 4. Submit a pull request (PR).
### 3⃣ Join as a Core Contributor ### 3⃣ Join as a Core Contributor
Interested in maintaining and growing the project? Fill out our [Contributor Application Form](https://forms.gle/TBSteXSDCtBDwr5m7). Interested in maintaining and growing the project? Fill out our [Contributor Application Form](https://forms.gle/TBSteXSDCtBDwr5m7).
--- ---
## ✅ Pull Request Guidelines ## ✅ Pull Request Guidelines
### PR Checklist ### PR Checklist
- Branch from the **main** branch.
- Update documentation, if needed.
- Test all functionality manually.
- Focus on one feature/bug per PR.
### Review Process - Branch from the **main** branch.
1. Manual testing by reviewers. - Update documentation, if needed.
2. At least one maintainer review required. - Test all functionality manually.
3. Address review comments. - Focus on one feature/bug per PR.
### Review Process
1. Manual testing by reviewers.
2. At least one maintainer review required.
3. Address review comments.
4. Maintain a clean commit history. 4. Maintain a clean commit history.
--- ---
## 📏 Coding Standards ## 📏 Coding Standards
### General Guidelines ### General Guidelines
- Follow existing code style.
- Comment complex logic. - Follow existing code style.
- Keep functions small and focused. - Comment complex logic.
- Keep functions small and focused.
- Use meaningful variable names. - Use meaningful variable names.
--- ---
## 🖥️ Development Setup ## 🖥️ Development Setup
### 1⃣ Initial Setup ### 1⃣ Initial Setup
- Clone the repository:
- Clone the repository:
```bash ```bash
git clone https://github.com/stackblitz-labs/bolt.diy.git git clone https://github.com/stackblitz-labs/bolt.diy.git
``` ```
- Install dependencies: - Install dependencies:
```bash ```bash
pnpm install pnpm install
``` ```
- Set up environment variables: - Set up environment variables:
1. Rename `.env.example` to `.env.local`. 1. Rename `.env.example` to `.env.local`.
2. Add your API keys: 2. Add your API keys:
```bash ```bash
GROQ_API_KEY=XXX GROQ_API_KEY=XXX
@ -88,23 +95,26 @@ Interested in maintaining and growing the project? Fill out our [Contributor App
OPENAI_API_KEY=XXX OPENAI_API_KEY=XXX
... ...
``` ```
3. Optionally set: 3. Optionally set:
- Debug level: `VITE_LOG_LEVEL=debug` - Debug level: `VITE_LOG_LEVEL=debug`
- Context size: `DEFAULT_NUM_CTX=32768` - Context size: `DEFAULT_NUM_CTX=32768`
**Note**: Never commit your `.env.local` file to version control. Its already in `.gitignore`. **Note**: Never commit your `.env.local` file to version control. Its already in `.gitignore`.
### 2⃣ Run Development Server ### 2⃣ Run Development Server
```bash ```bash
pnpm run dev pnpm run dev
``` ```
**Tip**: Use **Google Chrome Canary** for local testing. **Tip**: Use **Google Chrome Canary** for local testing.
--- ---
## 🧪 Testing ## 🧪 Testing
Run the test suite with: Run the test suite with:
```bash ```bash
pnpm test pnpm test
``` ```
@ -113,10 +123,12 @@ pnpm test
## 🚀 Deployment ## 🚀 Deployment
### Deploy to Cloudflare Pages ### Deploy to Cloudflare Pages
```bash ```bash
pnpm run deploy pnpm run deploy
``` ```
Ensure you have required permissions and that Wrangler is configured. Ensure you have required permissions and that Wrangler is configured.
--- ---
@ -127,67 +139,76 @@ This section outlines the methods for deploying the application using Docker. Th
--- ---
### 🧑‍💻 Development Environment ### 🧑‍💻 Development Environment
#### Build Options #### Build Options
**Option 1: Helper Scripts**
**Option 1: Helper Scripts**
```bash ```bash
# Development build # Development build
npm run dockerbuild npm run dockerbuild
``` ```
**Option 2: Direct Docker Build Command** **Option 2: Direct Docker Build Command**
```bash ```bash
docker build . --target bolt-ai-development docker build . --target bolt-ai-development
``` ```
**Option 3: Docker Compose Profile** **Option 3: Docker Compose Profile**
```bash ```bash
docker compose --profile development up docker compose --profile development up
``` ```
#### Running the Development Container #### Running the Development Container
```bash ```bash
docker run -p 5173:5173 --env-file .env.local bolt-ai:development docker run -p 5173:5173 --env-file .env.local bolt-ai:development
``` ```
--- ---
### 🏭 Production Environment ### 🏭 Production Environment
#### Build Options #### Build Options
**Option 1: Helper Scripts**
**Option 1: Helper Scripts**
```bash ```bash
# Production build # Production build
npm run dockerbuild:prod npm run dockerbuild:prod
``` ```
**Option 2: Direct Docker Build Command** **Option 2: Direct Docker Build Command**
```bash ```bash
docker build . --target bolt-ai-production docker build . --target bolt-ai-production
``` ```
**Option 3: Docker Compose Profile** **Option 3: Docker Compose Profile**
```bash ```bash
docker compose --profile production up docker compose --profile production up
``` ```
#### Running the Production Container #### Running the Production Container
```bash ```bash
docker run -p 5173:5173 --env-file .env.local bolt-ai:production docker run -p 5173:5173 --env-file .env.local bolt-ai:production
``` ```
--- ---
### Coolify Deployment ### Coolify Deployment
For an easy deployment process, use [Coolify](https://github.com/coollabsio/coolify): For an easy deployment process, use [Coolify](https://github.com/coollabsio/coolify):
1. Import your Git repository into Coolify.
2. Choose **Docker Compose** as the build pack. 1. Import your Git repository into Coolify.
3. Configure environment variables (e.g., API keys). 2. Choose **Docker Compose** as the build pack.
4. Set the start command: 3. Configure environment variables (e.g., API keys).
4. Set the start command:
```bash ```bash
docker compose --profile production up docker compose --profile production up
``` ```
@ -200,20 +221,22 @@ The `docker-compose.yaml` configuration is compatible with **VS Code Dev Contain
### Steps to Use Dev Containers ### Steps to Use Dev Containers
1. Open the command palette in VS Code (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS). 1. Open the command palette in VS Code (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS).
2. Select **Dev Containers: Reopen in Container**. 2. Select **Dev Containers: Reopen in Container**.
3. Choose the **development** profile when prompted. 3. Choose the **development** profile when prompted.
4. VS Code will rebuild the container and open it with the pre-configured environment. 4. VS Code will rebuild the container and open it with the pre-configured environment.
--- ---
## 🔑 Environment Variables ## 🔑 Environment Variables
Ensure `.env.local` is configured correctly with: Ensure `.env.local` is configured correctly with:
- API keys.
- Context-specific configurations. - API keys.
- Context-specific configurations.
Example for the `DEFAULT_NUM_CTX` variable:
Example for the `DEFAULT_NUM_CTX` variable:
```bash ```bash
DEFAULT_NUM_CTX=24576 # Uses 32GB VRAM DEFAULT_NUM_CTX=24576 # Uses 32GB VRAM
``` ```

View File

@ -3,7 +3,7 @@
## Models and Setup ## Models and Setup
??? question "What are the best models for bolt.diy?" ??? question "What are the best models for bolt.diy?"
For the best experience with bolt.diy, we recommend using the following models: For the best experience with bolt.diy, we recommend using the following models:
- **Claude 3.5 Sonnet (old)**: Best overall coder, providing excellent results across all use cases - **Claude 3.5 Sonnet (old)**: Best overall coder, providing excellent results across all use cases
- **Gemini 2.0 Flash**: Exceptional speed while maintaining good performance - **Gemini 2.0 Flash**: Exceptional speed while maintaining good performance
@ -17,79 +17,78 @@
## Best Practices ## Best Practices
??? question "How do I get the best results with bolt.diy?" ??? question "How do I get the best results with bolt.diy?" - **Be specific about your stack**:
- **Be specific about your stack**: Mention the frameworks or libraries you want to use (e.g., Astro, Tailwind, ShadCN) in your initial prompt. This ensures that bolt.diy scaffolds the project according to your preferences.
Mention the frameworks or libraries you want to use (e.g., Astro, Tailwind, ShadCN) in your initial prompt. This ensures that bolt.diy scaffolds the project according to your preferences.
- **Use the enhance prompt icon**: - **Use the enhance prompt icon**:
Before sending your prompt, click the *enhance* icon to let the AI refine your prompt. You can edit the suggested improvements before submitting. Before sending your prompt, click the *enhance* icon to let the AI refine your prompt. You can edit the suggested improvements before submitting.
- **Scaffold the basics first, then add features**: - **Scaffold the basics first, then add features**:
Ensure the foundational structure of your application is in place before introducing advanced functionality. This helps bolt.diy establish a solid base to build on. Ensure the foundational structure of your application is in place before introducing advanced functionality. This helps bolt.diy establish a solid base to build on.
- **Batch simple instructions**: - **Batch simple instructions**:
Combine simple tasks into a single prompt to save time and reduce API credit consumption. For example: Combine simple tasks into a single prompt to save time and reduce API credit consumption. For example:
*"Change the color scheme, add mobile responsiveness, and restart the dev server."* *"Change the color scheme, add mobile responsiveness, and restart the dev server."*
## Project Information ## Project Information
??? question "How do I contribute to bolt.diy?" ??? question "How do I contribute to bolt.diy?"
Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to get involved! Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to get involved!
??? question "What are the future plans for bolt.diy?" ??? question "What are the future plans for bolt.diy?"
Visit our [Roadmap](https://roadmap.sh/r/ottodev-roadmap-2ovzo) for the latest updates. Visit our [Roadmap](https://roadmap.sh/r/ottodev-roadmap-2ovzo) for the latest updates.
New features and improvements are on the way! New features and improvements are on the way!
??? question "Why are there so many open issues/pull requests?" ??? question "Why are there so many open issues/pull requests?"
bolt.diy began as a small showcase project on @ColeMedin's YouTube channel to explore editing open-source projects with local LLMs. However, it quickly grew into a massive community effort! bolt.diy began as a small showcase project on @ColeMedin's YouTube channel to explore editing open-source projects with local LLMs. However, it quickly grew into a massive community effort!
We're forming a team of maintainers to manage demand and streamline issue resolution. The maintainers are rockstars, and we're also exploring partnerships to help the project thrive. We're forming a team of maintainers to manage demand and streamline issue resolution. The maintainers are rockstars, and we're also exploring partnerships to help the project thrive.
## Model Comparisons ## Model Comparisons
??? question "How do local LLMs compare to larger models like Claude 3.5 Sonnet for bolt.diy?" ??? question "How do local LLMs compare to larger models like Claude 3.5 Sonnet for bolt.diy?"
While local LLMs are improving rapidly, larger models like GPT-4o, Claude 3.5 Sonnet, and DeepSeek Coder V2 236b still offer the best results for complex applications. Our ongoing focus is to improve prompts, agents, and the platform to better support smaller local LLMs. While local LLMs are improving rapidly, larger models like GPT-4o, Claude 3.5 Sonnet, and DeepSeek Coder V2 236b still offer the best results for complex applications. Our ongoing focus is to improve prompts, agents, and the platform to better support smaller local LLMs.
## Troubleshooting ## Troubleshooting
??? error "There was an error processing this request" ??? error "There was an error processing this request"
This generic error message means something went wrong. Check both: This generic error message means something went wrong. Check both:
- The terminal (if you started the app with Docker or `pnpm`). - The terminal (if you started the app with Docker or `pnpm`).
- The developer console in your browser (press `F12` or right-click > *Inspect*, then go to the *Console* tab). - The developer console in your browser (press `F12` or right-click > *Inspect*, then go to the *Console* tab).
??? error "x-api-key header missing" ??? error "x-api-key header missing"
This error is sometimes resolved by restarting the Docker container. This error is sometimes resolved by restarting the Docker container.
If that doesn't work, try switching from Docker to `pnpm` or vice versa. We're actively investigating this issue. If that doesn't work, try switching from Docker to `pnpm` or vice versa. We're actively investigating this issue.
??? error "Blank preview when running the app" ??? error "Blank preview when running the app"
A blank preview often occurs due to hallucinated bad code or incorrect commands. A blank preview often occurs due to hallucinated bad code or incorrect commands.
To troubleshoot: To troubleshoot:
- Check the developer console for errors. - Check the developer console for errors.
- Remember, previews are core functionality, so the app isn't broken! We're working on making these errors more transparent. - Remember, previews are core functionality, so the app isn't broken! We're working on making these errors more transparent.
??? error "Everything works, but the results are bad" ??? error "Everything works, but the results are bad"
Local LLMs like Qwen-2.5-Coder are powerful for small applications but still experimental for larger projects. For better results, consider using larger models like Local LLMs like Qwen-2.5-Coder are powerful for small applications but still experimental for larger projects. For better results, consider using larger models like
- GPT-4o - GPT-4o
- Claude 3.5 Sonnet - Claude 3.5 Sonnet
- DeepSeek Coder V2 236b - DeepSeek Coder V2 236b
??? error "Received structured exception #0xc0000005: access violation" ??? error "Received structured exception #0xc0000005: access violation"
If you are getting this, you are probably on Windows. The fix is generally to update the [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170) If you are getting this, you are probably on Windows. The fix is generally to update the [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170)
??? error "Miniflare or Wrangler errors in Windows" ??? error "Miniflare or Wrangler errors in Windows"
You will need to make sure you have the latest version of Visual Studio C++ installed (14.40.33816), more information here <a href="https://github.com/stackblitz-labs/bolt.diy/issues/19">Github Issues</a> You will need to make sure you have the latest version of Visual Studio C++ installed (14.40.33816), more information here <a href="https://github.com/stackblitz-labs/bolt.diy/issues/19">Github Issues</a>
--- ---
## Get Help & Support ## Get Help & Support
!!! tip "Community Support" !!! tip "Community Support"
[Join the bolt.diy Community](https://thinktank.ottomator.ai/c/bolt-diy/17){target=_blank} for discussions and help [Join the bolt.diy Community](https://thinktank.ottomator.ai/c/bolt-diy/17){target=\_blank} for discussions and help
!!! bug "Report Issues" !!! bug "Report Issues"
[Open an Issue](https://github.com/stackblitz-labs/bolt.diy/issues/19){target=_blank} in our GitHub Repository [Open an Issue](https://github.com/stackblitz-labs/bolt.diy/issues/19){target=\_blank} in our GitHub Repository

View File

@ -1,7 +1,9 @@
# Welcome to bolt diy # Welcome to bolt diy
bolt.diy allows you to choose the LLM that you use for each prompt! Currently, you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, LMStudio, Mistral, xAI, HuggingFace, DeepSeek, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See the instructions below for running this locally and extending it to include more models. bolt.diy allows you to choose the LLM that you use for each prompt! Currently, you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, LMStudio, Mistral, xAI, HuggingFace, DeepSeek, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See the instructions below for running this locally and extending it to include more models.
## Table of Contents ## Table of Contents
- [Join the community!](#join-the-community) - [Join the community!](#join-the-community)
- [Features](#features) - [Features](#features)
- [Setup](#setup) - [Setup](#setup)
@ -41,31 +43,31 @@ Also [this pinned post in our community](https://thinktank.ottomator.ai/t/videos
--- ---
## Setup ## Setup
If you're new to installing software from GitHub, don't worry! If you encounter any issues, feel free to submit an "issue" using the provided links or improve this documentation by forking the repository, editing the instructions, and submitting a pull request. The following instruction will help you get the stable branch up and running on your local machine in no time. If you're new to installing software from GitHub, don't worry! If you encounter any issues, feel free to submit an "issue" using the provided links or improve this documentation by forking the repository, editing the instructions, and submitting a pull request. The following instruction will help you get the stable branch up and running on your local machine in no time.
### Prerequisites ### Prerequisites
1. **Install Git**: [Download Git](https://git-scm.com/downloads) 1. **Install Git**: [Download Git](https://git-scm.com/downloads)
2. **Install Node.js**: [Download Node.js](https://nodejs.org/en/download/) 2. **Install Node.js**: [Download Node.js](https://nodejs.org/en/download/)
- After installation, the Node.js path is usually added to your system automatically. To verify: - After installation, the Node.js path is usually added to your system automatically. To verify:
- **Windows**: Search for "Edit the system environment variables," click "Environment Variables," and check if `Node.js` is in the `Path` variable. - **Windows**: Search for "Edit the system environment variables," click "Environment Variables," and check if `Node.js` is in the `Path` variable.
- **Mac/Linux**: Open a terminal and run: - **Mac/Linux**: Open a terminal and run:
```bash ```bash
echo $PATH echo $PATH
``` ```
Look for `/usr/local/bin` in the output. Look for `/usr/local/bin` in the output.
### Clone the Repository ### Clone the Repository
Alternatively, you can download the latest version of the project directly from the [Releases Page](https://github.com/stackblitz-labs/bolt.diy/releases/latest). Simply download the .zip file, extract it, and proceed with the setup instructions below. If you are comfertiable using git then run the command below. Alternatively, you can download the latest version of the project directly from the [Releases Page](https://github.com/stackblitz-labs/bolt.diy/releases/latest). Simply download the .zip file, extract it, and proceed with the setup instructions below. If you are comfertiable using git then run the command below.
Clone the repository using Git: Clone the repository using Git:
```bash ```bash
git clone -b stable https://github.com/stackblitz-labs/bolt.diy git clone -b stable https://github.com/stackblitz-labs/bolt.diy
``` ```
--- ---
@ -76,7 +78,7 @@ There are two ways to configure your API keys in bolt.diy:
#### 1. Set API Keys in the `.env.local` File #### 1. Set API Keys in the `.env.local` File
When setting up the application, you will need to add your API keys for the LLMs you wish to use. You can do this by renaming the `.env.example` file to `.env.local` and adding your API keys there. When setting up the application, you will need to add your API keys for the LLMs you wish to use. You can do this by renaming the `.env.example` file to `.env.local` and adding your API keys there.
- On **Mac**, you can find the file at `[your name]/bolt.diy/.env.example`. - On **Mac**, you can find the file at `[your name]/bolt.diy/.env.example`.
- On **Windows/Linux**, the path will be similar. - On **Windows/Linux**, the path will be similar.
@ -112,54 +114,60 @@ This method allows you to easily add or update your keys without needing to modi
Once you've configured your keys, the application will be ready to use the selected LLMs. Once you've configured your keys, the application will be ready to use the selected LLMs.
--- ---
## Run the Application ## Run the Application
### Option 1: Without Docker ### Option 1: Without Docker
1. **Install Dependencies**: 1. **Install Dependencies**:
```bash
pnpm install
```
If `pnpm` is not installed, install it using:
```bash
sudo npm install -g pnpm
```
2. **Start the Application**: ```bash
```bash pnpm install
pnpm run dev
``` ```
This will start the Remix Vite development server. You will need Google Chrome Canary to run this locally if you use Chrome! It's an easy install and a good browser for web development anyway.
### Option 2: With Docker If `pnpm` is not installed, install it using:
#### Prerequisites ```bash
- Ensure Git, Node.js, and Docker are installed: [Download Docker](https://www.docker.com/) sudo npm install -g pnpm
```
#### Steps 2. **Start the Application**:
```bash
pnpm run dev
```
This will start the Remix Vite development server. You will need Google Chrome Canary to run this locally if you use Chrome! It's an easy install and a good browser for web development anyway.
1. **Build the Docker Image**: ### Option 2: With Docker
Use the provided NPM scripts: #### Prerequisites
```bash
npm run dockerbuild
```
Alternatively, use Docker commands directly: - Ensure Git, Node.js, and Docker are installed: [Download Docker](https://www.docker.com/)
```bash
#### Steps
1. **Build the Docker Image**:
Use the provided NPM scripts:
```bash
npm run dockerbuild
```
Alternatively, use Docker commands directly:
```bash
docker build . --target bolt-ai-development docker build . --target bolt-ai-development
``` ```
2. **Run the Container**: 2. **Run the Container**:
Use Docker Compose profiles to manage environments: Use Docker Compose profiles to manage environments:
```bash
docker compose --profile development up
```
- With the development profile, changes to your code will automatically reflect in the running container (hot reloading). ```bash
docker compose --profile development up
```
- With the development profile, changes to your code will automatically reflect in the running container (hot reloading).
--- ---
@ -167,42 +175,46 @@ Once you've configured your keys, the application will be ready to use the selec
To keep your local version of bolt.diy up to date with the latest changes, follow these steps for your operating system: To keep your local version of bolt.diy up to date with the latest changes, follow these steps for your operating system:
#### 1. **Navigate to your project folder** #### 1. **Navigate to your project folder**
Navigate to the directory where you cloned the repository and open a terminal:
#### 2. **Fetch the Latest Changes** Navigate to the directory where you cloned the repository and open a terminal:
Use Git to pull the latest changes from the main repository:
```bash #### 2. **Fetch the Latest Changes**
git pull origin main
```
#### 3. **Update Dependencies** Use Git to pull the latest changes from the main repository:
After pulling the latest changes, update the project dependencies by running the following command:
```bash ```bash
pnpm install git pull origin main
``` ```
#### 4. **Rebuild and Start the Application** #### 3. **Update Dependencies**
- **If using Docker**, ensure you rebuild the Docker image to avoid using a cached version: After pulling the latest changes, update the project dependencies by running the following command:
```bash
docker compose --profile development up --build
```
- **If not using Docker**, you can start the application as usual with: ```bash
```bash pnpm install
pnpm run dev ```
```
This ensures that you're running the latest version of bolt.diy and can take advantage of all the newest features and bug fixes. #### 4. **Rebuild and Start the Application**
- **If using Docker**, ensure you rebuild the Docker image to avoid using a cached version:
```bash
docker compose --profile development up --build
```
- **If not using Docker**, you can start the application as usual with:
```bash
pnpm run dev
```
This ensures that you're running the latest version of bolt.diy and can take advantage of all the newest features and bug fixes.
--- ---
## Adding New LLMs: ## Adding New LLMs:
To make new LLMs available to use in this version of bolt.diy, head on over to `app/utils/constants.ts` and find the constant MODEL_LIST. Each element in this array is an object that has the model ID for the name (get this from the provider's API documentation), a label for the frontend model dropdown, and the provider. To make new LLMs available to use in this version of bolt.diy, head on over to `app/utils/constants.ts` and find the constant MODEL_LIST. Each element in this array is an object that has the model ID for the name (get this from the provider's API documentation), a label for the frontend model dropdown, and the provider.
By default, Anthropic, OpenAI, Groq, and Ollama are implemented as providers, but the YouTube video for this repo covers how to extend this to work with more providers if you wish! By default, Anthropic, OpenAI, Groq, and Ollama are implemented as providers, but the YouTube video for this repo covers how to extend this to work with more providers if you wish!

View File

@ -33,7 +33,7 @@ theme:
# favicon: assets/logo.png # favicon: assets/logo.png
repo_name: bolt.diy repo_name: bolt.diy
repo_url: https://github.com/stackblitz-labs/bolt.diy repo_url: https://github.com/stackblitz-labs/bolt.diy
edit_uri: "" edit_uri: ''
extra: extra:
generator: false generator: false
@ -51,9 +51,6 @@ extra:
link: https://bsky.app/profile/bolt.diy link: https://bsky.app/profile/bolt.diy
name: bolt.diy on Bluesky name: bolt.diy on Bluesky
markdown_extensions: markdown_extensions:
- pymdownx.highlight: - pymdownx.highlight:
anchor_linenums: true anchor_linenums: true
@ -73,4 +70,4 @@ markdown_extensions:
- pymdownx.tasklist: - pymdownx.tasklist:
custom_checkbox: true custom_checkbox: true
- toc: - toc:
permalink: true permalink: true

View File

@ -4,13 +4,7 @@ import { getNamingConventionRule, tsFileExtensions } from '@blitz/eslint-plugin/
export default [ export default [
{ {
ignores: [ ignores: ['**/dist', '**/node_modules', '**/.wrangler', '**/bolt/build', '**/.history'],
'**/dist',
'**/node_modules',
'**/.wrangler',
'**/bolt/build',
'**/.history',
],
}, },
...blitzPlugin.configs.recommended(), ...blitzPlugin.configs.recommended(),
{ {
@ -20,15 +14,15 @@ export default [
'@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-empty-object-type': 'off',
'@blitz/comment-syntax': 'off', '@blitz/comment-syntax': 'off',
'@blitz/block-scope-case': 'off', '@blitz/block-scope-case': 'off',
'array-bracket-spacing': ["error", "never"], 'array-bracket-spacing': ['error', 'never'],
'object-curly-newline': ["error", { "consistent": true }], 'object-curly-newline': ['error', { consistent: true }],
'keyword-spacing': ["error", { "before": true, "after": true }], 'keyword-spacing': ['error', { before: true, after: true }],
'consistent-return': "error", 'consistent-return': 'error',
'semi': ["error", "always"], semi: ['error', 'always'],
'curly': ["error"], curly: ['error'],
'no-eval': ["error"], 'no-eval': ['error'],
'linebreak-style': ["error", "unix"], 'linebreak-style': ['error', 'unix'],
'arrow-spacing': ["error", { "before": true, "after": true }] 'arrow-spacing': ['error', { before: true, after: true }],
}, },
}, },
{ {
@ -53,7 +47,7 @@ export default [
patterns: [ patterns: [
{ {
group: ['../'], group: ['../'],
message: 'Relative imports are not allowed. Please use \'~/\' instead.', message: "Relative imports are not allowed. Please use '~/' instead.",
}, },
], ],
}, },

View File

@ -84,8 +84,8 @@
"ai": "^4.1.2", "ai": "^4.1.2",
"chalk": "^5.4.1", "chalk": "^5.4.1",
"chart.js": "^4.4.7", "chart.js": "^4.4.7",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"diff": "^5.2.0", "diff": "^5.2.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
@ -117,7 +117,7 @@
"remix-island": "^0.2.0", "remix-island": "^0.2.0",
"remix-utils": "^7.7.0", "remix-utils": "^7.7.0",
"shiki": "^1.24.0", "shiki": "^1.24.0",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.2.1",
"unist-util-visit": "^5.0.0" "unist-util-visit": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
const { execSync } =require('child_process'); const { execSync } = require('child_process');
// Get git hash with fallback // Get git hash with fallback
const getGitHash = () => { const getGitHash = () => {

View File

@ -1,7 +1,12 @@
{ {
"compilerOptions": { "compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ESNext"], "lib": ["DOM", "DOM.Iterable", "ESNext"],
"types": ["@remix-run/cloudflare", "vite/client", "@cloudflare/workers-types/2023-07-01", "@types/dom-speech-recognition"], "types": [
"@remix-run/cloudflare",
"vite/client",
"@cloudflare/workers-types/2023-07-01",
"@types/dom-speech-recognition"
],
"isolatedModules": true, "isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,
"jsx": "react-jsx", "jsx": "react-jsx",

View File

@ -18,9 +18,6 @@ const getGitHash = () => {
} }
}; };
export default defineConfig((config) => { export default defineConfig((config) => {
return { return {
define: { define: {
@ -41,7 +38,7 @@ export default defineConfig((config) => {
v3_fetcherPersist: true, v3_fetcherPersist: true,
v3_relativeSplatPath: true, v3_relativeSplatPath: true,
v3_throwAbortReason: true, v3_throwAbortReason: true,
v3_lazyRouteDiscovery: true v3_lazyRouteDiscovery: true,
}, },
}), }),
UnoCSS(), UnoCSS(),
@ -49,7 +46,13 @@ export default defineConfig((config) => {
chrome129IssuePlugin(), chrome129IssuePlugin(),
config.mode === 'production' && optimizeCssModules({ apply: 'build' }), config.mode === 'production' && optimizeCssModules({ apply: 'build' }),
], ],
envPrefix: ["VITE_","OPENAI_LIKE_API_BASE_URL", "OLLAMA_API_BASE_URL", "LMSTUDIO_API_BASE_URL","TOGETHER_API_BASE_URL"], envPrefix: [
'VITE_',
'OPENAI_LIKE_API_BASE_URL',
'OLLAMA_API_BASE_URL',
'LMSTUDIO_API_BASE_URL',
'TOGETHER_API_BASE_URL',
],
css: { css: {
preprocessorOptions: { preprocessorOptions: {
scss: { scss: {