Stijnus commited on
Commit
c4c7362
·
1 Parent(s): 58d3853

Fix ESLint issues

Browse files
Files changed (44) hide show
  1. .cursorrules +2 -0
  2. .github/ISSUE_TEMPLATE/bug_report.yml +1 -1
  3. .github/ISSUE_TEMPLATE/epic.md +1 -1
  4. .github/ISSUE_TEMPLATE/feature.md +3 -3
  5. .github/workflows/docker.yaml +2 -2
  6. .github/workflows/docs.yaml +3 -3
  7. .github/workflows/pr-release-validation.yaml +3 -3
  8. .github/workflows/semantic-pr.yaml +1 -1
  9. .github/workflows/stale.yml +11 -11
  10. .github/workflows/update-stable.yml +5 -6
  11. CONTRIBUTING.md +87 -64
  12. FAQ.md +19 -5
  13. PROJECT.md +4 -4
  14. README.md +4 -2
  15. app/components/chat/BaseChat.tsx +4 -2
  16. app/components/chat/GitCloneButton.tsx +105 -50
  17. app/components/chat/ImportFolderButton.tsx +16 -4
  18. app/components/chat/chatExportAndImport/ImportButtons.tsx +23 -5
  19. app/components/settings/connections/components/PushToGitHubDialog.tsx +459 -0
  20. app/components/settings/connections/components/RepositorySelectionDialog.tsx +692 -0
  21. app/components/settings/developer/DeveloperWindow.tsx +21 -5
  22. app/components/settings/user/UsersWindow.tsx +22 -3
  23. app/components/sidebar/HistoryItem.tsx +35 -37
  24. app/components/sidebar/Menu.client.tsx +126 -92
  25. app/components/sidebar/date-binning.ts +7 -7
  26. app/components/ui/Button.tsx +46 -0
  27. app/components/ui/Dialog.tsx +5 -4
  28. app/components/workbench/Workbench.client.tsx +20 -63
  29. app/lib/modules/llm/providers/groq.ts +6 -1
  30. app/lib/stores/workbench.ts +1 -1
  31. app/types/GitHub.ts +133 -0
  32. app/utils/formatSize.ts +12 -0
  33. changelog.md +7 -10
  34. docker-compose.yaml +11 -11
  35. docs/docs/CONTRIBUTING.md +87 -64
  36. docs/docs/FAQ.md +23 -24
  37. docs/docs/index.md +83 -71
  38. docs/mkdocs.yml +2 -5
  39. eslint.config.mjs +11 -17
  40. package.json +3 -3
  41. pnpm-lock.yaml +0 -0
  42. pre-start.cjs +1 -1
  43. tsconfig.json +6 -1
  44. vite.config.ts +8 -5
.cursorrules CHANGED
@@ -8,6 +8,8 @@ bolt.diy (previously oTToDev) is an open-source AI-powered full-stack web develo
8
  - Focus on best practices and clean code
9
  - Provide clear explanations for code changes
10
  - Maintain consistent code style with the existing codebase
 
 
11
 
12
  # Techstack
13
 
 
8
  - Focus on best practices and clean code
9
  - Provide clear explanations for code changes
10
  - Maintain consistent code style with the existing codebase
11
+ - Always write comments that are relevant to the code they describe
12
+ - Always write a changelog what you did and save it in a file called changelog.md in the root of the project
13
 
14
  # Techstack
15
 
.github/ISSUE_TEMPLATE/bug_report.yml CHANGED
@@ -1,4 +1,4 @@
1
- name: "Bug report"
2
  description: Create a report to help us improve
3
  body:
4
  - type: markdown
 
1
+ name: 'Bug report'
2
  description: Create a report to help us improve
3
  body:
4
  - type: markdown
.github/ISSUE_TEMPLATE/epic.md CHANGED
@@ -19,5 +19,5 @@ Usual values: Software Developers using the IDE | Contributors -->
19
 
20
  # Capabilities
21
 
22
- <!-- 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.
23
  Once features are actually being planned / described in detail, they can be linked here. -->
 
19
 
20
  # Capabilities
21
 
22
+ <!-- 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.
23
  Once features are actually being planned / described in detail, they can be linked here. -->
.github/ISSUE_TEMPLATE/feature.md CHANGED
@@ -13,13 +13,13 @@ assignees: ''
13
 
14
  # Scope
15
 
16
- <!-- This is kind-of the definition-of-done for a feature.
17
  Try to keep the scope as small as possible and prefer creating multiple, small features which each solve a single problem / make something better
18
  -->
19
 
20
  # Options
21
-
22
- <!-- If you already have an idea how this can be implemented, please describe it here.
23
  This allows potential other contributors to join forces and provide meaningful feedback prio to even starting work on it.
24
  -->
25
 
 
13
 
14
  # Scope
15
 
16
+ <!-- This is kind-of the definition-of-done for a feature.
17
  Try to keep the scope as small as possible and prefer creating multiple, small features which each solve a single problem / make something better
18
  -->
19
 
20
  # Options
21
+
22
+ <!-- If you already have an idea how this can be implemented, please describe it here.
23
  This allows potential other contributors to join forces and provide meaningful feedback prio to even starting work on it.
24
  -->
25
 
.github/workflows/docker.yaml CHANGED
@@ -8,7 +8,7 @@ on:
8
  - main
9
  tags:
10
  - v*
11
- - "*"
12
 
13
  permissions:
14
  packages: write
@@ -57,7 +57,7 @@ jobs:
57
  with:
58
  registry: ${{ env.REGISTRY }}
59
  username: ${{ github.actor }} # ${{ secrets.DOCKER_USERNAME }}
60
- password: ${{ secrets.GITHUB_TOKEN }} # ${{ secrets.DOCKER_PASSWORD }}
61
 
62
  - name: Build and push
63
  uses: docker/build-push-action@v6
 
8
  - main
9
  tags:
10
  - v*
11
+ - '*'
12
 
13
  permissions:
14
  packages: write
 
57
  with:
58
  registry: ${{ env.REGISTRY }}
59
  username: ${{ github.actor }} # ${{ secrets.DOCKER_USERNAME }}
60
+ password: ${{ secrets.GITHUB_TOKEN }} # ${{ secrets.DOCKER_PASSWORD }}
61
 
62
  - name: Build and push
63
  uses: docker/build-push-action@v6
.github/workflows/docs.yaml CHANGED
@@ -5,7 +5,7 @@ on:
5
  branches:
6
  - main
7
  paths:
8
- - 'docs/**' # This will only trigger the workflow when files in docs directory change
9
  permissions:
10
  contents: write
11
  jobs:
@@ -23,7 +23,7 @@ jobs:
23
  - uses: actions/setup-python@v5
24
  with:
25
  python-version: 3.x
26
- - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
27
  - uses: actions/cache@v4
28
  with:
29
  key: mkdocs-material-${{ env.cache_id }}
@@ -32,4 +32,4 @@ jobs:
32
  mkdocs-material-
33
 
34
  - run: pip install mkdocs-material
35
- - run: mkdocs gh-deploy --force
 
5
  branches:
6
  - main
7
  paths:
8
+ - 'docs/**' # This will only trigger the workflow when files in docs directory change
9
  permissions:
10
  contents: write
11
  jobs:
 
23
  - uses: actions/setup-python@v5
24
  with:
25
  python-version: 3.x
26
+ - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
27
  - uses: actions/cache@v4
28
  with:
29
  key: mkdocs-material-${{ env.cache_id }}
 
32
  mkdocs-material-
33
 
34
  - run: pip install mkdocs-material
35
+ - run: mkdocs gh-deploy --force
.github/workflows/pr-release-validation.yaml CHANGED
@@ -9,10 +9,10 @@ on:
9
  jobs:
10
  validate:
11
  runs-on: ubuntu-latest
12
-
13
  steps:
14
  - uses: actions/checkout@v4
15
-
16
  - name: Validate PR Labels
17
  run: |
18
  if [[ "${{ contains(github.event.pull_request.labels.*.name, 'stable-release') }}" == "true" ]]; then
@@ -28,4 +28,4 @@ jobs:
28
  fi
29
  else
30
  echo "This PR doesn't have the stable-release label. No release will be created."
31
- fi
 
9
  jobs:
10
  validate:
11
  runs-on: ubuntu-latest
12
+
13
  steps:
14
  - uses: actions/checkout@v4
15
+
16
  - name: Validate PR Labels
17
  run: |
18
  if [[ "${{ contains(github.event.pull_request.labels.*.name, 'stable-release') }}" == "true" ]]; then
 
28
  fi
29
  else
30
  echo "This PR doesn't have the stable-release label. No release will be created."
31
+ fi
.github/workflows/semantic-pr.yaml CHANGED
@@ -29,4 +29,4 @@ jobs:
29
  docs
30
  refactor
31
  revert
32
- test
 
29
  docs
30
  refactor
31
  revert
32
+ test
.github/workflows/stale.yml CHANGED
@@ -2,8 +2,8 @@ name: Mark Stale Issues and Pull Requests
2
 
3
  on:
4
  schedule:
5
- - cron: '0 2 * * *' # Runs daily at 2:00 AM UTC
6
- workflow_dispatch: # Allows manual triggering of the workflow
7
 
8
  jobs:
9
  stale:
@@ -14,12 +14,12 @@ jobs:
14
  uses: actions/stale@v8
15
  with:
16
  repo-token: ${{ secrets.GITHUB_TOKEN }}
17
- 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."
18
- 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."
19
- days-before-stale: 10 # Number of days before marking an issue or PR as stale
20
- days-before-close: 4 # Number of days after being marked stale before closing
21
- stale-issue-label: "stale" # Label to apply to stale issues
22
- stale-pr-label: "stale" # Label to apply to stale pull requests
23
- exempt-issue-labels: "pinned,important" # Issues with these labels won't be marked stale
24
- exempt-pr-labels: "pinned,important" # PRs with these labels won't be marked stale
25
- operations-per-run: 75 # Limits the number of actions per run to avoid API rate limits
 
2
 
3
  on:
4
  schedule:
5
+ - cron: '0 2 * * *' # Runs daily at 2:00 AM UTC
6
+ workflow_dispatch: # Allows manual triggering of the workflow
7
 
8
  jobs:
9
  stale:
 
14
  uses: actions/stale@v8
15
  with:
16
  repo-token: ${{ secrets.GITHUB_TOKEN }}
17
+ 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.'
18
+ 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.'
19
+ days-before-stale: 10 # Number of days before marking an issue or PR as stale
20
+ days-before-close: 4 # Number of days after being marked stale before closing
21
+ stale-issue-label: 'stale' # Label to apply to stale issues
22
+ stale-pr-label: 'stale' # Label to apply to stale pull requests
23
+ exempt-issue-labels: 'pinned,important' # Issues with these labels won't be marked stale
24
+ exempt-pr-labels: 'pinned,important' # PRs with these labels won't be marked stale
25
+ operations-per-run: 75 # Limits the number of actions per run to avoid API rate limits
.github/workflows/update-stable.yml CHANGED
@@ -7,12 +7,12 @@ on:
7
 
8
  permissions:
9
  contents: write
10
-
11
  jobs:
12
  prepare-release:
13
  if: contains(github.event.head_commit.message, '#release')
14
  runs-on: ubuntu-latest
15
-
16
  steps:
17
  - uses: actions/checkout@v4
18
  with:
@@ -80,7 +80,6 @@ jobs:
80
  NEW_VERSION=${{ steps.bump_version.outputs.new_version }}
81
  pnpm version $NEW_VERSION --no-git-tag-version --allow-same-version
82
 
83
-
84
  - name: Prepare changelog script
85
  run: chmod +x .github/scripts/generate-changelog.sh
86
 
@@ -89,14 +88,14 @@ jobs:
89
  env:
90
  NEW_VERSION: ${{ steps.bump_version.outputs.new_version }}
91
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
92
-
93
  run: .github/scripts/generate-changelog.sh
94
 
95
  - name: Get the latest commit hash and version tag
96
  run: |
97
  echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV
98
  echo "NEW_VERSION=${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_ENV
99
-
100
  - name: Commit and Tag Release
101
  run: |
102
  git pull
@@ -123,4 +122,4 @@ jobs:
123
  gh release create "$VERSION" \
124
  --title "Release $VERSION" \
125
  --notes "${{ steps.changelog.outputs.content }}" \
126
- --target stable
 
7
 
8
  permissions:
9
  contents: write
10
+
11
  jobs:
12
  prepare-release:
13
  if: contains(github.event.head_commit.message, '#release')
14
  runs-on: ubuntu-latest
15
+
16
  steps:
17
  - uses: actions/checkout@v4
18
  with:
 
80
  NEW_VERSION=${{ steps.bump_version.outputs.new_version }}
81
  pnpm version $NEW_VERSION --no-git-tag-version --allow-same-version
82
 
 
83
  - name: Prepare changelog script
84
  run: chmod +x .github/scripts/generate-changelog.sh
85
 
 
88
  env:
89
  NEW_VERSION: ${{ steps.bump_version.outputs.new_version }}
90
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
91
+
92
  run: .github/scripts/generate-changelog.sh
93
 
94
  - name: Get the latest commit hash and version tag
95
  run: |
96
  echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV
97
  echo "NEW_VERSION=${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_ENV
98
+
99
  - name: Commit and Tag Release
100
  run: |
101
  git pull
 
122
  gh release create "$VERSION" \
123
  --title "Release $VERSION" \
124
  --notes "${{ steps.changelog.outputs.content }}" \
125
+ --target stable
CONTRIBUTING.md CHANGED
@@ -6,15 +6,15 @@ Welcome! This guide provides all the details you need to contribute effectively
6
 
7
  ## 📋 Table of Contents
8
 
9
- 1. [Code of Conduct](#code-of-conduct)
10
- 2. [How Can I Contribute?](#how-can-i-contribute)
11
- 3. [Pull Request Guidelines](#pull-request-guidelines)
12
- 4. [Coding Standards](#coding-standards)
13
- 5. [Development Setup](#development-setup)
14
- 6. [Testing](#testing)
15
- 7. [Deployment](#deployment)
16
- 8. [Docker Deployment](#docker-deployment)
17
- 9. [VS Code Dev Containers Integration](#vs-code-dev-containers-integration)
18
 
19
  ---
20
 
@@ -27,60 +27,67 @@ This project is governed by our **Code of Conduct**. By participating, you agree
27
  ## 🛠️ How Can I Contribute?
28
 
29
  ### 1️⃣ Reporting Bugs or Feature Requests
 
30
  - Check the [issue tracker](#) to avoid duplicates.
31
- - Use issue templates (if available).
32
  - Provide detailed, relevant information and steps to reproduce bugs.
33
 
34
  ### 2️⃣ Code Contributions
35
- 1. Fork the repository.
36
- 2. Create a feature or fix branch.
37
- 3. Write and test your code.
 
38
  4. Submit a pull request (PR).
39
 
40
- ### 3️⃣ Join as a Core Contributor
 
41
  Interested in maintaining and growing the project? Fill out our [Contributor Application Form](https://forms.gle/TBSteXSDCtBDwr5m7).
42
 
43
  ---
44
 
45
  ## ✅ Pull Request Guidelines
46
 
47
- ### PR Checklist
48
- - Branch from the **main** branch.
49
- - Update documentation, if needed.
50
- - Test all functionality manually.
51
- - Focus on one feature/bug per PR.
52
 
53
- ### Review Process
54
- 1. Manual testing by reviewers.
55
- 2. At least one maintainer review required.
56
- 3. Address review comments.
 
 
 
 
 
 
57
  4. Maintain a clean commit history.
58
 
59
  ---
60
 
61
  ## 📏 Coding Standards
62
 
63
- ### General Guidelines
64
- - Follow existing code style.
65
- - Comment complex logic.
66
- - Keep functions small and focused.
 
67
  - Use meaningful variable names.
68
 
69
  ---
70
 
71
  ## 🖥️ Development Setup
72
 
73
- ### 1️⃣ Initial Setup
74
- - Clone the repository:
 
75
  ```bash
76
  git clone https://github.com/stackblitz-labs/bolt.diy.git
77
  ```
78
- - Install dependencies:
79
  ```bash
80
  pnpm install
81
  ```
82
- - Set up environment variables:
83
- 1. Rename `.env.example` to `.env.local`.
84
  2. Add your API keys:
85
  ```bash
86
  GROQ_API_KEY=XXX
@@ -88,23 +95,26 @@ Interested in maintaining and growing the project? Fill out our [Contributor App
88
  OPENAI_API_KEY=XXX
89
  ...
90
  ```
91
- 3. Optionally set:
92
- - Debug level: `VITE_LOG_LEVEL=debug`
93
- - Context size: `DEFAULT_NUM_CTX=32768`
94
 
95
  **Note**: Never commit your `.env.local` file to version control. It’s already in `.gitignore`.
96
 
97
- ### 2️⃣ Run Development Server
 
98
  ```bash
99
  pnpm run dev
100
  ```
 
101
  **Tip**: Use **Google Chrome Canary** for local testing.
102
 
103
  ---
104
 
105
  ## 🧪 Testing
106
 
107
- Run the test suite with:
 
108
  ```bash
109
  pnpm test
110
  ```
@@ -113,10 +123,12 @@ pnpm test
113
 
114
  ## 🚀 Deployment
115
 
116
- ### Deploy to Cloudflare Pages
 
117
  ```bash
118
  pnpm run deploy
119
  ```
 
120
  Ensure you have required permissions and that Wrangler is configured.
121
 
122
  ---
@@ -127,67 +139,76 @@ This section outlines the methods for deploying the application using Docker. Th
127
 
128
  ---
129
 
130
- ### 🧑‍💻 Development Environment
131
 
132
- #### Build Options
 
 
133
 
134
- **Option 1: Helper Scripts**
135
  ```bash
136
  # Development build
137
  npm run dockerbuild
138
  ```
139
 
140
- **Option 2: Direct Docker Build Command**
 
141
  ```bash
142
  docker build . --target bolt-ai-development
143
  ```
144
 
145
- **Option 3: Docker Compose Profile**
 
146
  ```bash
147
  docker compose --profile development up
148
  ```
149
 
150
- #### Running the Development Container
 
151
  ```bash
152
  docker run -p 5173:5173 --env-file .env.local bolt-ai:development
153
  ```
154
 
155
  ---
156
 
157
- ### 🏭 Production Environment
 
 
158
 
159
- #### Build Options
160
 
161
- **Option 1: Helper Scripts**
162
  ```bash
163
  # Production build
164
  npm run dockerbuild:prod
165
  ```
166
 
167
- **Option 2: Direct Docker Build Command**
 
168
  ```bash
169
  docker build . --target bolt-ai-production
170
  ```
171
 
172
- **Option 3: Docker Compose Profile**
 
173
  ```bash
174
  docker compose --profile production up
175
  ```
176
 
177
- #### Running the Production Container
 
178
  ```bash
179
  docker run -p 5173:5173 --env-file .env.local bolt-ai:production
180
  ```
181
 
182
  ---
183
 
184
- ### Coolify Deployment
 
 
185
 
186
- For an easy deployment process, use [Coolify](https://github.com/coollabsio/coolify):
187
- 1. Import your Git repository into Coolify.
188
- 2. Choose **Docker Compose** as the build pack.
189
- 3. Configure environment variables (e.g., API keys).
190
- 4. Set the start command:
191
  ```bash
192
  docker compose --profile production up
193
  ```
@@ -200,20 +221,22 @@ The `docker-compose.yaml` configuration is compatible with **VS Code Dev Contain
200
 
201
  ### Steps to Use Dev Containers
202
 
203
- 1. Open the command palette in VS Code (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS).
204
- 2. Select **Dev Containers: Reopen in Container**.
205
- 3. Choose the **development** profile when prompted.
206
  4. VS Code will rebuild the container and open it with the pre-configured environment.
207
 
208
  ---
209
 
210
  ## 🔑 Environment Variables
211
 
212
- Ensure `.env.local` is configured correctly with:
213
- - API keys.
214
- - Context-specific configurations.
 
 
 
215
 
216
- Example for the `DEFAULT_NUM_CTX` variable:
217
  ```bash
218
  DEFAULT_NUM_CTX=24576 # Uses 32GB VRAM
219
- ```
 
6
 
7
  ## 📋 Table of Contents
8
 
9
+ 1. [Code of Conduct](#code-of-conduct)
10
+ 2. [How Can I Contribute?](#how-can-i-contribute)
11
+ 3. [Pull Request Guidelines](#pull-request-guidelines)
12
+ 4. [Coding Standards](#coding-standards)
13
+ 5. [Development Setup](#development-setup)
14
+ 6. [Testing](#testing)
15
+ 7. [Deployment](#deployment)
16
+ 8. [Docker Deployment](#docker-deployment)
17
+ 9. [VS Code Dev Containers Integration](#vs-code-dev-containers-integration)
18
 
19
  ---
20
 
 
27
  ## 🛠️ How Can I Contribute?
28
 
29
  ### 1️⃣ Reporting Bugs or Feature Requests
30
+
31
  - Check the [issue tracker](#) to avoid duplicates.
32
+ - Use issue templates (if available).
33
  - Provide detailed, relevant information and steps to reproduce bugs.
34
 
35
  ### 2️⃣ Code Contributions
36
+
37
+ 1. Fork the repository.
38
+ 2. Create a feature or fix branch.
39
+ 3. Write and test your code.
40
  4. Submit a pull request (PR).
41
 
42
+ ### 3️⃣ Join as a Core Contributor
43
+
44
  Interested in maintaining and growing the project? Fill out our [Contributor Application Form](https://forms.gle/TBSteXSDCtBDwr5m7).
45
 
46
  ---
47
 
48
  ## ✅ Pull Request Guidelines
49
 
50
+ ### PR Checklist
 
 
 
 
51
 
52
+ - Branch from the **main** branch.
53
+ - Update documentation, if needed.
54
+ - Test all functionality manually.
55
+ - Focus on one feature/bug per PR.
56
+
57
+ ### Review Process
58
+
59
+ 1. Manual testing by reviewers.
60
+ 2. At least one maintainer review required.
61
+ 3. Address review comments.
62
  4. Maintain a clean commit history.
63
 
64
  ---
65
 
66
  ## 📏 Coding Standards
67
 
68
+ ### General Guidelines
69
+
70
+ - Follow existing code style.
71
+ - Comment complex logic.
72
+ - Keep functions small and focused.
73
  - Use meaningful variable names.
74
 
75
  ---
76
 
77
  ## 🖥️ Development Setup
78
 
79
+ ### 1️⃣ Initial Setup
80
+
81
+ - Clone the repository:
82
  ```bash
83
  git clone https://github.com/stackblitz-labs/bolt.diy.git
84
  ```
85
+ - Install dependencies:
86
  ```bash
87
  pnpm install
88
  ```
89
+ - Set up environment variables:
90
+ 1. Rename `.env.example` to `.env.local`.
91
  2. Add your API keys:
92
  ```bash
93
  GROQ_API_KEY=XXX
 
95
  OPENAI_API_KEY=XXX
96
  ...
97
  ```
98
+ 3. Optionally set:
99
+ - Debug level: `VITE_LOG_LEVEL=debug`
100
+ - Context size: `DEFAULT_NUM_CTX=32768`
101
 
102
  **Note**: Never commit your `.env.local` file to version control. It’s already in `.gitignore`.
103
 
104
+ ### 2️⃣ Run Development Server
105
+
106
  ```bash
107
  pnpm run dev
108
  ```
109
+
110
  **Tip**: Use **Google Chrome Canary** for local testing.
111
 
112
  ---
113
 
114
  ## 🧪 Testing
115
 
116
+ Run the test suite with:
117
+
118
  ```bash
119
  pnpm test
120
  ```
 
123
 
124
  ## 🚀 Deployment
125
 
126
+ ### Deploy to Cloudflare Pages
127
+
128
  ```bash
129
  pnpm run deploy
130
  ```
131
+
132
  Ensure you have required permissions and that Wrangler is configured.
133
 
134
  ---
 
139
 
140
  ---
141
 
142
+ ### 🧑‍💻 Development Environment
143
 
144
+ #### Build Options
145
+
146
+ **Option 1: Helper Scripts**
147
 
 
148
  ```bash
149
  # Development build
150
  npm run dockerbuild
151
  ```
152
 
153
+ **Option 2: Direct Docker Build Command**
154
+
155
  ```bash
156
  docker build . --target bolt-ai-development
157
  ```
158
 
159
+ **Option 3: Docker Compose Profile**
160
+
161
  ```bash
162
  docker compose --profile development up
163
  ```
164
 
165
+ #### Running the Development Container
166
+
167
  ```bash
168
  docker run -p 5173:5173 --env-file .env.local bolt-ai:development
169
  ```
170
 
171
  ---
172
 
173
+ ### 🏭 Production Environment
174
+
175
+ #### Build Options
176
 
177
+ **Option 1: Helper Scripts**
178
 
 
179
  ```bash
180
  # Production build
181
  npm run dockerbuild:prod
182
  ```
183
 
184
+ **Option 2: Direct Docker Build Command**
185
+
186
  ```bash
187
  docker build . --target bolt-ai-production
188
  ```
189
 
190
+ **Option 3: Docker Compose Profile**
191
+
192
  ```bash
193
  docker compose --profile production up
194
  ```
195
 
196
+ #### Running the Production Container
197
+
198
  ```bash
199
  docker run -p 5173:5173 --env-file .env.local bolt-ai:production
200
  ```
201
 
202
  ---
203
 
204
+ ### Coolify Deployment
205
+
206
+ For an easy deployment process, use [Coolify](https://github.com/coollabsio/coolify):
207
 
208
+ 1. Import your Git repository into Coolify.
209
+ 2. Choose **Docker Compose** as the build pack.
210
+ 3. Configure environment variables (e.g., API keys).
211
+ 4. Set the start command:
 
212
  ```bash
213
  docker compose --profile production up
214
  ```
 
221
 
222
  ### Steps to Use Dev Containers
223
 
224
+ 1. Open the command palette in VS Code (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS).
225
+ 2. Select **Dev Containers: Reopen in Container**.
226
+ 3. Choose the **development** profile when prompted.
227
  4. VS Code will rebuild the container and open it with the pre-configured environment.
228
 
229
  ---
230
 
231
  ## 🔑 Environment Variables
232
 
233
+ Ensure `.env.local` is configured correctly with:
234
+
235
+ - API keys.
236
+ - Context-specific configurations.
237
+
238
+ Example for the `DEFAULT_NUM_CTX` variable:
239
 
 
240
  ```bash
241
  DEFAULT_NUM_CTX=24576 # Uses 32GB VRAM
242
+ ```
FAQ.md CHANGED
@@ -12,6 +12,7 @@ For the best experience with bolt.diy, we recommend using the following models:
12
  - **Qwen 2.5 Coder 32b**: Best model for self-hosting with reasonable hardware requirements
13
 
14
  **Note**: Models with less than 7b parameters typically lack the capability to properly interact with bolt!
 
15
  </details>
16
 
17
  <details>
@@ -21,20 +22,21 @@ For the best experience with bolt.diy, we recommend using the following models:
21
  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.
22
 
23
  - **Use the enhance prompt icon**:
24
- Before sending your prompt, click the *enhance* icon to let the AI refine your prompt. You can edit the suggested improvements before submitting.
25
 
26
  - **Scaffold the basics first, then add features**:
27
  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.
28
 
29
  - **Batch simple instructions**:
30
- Combine simple tasks into a single prompt to save time and reduce API credit consumption. For example:
31
- *"Change the color scheme, add mobile responsiveness, and restart the dev server."*
32
  </details>
33
 
34
  <details>
35
  <summary><strong>How do I contribute to bolt.diy?</strong></summary>
36
 
37
  Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to get involved!
 
38
  </details>
39
 
40
  <details>
@@ -42,48 +44,60 @@ Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to g
42
 
43
  Visit our [Roadmap](https://roadmap.sh/r/ottodev-roadmap-2ovzo) for the latest updates.
44
  New features and improvements are on the way!
 
45
  </details>
46
 
47
  <details>
48
  <summary><strong>Why are there so many open issues/pull requests?</strong></summary>
49
 
50
- 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!
51
 
52
  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.
 
53
  </details>
54
 
55
  <details>
56
  <summary><strong>How do local LLMs compare to larger models like Claude 3.5 Sonnet for bolt.diy?</strong></summary>
57
 
58
  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.
 
59
  </details>
60
 
61
  <details>
62
  <summary><strong>Common Errors and Troubleshooting</strong></summary>
63
 
64
  ### **"There was an error processing this request"**
 
65
  This generic error message means something went wrong. Check both:
 
66
  - The terminal (if you started the app with Docker or `pnpm`).
67
- - The developer console in your browser (press `F12` or right-click > *Inspect*, then go to the *Console* tab).
68
 
69
  ### **"x-api-key header missing"**
 
70
  This error is sometimes resolved by restarting the Docker container.
71
  If that doesn't work, try switching from Docker to `pnpm` or vice versa. We're actively investigating this issue.
72
 
73
  ### **Blank preview when running the app**
 
74
  A blank preview often occurs due to hallucinated bad code or incorrect commands.
75
  To troubleshoot:
 
76
  - Check the developer console for errors.
77
  - Remember, previews are core functionality, so the app isn't broken! We're working on making these errors more transparent.
78
 
79
  ### **"Everything works, but the results are bad"**
 
80
  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.
81
 
82
  ### **"Received structured exception #0xc0000005: access violation"**
 
83
  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)
84
 
85
  ### **"Miniflare or Wrangler errors in Windows"**
 
86
  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.
 
87
  </details>
88
 
89
  ---
 
12
  - **Qwen 2.5 Coder 32b**: Best model for self-hosting with reasonable hardware requirements
13
 
14
  **Note**: Models with less than 7b parameters typically lack the capability to properly interact with bolt!
15
+
16
  </details>
17
 
18
  <details>
 
22
  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.
23
 
24
  - **Use the enhance prompt icon**:
25
+ Before sending your prompt, click the _enhance_ icon to let the AI refine your prompt. You can edit the suggested improvements before submitting.
26
 
27
  - **Scaffold the basics first, then add features**:
28
  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.
29
 
30
  - **Batch simple instructions**:
31
+ Combine simple tasks into a single prompt to save time and reduce API credit consumption. For example:
32
+ _"Change the color scheme, add mobile responsiveness, and restart the dev server."_
33
  </details>
34
 
35
  <details>
36
  <summary><strong>How do I contribute to bolt.diy?</strong></summary>
37
 
38
  Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to get involved!
39
+
40
  </details>
41
 
42
  <details>
 
44
 
45
  Visit our [Roadmap](https://roadmap.sh/r/ottodev-roadmap-2ovzo) for the latest updates.
46
  New features and improvements are on the way!
47
+
48
  </details>
49
 
50
  <details>
51
  <summary><strong>Why are there so many open issues/pull requests?</strong></summary>
52
 
53
+ 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!
54
 
55
  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.
56
+
57
  </details>
58
 
59
  <details>
60
  <summary><strong>How do local LLMs compare to larger models like Claude 3.5 Sonnet for bolt.diy?</strong></summary>
61
 
62
  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.
63
+
64
  </details>
65
 
66
  <details>
67
  <summary><strong>Common Errors and Troubleshooting</strong></summary>
68
 
69
  ### **"There was an error processing this request"**
70
+
71
  This generic error message means something went wrong. Check both:
72
+
73
  - The terminal (if you started the app with Docker or `pnpm`).
74
+ - The developer console in your browser (press `F12` or right-click > _Inspect_, then go to the _Console_ tab).
75
 
76
  ### **"x-api-key header missing"**
77
+
78
  This error is sometimes resolved by restarting the Docker container.
79
  If that doesn't work, try switching from Docker to `pnpm` or vice versa. We're actively investigating this issue.
80
 
81
  ### **Blank preview when running the app**
82
+
83
  A blank preview often occurs due to hallucinated bad code or incorrect commands.
84
  To troubleshoot:
85
+
86
  - Check the developer console for errors.
87
  - Remember, previews are core functionality, so the app isn't broken! We're working on making these errors more transparent.
88
 
89
  ### **"Everything works, but the results are bad"**
90
+
91
  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.
92
 
93
  ### **"Received structured exception #0xc0000005: access violation"**
94
+
95
  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)
96
 
97
  ### **"Miniflare or Wrangler errors in Windows"**
98
+
99
  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.
100
+
101
  </details>
102
 
103
  ---
PROJECT.md CHANGED
@@ -31,7 +31,7 @@ and this way communicate where the focus currently is.
31
 
32
  2. Grouping of features
33
 
34
- By linking features with epics, we can keep them together and document *why* we invest work into a particular thing.
35
 
36
  ## Features (mid-term)
37
 
@@ -41,13 +41,13 @@ function, you name it).
41
  However, we intentionally describe features in a more vague manner. Why? Everybody loves crisp, well-defined
42
  acceptance-criteria, no? Well, every product owner loves it. because he knows what he’ll get once it’s done.
43
 
44
- 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.
45
 
46
- The feature therefore tries to describe *what* should be improved but not in detail *how*.
47
 
48
  ## PRs as materialized features (short-term)
49
 
50
- 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.
51
 
52
  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 🤓
53
 
 
31
 
32
  2. Grouping of features
33
 
34
+ By linking features with epics, we can keep them together and document _why_ we invest work into a particular thing.
35
 
36
  ## Features (mid-term)
37
 
 
41
  However, we intentionally describe features in a more vague manner. Why? Everybody loves crisp, well-defined
42
  acceptance-criteria, no? Well, every product owner loves it. because he knows what he’ll get once it’s done.
43
 
44
+ 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.
45
 
46
+ The feature therefore tries to describe _what_ should be improved but not in detail _how_.
47
 
48
  ## PRs as materialized features (short-term)
49
 
50
+ 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.
51
 
52
  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 🤓
53
 
README.md CHANGED
@@ -4,10 +4,12 @@
4
 
5
  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.
6
 
7
- -----
 
8
  Check the [bolt.diy Docs](https://stackblitz-labs.github.io/bolt.diy/) for more offical installation instructions and more informations.
9
 
10
- -----
 
11
  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!
12
 
13
  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/).
 
4
 
5
  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.
6
 
7
+ ---
8
+
9
  Check the [bolt.diy Docs](https://stackblitz-labs.github.io/bolt.diy/) for more offical installation instructions and more informations.
10
 
11
+ ---
12
+
13
  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!
14
 
15
  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/).
app/components/chat/BaseChat.tsx CHANGED
@@ -572,8 +572,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
572
  <div className="flex flex-col justify-center gap-5">
573
  {!chatStarted && (
574
  <div className="flex justify-center gap-2">
575
- {ImportButtons(importChat)}
576
- <GitCloneButton importChat={importChat} />
 
 
577
  </div>
578
  )}
579
  {!chatStarted &&
 
572
  <div className="flex flex-col justify-center gap-5">
573
  {!chatStarted && (
574
  <div className="flex justify-center gap-2">
575
+ <div className="flex items-center gap-2">
576
+ {ImportButtons(importChat)}
577
+ <GitCloneButton importChat={importChat} className="min-w-[120px]" />
578
+ </div>
579
  </div>
580
  )}
581
  {!chatStarted &&
app/components/chat/GitCloneButton.tsx CHANGED
@@ -6,22 +6,21 @@ import { generateId } from '~/utils/fileUtils';
6
  import { useState } from 'react';
7
  import { toast } from 'react-toastify';
8
  import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
9
- import type { IChatMetadata } from '~/lib/persistence';
 
 
 
10
 
11
  const IGNORE_PATTERNS = [
12
  'node_modules/**',
13
  '.git/**',
14
  '.github/**',
15
  '.vscode/**',
16
- '**/*.jpg',
17
- '**/*.jpeg',
18
- '**/*.png',
19
  'dist/**',
20
  'build/**',
21
  '.next/**',
22
  'coverage/**',
23
  '.cache/**',
24
- '.vscode/**',
25
  '.idea/**',
26
  '**/*.log',
27
  '**/.DS_Store',
@@ -34,51 +33,94 @@ const IGNORE_PATTERNS = [
34
 
35
  const ig = ignore().add(IGNORE_PATTERNS);
36
 
 
 
 
37
  interface GitCloneButtonProps {
38
  className?: string;
39
  importChat?: (description: string, messages: Message[], metadata?: IChatMetadata) => Promise<void>;
40
  }
41
 
42
- export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
43
  const { ready, gitClone } = useGit();
44
  const [loading, setLoading] = useState(false);
 
45
 
46
- const onClick = async (_e: any) => {
47
  if (!ready) {
48
  return;
49
  }
50
 
51
- const repoUrl = prompt('Enter the Git url');
 
 
 
52
 
53
- if (repoUrl) {
54
- setLoading(true);
 
55
 
56
- try {
57
- const { workdir, data } = await gitClone(repoUrl);
 
58
 
59
- if (importChat) {
60
- const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
61
- console.log(filePaths);
62
 
63
- const textDecoder = new TextDecoder('utf-8');
 
 
 
 
 
 
 
64
 
65
- const fileContents = filePaths
66
- .map((filePath) => {
67
- const { data: content, encoding } = data[filePath];
68
- return {
69
- path: filePath,
70
- content:
71
- encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '',
72
- };
73
- })
74
- .filter((f) => f.content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- const commands = await detectProjectCommands(fileContents);
77
- const commandsMessage = createCommandsMessage(commands);
 
 
 
 
 
 
 
 
 
 
78
 
79
- const filesMessage: Message = {
80
- role: 'assistant',
81
- content: `Cloning the repo ${repoUrl} into ${workdir}
82
  <boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">
83
  ${fileContents
84
  .map(
@@ -89,37 +131,50 @@ ${escapeBoltTags(file.content)}
89
  )
90
  .join('\n')}
91
  </boltArtifact>`,
92
- id: generateId(),
93
- createdAt: new Date(),
94
- };
95
-
96
- const messages = [filesMessage];
97
 
98
- if (commandsMessage) {
99
- messages.push(commandsMessage);
100
- }
101
 
102
- await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages, { gitUrl: repoUrl });
 
103
  }
104
- } catch (error) {
105
- console.error('Error during import:', error);
106
- toast.error('Failed to import repository');
107
- } finally {
108
- setLoading(false);
109
  }
 
 
 
 
 
110
  }
111
  };
112
 
113
  return (
114
  <>
115
- <button
116
- onClick={onClick}
117
  title="Clone a Git Repo"
118
- 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"
 
 
 
 
 
 
 
 
 
 
 
119
  >
120
- <span className="i-ph:git-branch" />
121
  Clone a Git Repo
122
- </button>
 
 
 
123
  {loading && <LoadingOverlay message="Please wait while we clone the repository..." />}
124
  </>
125
  );
 
6
  import { useState } from 'react';
7
  import { toast } from 'react-toastify';
8
  import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
9
+ import { RepositorySelectionDialog } from '~/components/settings/connections/components/RepositorySelectionDialog';
10
+ import { cn } from '~/lib/utils';
11
+ import { Button } from '~/components/ui/Button';
12
+ import type { IChatMetadata } from '~/lib/persistence/db';
13
 
14
  const IGNORE_PATTERNS = [
15
  'node_modules/**',
16
  '.git/**',
17
  '.github/**',
18
  '.vscode/**',
 
 
 
19
  'dist/**',
20
  'build/**',
21
  '.next/**',
22
  'coverage/**',
23
  '.cache/**',
 
24
  '.idea/**',
25
  '**/*.log',
26
  '**/.DS_Store',
 
33
 
34
  const ig = ignore().add(IGNORE_PATTERNS);
35
 
36
+ const MAX_FILE_SIZE = 100 * 1024; // 100KB limit per file
37
+ const MAX_TOTAL_SIZE = 500 * 1024; // 500KB total limit
38
+
39
  interface GitCloneButtonProps {
40
  className?: string;
41
  importChat?: (description: string, messages: Message[], metadata?: IChatMetadata) => Promise<void>;
42
  }
43
 
44
+ export default function GitCloneButton({ importChat, className }: GitCloneButtonProps) {
45
  const { ready, gitClone } = useGit();
46
  const [loading, setLoading] = useState(false);
47
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
48
 
49
+ const handleClone = async (repoUrl: string) => {
50
  if (!ready) {
51
  return;
52
  }
53
 
54
+ setLoading(true);
55
+
56
+ try {
57
+ const { workdir, data } = await gitClone(repoUrl);
58
 
59
+ if (importChat) {
60
+ const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
61
+ const textDecoder = new TextDecoder('utf-8');
62
 
63
+ let totalSize = 0;
64
+ const skippedFiles: string[] = [];
65
+ const fileContents = [];
66
 
67
+ for (const filePath of filePaths) {
68
+ const { data: content, encoding } = data[filePath];
 
69
 
70
+ // Skip binary files
71
+ if (
72
+ content instanceof Uint8Array &&
73
+ !filePath.match(/\.(txt|md|js|jsx|ts|tsx|json|html|css|scss|less|yml|yaml|xml|svg)$/i)
74
+ ) {
75
+ skippedFiles.push(filePath);
76
+ continue;
77
+ }
78
 
79
+ try {
80
+ const textContent =
81
+ encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '';
82
+
83
+ if (!textContent) {
84
+ continue;
85
+ }
86
+
87
+ // Check file size
88
+ const fileSize = new TextEncoder().encode(textContent).length;
89
+
90
+ if (fileSize > MAX_FILE_SIZE) {
91
+ skippedFiles.push(`${filePath} (too large: ${Math.round(fileSize / 1024)}KB)`);
92
+ continue;
93
+ }
94
+
95
+ // Check total size
96
+ if (totalSize + fileSize > MAX_TOTAL_SIZE) {
97
+ skippedFiles.push(`${filePath} (would exceed total size limit)`);
98
+ continue;
99
+ }
100
+
101
+ totalSize += fileSize;
102
+ fileContents.push({
103
+ path: filePath,
104
+ content: textContent,
105
+ });
106
+ } catch (e: any) {
107
+ skippedFiles.push(`${filePath} (error: ${e.message})`);
108
+ }
109
+ }
110
 
111
+ const commands = await detectProjectCommands(fileContents);
112
+ const commandsMessage = createCommandsMessage(commands);
113
+
114
+ const filesMessage: Message = {
115
+ role: 'assistant',
116
+ content: `Cloning the repo ${repoUrl} into ${workdir}
117
+ ${
118
+ skippedFiles.length > 0
119
+ ? `\nSkipped files (${skippedFiles.length}):
120
+ ${skippedFiles.map((f) => `- ${f}`).join('\n')}`
121
+ : ''
122
+ }
123
 
 
 
 
124
  <boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">
125
  ${fileContents
126
  .map(
 
131
  )
132
  .join('\n')}
133
  </boltArtifact>`,
134
+ id: generateId(),
135
+ createdAt: new Date(),
136
+ };
 
 
137
 
138
+ const messages = [filesMessage];
 
 
139
 
140
+ if (commandsMessage) {
141
+ messages.push(commandsMessage);
142
  }
143
+
144
+ await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
 
 
 
145
  }
146
+ } catch (error) {
147
+ console.error('Error during import:', error);
148
+ toast.error('Failed to import repository');
149
+ } finally {
150
+ setLoading(false);
151
  }
152
  };
153
 
154
  return (
155
  <>
156
+ <Button
157
+ onClick={() => setIsDialogOpen(true)}
158
  title="Clone a Git Repo"
159
+ variant="outline"
160
+ size="lg"
161
+ className={cn(
162
+ 'gap-2 bg-[#F5F5F5] dark:bg-[#252525]',
163
+ 'text-bolt-elements-textPrimary dark:text-white',
164
+ 'hover:bg-[#E5E5E5] dark:hover:bg-[#333333]',
165
+ 'border-[#E5E5E5] dark:border-[#333333]',
166
+ 'h-10 px-4 py-2 min-w-[120px] justify-center',
167
+ 'transition-all duration-200 ease-in-out',
168
+ className,
169
+ )}
170
+ disabled={!ready || loading}
171
  >
172
+ <span className="i-ph:git-branch w-4 h-4" />
173
  Clone a Git Repo
174
+ </Button>
175
+
176
+ <RepositorySelectionDialog isOpen={isDialogOpen} onClose={() => setIsDialogOpen(false)} onSelect={handleClone} />
177
+
178
  {loading && <LoadingOverlay message="Please wait while we clone the repository..." />}
179
  </>
180
  );
app/components/chat/ImportFolderButton.tsx CHANGED
@@ -4,6 +4,8 @@ import { toast } from 'react-toastify';
4
  import { MAX_FILES, isBinaryFile, shouldIncludeFile } from '~/utils/fileUtils';
5
  import { createChatFromFolder } from '~/utils/folderImport';
6
  import { logStore } from '~/lib/stores/logs'; // Assuming logStore is imported from this location
 
 
7
 
8
  interface ImportFolderButtonProps {
9
  className?: string;
@@ -112,17 +114,27 @@ export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ classNam
112
  onChange={handleFileChange}
113
  {...({} as any)}
114
  />
115
- <button
116
  onClick={() => {
117
  const input = document.getElementById('folder-import');
118
  input?.click();
119
  }}
120
- className={className}
 
 
 
 
 
 
 
 
 
 
121
  disabled={isLoading}
122
  >
123
- <div className="i-ph:upload-simple" />
124
  {isLoading ? 'Importing...' : 'Import Folder'}
125
- </button>
126
  </>
127
  );
128
  };
 
4
  import { MAX_FILES, isBinaryFile, shouldIncludeFile } from '~/utils/fileUtils';
5
  import { createChatFromFolder } from '~/utils/folderImport';
6
  import { logStore } from '~/lib/stores/logs'; // Assuming logStore is imported from this location
7
+ import { Button } from '~/components/ui/Button';
8
+ import { cn } from '~/lib/utils';
9
 
10
  interface ImportFolderButtonProps {
11
  className?: string;
 
114
  onChange={handleFileChange}
115
  {...({} as any)}
116
  />
117
+ <Button
118
  onClick={() => {
119
  const input = document.getElementById('folder-import');
120
  input?.click();
121
  }}
122
+ variant="outline"
123
+ size="lg"
124
+ className={cn(
125
+ 'gap-2 bg-[#F5F5F5] dark:bg-[#252525]',
126
+ 'text-bolt-elements-textPrimary dark:text-white',
127
+ 'hover:bg-[#E5E5E5] dark:hover:bg-[#333333]',
128
+ 'border-[#E5E5E5] dark:border-[#333333]',
129
+ 'h-10 px-4 py-2 min-w-[120px] justify-center',
130
+ 'transition-all duration-200 ease-in-out',
131
+ className,
132
+ )}
133
  disabled={isLoading}
134
  >
135
+ <span className="i-ph:upload-simple w-4 h-4" />
136
  {isLoading ? 'Importing...' : 'Import Folder'}
137
+ </Button>
138
  </>
139
  );
140
  };
app/components/chat/chatExportAndImport/ImportButtons.tsx CHANGED
@@ -1,6 +1,8 @@
1
  import type { Message } from 'ai';
2
  import { toast } from 'react-toastify';
3
  import { ImportFolderButton } from '~/components/chat/ImportFolderButton';
 
 
4
 
5
  type ChatData = {
6
  messages?: Message[]; // Standard Bolt format
@@ -57,19 +59,35 @@ export function ImportButtons(importChat: ((description: string, messages: Messa
57
  />
58
  <div className="flex flex-col items-center gap-4 max-w-2xl text-center">
59
  <div className="flex gap-2">
60
- <button
61
  onClick={() => {
62
  const input = document.getElementById('chat-import');
63
  input?.click();
64
  }}
65
- 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"
 
 
 
 
 
 
 
 
 
66
  >
67
- <div className="i-ph:upload-simple" />
68
  Import Chat
69
- </button>
70
  <ImportFolderButton
71
  importChat={importChat}
72
- 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"
 
 
 
 
 
 
 
73
  />
74
  </div>
75
  </div>
 
1
  import type { Message } from 'ai';
2
  import { toast } from 'react-toastify';
3
  import { ImportFolderButton } from '~/components/chat/ImportFolderButton';
4
+ import { Button } from '~/components/ui/Button';
5
+ import { cn } from '~/lib/utils';
6
 
7
  type ChatData = {
8
  messages?: Message[]; // Standard Bolt format
 
59
  />
60
  <div className="flex flex-col items-center gap-4 max-w-2xl text-center">
61
  <div className="flex gap-2">
62
+ <Button
63
  onClick={() => {
64
  const input = document.getElementById('chat-import');
65
  input?.click();
66
  }}
67
+ variant="outline"
68
+ size="lg"
69
+ className={cn(
70
+ 'gap-2 bg-[#F5F5F5] dark:bg-[#252525]',
71
+ 'text-bolt-elements-textPrimary dark:text-white',
72
+ 'hover:bg-[#E5E5E5] dark:hover:bg-[#333333]',
73
+ 'border-[#E5E5E5] dark:border-[#333333]',
74
+ 'h-10 px-4 py-2 min-w-[120px] justify-center',
75
+ 'transition-all duration-200 ease-in-out',
76
+ )}
77
  >
78
+ <span className="i-ph:upload-simple w-4 h-4" />
79
  Import Chat
80
+ </Button>
81
  <ImportFolderButton
82
  importChat={importChat}
83
+ className={cn(
84
+ 'gap-2 bg-[#F5F5F5] dark:bg-[#252525]',
85
+ 'text-bolt-elements-textPrimary dark:text-white',
86
+ 'hover:bg-[#E5E5E5] dark:hover:bg-[#333333]',
87
+ 'border border-[#E5E5E5] dark:border-[#333333]',
88
+ 'h-10 px-4 py-2 min-w-[120px] justify-center',
89
+ 'transition-all duration-200 ease-in-out rounded-lg',
90
+ )}
91
  />
92
  </div>
93
  </div>
app/components/settings/connections/components/PushToGitHubDialog.tsx ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as Dialog from '@radix-ui/react-dialog';
2
+ import { useState, useEffect } from 'react';
3
+ import { toast } from 'react-toastify';
4
+ import { motion } from 'framer-motion';
5
+ import { getLocalStorage } from '~/utils/localStorage';
6
+ import { classNames } from '~/utils/classNames';
7
+ import type { GitHubUserResponse } from '~/types/GitHub';
8
+ import { logStore } from '~/lib/stores/logs';
9
+
10
+ interface PushToGitHubDialogProps {
11
+ isOpen: boolean;
12
+ onClose: () => void;
13
+ onPush: (repoName: string, username?: string, token?: string) => Promise<void>;
14
+ }
15
+
16
+ interface GitHubRepo {
17
+ name: string;
18
+ full_name: string;
19
+ html_url: string;
20
+ description: string;
21
+ stargazers_count: number;
22
+ forks_count: number;
23
+ default_branch: string;
24
+ updated_at: string;
25
+ language: string;
26
+ private: boolean;
27
+ }
28
+
29
+ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDialogProps) {
30
+ const [repoName, setRepoName] = useState('');
31
+ const [isPrivate, setIsPrivate] = useState(false);
32
+ const [isLoading, setIsLoading] = useState(false);
33
+ const [user, setUser] = useState<GitHubUserResponse | null>(null);
34
+ const [recentRepos, setRecentRepos] = useState<GitHubRepo[]>([]);
35
+ const [isFetchingRepos, setIsFetchingRepos] = useState(false);
36
+ const [showSuccessDialog, setShowSuccessDialog] = useState(false);
37
+ const [createdRepoUrl, setCreatedRepoUrl] = useState('');
38
+
39
+ // Load GitHub connection on mount
40
+ useEffect(() => {
41
+ if (isOpen) {
42
+ const connection = getLocalStorage('github_connection');
43
+
44
+ if (connection?.user && connection?.token) {
45
+ setUser(connection.user);
46
+
47
+ // Only fetch if we have both user and token
48
+ if (connection.token.trim()) {
49
+ fetchRecentRepos(connection.token);
50
+ }
51
+ }
52
+ }
53
+ }, [isOpen]);
54
+
55
+ const fetchRecentRepos = async (token: string) => {
56
+ if (!token) {
57
+ logStore.logError('No GitHub token available');
58
+ toast.error('GitHub authentication required');
59
+
60
+ return;
61
+ }
62
+
63
+ try {
64
+ setIsFetchingRepos(true);
65
+
66
+ const response = await fetch(
67
+ 'https://api.github.com/user/repos?sort=updated&per_page=5&type=all&affiliation=owner,organization_member',
68
+ {
69
+ headers: {
70
+ Accept: 'application/vnd.github.v3+json',
71
+ Authorization: `Bearer ${token.trim()}`,
72
+ },
73
+ },
74
+ );
75
+
76
+ if (!response.ok) {
77
+ const errorData = await response.json().catch(() => ({}));
78
+
79
+ if (response.status === 401) {
80
+ toast.error('GitHub token expired. Please reconnect your account.');
81
+
82
+ // Clear invalid token
83
+ const connection = getLocalStorage('github_connection');
84
+
85
+ if (connection) {
86
+ localStorage.removeItem('github_connection');
87
+ setUser(null);
88
+ }
89
+ } else {
90
+ logStore.logError('Failed to fetch GitHub repositories', {
91
+ status: response.status,
92
+ statusText: response.statusText,
93
+ error: errorData,
94
+ });
95
+ toast.error(`Failed to fetch repositories: ${response.statusText}`);
96
+ }
97
+
98
+ return;
99
+ }
100
+
101
+ const repos = (await response.json()) as GitHubRepo[];
102
+ setRecentRepos(repos);
103
+ } catch (error) {
104
+ logStore.logError('Failed to fetch GitHub repositories', { error });
105
+ toast.error('Failed to fetch recent repositories');
106
+ } finally {
107
+ setIsFetchingRepos(false);
108
+ }
109
+ };
110
+
111
+ const handleSubmit = async (e: React.FormEvent) => {
112
+ e.preventDefault();
113
+
114
+ const connection = getLocalStorage('github_connection');
115
+
116
+ if (!connection?.token || !connection?.user) {
117
+ toast.error('Please connect your GitHub account in Settings > Connections first');
118
+ return;
119
+ }
120
+
121
+ if (!repoName.trim()) {
122
+ toast.error('Repository name is required');
123
+ return;
124
+ }
125
+
126
+ setIsLoading(true);
127
+
128
+ try {
129
+ await onPush(repoName, connection.user.login, connection.token);
130
+
131
+ const repoUrl = `https://github.com/${connection.user.login}/${repoName}`;
132
+ setCreatedRepoUrl(repoUrl);
133
+ setShowSuccessDialog(true);
134
+ } catch (error) {
135
+ console.error('Error pushing to GitHub:', error);
136
+ toast.error('Failed to push to GitHub. Please check your repository name and try again.');
137
+ } finally {
138
+ setIsLoading(false);
139
+ }
140
+ };
141
+
142
+ const handleClose = () => {
143
+ setRepoName('');
144
+ setIsPrivate(false);
145
+ setShowSuccessDialog(false);
146
+ setCreatedRepoUrl('');
147
+ onClose();
148
+ };
149
+
150
+ // Success Dialog
151
+ if (showSuccessDialog) {
152
+ return (
153
+ <Dialog.Root open={isOpen} onOpenChange={(open) => !open && handleClose()}>
154
+ <Dialog.Portal>
155
+ <Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[9999]" />
156
+ <div className="fixed inset-0 flex items-center justify-center z-[9999]">
157
+ <motion.div
158
+ initial={{ opacity: 0, scale: 0.95 }}
159
+ animate={{ opacity: 1, scale: 1 }}
160
+ exit={{ opacity: 0, scale: 0.95 }}
161
+ transition={{ duration: 0.2 }}
162
+ className="w-[90vw] md:w-[500px]"
163
+ >
164
+ <Dialog.Content className="bg-white dark:bg-[#0A0A0A] rounded-lg p-6 border border-[#E5E5E5] dark:border-[#1A1A1A] shadow-xl">
165
+ <div className="text-center space-y-4">
166
+ <motion.div
167
+ initial={{ scale: 0.8 }}
168
+ animate={{ scale: 1 }}
169
+ transition={{ delay: 0.1 }}
170
+ className="mx-auto w-12 h-12 rounded-xl bg-green-500/10 flex items-center justify-center text-green-500"
171
+ >
172
+ <div className="i-ph:check-circle-bold w-6 h-6" />
173
+ </motion.div>
174
+ <div className="space-y-2">
175
+ <h3 className="text-lg font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark">
176
+ Repository Created Successfully!
177
+ </h3>
178
+ <p className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark">
179
+ Your code has been pushed to GitHub and is ready to use.
180
+ </p>
181
+ </div>
182
+ <div className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg p-3 text-left">
183
+ <p className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark mb-2">
184
+ Repository URL
185
+ </p>
186
+ <div className="flex items-center gap-2">
187
+ <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">
188
+ {createdRepoUrl}
189
+ </code>
190
+ <motion.button
191
+ onClick={() => {
192
+ navigator.clipboard.writeText(createdRepoUrl);
193
+ toast.success('URL copied to clipboard');
194
+ }}
195
+ 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"
196
+ whileHover={{ scale: 1.1 }}
197
+ whileTap={{ scale: 0.9 }}
198
+ >
199
+ <div className="i-ph:copy w-4 h-4" />
200
+ </motion.button>
201
+ </div>
202
+ </div>
203
+ <div className="flex gap-2 pt-2">
204
+ <motion.button
205
+ onClick={handleClose}
206
+ 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"
207
+ whileHover={{ scale: 1.02 }}
208
+ whileTap={{ scale: 0.98 }}
209
+ >
210
+ Close
211
+ </motion.button>
212
+ <motion.a
213
+ href={createdRepoUrl}
214
+ target="_blank"
215
+ rel="noopener noreferrer"
216
+ 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"
217
+ whileHover={{ scale: 1.02 }}
218
+ whileTap={{ scale: 0.98 }}
219
+ >
220
+ <div className="i-ph:github-logo w-4 h-4" />
221
+ Open Repository
222
+ </motion.a>
223
+ </div>
224
+ </div>
225
+ </Dialog.Content>
226
+ </motion.div>
227
+ </div>
228
+ </Dialog.Portal>
229
+ </Dialog.Root>
230
+ );
231
+ }
232
+
233
+ if (!user) {
234
+ return (
235
+ <Dialog.Root open={isOpen} onOpenChange={(open) => !open && handleClose()}>
236
+ <Dialog.Portal>
237
+ <Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[9999]" />
238
+ <div className="fixed inset-0 flex items-center justify-center z-[9999]">
239
+ <motion.div
240
+ initial={{ opacity: 0, scale: 0.95 }}
241
+ animate={{ opacity: 1, scale: 1 }}
242
+ exit={{ opacity: 0, scale: 0.95 }}
243
+ transition={{ duration: 0.2 }}
244
+ className="w-[90vw] md:w-[500px]"
245
+ >
246
+ <Dialog.Content className="bg-white dark:bg-[#0A0A0A] rounded-lg p-6 border border-[#E5E5E5] dark:border-[#1A1A1A] shadow-xl">
247
+ <div className="text-center space-y-4">
248
+ <motion.div
249
+ initial={{ scale: 0.8 }}
250
+ animate={{ scale: 1 }}
251
+ transition={{ delay: 0.1 }}
252
+ className="mx-auto w-12 h-12 rounded-xl bg-bolt-elements-background-depth-3 flex items-center justify-center text-purple-500"
253
+ >
254
+ <div className="i-ph:github-logo w-6 h-6" />
255
+ </motion.div>
256
+ <h3 className="text-lg font-medium text-gray-900 dark:text-white">GitHub Connection Required</h3>
257
+ <p className="text-sm text-gray-600 dark:text-gray-400">
258
+ Please connect your GitHub account in Settings {'>'} Connections to push your code to GitHub.
259
+ </p>
260
+ <motion.button
261
+ className="px-4 py-2 rounded-lg bg-purple-500 text-white text-sm hover:bg-purple-600 inline-flex items-center gap-2"
262
+ whileHover={{ scale: 1.02 }}
263
+ whileTap={{ scale: 0.98 }}
264
+ onClick={handleClose}
265
+ >
266
+ <div className="i-ph:x-circle" />
267
+ Close
268
+ </motion.button>
269
+ </div>
270
+ </Dialog.Content>
271
+ </motion.div>
272
+ </div>
273
+ </Dialog.Portal>
274
+ </Dialog.Root>
275
+ );
276
+ }
277
+
278
+ return (
279
+ <Dialog.Root open={isOpen} onOpenChange={(open) => !open && handleClose()}>
280
+ <Dialog.Portal>
281
+ <Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[9999]" />
282
+ <div className="fixed inset-0 flex items-center justify-center z-[9999]">
283
+ <motion.div
284
+ initial={{ opacity: 0, scale: 0.95 }}
285
+ animate={{ opacity: 1, scale: 1 }}
286
+ exit={{ opacity: 0, scale: 0.95 }}
287
+ transition={{ duration: 0.2 }}
288
+ className="w-[90vw] md:w-[500px]"
289
+ >
290
+ <Dialog.Content className="bg-white dark:bg-[#0A0A0A] rounded-lg border border-[#E5E5E5] dark:border-[#1A1A1A] shadow-xl">
291
+ <div className="p-6">
292
+ <div className="flex items-center gap-4 mb-6">
293
+ <motion.div
294
+ initial={{ scale: 0.8 }}
295
+ animate={{ scale: 1 }}
296
+ transition={{ delay: 0.1 }}
297
+ className="w-10 h-10 rounded-xl bg-bolt-elements-background-depth-3 flex items-center justify-center text-purple-500"
298
+ >
299
+ <div className="i-ph:git-branch w-5 h-5" />
300
+ </motion.div>
301
+ <div>
302
+ <Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">
303
+ Push to GitHub
304
+ </Dialog.Title>
305
+ <p className="text-sm text-gray-600 dark:text-gray-400">
306
+ Push your code to a new or existing GitHub repository
307
+ </p>
308
+ </div>
309
+ <Dialog.Close
310
+ className="ml-auto p-2 text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400"
311
+ onClick={handleClose}
312
+ >
313
+ <div className="i-ph:x w-5 h-5" />
314
+ </Dialog.Close>
315
+ </div>
316
+
317
+ <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">
318
+ <img src={user.avatar_url} alt={user.login} className="w-10 h-10 rounded-full" />
319
+ <div>
320
+ <p className="text-sm font-medium text-gray-900 dark:text-white">{user.name || user.login}</p>
321
+ <p className="text-sm text-gray-500 dark:text-gray-400">@{user.login}</p>
322
+ </div>
323
+ </div>
324
+
325
+ <form onSubmit={handleSubmit} className="space-y-4">
326
+ <div className="space-y-2">
327
+ <label htmlFor="repoName" className="text-sm text-gray-600 dark:text-gray-400">
328
+ Repository Name
329
+ </label>
330
+ <input
331
+ id="repoName"
332
+ type="text"
333
+ value={repoName}
334
+ onChange={(e) => setRepoName(e.target.value)}
335
+ placeholder="my-awesome-project"
336
+ 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"
337
+ required
338
+ />
339
+ </div>
340
+
341
+ {recentRepos.length > 0 && (
342
+ <div className="space-y-2">
343
+ <label className="text-sm text-gray-600 dark:text-gray-400">Recent Repositories</label>
344
+ <div className="space-y-2">
345
+ {recentRepos.map((repo) => (
346
+ <motion.button
347
+ key={repo.full_name}
348
+ type="button"
349
+ onClick={() => setRepoName(repo.name)}
350
+ 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"
351
+ whileHover={{ scale: 1.01 }}
352
+ whileTap={{ scale: 0.99 }}
353
+ >
354
+ <div className="flex items-center justify-between">
355
+ <div className="flex items-center gap-2">
356
+ <div className="i-ph:git-repository w-4 h-4 text-purple-500" />
357
+ <span className="text-sm font-medium text-gray-900 dark:text-white group-hover:text-purple-500">
358
+ {repo.name}
359
+ </span>
360
+ </div>
361
+ {repo.private && (
362
+ <span className="text-xs px-2 py-1 rounded-full bg-purple-500/10 text-purple-500">
363
+ Private
364
+ </span>
365
+ )}
366
+ </div>
367
+ {repo.description && (
368
+ <p className="mt-1 text-xs text-gray-500 dark:text-gray-400 line-clamp-2">
369
+ {repo.description}
370
+ </p>
371
+ )}
372
+ <div className="mt-2 flex items-center gap-3 text-xs text-gray-400 dark:text-gray-500">
373
+ {repo.language && (
374
+ <span className="flex items-center gap-1">
375
+ <div className="i-ph:code w-3 h-3" />
376
+ {repo.language}
377
+ </span>
378
+ )}
379
+ <span className="flex items-center gap-1">
380
+ <div className="i-ph:star w-3 h-3" />
381
+ {repo.stargazers_count.toLocaleString()}
382
+ </span>
383
+ <span className="flex items-center gap-1">
384
+ <div className="i-ph:git-fork w-3 h-3" />
385
+ {repo.forks_count.toLocaleString()}
386
+ </span>
387
+ <span className="flex items-center gap-1">
388
+ <div className="i-ph:clock w-3 h-3" />
389
+ {new Date(repo.updated_at).toLocaleDateString()}
390
+ </span>
391
+ </div>
392
+ </motion.button>
393
+ ))}
394
+ </div>
395
+ </div>
396
+ )}
397
+
398
+ {isFetchingRepos && (
399
+ <div className="flex items-center justify-center py-4 text-gray-500 dark:text-gray-400">
400
+ <div className="i-ph:spinner-gap-bold animate-spin w-4 h-4 mr-2" />
401
+ Loading repositories...
402
+ </div>
403
+ )}
404
+
405
+ <div className="flex items-center gap-2">
406
+ <input
407
+ type="checkbox"
408
+ id="private"
409
+ checked={isPrivate}
410
+ onChange={(e) => setIsPrivate(e.target.checked)}
411
+ className="rounded border-[#E5E5E5] dark:border-[#1A1A1A] text-purple-500 focus:ring-purple-500 dark:bg-[#0A0A0A]"
412
+ />
413
+ <label htmlFor="private" className="text-sm text-gray-600 dark:text-gray-400">
414
+ Make repository private
415
+ </label>
416
+ </div>
417
+
418
+ <div className="pt-4 flex gap-2">
419
+ <motion.button
420
+ type="button"
421
+ onClick={handleClose}
422
+ 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"
423
+ whileHover={{ scale: 1.02 }}
424
+ whileTap={{ scale: 0.98 }}
425
+ >
426
+ Cancel
427
+ </motion.button>
428
+ <motion.button
429
+ type="submit"
430
+ disabled={isLoading}
431
+ className={classNames(
432
+ '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',
433
+ isLoading ? 'opacity-50 cursor-not-allowed' : '',
434
+ )}
435
+ whileHover={!isLoading ? { scale: 1.02 } : {}}
436
+ whileTap={!isLoading ? { scale: 0.98 } : {}}
437
+ >
438
+ {isLoading ? (
439
+ <>
440
+ <div className="i-ph:spinner-gap-bold animate-spin w-4 h-4" />
441
+ Pushing...
442
+ </>
443
+ ) : (
444
+ <>
445
+ <div className="i-ph:git-branch w-4 h-4" />
446
+ Push to GitHub
447
+ </>
448
+ )}
449
+ </motion.button>
450
+ </div>
451
+ </form>
452
+ </div>
453
+ </Dialog.Content>
454
+ </motion.div>
455
+ </div>
456
+ </Dialog.Portal>
457
+ </Dialog.Root>
458
+ );
459
+ }
app/components/settings/connections/components/RepositorySelectionDialog.tsx ADDED
@@ -0,0 +1,692 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { GitHubRepoInfo, GitHubContent, RepositoryStats } from '~/types/GitHub';
2
+ import { useState, useEffect } from 'react';
3
+ import { toast } from 'react-toastify';
4
+ import * as Dialog from '@radix-ui/react-dialog';
5
+ import { cn } from '~/lib/utils';
6
+ import { getLocalStorage } from '~/utils/localStorage';
7
+ import { classNames as utilsClassNames } from '~/utils/classNames';
8
+ import { motion } from 'framer-motion';
9
+ import { formatSize } from '~/utils/formatSize';
10
+
11
+ interface GitHubTreeResponse {
12
+ tree: Array<{
13
+ path: string;
14
+ type: string;
15
+ size?: number;
16
+ }>;
17
+ }
18
+
19
+ interface RepositorySelectionDialogProps {
20
+ isOpen: boolean;
21
+ onClose: () => void;
22
+ onSelect: (url: string) => void;
23
+ }
24
+
25
+ interface SearchFilters {
26
+ language?: string;
27
+ stars?: number;
28
+ forks?: number;
29
+ }
30
+
31
+ interface StatsDialogProps {
32
+ isOpen: boolean;
33
+ onClose: () => void;
34
+ onConfirm: () => void;
35
+ stats: RepositoryStats;
36
+ isLargeRepo?: boolean;
37
+ }
38
+
39
+ function StatsDialog({ isOpen, onClose, onConfirm, stats, isLargeRepo }: StatsDialogProps) {
40
+ return (
41
+ <Dialog.Root open={isOpen} onOpenChange={(open) => !open && onClose()}>
42
+ <Dialog.Portal>
43
+ <Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[9999]" />
44
+ <div className="fixed inset-0 flex items-center justify-center z-[9999]">
45
+ <motion.div
46
+ initial={{ opacity: 0, scale: 0.95 }}
47
+ animate={{ opacity: 1, scale: 1 }}
48
+ exit={{ opacity: 0, scale: 0.95 }}
49
+ transition={{ duration: 0.2 }}
50
+ className="w-[90vw] md:w-[500px]"
51
+ >
52
+ <Dialog.Content className="bg-white dark:bg-[#1E1E1E] rounded-lg border border-[#E5E5E5] dark:border-[#333333] shadow-xl">
53
+ <div className="p-6 space-y-4">
54
+ <div>
55
+ <h3 className="text-lg font-medium text-[#111111] dark:text-white">Repository Overview</h3>
56
+ <div className="mt-4 space-y-2">
57
+ <p className="text-sm text-[#666666] dark:text-[#999999]">Repository Statistics:</p>
58
+ <div className="space-y-2 text-sm text-[#111111] dark:text-white">
59
+ <div className="flex items-center gap-2">
60
+ <span className="i-ph:files text-purple-500 w-4 h-4" />
61
+ <span>Total Files: {stats.totalFiles}</span>
62
+ </div>
63
+ <div className="flex items-center gap-2">
64
+ <span className="i-ph:database text-purple-500 w-4 h-4" />
65
+ <span>Total Size: {formatSize(stats.totalSize)}</span>
66
+ </div>
67
+ <div className="flex items-center gap-2">
68
+ <span className="i-ph:code text-purple-500 w-4 h-4" />
69
+ <span>
70
+ Languages:{' '}
71
+ {Object.entries(stats.languages)
72
+ .sort(([, a], [, b]) => b - a)
73
+ .slice(0, 3)
74
+ .map(([lang, size]) => `${lang} (${formatSize(size)})`)
75
+ .join(', ')}
76
+ </span>
77
+ </div>
78
+ {stats.hasPackageJson && (
79
+ <div className="flex items-center gap-2">
80
+ <span className="i-ph:package text-purple-500 w-4 h-4" />
81
+ <span>Has package.json</span>
82
+ </div>
83
+ )}
84
+ {stats.hasDependencies && (
85
+ <div className="flex items-center gap-2">
86
+ <span className="i-ph:tree-structure text-purple-500 w-4 h-4" />
87
+ <span>Has dependencies</span>
88
+ </div>
89
+ )}
90
+ </div>
91
+ </div>
92
+ {isLargeRepo && (
93
+ <div className="mt-4 p-3 bg-yellow-50 dark:bg-yellow-500/10 rounded-lg text-sm flex items-start gap-2">
94
+ <span className="i-ph:warning text-yellow-600 dark:text-yellow-500 w-4 h-4 flex-shrink-0 mt-0.5" />
95
+ <div className="text-yellow-800 dark:text-yellow-500">
96
+ This repository is quite large ({formatSize(stats.totalSize)}). Importing it might take a while
97
+ and could impact performance.
98
+ </div>
99
+ </div>
100
+ )}
101
+ </div>
102
+ </div>
103
+ <div className="border-t border-[#E5E5E5] dark:border-[#333333] p-4 flex justify-end gap-3 bg-[#F9F9F9] dark:bg-[#252525] rounded-b-lg">
104
+ <button
105
+ onClick={onClose}
106
+ 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"
107
+ >
108
+ Cancel
109
+ </button>
110
+ <button
111
+ onClick={onConfirm}
112
+ className="px-4 py-2 rounded-lg bg-purple-500 text-white hover:bg-purple-600 transition-colors"
113
+ >
114
+ OK
115
+ </button>
116
+ </div>
117
+ </Dialog.Content>
118
+ </motion.div>
119
+ </div>
120
+ </Dialog.Portal>
121
+ </Dialog.Root>
122
+ );
123
+ }
124
+
125
+ export function RepositorySelectionDialog({ isOpen, onClose, onSelect }: RepositorySelectionDialogProps) {
126
+ const [selectedRepository, setSelectedRepository] = useState<GitHubRepoInfo | null>(null);
127
+ const [isLoading, setIsLoading] = useState(false);
128
+ const [repositories, setRepositories] = useState<GitHubRepoInfo[]>([]);
129
+ const [searchQuery, setSearchQuery] = useState('');
130
+ const [searchResults, setSearchResults] = useState<GitHubRepoInfo[]>([]);
131
+ const [activeTab, setActiveTab] = useState<'my-repos' | 'search' | 'url'>('my-repos');
132
+ const [customUrl, setCustomUrl] = useState('');
133
+ const [branches, setBranches] = useState<{ name: string; default?: boolean }[]>([]);
134
+ const [selectedBranch, setSelectedBranch] = useState('');
135
+ const [filters, setFilters] = useState<SearchFilters>({});
136
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
137
+ const [stats, setStats] = useState<RepositoryStats | null>(null);
138
+ const [showStatsDialog, setShowStatsDialog] = useState(false);
139
+ const [currentStats, setCurrentStats] = useState<RepositoryStats | null>(null);
140
+ const [pendingGitUrl, setPendingGitUrl] = useState<string>('');
141
+
142
+ // Fetch user's repositories when dialog opens
143
+ useEffect(() => {
144
+ if (isOpen && activeTab === 'my-repos') {
145
+ fetchUserRepos();
146
+ }
147
+ }, [isOpen, activeTab]);
148
+
149
+ const fetchUserRepos = async () => {
150
+ const connection = getLocalStorage('github_connection');
151
+
152
+ if (!connection?.token) {
153
+ toast.error('Please connect your GitHub account first');
154
+ return;
155
+ }
156
+
157
+ setIsLoading(true);
158
+
159
+ try {
160
+ const response = await fetch('https://api.github.com/user/repos?sort=updated&per_page=100&type=all', {
161
+ headers: {
162
+ Authorization: `Bearer ${connection.token}`,
163
+ },
164
+ });
165
+
166
+ if (!response.ok) {
167
+ throw new Error('Failed to fetch repositories');
168
+ }
169
+
170
+ const data = await response.json();
171
+
172
+ // Add type assertion and validation
173
+ if (
174
+ Array.isArray(data) &&
175
+ data.every((item) => typeof item === 'object' && item !== null && 'full_name' in item)
176
+ ) {
177
+ setRepositories(data as GitHubRepoInfo[]);
178
+ } else {
179
+ throw new Error('Invalid repository data format');
180
+ }
181
+ } catch (error) {
182
+ console.error('Error fetching repos:', error);
183
+ toast.error('Failed to fetch your repositories');
184
+ } finally {
185
+ setIsLoading(false);
186
+ }
187
+ };
188
+
189
+ const handleSearch = async (query: string) => {
190
+ setIsLoading(true);
191
+ setSearchResults([]);
192
+
193
+ try {
194
+ let searchQuery = query;
195
+
196
+ if (filters.language) {
197
+ searchQuery += ` language:${filters.language}`;
198
+ }
199
+
200
+ if (filters.stars) {
201
+ searchQuery += ` stars:>${filters.stars}`;
202
+ }
203
+
204
+ if (filters.forks) {
205
+ searchQuery += ` forks:>${filters.forks}`;
206
+ }
207
+
208
+ const response = await fetch(
209
+ `https://api.github.com/search/repositories?q=${encodeURIComponent(searchQuery)}&sort=stars&order=desc`,
210
+ {
211
+ headers: {
212
+ Accept: 'application/vnd.github.v3+json',
213
+ },
214
+ },
215
+ );
216
+
217
+ if (!response.ok) {
218
+ throw new Error('Failed to search repositories');
219
+ }
220
+
221
+ const data = await response.json();
222
+
223
+ // Add type assertion and validation
224
+ if (typeof data === 'object' && data !== null && 'items' in data && Array.isArray(data.items)) {
225
+ setSearchResults(data.items as GitHubRepoInfo[]);
226
+ } else {
227
+ throw new Error('Invalid search results format');
228
+ }
229
+ } catch (error) {
230
+ console.error('Error searching repos:', error);
231
+ toast.error('Failed to search repositories');
232
+ } finally {
233
+ setIsLoading(false);
234
+ }
235
+ };
236
+
237
+ const fetchBranches = async (repo: GitHubRepoInfo) => {
238
+ setIsLoading(true);
239
+
240
+ try {
241
+ const response = await fetch(`https://api.github.com/repos/${repo.full_name}/branches`, {
242
+ headers: {
243
+ Authorization: `Bearer ${getLocalStorage('github_connection')?.token}`,
244
+ },
245
+ });
246
+
247
+ if (!response.ok) {
248
+ throw new Error('Failed to fetch branches');
249
+ }
250
+
251
+ const data = await response.json();
252
+
253
+ // Add type assertion and validation
254
+ if (Array.isArray(data) && data.every((item) => typeof item === 'object' && item !== null && 'name' in item)) {
255
+ setBranches(
256
+ data.map((branch) => ({
257
+ name: branch.name,
258
+ default: branch.name === repo.default_branch,
259
+ })),
260
+ );
261
+ } else {
262
+ throw new Error('Invalid branch data format');
263
+ }
264
+ } catch (error) {
265
+ console.error('Error fetching branches:', error);
266
+ toast.error('Failed to fetch branches');
267
+ } finally {
268
+ setIsLoading(false);
269
+ }
270
+ };
271
+
272
+ const handleRepoSelect = async (repo: GitHubRepoInfo) => {
273
+ setSelectedRepository(repo);
274
+ await fetchBranches(repo);
275
+ };
276
+
277
+ const formatGitUrl = (url: string): string => {
278
+ // Remove any tree references and ensure .git extension
279
+ const baseUrl = url
280
+ .replace(/\/tree\/[^/]+/, '') // Remove /tree/branch-name
281
+ .replace(/\/$/, '') // Remove trailing slash
282
+ .replace(/\.git$/, ''); // Remove .git if present
283
+ return `${baseUrl}.git`;
284
+ };
285
+
286
+ const verifyRepository = async (repoUrl: string): Promise<RepositoryStats | null> => {
287
+ try {
288
+ const [owner, repo] = repoUrl
289
+ .replace(/\.git$/, '')
290
+ .split('/')
291
+ .slice(-2);
292
+
293
+ const connection = getLocalStorage('github_connection');
294
+ const headers: HeadersInit = connection?.token ? { Authorization: `Bearer ${connection.token}` } : {};
295
+
296
+ // Fetch repository tree
297
+ const treeResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/main?recursive=1`, {
298
+ headers,
299
+ });
300
+
301
+ if (!treeResponse.ok) {
302
+ throw new Error('Failed to fetch repository structure');
303
+ }
304
+
305
+ const treeData = (await treeResponse.json()) as GitHubTreeResponse;
306
+
307
+ // Calculate repository stats
308
+ let totalSize = 0;
309
+ let totalFiles = 0;
310
+ const languages: { [key: string]: number } = {};
311
+ let hasPackageJson = false;
312
+ let hasDependencies = false;
313
+
314
+ for (const file of treeData.tree) {
315
+ if (file.type === 'blob') {
316
+ totalFiles++;
317
+
318
+ if (file.size) {
319
+ totalSize += file.size;
320
+ }
321
+
322
+ // Check for package.json
323
+ if (file.path === 'package.json') {
324
+ hasPackageJson = true;
325
+
326
+ // Fetch package.json content to check dependencies
327
+ const contentResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/package.json`, {
328
+ headers,
329
+ });
330
+
331
+ if (contentResponse.ok) {
332
+ const content = (await contentResponse.json()) as GitHubContent;
333
+ const packageJson = JSON.parse(Buffer.from(content.content, 'base64').toString());
334
+ hasDependencies = !!(
335
+ packageJson.dependencies ||
336
+ packageJson.devDependencies ||
337
+ packageJson.peerDependencies
338
+ );
339
+ }
340
+ }
341
+
342
+ // Detect language based on file extension
343
+ const ext = file.path.split('.').pop()?.toLowerCase();
344
+
345
+ if (ext) {
346
+ languages[ext] = (languages[ext] || 0) + (file.size || 0);
347
+ }
348
+ }
349
+ }
350
+
351
+ const stats: RepositoryStats = {
352
+ totalFiles,
353
+ totalSize,
354
+ languages,
355
+ hasPackageJson,
356
+ hasDependencies,
357
+ };
358
+
359
+ setStats(stats);
360
+
361
+ return stats;
362
+ } catch (error) {
363
+ console.error('Error verifying repository:', error);
364
+ toast.error('Failed to verify repository');
365
+
366
+ return null;
367
+ }
368
+ };
369
+
370
+ const handleImport = async () => {
371
+ try {
372
+ let gitUrl: string;
373
+
374
+ if (activeTab === 'url' && customUrl) {
375
+ gitUrl = formatGitUrl(customUrl);
376
+ } else if (selectedRepository) {
377
+ gitUrl = formatGitUrl(selectedRepository.html_url);
378
+
379
+ if (selectedBranch) {
380
+ gitUrl = `${gitUrl}#${selectedBranch}`;
381
+ }
382
+ } else {
383
+ return;
384
+ }
385
+
386
+ // Verify repository before importing
387
+ const stats = await verifyRepository(gitUrl);
388
+
389
+ if (!stats) {
390
+ return;
391
+ }
392
+
393
+ setCurrentStats(stats);
394
+ setPendingGitUrl(gitUrl);
395
+ setShowStatsDialog(true);
396
+ } catch (error) {
397
+ console.error('Error preparing repository:', error);
398
+ toast.error('Failed to prepare repository. Please try again.');
399
+ }
400
+ };
401
+
402
+ const handleStatsConfirm = () => {
403
+ setShowStatsDialog(false);
404
+
405
+ if (pendingGitUrl) {
406
+ onSelect(pendingGitUrl);
407
+ onClose();
408
+ }
409
+ };
410
+
411
+ const handleFilterChange = (key: keyof SearchFilters, value: string) => {
412
+ let parsedValue: string | number | undefined = value;
413
+
414
+ if (key === 'stars' || key === 'forks') {
415
+ parsedValue = value ? parseInt(value, 10) : undefined;
416
+ }
417
+
418
+ setFilters((prev) => ({ ...prev, [key]: parsedValue }));
419
+ handleSearch(searchQuery);
420
+ };
421
+
422
+ // Handle dialog close properly
423
+ const handleClose = () => {
424
+ setIsLoading(false); // Reset loading state
425
+ setSearchQuery(''); // Reset search
426
+ setSearchResults([]); // Reset results
427
+ onClose();
428
+ };
429
+
430
+ return (
431
+ <Dialog.Root
432
+ open={isOpen}
433
+ onOpenChange={(open) => {
434
+ if (!open) {
435
+ handleClose();
436
+ }
437
+ }}
438
+ >
439
+ <Dialog.Portal>
440
+ <Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50" />
441
+ <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]">
442
+ <div className="p-4 border-b border-[#E5E5E5] dark:border-[#333333] flex items-center justify-between">
443
+ <Dialog.Title className="text-lg font-semibold text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark">
444
+ Import GitHub Repository
445
+ </Dialog.Title>
446
+ <Dialog.Close
447
+ onClick={handleClose}
448
+ className={cn(
449
+ 'p-2 rounded-lg transition-all duration-200 ease-in-out',
450
+ 'text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary',
451
+ 'dark:text-bolt-elements-textTertiary-dark dark:hover:text-bolt-elements-textPrimary-dark',
452
+ 'hover:bg-bolt-elements-background-depth-2 dark:hover:bg-bolt-elements-background-depth-3',
453
+ 'focus:outline-none focus:ring-2 focus:ring-bolt-elements-borderColor dark:focus:ring-bolt-elements-borderColor-dark',
454
+ )}
455
+ >
456
+ <span className="i-ph:x block w-5 h-5" aria-hidden="true" />
457
+ <span className="sr-only">Close dialog</span>
458
+ </Dialog.Close>
459
+ </div>
460
+
461
+ <div className="p-4">
462
+ <div className="flex gap-2 mb-4">
463
+ <TabButton active={activeTab === 'my-repos'} onClick={() => setActiveTab('my-repos')}>
464
+ <span className="i-ph:book-bookmark" />
465
+ My Repos
466
+ </TabButton>
467
+ <TabButton active={activeTab === 'search'} onClick={() => setActiveTab('search')}>
468
+ <span className="i-ph:magnifying-glass" />
469
+ Search
470
+ </TabButton>
471
+ <TabButton active={activeTab === 'url'} onClick={() => setActiveTab('url')}>
472
+ <span className="i-ph:link" />
473
+ URL
474
+ </TabButton>
475
+ </div>
476
+
477
+ {activeTab === 'url' ? (
478
+ <div className="space-y-4">
479
+ <input
480
+ type="text"
481
+ placeholder="Enter GitHub repository URL..."
482
+ value={customUrl}
483
+ onChange={(e) => setCustomUrl(e.target.value)}
484
+ className="w-full px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333] text-bolt-elements-textPrimary"
485
+ />
486
+ <button
487
+ onClick={handleImport}
488
+ disabled={!customUrl}
489
+ 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"
490
+ >
491
+ Import Repository
492
+ </button>
493
+ </div>
494
+ ) : (
495
+ <>
496
+ {activeTab === 'search' && (
497
+ <div className="space-y-4 mb-4">
498
+ <div className="flex gap-2">
499
+ <input
500
+ type="text"
501
+ placeholder="Search repositories..."
502
+ value={searchQuery}
503
+ onChange={(e) => {
504
+ setSearchQuery(e.target.value);
505
+ handleSearch(e.target.value);
506
+ }}
507
+ className="flex-1 px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333] text-bolt-elements-textPrimary"
508
+ />
509
+ <button
510
+ onClick={() => setFilters({})}
511
+ className="px-3 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#252525] text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary"
512
+ >
513
+ <span className="i-ph:funnel-simple" />
514
+ </button>
515
+ </div>
516
+ <div className="grid grid-cols-2 gap-2">
517
+ <input
518
+ type="text"
519
+ placeholder="Filter by language..."
520
+ value={filters.language || ''}
521
+ onChange={(e) => {
522
+ setFilters({ ...filters, language: e.target.value });
523
+ handleSearch(searchQuery);
524
+ }}
525
+ className="px-3 py-1.5 text-sm rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333]"
526
+ />
527
+ <input
528
+ type="number"
529
+ placeholder="Min stars..."
530
+ value={filters.stars || ''}
531
+ onChange={(e) => handleFilterChange('stars', e.target.value)}
532
+ className="px-3 py-1.5 text-sm rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333]"
533
+ />
534
+ </div>
535
+ <input
536
+ type="number"
537
+ placeholder="Min forks..."
538
+ value={filters.forks || ''}
539
+ onChange={(e) => handleFilterChange('forks', e.target.value)}
540
+ className="px-3 py-1.5 text-sm rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333]"
541
+ />
542
+ </div>
543
+ )}
544
+
545
+ <div className="space-y-3 max-h-[400px] overflow-y-auto pr-2 custom-scrollbar">
546
+ {selectedRepository ? (
547
+ <div className="space-y-4">
548
+ <div className="flex items-center gap-2">
549
+ <button
550
+ onClick={() => setSelectedRepository(null)}
551
+ className="p-1.5 rounded-lg hover:bg-[#F5F5F5] dark:hover:bg-[#252525]"
552
+ >
553
+ <span className="i-ph:arrow-left w-4 h-4" />
554
+ </button>
555
+ <h3 className="font-medium">{selectedRepository.full_name}</h3>
556
+ </div>
557
+ <div className="space-y-2">
558
+ <label className="text-sm text-bolt-elements-textSecondary">Select Branch</label>
559
+ <select
560
+ value={selectedBranch}
561
+ onChange={(e) => setSelectedBranch(e.target.value)}
562
+ 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"
563
+ >
564
+ {branches.map((branch) => (
565
+ <option
566
+ key={branch.name}
567
+ value={branch.name}
568
+ className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark"
569
+ >
570
+ {branch.name} {branch.default ? '(default)' : ''}
571
+ </option>
572
+ ))}
573
+ </select>
574
+ <button
575
+ onClick={handleImport}
576
+ 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"
577
+ >
578
+ Import Selected Branch
579
+ </button>
580
+ </div>
581
+ </div>
582
+ ) : (
583
+ <RepositoryList
584
+ repos={activeTab === 'my-repos' ? repositories : searchResults}
585
+ isLoading={isLoading}
586
+ onSelect={handleRepoSelect}
587
+ activeTab={activeTab}
588
+ />
589
+ )}
590
+ </div>
591
+ </>
592
+ )}
593
+ </div>
594
+ </Dialog.Content>
595
+ </Dialog.Portal>
596
+ {currentStats && (
597
+ <StatsDialog
598
+ isOpen={showStatsDialog}
599
+ onClose={handleStatsConfirm}
600
+ onConfirm={handleStatsConfirm}
601
+ stats={currentStats}
602
+ isLargeRepo={currentStats.totalSize > 50 * 1024 * 1024}
603
+ />
604
+ )}
605
+ </Dialog.Root>
606
+ );
607
+ }
608
+
609
+ function TabButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {
610
+ return (
611
+ <button
612
+ onClick={onClick}
613
+ className={utilsClassNames(
614
+ 'px-4 py-2 h-10 rounded-lg transition-all duration-200 flex items-center gap-2 min-w-[120px] justify-center',
615
+ active
616
+ ? 'bg-purple-500 text-white hover:bg-purple-600'
617
+ : '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]',
618
+ )}
619
+ >
620
+ {children}
621
+ </button>
622
+ );
623
+ }
624
+
625
+ function RepositoryList({
626
+ repos,
627
+ isLoading,
628
+ onSelect,
629
+ activeTab,
630
+ }: {
631
+ repos: GitHubRepoInfo[];
632
+ isLoading: boolean;
633
+ onSelect: (repo: GitHubRepoInfo) => void;
634
+ activeTab: string;
635
+ }) {
636
+ if (isLoading) {
637
+ return (
638
+ <div className="flex items-center justify-center py-8 text-bolt-elements-textSecondary">
639
+ <span className="i-ph:spinner animate-spin mr-2" />
640
+ Loading repositories...
641
+ </div>
642
+ );
643
+ }
644
+
645
+ if (repos.length === 0) {
646
+ return (
647
+ <div className="flex flex-col items-center justify-center py-8 text-bolt-elements-textSecondary">
648
+ <span className="i-ph:folder-simple-dashed w-12 h-12 mb-2 opacity-50" />
649
+ <p>{activeTab === 'my-repos' ? 'No repositories found' : 'Search for repositories'}</p>
650
+ </div>
651
+ );
652
+ }
653
+
654
+ return repos.map((repo) => <RepositoryCard key={repo.full_name} repo={repo} onSelect={() => onSelect(repo)} />);
655
+ }
656
+
657
+ function RepositoryCard({ repo, onSelect }: { repo: GitHubRepoInfo; onSelect: () => void }) {
658
+ return (
659
+ <div className="p-4 rounded-lg bg-[#F5F5F5] dark:bg-[#252525] border border-[#E5E5E5] dark:border-[#333333] hover:border-purple-500/50 transition-colors">
660
+ <div className="flex items-center justify-between mb-2">
661
+ <div className="flex items-center gap-2">
662
+ <span className="i-ph:git-repository text-bolt-elements-textTertiary" />
663
+ <h3 className="font-medium text-bolt-elements-textPrimary dark:text-white">{repo.name}</h3>
664
+ </div>
665
+ <button
666
+ onClick={onSelect}
667
+ 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"
668
+ >
669
+ <span className="i-ph:download-simple w-4 h-4" />
670
+ Import
671
+ </button>
672
+ </div>
673
+ {repo.description && <p className="text-sm text-bolt-elements-textSecondary mb-3">{repo.description}</p>}
674
+ <div className="flex items-center gap-4 text-sm text-bolt-elements-textTertiary">
675
+ {repo.language && (
676
+ <span className="flex items-center gap-1">
677
+ <span className="i-ph:code" />
678
+ {repo.language}
679
+ </span>
680
+ )}
681
+ <span className="flex items-center gap-1">
682
+ <span className="i-ph:star" />
683
+ {repo.stargazers_count.toLocaleString()}
684
+ </span>
685
+ <span className="flex items-center gap-1">
686
+ <span className="i-ph:clock" />
687
+ {new Date(repo.updated_at).toLocaleDateString()}
688
+ </span>
689
+ </div>
690
+ </div>
691
+ );
692
+ }
app/components/settings/developer/DeveloperWindow.tsx CHANGED
@@ -370,14 +370,25 @@ export const DeveloperWindow = ({ open, onClose }: DeveloperWindowProps) => {
370
  }
371
  };
372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  return (
374
  <DndProvider backend={HTML5Backend}>
375
  <RadixDialog.Root open={open}>
376
  <RadixDialog.Portal>
377
- <div
378
- className="fixed inset-0 flex items-center justify-center z-[60]"
379
- style={{ opacity: developerMode ? 1 : 0, transition: 'opacity 0.2s ease-in-out' }}
380
- >
381
  <RadixDialog.Overlay className="fixed inset-0">
382
  <motion.div
383
  className="absolute inset-0 bg-black/50 backdrop-blur-sm"
@@ -388,7 +399,12 @@ export const DeveloperWindow = ({ open, onClose }: DeveloperWindowProps) => {
388
  />
389
  </RadixDialog.Overlay>
390
 
391
- <RadixDialog.Content aria-describedby={undefined} className="relative z-[61]">
 
 
 
 
 
392
  <motion.div
393
  className={classNames(
394
  'w-[1200px] h-[90vh]',
 
370
  }
371
  };
372
 
373
+ // Trap focus when window is open
374
+ useEffect(() => {
375
+ if (open) {
376
+ // Prevent background scrolling
377
+ document.body.style.overflow = 'hidden';
378
+ } else {
379
+ document.body.style.overflow = 'unset';
380
+ }
381
+
382
+ return () => {
383
+ document.body.style.overflow = 'unset';
384
+ };
385
+ }, [open]);
386
+
387
  return (
388
  <DndProvider backend={HTML5Backend}>
389
  <RadixDialog.Root open={open}>
390
  <RadixDialog.Portal>
391
+ <div className="fixed inset-0 flex items-center justify-center z-[100]">
 
 
 
392
  <RadixDialog.Overlay className="fixed inset-0">
393
  <motion.div
394
  className="absolute inset-0 bg-black/50 backdrop-blur-sm"
 
399
  />
400
  </RadixDialog.Overlay>
401
 
402
+ <RadixDialog.Content
403
+ aria-describedby={undefined}
404
+ onEscapeKeyDown={onClose}
405
+ onPointerDownOutside={onClose}
406
+ className="relative z-[101]"
407
+ >
408
  <motion.div
409
  className={classNames(
410
  'w-[1200px] h-[90vh]',
app/components/settings/user/UsersWindow.tsx CHANGED
@@ -515,13 +515,27 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
515
  </div>
516
  );
517
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
  return (
519
  <>
520
  <DeveloperWindow open={showDeveloperWindow} onClose={handleDeveloperWindowClose} />
521
  <DndProvider backend={HTML5Backend}>
522
- <RadixDialog.Root open={open && !showDeveloperWindow}>
523
  <RadixDialog.Portal>
524
- <div className="fixed inset-0 flex items-center justify-center z-[50]">
525
  <RadixDialog.Overlay asChild>
526
  <motion.div
527
  className="absolute inset-0 bg-black/50 backdrop-blur-sm"
@@ -531,7 +545,12 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
531
  transition={{ duration: 0.2 }}
532
  />
533
  </RadixDialog.Overlay>
534
- <RadixDialog.Content aria-describedby={undefined} asChild>
 
 
 
 
 
535
  <motion.div
536
  className={classNames(
537
  'relative',
 
515
  </div>
516
  );
517
 
518
+ // Trap focus when window is open
519
+ useEffect(() => {
520
+ if (open) {
521
+ // Prevent background scrolling
522
+ document.body.style.overflow = 'hidden';
523
+ } else {
524
+ document.body.style.overflow = 'unset';
525
+ }
526
+
527
+ return () => {
528
+ document.body.style.overflow = 'unset';
529
+ };
530
+ }, [open]);
531
+
532
  return (
533
  <>
534
  <DeveloperWindow open={showDeveloperWindow} onClose={handleDeveloperWindowClose} />
535
  <DndProvider backend={HTML5Backend}>
536
+ <RadixDialog.Root open={open}>
537
  <RadixDialog.Portal>
538
+ <div className="fixed inset-0 flex items-center justify-center z-[100]">
539
  <RadixDialog.Overlay asChild>
540
  <motion.div
541
  className="absolute inset-0 bg-black/50 backdrop-blur-sm"
 
545
  transition={{ duration: 0.2 }}
546
  />
547
  </RadixDialog.Overlay>
548
+ <RadixDialog.Content
549
+ aria-describedby={undefined}
550
+ onEscapeKeyDown={onClose}
551
+ onPointerDownOutside={onClose}
552
+ className="relative z-[101]"
553
+ >
554
  <motion.div
555
  className={classNames(
556
  'relative',
app/components/sidebar/HistoryItem.tsx CHANGED
@@ -24,47 +24,45 @@ export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: History
24
  syncWithGlobalStore: isActiveChat,
25
  });
26
 
27
- const renderDescriptionForm = (
28
- <form onSubmit={handleSubmit} className="flex-1 flex items-center">
29
- <input
30
- type="text"
31
- className="flex-1 bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 mr-2"
32
- autoFocus
33
- value={currentDescription}
34
- onChange={handleChange}
35
- onBlur={handleBlur}
36
- onKeyDown={handleKeyDown}
37
- />
38
- <button
39
- type="submit"
40
- className="i-ph:check scale-110 hover:text-bolt-elements-item-contentAccent"
41
- onMouseDown={handleSubmit}
42
- />
43
- </form>
44
- );
45
-
46
  return (
47
  <div
48
  className={classNames(
49
- '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',
50
- { '[&&]:text-bolt-elements-textPrimary bg-bolt-elements-background-depth-3': isActiveChat },
51
  )}
52
  >
53
  {editing ? (
54
- renderDescriptionForm
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  ) : (
56
  <a href={`/chat/${item.urlId}`} className="flex w-full relative truncate block">
57
- {currentDescription}
 
 
58
  <div
59
  className={classNames(
60
- '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%',
61
- { 'from-bolt-elements-background-depth-3 w-10 ': isActiveChat },
62
  )}
63
  >
64
- <div className="flex items-center p-1 text-bolt-elements-textSecondary opacity-0 group-hover:opacity-100 transition-opacity">
65
  <ChatActionButton
66
- toolTipContent="Export chat"
67
- icon="i-ph:download-simple"
68
  onClick={(event) => {
69
  event.preventDefault();
70
  exportChat(item.id);
@@ -72,14 +70,14 @@ export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: History
72
  />
73
  {onDuplicate && (
74
  <ChatActionButton
75
- toolTipContent="Duplicate chat"
76
- icon="i-ph:copy"
77
  onClick={() => onDuplicate?.(item.id)}
78
  />
79
  )}
80
  <ChatActionButton
81
- toolTipContent="Rename chat"
82
- icon="i-ph:pencil-fill"
83
  onClick={(event) => {
84
  event.preventDefault();
85
  toggleEditMode();
@@ -87,9 +85,9 @@ export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: History
87
  />
88
  <Dialog.Trigger asChild>
89
  <ChatActionButton
90
- toolTipContent="Delete chat"
91
- icon="i-ph:trash"
92
- className="[&&]:hover:text-bolt-elements-button-danger-text"
93
  onClick={(event) => {
94
  event.preventDefault();
95
  onDelete?.(event);
@@ -121,11 +119,11 @@ const ChatActionButton = forwardRef(
121
  ref: ForwardedRef<HTMLButtonElement>,
122
  ) => {
123
  return (
124
- <WithTooltip tooltip={toolTipContent}>
125
  <button
126
  ref={ref}
127
  type="button"
128
- className={`scale-110 mr-2 hover:text-bolt-elements-item-contentAccent ${icon} ${className ? className : ''}`}
129
  onClick={onClick}
130
  />
131
  </WithTooltip>
 
24
  syncWithGlobalStore: isActiveChat,
25
  });
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  return (
28
  <div
29
  className={classNames(
30
+ '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',
31
+ { 'text-gray-900 dark:text-white bg-gray-50/80 dark:bg-gray-800/30': isActiveChat },
32
  )}
33
  >
34
  {editing ? (
35
+ <form onSubmit={handleSubmit} className="flex-1 flex items-center gap-2">
36
+ <input
37
+ type="text"
38
+ 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"
39
+ autoFocus
40
+ value={currentDescription}
41
+ onChange={handleChange}
42
+ onBlur={handleBlur}
43
+ onKeyDown={handleKeyDown}
44
+ />
45
+ <button
46
+ type="submit"
47
+ className="i-ph:check h-4 w-4 text-gray-500 hover:text-purple-500 transition-colors"
48
+ onMouseDown={handleSubmit}
49
+ />
50
+ </form>
51
  ) : (
52
  <a href={`/chat/${item.urlId}`} className="flex w-full relative truncate block">
53
+ <WithTooltip tooltip={currentDescription}>
54
+ <span className="truncate pr-24">{currentDescription}</span>
55
+ </WithTooltip>
56
  <div
57
  className={classNames(
58
+ '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',
59
+ { 'bg-gray-50/80 dark:bg-gray-800/30': isActiveChat },
60
  )}
61
  >
62
+ <div className="flex items-center gap-2.5 text-gray-400 dark:text-gray-500 opacity-0 group-hover:opacity-100 transition-opacity">
63
  <ChatActionButton
64
+ toolTipContent="Export"
65
+ icon="i-ph:download-simple h-4 w-4"
66
  onClick={(event) => {
67
  event.preventDefault();
68
  exportChat(item.id);
 
70
  />
71
  {onDuplicate && (
72
  <ChatActionButton
73
+ toolTipContent="Duplicate"
74
+ icon="i-ph:copy h-4 w-4"
75
  onClick={() => onDuplicate?.(item.id)}
76
  />
77
  )}
78
  <ChatActionButton
79
+ toolTipContent="Rename"
80
+ icon="i-ph:pencil-fill h-4 w-4"
81
  onClick={(event) => {
82
  event.preventDefault();
83
  toggleEditMode();
 
85
  />
86
  <Dialog.Trigger asChild>
87
  <ChatActionButton
88
+ toolTipContent="Delete"
89
+ icon="i-ph:trash h-4 w-4"
90
+ className="hover:text-red-500"
91
  onClick={(event) => {
92
  event.preventDefault();
93
  onDelete?.(event);
 
119
  ref: ForwardedRef<HTMLButtonElement>,
120
  ) => {
121
  return (
122
+ <WithTooltip tooltip={toolTipContent} position="bottom" sideOffset={4}>
123
  <button
124
  ref={ref}
125
  type="button"
126
+ className={`text-gray-400 dark:text-gray-500 hover:text-purple-500 dark:hover:text-purple-400 transition-colors ${icon} ${className ? className : ''}`}
127
  onClick={onClick}
128
  />
129
  </WithTooltip>
app/components/sidebar/Menu.client.tsx CHANGED
@@ -11,12 +11,13 @@ import { logger } from '~/utils/logger';
11
  import { HistoryItem } from './HistoryItem';
12
  import { binDates } from './date-binning';
13
  import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
 
14
 
15
  const menuVariants = {
16
  closed: {
17
  opacity: 0,
18
  visibility: 'hidden',
19
- left: '-150px',
20
  transition: {
21
  duration: 0.2,
22
  ease: cubicEasingFn,
@@ -41,15 +42,18 @@ function CurrentDateTime() {
41
  useEffect(() => {
42
  const timer = setInterval(() => {
43
  setDateTime(new Date());
44
- }, 60000); // Update every minute
45
 
46
  return () => clearInterval(timer);
47
  }, []);
48
 
49
  return (
50
- <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">
51
- <div className="h-4 w-4 i-ph:clock-thin" />
52
- {dateTime.toLocaleDateString()} {dateTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
 
 
 
53
  </div>
54
  );
55
  }
@@ -111,6 +115,10 @@ export const Menu = () => {
111
  const exitThreshold = 40;
112
 
113
  function onMouseMove(event: MouseEvent) {
 
 
 
 
114
  if (event.pageX < enterThreshold) {
115
  setOpen(true);
116
  }
@@ -125,7 +133,7 @@ export const Menu = () => {
125
  return () => {
126
  window.removeEventListener('mousemove', onMouseMove);
127
  };
128
- }, []);
129
 
130
  const handleDeleteClick = (event: React.UIEvent, item: ChatHistoryItem) => {
131
  event.preventDefault();
@@ -137,96 +145,122 @@ export const Menu = () => {
137
  loadEntries(); // Reload the list after duplication
138
  };
139
 
 
 
 
 
 
 
 
 
 
140
  return (
141
- <motion.div
142
- ref={menuRef}
143
- initial="closed"
144
- animate={open ? 'open' : 'closed'}
145
- variants={menuVariants}
146
- 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"
147
- >
148
- <div className="h-[60px]" /> {/* Spacer for top margin */}
149
- <CurrentDateTime />
150
- <div className="flex-1 flex flex-col h-full w-full overflow-hidden">
151
- <div className="p-4 select-none">
152
- <a
153
- href="/"
154
- 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"
155
- >
156
- <span className="inline-block i-bolt:chat scale-110" />
157
- Start new chat
158
- </a>
159
- <div className="relative w-full">
160
- <input
161
- 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"
162
- type="search"
163
- placeholder="Search"
164
- onChange={handleSearchChange}
165
- aria-label="Search chats"
166
- />
167
- </div>
168
- </div>
169
- <div className="text-bolt-elements-textPrimary font-medium pl-6 pr-5 my-2">Your Chats</div>
170
- <div className="flex-1 overflow-auto pl-4 pr-5 pb-5">
171
- {filteredList.length === 0 && (
172
- <div className="pl-2 text-bolt-elements-textTertiary">
173
- {list.length === 0 ? 'No previous conversations' : 'No matches found'}
 
 
 
174
  </div>
175
- )}
176
- <DialogRoot open={dialogContent !== null}>
177
- {binDates(filteredList).map(({ category, items }) => (
178
- <div key={category} className="mt-4 first:mt-0 space-y-1">
179
- <div className="text-bolt-elements-textTertiary sticky top-0 z-1 bg-bolt-elements-background-depth-2 pl-2 pt-2 pb-1">
180
- {category}
181
- </div>
182
- {items.map((item) => (
183
- <HistoryItem
184
- key={item.id}
185
- item={item}
186
- exportChat={exportChat}
187
- onDelete={(event) => handleDeleteClick(event, item)}
188
- onDuplicate={() => handleDuplicate(item.id)}
189
- />
190
- ))}
191
  </div>
192
- ))}
193
- <Dialog onBackdrop={closeDialog} onClose={closeDialog}>
194
- {dialogContent?.type === 'delete' && (
195
- <>
196
- <DialogTitle>Delete Chat?</DialogTitle>
197
- <DialogDescription asChild>
198
- <div>
199
- <p>
200
- You are about to delete <strong>{dialogContent.item.description}</strong>.
201
- </p>
202
- <p className="mt-1">Are you sure you want to delete this chat?</p>
203
- </div>
204
- </DialogDescription>
205
- <div className="px-5 pb-4 bg-bolt-elements-background-depth-2 flex gap-2 justify-end">
206
- <DialogButton type="secondary" onClick={closeDialog}>
207
- Cancel
208
- </DialogButton>
209
- <DialogButton
210
- type="danger"
211
- onClick={(event) => {
212
- deleteItem(event, dialogContent.item);
213
- closeDialog();
214
- }}
215
- >
216
- Delete
217
- </DialogButton>
218
  </div>
219
- </>
220
- )}
221
- </Dialog>
222
- </DialogRoot>
223
- </div>
224
- <div className="flex items-center justify-between border-t border-bolt-elements-borderColor p-4">
225
- <SettingsButton onClick={() => setIsSettingsOpen(true)} />
226
- <ThemeSwitch />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  </div>
228
- </div>
229
- <UsersWindow open={isSettingsOpen} onClose={() => setIsSettingsOpen(false)} />
230
- </motion.div>
 
231
  );
232
  };
 
11
  import { HistoryItem } from './HistoryItem';
12
  import { binDates } from './date-binning';
13
  import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
14
+ import { classNames } from '~/utils/classNames';
15
 
16
  const menuVariants = {
17
  closed: {
18
  opacity: 0,
19
  visibility: 'hidden',
20
+ left: '-340px',
21
  transition: {
22
  duration: 0.2,
23
  ease: cubicEasingFn,
 
42
  useEffect(() => {
43
  const timer = setInterval(() => {
44
  setDateTime(new Date());
45
+ }, 60000);
46
 
47
  return () => clearInterval(timer);
48
  }, []);
49
 
50
  return (
51
+ <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">
52
+ <div className="h-4 w-4 i-lucide:clock opacity-80" />
53
+ <div className="flex gap-2">
54
+ <span>{dateTime.toLocaleDateString()}</span>
55
+ <span>{dateTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
56
+ </div>
57
  </div>
58
  );
59
  }
 
115
  const exitThreshold = 40;
116
 
117
  function onMouseMove(event: MouseEvent) {
118
+ if (isSettingsOpen) {
119
+ return;
120
+ }
121
+
122
  if (event.pageX < enterThreshold) {
123
  setOpen(true);
124
  }
 
133
  return () => {
134
  window.removeEventListener('mousemove', onMouseMove);
135
  };
136
+ }, [isSettingsOpen]);
137
 
138
  const handleDeleteClick = (event: React.UIEvent, item: ChatHistoryItem) => {
139
  event.preventDefault();
 
145
  loadEntries(); // Reload the list after duplication
146
  };
147
 
148
+ const handleSettingsClick = () => {
149
+ setIsSettingsOpen(true);
150
+ setOpen(false);
151
+ };
152
+
153
+ const handleSettingsClose = () => {
154
+ setIsSettingsOpen(false);
155
+ };
156
+
157
  return (
158
+ <>
159
+ <motion.div
160
+ ref={menuRef}
161
+ initial="closed"
162
+ animate={open ? 'open' : 'closed'}
163
+ variants={menuVariants}
164
+ style={{ width: '340px' }}
165
+ className={classNames(
166
+ 'flex selection-accent flex-col side-menu fixed top-0 h-full',
167
+ 'bg-white dark:bg-gray-950 border-r border-gray-100 dark:border-gray-800/50',
168
+ 'shadow-sm text-sm',
169
+ isSettingsOpen ? 'z-40' : 'z-sidebar',
170
+ )}
171
+ >
172
+ <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>
173
+ <CurrentDateTime />
174
+ <div className="flex-1 flex flex-col h-full w-full overflow-hidden">
175
+ <div className="p-4 space-y-3">
176
+ <a
177
+ href="/"
178
+ 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"
179
+ >
180
+ <span className="inline-block i-lucide:message-square h-4 w-4" />
181
+ <span className="text-sm font-medium">Start new chat</span>
182
+ </a>
183
+ <div className="relative w-full">
184
+ <div className="absolute left-3 top-1/2 -translate-y-1/2">
185
+ <span className="i-lucide:search h-4 w-4 text-gray-400 dark:text-gray-500" />
186
+ </div>
187
+ <input
188
+ 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"
189
+ type="search"
190
+ placeholder="Search chats..."
191
+ onChange={handleSearchChange}
192
+ aria-label="Search chats"
193
+ />
194
  </div>
195
+ </div>
196
+ <div className="text-gray-600 dark:text-gray-400 text-sm font-medium px-4 py-2">Your Chats</div>
197
+ <div className="flex-1 overflow-auto px-3 pb-3">
198
+ {filteredList.length === 0 && (
199
+ <div className="px-4 text-gray-500 dark:text-gray-400 text-sm">
200
+ {list.length === 0 ? 'No previous conversations' : 'No matches found'}
 
 
 
 
 
 
 
 
 
 
201
  </div>
202
+ )}
203
+ <DialogRoot open={dialogContent !== null}>
204
+ {binDates(filteredList).map(({ category, items }) => (
205
+ <div key={category} className="mt-2 first:mt-0 space-y-1">
206
+ <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">
207
+ {category}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  </div>
209
+ <div className="space-y-0.5 pr-1">
210
+ {items.map((item) => (
211
+ <HistoryItem
212
+ key={item.id}
213
+ item={item}
214
+ exportChat={exportChat}
215
+ onDelete={(event) => handleDeleteClick(event, item)}
216
+ onDuplicate={() => handleDuplicate(item.id)}
217
+ />
218
+ ))}
219
+ </div>
220
+ </div>
221
+ ))}
222
+ <Dialog onBackdrop={closeDialog} onClose={closeDialog}>
223
+ {dialogContent?.type === 'delete' && (
224
+ <>
225
+ <div className="p-6 bg-white dark:bg-gray-950">
226
+ <DialogTitle className="text-gray-900 dark:text-white">Delete Chat?</DialogTitle>
227
+ <DialogDescription className="mt-2 text-gray-600 dark:text-gray-400">
228
+ <p>
229
+ You are about to delete{' '}
230
+ <span className="font-medium text-gray-900 dark:text-white">
231
+ {dialogContent.item.description}
232
+ </span>
233
+ </p>
234
+ <p className="mt-2">Are you sure you want to delete this chat?</p>
235
+ </DialogDescription>
236
+ </div>
237
+ <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">
238
+ <DialogButton type="secondary" onClick={closeDialog}>
239
+ Cancel
240
+ </DialogButton>
241
+ <DialogButton
242
+ type="danger"
243
+ onClick={(event) => {
244
+ deleteItem(event, dialogContent.item);
245
+ closeDialog();
246
+ }}
247
+ >
248
+ Delete
249
+ </DialogButton>
250
+ </div>
251
+ </>
252
+ )}
253
+ </Dialog>
254
+ </DialogRoot>
255
+ </div>
256
+ <div className="flex items-center justify-between border-t border-gray-200 dark:border-gray-800 px-4 py-3">
257
+ <SettingsButton onClick={handleSettingsClick} />
258
+ <ThemeSwitch />
259
+ </div>
260
  </div>
261
+ </motion.div>
262
+
263
+ <UsersWindow open={isSettingsOpen} onClose={handleSettingsClose} />
264
+ </>
265
  );
266
  };
app/components/sidebar/date-binning.ts CHANGED
@@ -39,21 +39,21 @@ function dateCategory(date: Date) {
39
  }
40
 
41
  if (isThisWeek(date)) {
42
- // e.g., "Monday"
43
- return format(date, 'eeee');
44
  }
45
 
46
  const thirtyDaysAgo = subDays(new Date(), 30);
47
 
48
  if (isAfter(date, thirtyDaysAgo)) {
49
- return 'Last 30 Days';
50
  }
51
 
52
  if (isThisYear(date)) {
53
- // e.g., "July"
54
- return format(date, 'MMMM');
55
  }
56
 
57
- // e.g., "July 2023"
58
- return format(date, 'MMMM yyyy');
59
  }
 
39
  }
40
 
41
  if (isThisWeek(date)) {
42
+ // e.g., "Mon" instead of "Monday"
43
+ return format(date, 'EEE');
44
  }
45
 
46
  const thirtyDaysAgo = subDays(new Date(), 30);
47
 
48
  if (isAfter(date, thirtyDaysAgo)) {
49
+ return 'Past 30 Days';
50
  }
51
 
52
  if (isThisYear(date)) {
53
+ // e.g., "Jan" instead of "January"
54
+ return format(date, 'LLL');
55
  }
56
 
57
+ // e.g., "Jan 2023" instead of "January 2023"
58
+ return format(date, 'LLL yyyy');
59
  }
app/components/ui/Button.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '~/lib/utils';
4
+
5
+ const buttonVariants = cva(
6
+ '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',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: 'bg-bolt-elements-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-2',
11
+ destructive: 'bg-red-500 text-white hover:bg-red-600',
12
+ outline:
13
+ 'border border-input bg-transparent hover:bg-bolt-elements-background-depth-2 hover:text-bolt-elements-textPrimary',
14
+ secondary:
15
+ 'bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-2',
16
+ ghost: 'hover:bg-bolt-elements-background-depth-1 hover:text-bolt-elements-textPrimary',
17
+ link: 'text-bolt-elements-textPrimary underline-offset-4 hover:underline',
18
+ },
19
+ size: {
20
+ default: 'h-9 px-4 py-2',
21
+ sm: 'h-8 rounded-md px-3 text-xs',
22
+ lg: 'h-10 rounded-md px-8',
23
+ icon: 'h-9 w-9',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: 'default',
28
+ size: 'default',
29
+ },
30
+ },
31
+ );
32
+
33
+ export interface ButtonProps
34
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
35
+ VariantProps<typeof buttonVariants> {
36
+ _asChild?: boolean;
37
+ }
38
+
39
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
40
+ ({ className, variant, size, _asChild = false, ...props }, ref) => {
41
+ return <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
42
+ },
43
+ );
44
+ Button.displayName = 'Button';
45
+
46
+ export { Button, buttonVariants };
app/components/ui/Dialog.tsx CHANGED
@@ -17,10 +17,11 @@ interface DialogButtonProps {
17
  export const DialogButton = memo(({ type, children, onClick, disabled }: DialogButtonProps) => {
18
  return (
19
  <button
20
- className={classNames('inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm', {
21
- 'bg-purple-500 text-white hover:bg-purple-600': type === 'primary',
22
- 'text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary': type === 'secondary',
23
- 'text-red-500 hover:bg-red-50 dark:hover:bg-red-500/10': type === 'danger',
 
24
  })}
25
  onClick={onClick}
26
  disabled={disabled}
 
17
  export const DialogButton = memo(({ type, children, onClick, disabled }: DialogButtonProps) => {
18
  return (
19
  <button
20
+ className={classNames('inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors', {
21
+ 'bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-500 dark:hover:bg-purple-600': type === 'primary',
22
+ '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':
23
+ type === 'secondary',
24
+ 'bg-transparent text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-500/10': type === 'danger',
25
  })}
26
  onClick={onClick}
27
  disabled={disabled}
app/components/workbench/Workbench.client.tsx CHANGED
@@ -17,8 +17,7 @@ import { renderLogger } from '~/utils/logger';
17
  import { EditorPanel } from './EditorPanel';
18
  import { Preview } from './Preview';
19
  import useViewport from '~/lib/hooks';
20
- import Cookies from 'js-cookie';
21
- import { chatMetadata, useChatHistory } from '~/lib/persistence';
22
 
23
  interface WorkspaceProps {
24
  chatStarted?: boolean;
@@ -59,6 +58,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
59
  renderLogger.trace('Workbench');
60
 
61
  const [isSyncing, setIsSyncing] = useState(false);
 
62
 
63
  const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
64
  const showWorkbench = useStore(workbenchStore.showWorkbench);
@@ -67,8 +67,6 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
67
  const unsavedFiles = useStore(workbenchStore.unsavedFiles);
68
  const files = useStore(workbenchStore.files);
69
  const selectedView = useStore(workbenchStore.currentView);
70
- const metadata = useStore(chatMetadata);
71
- const { updateChatMestaData } = useChatHistory();
72
 
73
  const isSmallViewport = useViewport(1024);
74
 
@@ -171,65 +169,8 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
171
  <div className="i-ph:terminal" />
172
  Toggle Terminal
173
  </PanelHeaderButton>
174
- <PanelHeaderButton
175
- className="mr-1 text-sm"
176
- onClick={() => {
177
- let repoName = metadata?.gitUrl?.split('/').slice(-1)[0]?.replace('.git', '') || null;
178
- let repoConfirmed: boolean = true;
179
-
180
- if (repoName) {
181
- repoConfirmed = confirm(`Do you want to push to the repository ${repoName}?`);
182
- }
183
-
184
- if (!repoName || !repoConfirmed) {
185
- repoName = prompt(
186
- 'Please enter a name for your new GitHub repository:',
187
- 'bolt-generated-project',
188
- );
189
- } else {
190
- }
191
-
192
- if (!repoName) {
193
- alert('Repository name is required. Push to GitHub cancelled.');
194
- return;
195
- }
196
-
197
- let githubUsername = Cookies.get('githubUsername');
198
- let githubToken = Cookies.get('githubToken');
199
-
200
- if (!githubUsername || !githubToken) {
201
- const usernameInput = prompt('Please enter your GitHub username:');
202
- const tokenInput = prompt('Please enter your GitHub personal access token:');
203
-
204
- if (!usernameInput || !tokenInput) {
205
- alert('GitHub username and token are required. Push to GitHub cancelled.');
206
- return;
207
- }
208
-
209
- githubUsername = usernameInput;
210
- githubToken = tokenInput;
211
-
212
- Cookies.set('githubUsername', usernameInput);
213
- Cookies.set('githubToken', tokenInput);
214
- Cookies.set(
215
- 'git:github.com',
216
- JSON.stringify({ username: tokenInput, password: 'x-oauth-basic' }),
217
- );
218
- }
219
-
220
- const commitMessage =
221
- prompt('Please enter a commit message:', 'Initial commit') || 'Initial commit';
222
- workbenchStore.pushToGitHub(repoName, commitMessage, githubUsername, githubToken);
223
-
224
- if (!metadata?.gitUrl) {
225
- updateChatMestaData({
226
- ...(metadata || {}),
227
- gitUrl: `https://github.com/${githubUsername}/${repoName}.git`,
228
- });
229
- }
230
- }}
231
- >
232
- <div className="i-ph:github-logo" />
233
  Push to GitHub
234
  </PanelHeaderButton>
235
  </div>
@@ -271,10 +212,26 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
271
  </div>
272
  </div>
273
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  </motion.div>
275
  )
276
  );
277
  });
 
278
  interface ViewProps extends HTMLMotionProps<'div'> {
279
  children: JSX.Element;
280
  }
 
17
  import { EditorPanel } from './EditorPanel';
18
  import { Preview } from './Preview';
19
  import useViewport from '~/lib/hooks';
20
+ import { PushToGitHubDialog } from '~/components/settings/connections/components/PushToGitHubDialog';
 
21
 
22
  interface WorkspaceProps {
23
  chatStarted?: boolean;
 
58
  renderLogger.trace('Workbench');
59
 
60
  const [isSyncing, setIsSyncing] = useState(false);
61
+ const [isPushDialogOpen, setIsPushDialogOpen] = useState(false);
62
 
63
  const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
64
  const showWorkbench = useStore(workbenchStore.showWorkbench);
 
67
  const unsavedFiles = useStore(workbenchStore.unsavedFiles);
68
  const files = useStore(workbenchStore.files);
69
  const selectedView = useStore(workbenchStore.currentView);
 
 
70
 
71
  const isSmallViewport = useViewport(1024);
72
 
 
169
  <div className="i-ph:terminal" />
170
  Toggle Terminal
171
  </PanelHeaderButton>
172
+ <PanelHeaderButton className="mr-1 text-sm" onClick={() => setIsPushDialogOpen(true)}>
173
+ <div className="i-ph:git-branch" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  Push to GitHub
175
  </PanelHeaderButton>
176
  </div>
 
212
  </div>
213
  </div>
214
  </div>
215
+ <PushToGitHubDialog
216
+ isOpen={isPushDialogOpen}
217
+ onClose={() => setIsPushDialogOpen(false)}
218
+ onPush={async (repoName, username, token) => {
219
+ try {
220
+ await workbenchStore.pushToGitHub(repoName, undefined, username, token);
221
+
222
+ // Success dialog will be shown by PushToGitHubDialog component
223
+ } catch (error) {
224
+ console.error('Error pushing to GitHub:', error);
225
+ toast.error('Failed to push to GitHub');
226
+ throw error; // Rethrow to let PushToGitHubDialog handle the error state
227
+ }
228
+ }}
229
+ />
230
  </motion.div>
231
  )
232
  );
233
  });
234
+
235
  interface ViewProps extends HTMLMotionProps<'div'> {
236
  children: JSX.Element;
237
  }
app/lib/modules/llm/providers/groq.ts CHANGED
@@ -19,7 +19,12 @@ export default class GroqProvider extends BaseProvider {
19
  { name: 'llama-3.2-3b-preview', label: 'Llama 3.2 3b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
20
  { name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
21
  { name: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
22
- { name: 'deepseek-r1-distill-llama-70b', label: 'Deepseek R1 Distill Llama 70b (Groq)', provider: 'Groq', maxTokenAllowed: 131072 },
 
 
 
 
 
23
  ];
24
 
25
  getModelInstance(options: {
 
19
  { name: 'llama-3.2-3b-preview', label: 'Llama 3.2 3b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
20
  { name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
21
  { name: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
22
+ {
23
+ name: 'deepseek-r1-distill-llama-70b',
24
+ label: 'Deepseek R1 Distill Llama 70b (Groq)',
25
+ provider: 'Groq',
26
+ maxTokenAllowed: 131072,
27
+ },
28
  ];
29
 
30
  getModelInstance(options: {
app/lib/stores/workbench.ts CHANGED
@@ -536,7 +536,7 @@ export class WorkbenchStore {
536
  sha: newCommit.sha,
537
  });
538
 
539
- alert(`Repository created and code pushed: ${repo.html_url}`);
540
  } catch (error) {
541
  console.error('Error pushing to GitHub:', error);
542
  throw error; // Rethrow the error for further handling
 
536
  sha: newCommit.sha,
537
  });
538
 
539
+ return repo.html_url; // Return the URL instead of showing alert
540
  } catch (error) {
541
  console.error('Error pushing to GitHub:', error);
542
  throw error; // Rethrow the error for further handling
app/types/GitHub.ts ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface GitHubUserResponse {
2
+ login: string;
3
+ avatar_url: string;
4
+ html_url: string;
5
+ name: string;
6
+ bio: string;
7
+ public_repos: number;
8
+ followers: number;
9
+ following: number;
10
+ public_gists: number;
11
+ created_at: string;
12
+ updated_at: string;
13
+ }
14
+
15
+ export interface GitHubRepoInfo {
16
+ name: string;
17
+ full_name: string;
18
+ html_url: string;
19
+ description: string;
20
+ stargazers_count: number;
21
+ forks_count: number;
22
+ default_branch: string;
23
+ updated_at: string;
24
+ language: string;
25
+ languages_url: string;
26
+ }
27
+
28
+ export interface GitHubContent {
29
+ name: string;
30
+ path: string;
31
+ sha: string;
32
+ size: number;
33
+ url: string;
34
+ html_url: string;
35
+ git_url: string;
36
+ download_url: string;
37
+ type: string;
38
+ content: string;
39
+ encoding: string;
40
+ }
41
+
42
+ export interface GitHubBranch {
43
+ name: string;
44
+ commit: {
45
+ sha: string;
46
+ url: string;
47
+ };
48
+ }
49
+
50
+ export interface GitHubBlobResponse {
51
+ content: string;
52
+ encoding: string;
53
+ sha: string;
54
+ size: number;
55
+ url: string;
56
+ }
57
+
58
+ export interface GitHubOrganization {
59
+ login: string;
60
+ avatar_url: string;
61
+ description: string;
62
+ html_url: string;
63
+ }
64
+
65
+ export interface GitHubEvent {
66
+ id: string;
67
+ type: string;
68
+ created_at: string;
69
+ repo: {
70
+ name: string;
71
+ url: string;
72
+ };
73
+ payload: {
74
+ action?: string;
75
+ ref?: string;
76
+ ref_type?: string;
77
+ description?: string;
78
+ };
79
+ }
80
+
81
+ export interface GitHubLanguageStats {
82
+ [key: string]: number;
83
+ }
84
+
85
+ export interface GitHubStats {
86
+ repos: GitHubRepoInfo[];
87
+ totalStars: number;
88
+ totalForks: number;
89
+ organizations: GitHubOrganization[];
90
+ recentActivity: GitHubEvent[];
91
+ languages: GitHubLanguageStats;
92
+ totalGists: number;
93
+ }
94
+
95
+ export interface GitHubConnection {
96
+ user: GitHubUserResponse | null;
97
+ token: string;
98
+ tokenType: 'classic' | 'fine-grained';
99
+ stats?: GitHubStats;
100
+ }
101
+
102
+ export interface GitHubTokenInfo {
103
+ token: string;
104
+ scope: string[];
105
+ avatar_url: string;
106
+ name: string | null;
107
+ created_at: string;
108
+ followers: number;
109
+ }
110
+
111
+ export interface GitHubRateLimits {
112
+ limit: number;
113
+ remaining: number;
114
+ reset: Date;
115
+ used: number;
116
+ }
117
+
118
+ export interface GitHubAuthState {
119
+ username: string;
120
+ tokenInfo: GitHubTokenInfo | null;
121
+ isConnected: boolean;
122
+ isVerifying: boolean;
123
+ isLoadingRepos: boolean;
124
+ rateLimits?: GitHubRateLimits;
125
+ }
126
+
127
+ export interface RepositoryStats {
128
+ totalFiles: number;
129
+ totalSize: number;
130
+ languages: Record<string, number>;
131
+ hasPackageJson: boolean;
132
+ hasDependencies: boolean;
133
+ }
app/utils/formatSize.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function formatSize(bytes: number): string {
2
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
3
+ let size = bytes;
4
+ let unitIndex = 0;
5
+
6
+ while (size >= 1024 && unitIndex < units.length - 1) {
7
+ size /= 1024;
8
+ unitIndex++;
9
+ }
10
+
11
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
12
+ }
changelog.md CHANGED
@@ -6,19 +6,16 @@ Release v0.0.6
6
 
7
  ### ✨ Features
8
 
9
- * 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
10
- * enhance chat import with multi-format support ([#936](https://github.com/stackblitz-labs/bolt.diy/pull/936)) by @sidbetatester
11
- * added Github provider ([#1109](https://github.com/stackblitz-labs/bolt.diy/pull/1109)) by @newnol
12
- * added the "Open Preview in a New Tab" ([#1101](https://github.com/stackblitz-labs/bolt.diy/pull/1101)) by @Stijnus
13
- * configure dynamic providers via .env ([#1108](https://github.com/stackblitz-labs/bolt.diy/pull/1108)) by @mrsimpson
14
- * added deepseek reasoner model in deepseek provider ([#1151](https://github.com/stackblitz-labs/bolt.diy/pull/1151)) by @thecodacus
15
- * enhance context handling by adding code context selection and implementing summary generation ([#1091](https://github.com/stackblitz-labs/bolt.diy/pull/1091)) by @thecodacus
16
-
17
 
18
  ### 🐛 Bug Fixes
19
 
20
-
21
-
22
  ## 📈 Stats
23
 
24
  **Full Changelog**: [`v0.0.5..v0.0.6`](https://github.com/stackblitz-labs/bolt.diy/compare/v0.0.5...v0.0.6)
 
6
 
7
  ### ✨ Features
8
 
9
+ - 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
10
+ - enhance chat import with multi-format support ([#936](https://github.com/stackblitz-labs/bolt.diy/pull/936)) by @sidbetatester
11
+ - added Github provider ([#1109](https://github.com/stackblitz-labs/bolt.diy/pull/1109)) by @newnol
12
+ - added the "Open Preview in a New Tab" ([#1101](https://github.com/stackblitz-labs/bolt.diy/pull/1101)) by @Stijnus
13
+ - configure dynamic providers via .env ([#1108](https://github.com/stackblitz-labs/bolt.diy/pull/1108)) by @mrsimpson
14
+ - added deepseek reasoner model in deepseek provider ([#1151](https://github.com/stackblitz-labs/bolt.diy/pull/1151)) by @thecodacus
15
+ - enhance context handling by adding code context selection and implementing summary generation ([#1091](https://github.com/stackblitz-labs/bolt.diy/pull/1091)) by @thecodacus
 
16
 
17
  ### 🐛 Bug Fixes
18
 
 
 
19
  ## 📈 Stats
20
 
21
  **Full Changelog**: [`v0.0.5..v0.0.6`](https://github.com/stackblitz-labs/bolt.diy/compare/v0.0.5...v0.0.6)
docker-compose.yaml CHANGED
@@ -6,8 +6,8 @@ services:
6
  dockerfile: Dockerfile
7
  target: bolt-ai-production
8
  ports:
9
- - "5173:5173"
10
- env_file: ".env.local"
11
  environment:
12
  - NODE_ENV=production
13
  - COMPOSE_PROFILES=production
@@ -28,7 +28,7 @@ services:
28
  - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
29
  - RUNNING_IN_DOCKER=true
30
  extra_hosts:
31
- - "host.docker.internal:host-gateway"
32
  command: pnpm run dockerstart
33
  profiles:
34
  - production
@@ -37,7 +37,7 @@ services:
37
  image: bolt-ai:development
38
  build:
39
  target: bolt-ai-development
40
- env_file: ".env.local"
41
  environment:
42
  - NODE_ENV=development
43
  - VITE_HMR_PROTOCOL=ws
@@ -61,7 +61,7 @@ services:
61
  - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
62
  - RUNNING_IN_DOCKER=true
63
  extra_hosts:
64
- - "host.docker.internal:host-gateway"
65
  volumes:
66
  - type: bind
67
  source: .
@@ -69,14 +69,14 @@ services:
69
  consistency: cached
70
  - /app/node_modules
71
  ports:
72
- - "5173:5173"
73
  command: pnpm run dev --host 0.0.0.0
74
- profiles: ["development", "default"]
75
 
76
  app-prebuild:
77
- image: ghcr.io/stackblitz-labs/bolt.diy:latest
78
  ports:
79
- - "5173:5173"
80
  environment:
81
  - NODE_ENV=production
82
  - COMPOSE_PROFILES=production
@@ -86,7 +86,7 @@ services:
86
  - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
87
  - RUNNING_IN_DOCKER=true
88
  extra_hosts:
89
- - "host.docker.internal:host-gateway"
90
  command: pnpm run dockerstart
91
  profiles:
92
- - prebuilt
 
6
  dockerfile: Dockerfile
7
  target: bolt-ai-production
8
  ports:
9
+ - '5173:5173'
10
+ env_file: '.env.local'
11
  environment:
12
  - NODE_ENV=production
13
  - COMPOSE_PROFILES=production
 
28
  - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
29
  - RUNNING_IN_DOCKER=true
30
  extra_hosts:
31
+ - 'host.docker.internal:host-gateway'
32
  command: pnpm run dockerstart
33
  profiles:
34
  - production
 
37
  image: bolt-ai:development
38
  build:
39
  target: bolt-ai-development
40
+ env_file: '.env.local'
41
  environment:
42
  - NODE_ENV=development
43
  - VITE_HMR_PROTOCOL=ws
 
61
  - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
62
  - RUNNING_IN_DOCKER=true
63
  extra_hosts:
64
+ - 'host.docker.internal:host-gateway'
65
  volumes:
66
  - type: bind
67
  source: .
 
69
  consistency: cached
70
  - /app/node_modules
71
  ports:
72
+ - '5173:5173'
73
  command: pnpm run dev --host 0.0.0.0
74
+ profiles: ['development', 'default']
75
 
76
  app-prebuild:
77
+ image: ghcr.io/stackblitz-labs/bolt.diy:latest
78
  ports:
79
+ - '5173:5173'
80
  environment:
81
  - NODE_ENV=production
82
  - COMPOSE_PROFILES=production
 
86
  - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
87
  - RUNNING_IN_DOCKER=true
88
  extra_hosts:
89
+ - 'host.docker.internal:host-gateway'
90
  command: pnpm run dockerstart
91
  profiles:
92
+ - prebuilt
docs/docs/CONTRIBUTING.md CHANGED
@@ -6,15 +6,15 @@ Welcome! This guide provides all the details you need to contribute effectively
6
 
7
  ## 📋 Table of Contents
8
 
9
- 1. [Code of Conduct](#code-of-conduct)
10
- 2. [How Can I Contribute?](#how-can-i-contribute)
11
- 3. [Pull Request Guidelines](#pull-request-guidelines)
12
- 4. [Coding Standards](#coding-standards)
13
- 5. [Development Setup](#development-setup)
14
- 6. [Testing](#testing)
15
- 7. [Deployment](#deployment)
16
- 8. [Docker Deployment](#docker-deployment)
17
- 9. [VS Code Dev Containers Integration](#vs-code-dev-containers-integration)
18
 
19
  ---
20
 
@@ -27,60 +27,67 @@ This project is governed by our **Code of Conduct**. By participating, you agree
27
  ## 🛠️ How Can I Contribute?
28
 
29
  ### 1️⃣ Reporting Bugs or Feature Requests
 
30
  - Check the [issue tracker](#) to avoid duplicates.
31
- - Use issue templates (if available).
32
  - Provide detailed, relevant information and steps to reproduce bugs.
33
 
34
  ### 2️⃣ Code Contributions
35
- 1. Fork the repository.
36
- 2. Create a feature or fix branch.
37
- 3. Write and test your code.
 
38
  4. Submit a pull request (PR).
39
 
40
- ### 3️⃣ Join as a Core Contributor
 
41
  Interested in maintaining and growing the project? Fill out our [Contributor Application Form](https://forms.gle/TBSteXSDCtBDwr5m7).
42
 
43
  ---
44
 
45
  ## ✅ Pull Request Guidelines
46
 
47
- ### PR Checklist
48
- - Branch from the **main** branch.
49
- - Update documentation, if needed.
50
- - Test all functionality manually.
51
- - Focus on one feature/bug per PR.
52
 
53
- ### Review Process
54
- 1. Manual testing by reviewers.
55
- 2. At least one maintainer review required.
56
- 3. Address review comments.
 
 
 
 
 
 
57
  4. Maintain a clean commit history.
58
 
59
  ---
60
 
61
  ## 📏 Coding Standards
62
 
63
- ### General Guidelines
64
- - Follow existing code style.
65
- - Comment complex logic.
66
- - Keep functions small and focused.
 
67
  - Use meaningful variable names.
68
 
69
  ---
70
 
71
  ## 🖥️ Development Setup
72
 
73
- ### 1️⃣ Initial Setup
74
- - Clone the repository:
 
75
  ```bash
76
  git clone https://github.com/stackblitz-labs/bolt.diy.git
77
  ```
78
- - Install dependencies:
79
  ```bash
80
  pnpm install
81
  ```
82
- - Set up environment variables:
83
- 1. Rename `.env.example` to `.env.local`.
84
  2. Add your API keys:
85
  ```bash
86
  GROQ_API_KEY=XXX
@@ -88,23 +95,26 @@ Interested in maintaining and growing the project? Fill out our [Contributor App
88
  OPENAI_API_KEY=XXX
89
  ...
90
  ```
91
- 3. Optionally set:
92
- - Debug level: `VITE_LOG_LEVEL=debug`
93
- - Context size: `DEFAULT_NUM_CTX=32768`
94
 
95
  **Note**: Never commit your `.env.local` file to version control. It’s already in `.gitignore`.
96
 
97
- ### 2️⃣ Run Development Server
 
98
  ```bash
99
  pnpm run dev
100
  ```
 
101
  **Tip**: Use **Google Chrome Canary** for local testing.
102
 
103
  ---
104
 
105
  ## 🧪 Testing
106
 
107
- Run the test suite with:
 
108
  ```bash
109
  pnpm test
110
  ```
@@ -113,10 +123,12 @@ pnpm test
113
 
114
  ## 🚀 Deployment
115
 
116
- ### Deploy to Cloudflare Pages
 
117
  ```bash
118
  pnpm run deploy
119
  ```
 
120
  Ensure you have required permissions and that Wrangler is configured.
121
 
122
  ---
@@ -127,67 +139,76 @@ This section outlines the methods for deploying the application using Docker. Th
127
 
128
  ---
129
 
130
- ### 🧑‍💻 Development Environment
131
 
132
- #### Build Options
 
 
133
 
134
- **Option 1: Helper Scripts**
135
  ```bash
136
  # Development build
137
  npm run dockerbuild
138
  ```
139
 
140
- **Option 2: Direct Docker Build Command**
 
141
  ```bash
142
  docker build . --target bolt-ai-development
143
  ```
144
 
145
- **Option 3: Docker Compose Profile**
 
146
  ```bash
147
  docker compose --profile development up
148
  ```
149
 
150
- #### Running the Development Container
 
151
  ```bash
152
  docker run -p 5173:5173 --env-file .env.local bolt-ai:development
153
  ```
154
 
155
  ---
156
 
157
- ### 🏭 Production Environment
 
 
158
 
159
- #### Build Options
160
 
161
- **Option 1: Helper Scripts**
162
  ```bash
163
  # Production build
164
  npm run dockerbuild:prod
165
  ```
166
 
167
- **Option 2: Direct Docker Build Command**
 
168
  ```bash
169
  docker build . --target bolt-ai-production
170
  ```
171
 
172
- **Option 3: Docker Compose Profile**
 
173
  ```bash
174
  docker compose --profile production up
175
  ```
176
 
177
- #### Running the Production Container
 
178
  ```bash
179
  docker run -p 5173:5173 --env-file .env.local bolt-ai:production
180
  ```
181
 
182
  ---
183
 
184
- ### Coolify Deployment
 
 
185
 
186
- For an easy deployment process, use [Coolify](https://github.com/coollabsio/coolify):
187
- 1. Import your Git repository into Coolify.
188
- 2. Choose **Docker Compose** as the build pack.
189
- 3. Configure environment variables (e.g., API keys).
190
- 4. Set the start command:
191
  ```bash
192
  docker compose --profile production up
193
  ```
@@ -200,20 +221,22 @@ The `docker-compose.yaml` configuration is compatible with **VS Code Dev Contain
200
 
201
  ### Steps to Use Dev Containers
202
 
203
- 1. Open the command palette in VS Code (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS).
204
- 2. Select **Dev Containers: Reopen in Container**.
205
- 3. Choose the **development** profile when prompted.
206
  4. VS Code will rebuild the container and open it with the pre-configured environment.
207
 
208
  ---
209
 
210
  ## 🔑 Environment Variables
211
 
212
- Ensure `.env.local` is configured correctly with:
213
- - API keys.
214
- - Context-specific configurations.
 
 
 
215
 
216
- Example for the `DEFAULT_NUM_CTX` variable:
217
  ```bash
218
  DEFAULT_NUM_CTX=24576 # Uses 32GB VRAM
219
- ```
 
6
 
7
  ## 📋 Table of Contents
8
 
9
+ 1. [Code of Conduct](#code-of-conduct)
10
+ 2. [How Can I Contribute?](#how-can-i-contribute)
11
+ 3. [Pull Request Guidelines](#pull-request-guidelines)
12
+ 4. [Coding Standards](#coding-standards)
13
+ 5. [Development Setup](#development-setup)
14
+ 6. [Testing](#testing)
15
+ 7. [Deployment](#deployment)
16
+ 8. [Docker Deployment](#docker-deployment)
17
+ 9. [VS Code Dev Containers Integration](#vs-code-dev-containers-integration)
18
 
19
  ---
20
 
 
27
  ## 🛠️ How Can I Contribute?
28
 
29
  ### 1️⃣ Reporting Bugs or Feature Requests
30
+
31
  - Check the [issue tracker](#) to avoid duplicates.
32
+ - Use issue templates (if available).
33
  - Provide detailed, relevant information and steps to reproduce bugs.
34
 
35
  ### 2️⃣ Code Contributions
36
+
37
+ 1. Fork the repository.
38
+ 2. Create a feature or fix branch.
39
+ 3. Write and test your code.
40
  4. Submit a pull request (PR).
41
 
42
+ ### 3️⃣ Join as a Core Contributor
43
+
44
  Interested in maintaining and growing the project? Fill out our [Contributor Application Form](https://forms.gle/TBSteXSDCtBDwr5m7).
45
 
46
  ---
47
 
48
  ## ✅ Pull Request Guidelines
49
 
50
+ ### PR Checklist
 
 
 
 
51
 
52
+ - Branch from the **main** branch.
53
+ - Update documentation, if needed.
54
+ - Test all functionality manually.
55
+ - Focus on one feature/bug per PR.
56
+
57
+ ### Review Process
58
+
59
+ 1. Manual testing by reviewers.
60
+ 2. At least one maintainer review required.
61
+ 3. Address review comments.
62
  4. Maintain a clean commit history.
63
 
64
  ---
65
 
66
  ## 📏 Coding Standards
67
 
68
+ ### General Guidelines
69
+
70
+ - Follow existing code style.
71
+ - Comment complex logic.
72
+ - Keep functions small and focused.
73
  - Use meaningful variable names.
74
 
75
  ---
76
 
77
  ## 🖥️ Development Setup
78
 
79
+ ### 1️⃣ Initial Setup
80
+
81
+ - Clone the repository:
82
  ```bash
83
  git clone https://github.com/stackblitz-labs/bolt.diy.git
84
  ```
85
+ - Install dependencies:
86
  ```bash
87
  pnpm install
88
  ```
89
+ - Set up environment variables:
90
+ 1. Rename `.env.example` to `.env.local`.
91
  2. Add your API keys:
92
  ```bash
93
  GROQ_API_KEY=XXX
 
95
  OPENAI_API_KEY=XXX
96
  ...
97
  ```
98
+ 3. Optionally set:
99
+ - Debug level: `VITE_LOG_LEVEL=debug`
100
+ - Context size: `DEFAULT_NUM_CTX=32768`
101
 
102
  **Note**: Never commit your `.env.local` file to version control. It’s already in `.gitignore`.
103
 
104
+ ### 2️⃣ Run Development Server
105
+
106
  ```bash
107
  pnpm run dev
108
  ```
109
+
110
  **Tip**: Use **Google Chrome Canary** for local testing.
111
 
112
  ---
113
 
114
  ## 🧪 Testing
115
 
116
+ Run the test suite with:
117
+
118
  ```bash
119
  pnpm test
120
  ```
 
123
 
124
  ## 🚀 Deployment
125
 
126
+ ### Deploy to Cloudflare Pages
127
+
128
  ```bash
129
  pnpm run deploy
130
  ```
131
+
132
  Ensure you have required permissions and that Wrangler is configured.
133
 
134
  ---
 
139
 
140
  ---
141
 
142
+ ### 🧑‍💻 Development Environment
143
 
144
+ #### Build Options
145
+
146
+ **Option 1: Helper Scripts**
147
 
 
148
  ```bash
149
  # Development build
150
  npm run dockerbuild
151
  ```
152
 
153
+ **Option 2: Direct Docker Build Command**
154
+
155
  ```bash
156
  docker build . --target bolt-ai-development
157
  ```
158
 
159
+ **Option 3: Docker Compose Profile**
160
+
161
  ```bash
162
  docker compose --profile development up
163
  ```
164
 
165
+ #### Running the Development Container
166
+
167
  ```bash
168
  docker run -p 5173:5173 --env-file .env.local bolt-ai:development
169
  ```
170
 
171
  ---
172
 
173
+ ### 🏭 Production Environment
174
+
175
+ #### Build Options
176
 
177
+ **Option 1: Helper Scripts**
178
 
 
179
  ```bash
180
  # Production build
181
  npm run dockerbuild:prod
182
  ```
183
 
184
+ **Option 2: Direct Docker Build Command**
185
+
186
  ```bash
187
  docker build . --target bolt-ai-production
188
  ```
189
 
190
+ **Option 3: Docker Compose Profile**
191
+
192
  ```bash
193
  docker compose --profile production up
194
  ```
195
 
196
+ #### Running the Production Container
197
+
198
  ```bash
199
  docker run -p 5173:5173 --env-file .env.local bolt-ai:production
200
  ```
201
 
202
  ---
203
 
204
+ ### Coolify Deployment
205
+
206
+ For an easy deployment process, use [Coolify](https://github.com/coollabsio/coolify):
207
 
208
+ 1. Import your Git repository into Coolify.
209
+ 2. Choose **Docker Compose** as the build pack.
210
+ 3. Configure environment variables (e.g., API keys).
211
+ 4. Set the start command:
 
212
  ```bash
213
  docker compose --profile production up
214
  ```
 
221
 
222
  ### Steps to Use Dev Containers
223
 
224
+ 1. Open the command palette in VS Code (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS).
225
+ 2. Select **Dev Containers: Reopen in Container**.
226
+ 3. Choose the **development** profile when prompted.
227
  4. VS Code will rebuild the container and open it with the pre-configured environment.
228
 
229
  ---
230
 
231
  ## 🔑 Environment Variables
232
 
233
+ Ensure `.env.local` is configured correctly with:
234
+
235
+ - API keys.
236
+ - Context-specific configurations.
237
+
238
+ Example for the `DEFAULT_NUM_CTX` variable:
239
 
 
240
  ```bash
241
  DEFAULT_NUM_CTX=24576 # Uses 32GB VRAM
242
+ ```
docs/docs/FAQ.md CHANGED
@@ -3,7 +3,7 @@
3
  ## Models and Setup
4
 
5
  ??? question "What are the best models for bolt.diy?"
6
- For the best experience with bolt.diy, we recommend using the following models:
7
 
8
  - **Claude 3.5 Sonnet (old)**: Best overall coder, providing excellent results across all use cases
9
  - **Gemini 2.0 Flash**: Exceptional speed while maintaining good performance
@@ -17,79 +17,78 @@
17
 
18
  ## Best Practices
19
 
20
- ??? question "How do I get the best results with bolt.diy?"
21
- - **Be specific about your stack**:
22
- 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.
23
 
24
- - **Use the enhance prompt icon**:
25
  Before sending your prompt, click the *enhance* icon to let the AI refine your prompt. You can edit the suggested improvements before submitting.
26
 
27
- - **Scaffold the basics first, then add features**:
28
  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.
29
 
30
- - **Batch simple instructions**:
31
- Combine simple tasks into a single prompt to save time and reduce API credit consumption. For example:
32
  *"Change the color scheme, add mobile responsiveness, and restart the dev server."*
33
 
34
  ## Project Information
35
 
36
  ??? question "How do I contribute to bolt.diy?"
37
- Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to get involved!
38
 
39
  ??? question "What are the future plans for bolt.diy?"
40
- Visit our [Roadmap](https://roadmap.sh/r/ottodev-roadmap-2ovzo) for the latest updates.
41
- New features and improvements are on the way!
42
 
43
  ??? question "Why are there so many open issues/pull requests?"
44
- 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!
45
 
46
  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.
47
 
48
  ## Model Comparisons
49
 
50
  ??? question "How do local LLMs compare to larger models like Claude 3.5 Sonnet for bolt.diy?"
51
- 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.
52
 
53
  ## Troubleshooting
54
 
55
  ??? error "There was an error processing this request"
56
- This generic error message means something went wrong. Check both:
57
 
58
  - The terminal (if you started the app with Docker or `pnpm`).
59
 
60
  - The developer console in your browser (press `F12` or right-click > *Inspect*, then go to the *Console* tab).
61
 
62
  ??? error "x-api-key header missing"
63
- This error is sometimes resolved by restarting the Docker container.
64
- If that doesn't work, try switching from Docker to `pnpm` or vice versa. We're actively investigating this issue.
65
 
66
  ??? error "Blank preview when running the app"
67
- A blank preview often occurs due to hallucinated bad code or incorrect commands.
68
- To troubleshoot:
69
 
70
  - Check the developer console for errors.
71
 
72
  - Remember, previews are core functionality, so the app isn't broken! We're working on making these errors more transparent.
73
 
74
  ??? error "Everything works, but the results are bad"
75
- 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
76
 
77
- - GPT-4o
78
  - Claude 3.5 Sonnet
79
  - DeepSeek Coder V2 236b
80
 
81
  ??? error "Received structured exception #0xc0000005: access violation"
82
- 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)
83
 
84
  ??? error "Miniflare or Wrangler errors in Windows"
85
- 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>
86
 
87
  ---
88
 
89
  ## Get Help & Support
90
 
91
  !!! tip "Community Support"
92
- [Join the bolt.diy Community](https://thinktank.ottomator.ai/c/bolt-diy/17){target=_blank} for discussions and help
93
 
94
  !!! bug "Report Issues"
95
- [Open an Issue](https://github.com/stackblitz-labs/bolt.diy/issues/19){target=_blank} in our GitHub Repository
 
3
  ## Models and Setup
4
 
5
  ??? question "What are the best models for bolt.diy?"
6
+ For the best experience with bolt.diy, we recommend using the following models:
7
 
8
  - **Claude 3.5 Sonnet (old)**: Best overall coder, providing excellent results across all use cases
9
  - **Gemini 2.0 Flash**: Exceptional speed while maintaining good performance
 
17
 
18
  ## Best Practices
19
 
20
+ ??? question "How do I get the best results with bolt.diy?" - **Be specific about your stack**:
21
+ 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.
 
22
 
23
+ - **Use the enhance prompt icon**:
24
  Before sending your prompt, click the *enhance* icon to let the AI refine your prompt. You can edit the suggested improvements before submitting.
25
 
26
+ - **Scaffold the basics first, then add features**:
27
  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.
28
 
29
+ - **Batch simple instructions**:
30
+ Combine simple tasks into a single prompt to save time and reduce API credit consumption. For example:
31
  *"Change the color scheme, add mobile responsiveness, and restart the dev server."*
32
 
33
  ## Project Information
34
 
35
  ??? question "How do I contribute to bolt.diy?"
36
+ Check out our [Contribution Guide](CONTRIBUTING.md) for more details on how to get involved!
37
 
38
  ??? question "What are the future plans for bolt.diy?"
39
+ Visit our [Roadmap](https://roadmap.sh/r/ottodev-roadmap-2ovzo) for the latest updates.
40
+ New features and improvements are on the way!
41
 
42
  ??? question "Why are there so many open issues/pull requests?"
43
+ 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!
44
 
45
  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.
46
 
47
  ## Model Comparisons
48
 
49
  ??? question "How do local LLMs compare to larger models like Claude 3.5 Sonnet for bolt.diy?"
50
+ 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.
51
 
52
  ## Troubleshooting
53
 
54
  ??? error "There was an error processing this request"
55
+ This generic error message means something went wrong. Check both:
56
 
57
  - The terminal (if you started the app with Docker or `pnpm`).
58
 
59
  - The developer console in your browser (press `F12` or right-click > *Inspect*, then go to the *Console* tab).
60
 
61
  ??? error "x-api-key header missing"
62
+ This error is sometimes resolved by restarting the Docker container.
63
+ If that doesn't work, try switching from Docker to `pnpm` or vice versa. We're actively investigating this issue.
64
 
65
  ??? error "Blank preview when running the app"
66
+ A blank preview often occurs due to hallucinated bad code or incorrect commands.
67
+ To troubleshoot:
68
 
69
  - Check the developer console for errors.
70
 
71
  - Remember, previews are core functionality, so the app isn't broken! We're working on making these errors more transparent.
72
 
73
  ??? error "Everything works, but the results are bad"
74
+ 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
75
 
76
+ - GPT-4o
77
  - Claude 3.5 Sonnet
78
  - DeepSeek Coder V2 236b
79
 
80
  ??? error "Received structured exception #0xc0000005: access violation"
81
+ 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)
82
 
83
  ??? error "Miniflare or Wrangler errors in Windows"
84
+ 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>
85
 
86
  ---
87
 
88
  ## Get Help & Support
89
 
90
  !!! tip "Community Support"
91
+ [Join the bolt.diy Community](https://thinktank.ottomator.ai/c/bolt-diy/17){target=\_blank} for discussions and help
92
 
93
  !!! bug "Report Issues"
94
+ [Open an Issue](https://github.com/stackblitz-labs/bolt.diy/issues/19){target=\_blank} in our GitHub Repository
docs/docs/index.md CHANGED
@@ -1,7 +1,9 @@
1
  # Welcome to bolt diy
 
2
  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.
3
 
4
  ## Table of Contents
 
5
  - [Join the community!](#join-the-community)
6
  - [Features](#features)
7
  - [Setup](#setup)
@@ -41,31 +43,31 @@ Also [this pinned post in our community](https://thinktank.ottomator.ai/t/videos
41
 
42
  ---
43
 
44
- ## Setup
45
 
46
- 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.
47
 
48
- ### Prerequisites
49
 
50
- 1. **Install Git**: [Download Git](https://git-scm.com/downloads)
51
- 2. **Install Node.js**: [Download Node.js](https://nodejs.org/en/download/)
52
 
53
- - After installation, the Node.js path is usually added to your system automatically. To verify:
54
- - **Windows**: Search for "Edit the system environment variables," click "Environment Variables," and check if `Node.js` is in the `Path` variable.
55
- - **Mac/Linux**: Open a terminal and run:
56
- ```bash
57
- echo $PATH
58
- ```
59
- Look for `/usr/local/bin` in the output.
60
 
61
  ### Clone the Repository
62
 
63
  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.
64
 
65
- Clone the repository using Git:
66
 
67
- ```bash
68
- git clone -b stable https://github.com/stackblitz-labs/bolt.diy
69
  ```
70
 
71
  ---
@@ -76,7 +78,7 @@ There are two ways to configure your API keys in bolt.diy:
76
 
77
  #### 1. Set API Keys in the `.env.local` File
78
 
79
- 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.
80
 
81
  - On **Mac**, you can find the file at `[your name]/bolt.diy/.env.example`.
82
  - 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
112
 
113
  Once you've configured your keys, the application will be ready to use the selected LLMs.
114
 
115
-
116
  ---
117
 
118
- ## Run the Application
119
 
120
  ### Option 1: Without Docker
121
 
122
- 1. **Install Dependencies**:
123
- ```bash
124
- pnpm install
125
- ```
126
- If `pnpm` is not installed, install it using:
127
- ```bash
128
- sudo npm install -g pnpm
129
- ```
130
-
131
- 2. **Start the Application**:
132
- ```bash
133
- pnpm run dev
134
  ```
135
- 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.
136
 
137
- ### Option 2: With Docker
138
 
139
- #### Prerequisites
140
- - Ensure Git, Node.js, and Docker are installed: [Download Docker](https://www.docker.com/)
 
141
 
142
- #### Steps
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
- 1. **Build the Docker Image**:
 
 
 
 
 
 
145
 
146
- Use the provided NPM scripts:
147
- ```bash
148
- npm run dockerbuild
149
- ```
150
 
151
- Alternatively, use Docker commands directly:
152
- ```bash
153
  docker build . --target bolt-ai-development
154
- ```
155
 
156
  2. **Run the Container**:
157
- Use Docker Compose profiles to manage environments:
158
- ```bash
159
- docker compose --profile development up
160
- ```
 
161
 
162
- - With the development profile, changes to your code will automatically reflect in the running container (hot reloading).
163
 
164
  ---
165
 
@@ -167,42 +175,46 @@ Once you've configured your keys, the application will be ready to use the selec
167
 
168
  To keep your local version of bolt.diy up to date with the latest changes, follow these steps for your operating system:
169
 
170
- #### 1. **Navigate to your project folder**
171
- Navigate to the directory where you cloned the repository and open a terminal:
172
 
173
- #### 2. **Fetch the Latest Changes**
174
- Use Git to pull the latest changes from the main repository:
175
 
176
- ```bash
177
- git pull origin main
178
- ```
179
 
180
- #### 3. **Update Dependencies**
181
- After pulling the latest changes, update the project dependencies by running the following command:
182
 
183
- ```bash
184
- pnpm install
185
- ```
 
 
 
 
 
 
 
 
 
 
186
 
187
- #### 4. **Rebuild and Start the Application**
188
 
189
- - **If using Docker**, ensure you rebuild the Docker image to avoid using a cached version:
190
- ```bash
191
- docker compose --profile development up --build
192
- ```
193
 
194
- - **If not using Docker**, you can start the application as usual with:
195
- ```bash
196
- pnpm run dev
197
- ```
198
 
199
- This ensures that you're running the latest version of bolt.diy and can take advantage of all the newest features and bug fixes.
200
 
201
  ---
202
 
203
  ## Adding New LLMs:
204
 
205
- 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.
206
 
207
  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!
208
 
 
1
  # Welcome to bolt diy
2
+
3
  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.
4
 
5
  ## Table of Contents
6
+
7
  - [Join the community!](#join-the-community)
8
  - [Features](#features)
9
  - [Setup](#setup)
 
43
 
44
  ---
45
 
46
+ ## Setup
47
 
48
+ 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.
49
 
50
+ ### Prerequisites
51
 
52
+ 1. **Install Git**: [Download Git](https://git-scm.com/downloads)
53
+ 2. **Install Node.js**: [Download Node.js](https://nodejs.org/en/download/)
54
 
55
+ - After installation, the Node.js path is usually added to your system automatically. To verify:
56
+ - **Windows**: Search for "Edit the system environment variables," click "Environment Variables," and check if `Node.js` is in the `Path` variable.
57
+ - **Mac/Linux**: Open a terminal and run:
58
+ ```bash
59
+ echo $PATH
60
+ ```
61
+ Look for `/usr/local/bin` in the output.
62
 
63
  ### Clone the Repository
64
 
65
  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.
66
 
67
+ Clone the repository using Git:
68
 
69
+ ```bash
70
+ git clone -b stable https://github.com/stackblitz-labs/bolt.diy
71
  ```
72
 
73
  ---
 
78
 
79
  #### 1. Set API Keys in the `.env.local` File
80
 
81
+ 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.
82
 
83
  - On **Mac**, you can find the file at `[your name]/bolt.diy/.env.example`.
84
  - On **Windows/Linux**, the path will be similar.
 
114
 
115
  Once you've configured your keys, the application will be ready to use the selected LLMs.
116
 
 
117
  ---
118
 
119
+ ## Run the Application
120
 
121
  ### Option 1: Without Docker
122
 
123
+ 1. **Install Dependencies**:
124
+
125
+ ```bash
126
+ pnpm install
 
 
 
 
 
 
 
 
127
  ```
 
128
 
129
+ If `pnpm` is not installed, install it using:
130
 
131
+ ```bash
132
+ sudo npm install -g pnpm
133
+ ```
134
 
135
+ 2. **Start the Application**:
136
+ ```bash
137
+ pnpm run dev
138
+ ```
139
+ 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.
140
+
141
+ ### Option 2: With Docker
142
+
143
+ #### Prerequisites
144
+
145
+ - Ensure Git, Node.js, and Docker are installed: [Download Docker](https://www.docker.com/)
146
+
147
+ #### Steps
148
 
149
+ 1. **Build the Docker Image**:
150
+
151
+ Use the provided NPM scripts:
152
+
153
+ ```bash
154
+ npm run dockerbuild
155
+ ```
156
 
157
+ Alternatively, use Docker commands directly:
 
 
 
158
 
159
+ ```bash
 
160
  docker build . --target bolt-ai-development
161
+ ```
162
 
163
  2. **Run the Container**:
164
+ Use Docker Compose profiles to manage environments:
165
+
166
+ ```bash
167
+ docker compose --profile development up
168
+ ```
169
 
170
+ - With the development profile, changes to your code will automatically reflect in the running container (hot reloading).
171
 
172
  ---
173
 
 
175
 
176
  To keep your local version of bolt.diy up to date with the latest changes, follow these steps for your operating system:
177
 
178
+ #### 1. **Navigate to your project folder**
 
179
 
180
+ Navigate to the directory where you cloned the repository and open a terminal:
 
181
 
182
+ #### 2. **Fetch the Latest Changes**
 
 
183
 
184
+ Use Git to pull the latest changes from the main repository:
 
185
 
186
+ ```bash
187
+ git pull origin main
188
+ ```
189
+
190
+ #### 3. **Update Dependencies**
191
+
192
+ After pulling the latest changes, update the project dependencies by running the following command:
193
+
194
+ ```bash
195
+ pnpm install
196
+ ```
197
+
198
+ #### 4. **Rebuild and Start the Application**
199
 
200
+ - **If using Docker**, ensure you rebuild the Docker image to avoid using a cached version:
201
 
202
+ ```bash
203
+ docker compose --profile development up --build
204
+ ```
 
205
 
206
+ - **If not using Docker**, you can start the application as usual with:
207
+ ```bash
208
+ pnpm run dev
209
+ ```
210
 
211
+ This ensures that you're running the latest version of bolt.diy and can take advantage of all the newest features and bug fixes.
212
 
213
  ---
214
 
215
  ## Adding New LLMs:
216
 
217
+ 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.
218
 
219
  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!
220
 
docs/mkdocs.yml CHANGED
@@ -33,7 +33,7 @@ theme:
33
  # favicon: assets/logo.png
34
  repo_name: bolt.diy
35
  repo_url: https://github.com/stackblitz-labs/bolt.diy
36
- edit_uri: ""
37
 
38
  extra:
39
  generator: false
@@ -51,9 +51,6 @@ extra:
51
  link: https://bsky.app/profile/bolt.diy
52
  name: bolt.diy on Bluesky
53
 
54
-
55
-
56
-
57
  markdown_extensions:
58
  - pymdownx.highlight:
59
  anchor_linenums: true
@@ -73,4 +70,4 @@ markdown_extensions:
73
  - pymdownx.tasklist:
74
  custom_checkbox: true
75
  - toc:
76
- permalink: true
 
33
  # favicon: assets/logo.png
34
  repo_name: bolt.diy
35
  repo_url: https://github.com/stackblitz-labs/bolt.diy
36
+ edit_uri: ''
37
 
38
  extra:
39
  generator: false
 
51
  link: https://bsky.app/profile/bolt.diy
52
  name: bolt.diy on Bluesky
53
 
 
 
 
54
  markdown_extensions:
55
  - pymdownx.highlight:
56
  anchor_linenums: true
 
70
  - pymdownx.tasklist:
71
  custom_checkbox: true
72
  - toc:
73
+ permalink: true
eslint.config.mjs CHANGED
@@ -4,13 +4,7 @@ import { getNamingConventionRule, tsFileExtensions } from '@blitz/eslint-plugin/
4
 
5
  export default [
6
  {
7
- ignores: [
8
- '**/dist',
9
- '**/node_modules',
10
- '**/.wrangler',
11
- '**/bolt/build',
12
- '**/.history',
13
- ],
14
  },
15
  ...blitzPlugin.configs.recommended(),
16
  {
@@ -20,15 +14,15 @@ export default [
20
  '@typescript-eslint/no-empty-object-type': 'off',
21
  '@blitz/comment-syntax': 'off',
22
  '@blitz/block-scope-case': 'off',
23
- 'array-bracket-spacing': ["error", "never"],
24
- 'object-curly-newline': ["error", { "consistent": true }],
25
- 'keyword-spacing': ["error", { "before": true, "after": true }],
26
- 'consistent-return': "error",
27
- 'semi': ["error", "always"],
28
- 'curly': ["error"],
29
- 'no-eval': ["error"],
30
- 'linebreak-style': ["error", "unix"],
31
- 'arrow-spacing': ["error", { "before": true, "after": true }]
32
  },
33
  },
34
  {
@@ -53,7 +47,7 @@ export default [
53
  patterns: [
54
  {
55
  group: ['../'],
56
- message: 'Relative imports are not allowed. Please use \'~/\' instead.',
57
  },
58
  ],
59
  },
 
4
 
5
  export default [
6
  {
7
+ ignores: ['**/dist', '**/node_modules', '**/.wrangler', '**/bolt/build', '**/.history'],
 
 
 
 
 
 
8
  },
9
  ...blitzPlugin.configs.recommended(),
10
  {
 
14
  '@typescript-eslint/no-empty-object-type': 'off',
15
  '@blitz/comment-syntax': 'off',
16
  '@blitz/block-scope-case': 'off',
17
+ 'array-bracket-spacing': ['error', 'never'],
18
+ 'object-curly-newline': ['error', { consistent: true }],
19
+ 'keyword-spacing': ['error', { before: true, after: true }],
20
+ 'consistent-return': 'error',
21
+ semi: ['error', 'always'],
22
+ curly: ['error'],
23
+ 'no-eval': ['error'],
24
+ 'linebreak-style': ['error', 'unix'],
25
+ 'arrow-spacing': ['error', { before: true, after: true }],
26
  },
27
  },
28
  {
 
47
  patterns: [
48
  {
49
  group: ['../'],
50
+ message: "Relative imports are not allowed. Please use '~/' instead.",
51
  },
52
  ],
53
  },
package.json CHANGED
@@ -84,8 +84,8 @@
84
  "ai": "^4.1.2",
85
  "chalk": "^5.4.1",
86
  "chart.js": "^4.4.7",
87
- "class-variance-authority": "^0.7.1",
88
- "clsx": "^2.1.1",
89
  "date-fns": "^3.6.0",
90
  "diff": "^5.2.0",
91
  "dotenv": "^16.4.7",
@@ -117,7 +117,7 @@
117
  "remix-island": "^0.2.0",
118
  "remix-utils": "^7.7.0",
119
  "shiki": "^1.24.0",
120
- "tailwind-merge": "^2.6.0",
121
  "unist-util-visit": "^5.0.0"
122
  },
123
  "devDependencies": {
 
84
  "ai": "^4.1.2",
85
  "chalk": "^5.4.1",
86
  "chart.js": "^4.4.7",
87
+ "class-variance-authority": "^0.7.0",
88
+ "clsx": "^2.1.0",
89
  "date-fns": "^3.6.0",
90
  "diff": "^5.2.0",
91
  "dotenv": "^16.4.7",
 
117
  "remix-island": "^0.2.0",
118
  "remix-utils": "^7.7.0",
119
  "shiki": "^1.24.0",
120
+ "tailwind-merge": "^2.2.1",
121
  "unist-util-visit": "^5.0.0"
122
  },
123
  "devDependencies": {
pnpm-lock.yaml CHANGED
The diff for this file is too large to render. See raw diff
 
pre-start.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const { execSync } =require('child_process');
2
 
3
  // Get git hash with fallback
4
  const getGitHash = () => {
 
1
+ const { execSync } = require('child_process');
2
 
3
  // Get git hash with fallback
4
  const getGitHash = () => {
tsconfig.json CHANGED
@@ -1,7 +1,12 @@
1
  {
2
  "compilerOptions": {
3
  "lib": ["DOM", "DOM.Iterable", "ESNext"],
4
- "types": ["@remix-run/cloudflare", "vite/client", "@cloudflare/workers-types/2023-07-01", "@types/dom-speech-recognition"],
 
 
 
 
 
5
  "isolatedModules": true,
6
  "esModuleInterop": true,
7
  "jsx": "react-jsx",
 
1
  {
2
  "compilerOptions": {
3
  "lib": ["DOM", "DOM.Iterable", "ESNext"],
4
+ "types": [
5
+ "@remix-run/cloudflare",
6
+ "vite/client",
7
+ "@cloudflare/workers-types/2023-07-01",
8
+ "@types/dom-speech-recognition"
9
+ ],
10
  "isolatedModules": true,
11
  "esModuleInterop": true,
12
  "jsx": "react-jsx",
vite.config.ts CHANGED
@@ -18,9 +18,6 @@ const getGitHash = () => {
18
  }
19
  };
20
 
21
-
22
-
23
-
24
  export default defineConfig((config) => {
25
  return {
26
  define: {
@@ -41,7 +38,7 @@ export default defineConfig((config) => {
41
  v3_fetcherPersist: true,
42
  v3_relativeSplatPath: true,
43
  v3_throwAbortReason: true,
44
- v3_lazyRouteDiscovery: true
45
  },
46
  }),
47
  UnoCSS(),
@@ -49,7 +46,13 @@ export default defineConfig((config) => {
49
  chrome129IssuePlugin(),
50
  config.mode === 'production' && optimizeCssModules({ apply: 'build' }),
51
  ],
52
- envPrefix: ["VITE_","OPENAI_LIKE_API_BASE_URL", "OLLAMA_API_BASE_URL", "LMSTUDIO_API_BASE_URL","TOGETHER_API_BASE_URL"],
 
 
 
 
 
 
53
  css: {
54
  preprocessorOptions: {
55
  scss: {
 
18
  }
19
  };
20
 
 
 
 
21
  export default defineConfig((config) => {
22
  return {
23
  define: {
 
38
  v3_fetcherPersist: true,
39
  v3_relativeSplatPath: true,
40
  v3_throwAbortReason: true,
41
+ v3_lazyRouteDiscovery: true,
42
  },
43
  }),
44
  UnoCSS(),
 
46
  chrome129IssuePlugin(),
47
  config.mode === 'production' && optimizeCssModules({ apply: 'build' }),
48
  ],
49
+ envPrefix: [
50
+ 'VITE_',
51
+ 'OPENAI_LIKE_API_BASE_URL',
52
+ 'OLLAMA_API_BASE_URL',
53
+ 'LMSTUDIO_API_BASE_URL',
54
+ 'TOGETHER_API_BASE_URL',
55
+ ],
56
  css: {
57
  preprocessorOptions: {
58
  scss: {