diff --git a/.gitignore b/.gitignore index f3acaccf6..b14a9dea3 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ public/test-models # Local Netlify folder .netlify +.env +deno.lock diff --git a/Bolt.md b/Bolt.md new file mode 100644 index 000000000..3f280fcf1 --- /dev/null +++ b/Bolt.md @@ -0,0 +1,142 @@ +# Bolt.new Integration + +This repository integrates bolt.new as a subpath at `/build`. + +## Local Development Setup + +### Prerequisites + +1. Clone the bolt.new fork alongside this repository: + ```bash + git clone https://github.com/bldrs-ai/bldrsbolt.new.git + ``` + +2. Install dependencies in bolt.new: + ```bash + cd bldrsbolt.new + pnpm install + ``` + +### Environment Variables + +Create a `.env` file in the root of this Share repository: + +```bash +GEMINI_API_KEY=your_gemini_api_key_here +ANTHROPIC_API_KEY=your_anthropic_api_key_here +``` + +> **⚠️ Important**: Never commit the `.env` file. Make sure it's in `.gitignore`. + +### Syncing Bolt.new Files + +The `sync-to-share.cjs` script builds bolt.new and copies the files into this repository: + +```bash +# Sync using default path (../bldrsbolt.new) +yarn sync-bolt + +# Or specify a custom bolt.new path +yarn sync-bolt /path/to/bldrsbolt.new + +# Or use environment variable +BOLT_PATH=/path/to/bldrsbolt.new yarn sync-bolt +``` + +This script: +1. Builds bolt.new from the specified directory +2. Copies client files to `docs/build/` +3. Copies server function to `netlify/functions/server/` + +### Running Locally + +Start the development server: + +```bash +# Sync bolt.new and start dev server in one command +yarn dev-with-bolt + +# Or manually +yarn sync-bolt +netlify dev +``` +Access Share at: `http://localhost:8888` +Access bolt.new at: `http://localhost:8888/build` + +### How It Works + +1. **Static Assets**: Files in `docs/build/assets/` are served directly by Netlify +2. **API Routes**: `/build/api/*` and `/build/chat/*` are routed to `netlify/functions/server` +3. **Bolt Pages**: Other `/build/*` routes are handled by the Remix SSR function +4. **Share SPA**: All other routes (`/*`) serve Share's `index.html` + +### Netlify Configuration + +The `_redirects` file contains: + +``` +# 1. Bolt static assets +/build/assets/* /build/assets/:splat 200! + +# 2. Bolt server routes +/build /.netlify/functions/server 200 +/build/* /.netlify/functions/server 200 + +# 3. Share SPA fallback +/* /index.html 200 +``` + +## Deployment + +### Setting Up Environment Variables + +In Netlify dashboard: +1. Go to Site settings → Build & deploy → Environment +2. Add the API keys: + - `GEMINI_API_KEY` + - `ANTHROPIC_API_KEY` + +Or via CLI: +```bash +netlify env:set GEMINI_API_KEY "your_key_here" +netlify env:set ANTHROPIC_API_KEY "your_key_here" +``` + +### Build Process + +The deployment uses `netlify-share.toml` which: +1. Clones bolt.new into a temporary directory +2. Builds it with `pnpm run build` +3. Copies files to `docs/build/` and `netlify/functions/server/` +4. Deploys everything together + +## Troubleshooting + +### 204 No Content on Assets +Make sure the assets redirect has the `!` (force) flag and comes before the catch-all `/build/*` redirect. + +### API Key Errors +Ensure the `.env` file exists in the Share repository (not just in bolt.new) when running `netlify dev`. + +### Build Fails +Check that: +- The bolt.new path is correct +- Dependencies are installed in bolt.new (`pnpm install`) +- The build completes successfully (`pnpm run build` in bolt.new) + +## File Structure + +``` +Share/ +├── docs/ +├── └── _redirects +│ └── build/ # Bolt.new client files (copied by script) +│ ├── assets/ +│ └── ... +├── netlify/ +│ └── functions/ +│ └── server/ # Bolt.new server function (copied by script) +├── sync-to-share.cjs # Build and sync script +├── netlify.toml # Production deployment config +└── .env # API keys (gitignored) +``` \ No newline at end of file diff --git a/README.md b/README.md index 8a50405fc..8199de654 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ from any device. ## Features ### Fast and easy access to your models for free Open your models on any device in our browser-based viewer. -- Drag-n-drop models to view, no data upload, offline capable +- Drag-n-drop models to view, no data upload, offline capable - Navigate and search elements, crop with section planes - Property editing, save changes locally (early access) - Powerful data slice-n-dice, csv export (early access) @@ -38,6 +38,7 @@ Start using Bldrs Share today to transform your CAD workflows. Join our communit ### Getting Started - Visit our [wiki](https://github.com/bldrs-ai/Share/wiki) for Share's Design, Developer Guide and instructions for hosting your models. - See our [Conway engine](https://github.com/bldrs-ai/conway) repository + - See [Bolt.md](Bolt.md) for bolt.new integration and AI-powered code generation - Join us on [Bldrs Discord](https://discord.gg/9SxguBkFfQ) - Follow us on [X @bldrs_ai](https://x.com/bldrs_ai) - Connect with us on [LinkedIn](https://www.linkedin.com/company/bldrs-ai/) diff --git a/netlify.toml b/netlify.toml index 478a25e4d..c347d886c 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,3 +1,42 @@ +# Configuration for integrating bolt.new into Share at /build path +# Note: This approach copies bolt.new's built files but may require additional +# configuration in bolt.new's vite.config.ts to set base: '/build/' + +[build] + command = """ + # Build Share first so its assets land in docs/ + yarn build && \ + # Clone and build bolt.new (private repo - requires GITHUB_BOLT_TOKEN) + git clone https://${GITHUB_BOLT_TOKEN}@github.com/bldrs-ai/bolt.new.git temp-bolt && \ + cd temp-bolt && \ + corepack enable && \ + NODE_ENV=development pnpm install && \ + pnpm run build && \ + cd .. && \ + # Place bolt.new client bundle under Share's docs/build (served at /build/*) + rm -rf docs/build && \ + mkdir -p docs/build && \ + cp -R temp-bolt/build/client/* docs/build/ && \ + # Copy bolt.new server bundle into Netlify functions directory + rm -rf netlify/functions/server && \ + mkdir -p netlify/functions/server && \ + cp -R temp-bolt/build/server/* netlify/functions/server/ && \ + rm -rf temp-bolt + """ + publish = "docs" + functions = "netlify/functions" + +# bolt.new's Remix server runs from /.netlify/functions/server (per build/server/server.js output) + +[build.environment] + NODE_VERSION = "20" + GEMINI_API_KEY = "${GEMINI_API_KEY}" + ANTHROPIC_API_KEY = "${ANTHROPIC_API_KEY}" + # GITHUB_BOLT_TOKEN must be set in Netlify environment variables + # Create a GitHub Personal Access Token with 'repo' scope at: + # https://github.com/settings/tokens + # Add it to Netlify: Site settings > Build & deploy > Environment > Environment variables (name it GITHUB_BOLT_TOKEN) + # Apply strict cross-origin isolation for all routes by default [[headers]] for = "/*" @@ -5,6 +44,20 @@ Cross-Origin-Opener-Policy = "same-origin" Cross-Origin-Embedder-Policy = "require-corp" +# Headers for /build path (bolt.new app) +[[headers]] + for = "/build/*" + [headers.values] + Cross-Origin-Opener-Policy = "same-origin" + Cross-Origin-Embedder-Policy = "require-corp" + Cross-Origin-Resource-Policy = "cross-origin" + +# Asset caching for bolt.new assets +[[headers]] + for = "/build/assets/*" + [headers.values] + Cache-Control = "public, max-age=31536000, immutable" + # Override headers for the /subscribe route so that no strict isolation is enforced. [[headers]] for = "/subscribe/*" @@ -17,4 +70,21 @@ from = "/subscribe" to = "/subscribe/" status = 301 - force = true \ No newline at end of file + force = true + +# Keep bolt.new static assets served from /build/assets without hitting the function +[[redirects]] + from = "/build/assets/*" + to = "/build/assets/:splat" + status = 200 + +# Route everything else under /build to the bolt Remix server function +[[redirects]] + from = "/build" + to = "/.netlify/functions/server" + status = 200 + +[[redirects]] + from = "/build/*" + to = "/.netlify/functions/server" + status = 200 diff --git a/package.json b/package.json index 7c75ec254..a1b4cdb90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bldrs", - "version": "1.0.1894", + "version": "1.0.1913", "main": "src/index.jsx", "license": "AGPL-3.0", "homepage": "https://github.com/bldrs-ai/Share", @@ -25,6 +25,8 @@ "build-share-copy-wasm-conway-MT": "shx cp node_modules/@bldrs-ai/conway/compiled/dependencies/conway-geom/Dist/* docs/static/js", "build-share-copy-wasm-conway-profile": "shx cp node_modules/@bldrs-ai/conway-web-ifc-adapter/node_modules/@bldrs-ai/conway/compiled/dependencies/conway-geom/Dist/* docs/static/js", "build-share-analyze": "ANALYZE=true node tools/esbuild/build.js", + "sync-bolt": "node tools/esbuild/sync-to-share.cjs ${BOLT_PATH:-../bldrsbolt.new}", + "serve-with-bolt": "yarn build && yarn sync-bolt && netlify dev", "clean": "shx rm -rf docs", "deps-graph": "npx dependency-cruiser src --include-only 'jsx?' --config --output-type dot | dot -T svg > deps.svg", "husky-init": "husky install", diff --git a/public/_redirects b/public/_redirects index 7797f7c6a..6a8de2704 100644 --- a/public/_redirects +++ b/public/_redirects @@ -1 +1,9 @@ -/* /index.html 200 +# 1. Let Bolt static assets be served directly +/build/assets/* /build/assets/:splat 200! + +# 2. Send /build and /build/* to the Remix server function +/build /.netlify/functions/server 200! +/build/* /.netlify/functions/server 200! + +# 3. Fallback: Share SPA handles everything else +/* /index.html 200 diff --git a/public/icons/CreateApp.svg b/public/icons/CreateApp.svg new file mode 100644 index 000000000..923d5649d --- /dev/null +++ b/public/icons/CreateApp.svg @@ -0,0 +1,13 @@ + + diff --git a/public/widgets/app.js b/public/widgets/app.js new file mode 100644 index 000000000..3c1f93a3a --- /dev/null +++ b/public/widgets/app.js @@ -0,0 +1,102 @@ +// --- Global state --- +let sharePort = null; +let currentSelection = []; + +const controls = { + loadModelBtn: document.getElementById('loadModelBtn'), + hideBtn: document.getElementById('hideBtn'), + unhideAllBtn: document.getElementById('unhideAllBtn'), +}; + +// --- Helper to log events to the screen --- +function logEvent(message) { + const logList = document.getElementById('event-log'); + const listItem = document.createElement('li'); + listItem.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; + logList.prepend(listItem); +} + +// --- Handlers for specific messages from Share --- +const messageHandlers = { + 'ai.bldrs-share.SelectionChanged': (data) => { + currentSelection = data.current; + logEvent(`Selection changed. ${currentSelection.length} elements selected.`); + }, + 'ai.bldrs-share.ModelLoaded': (data) => { + logEvent('Model loaded event received.'); + console.log('ModelLoaded data:', data); + }, + 'ai.bldrs-share.HiddenElements': (data) => { + logEvent(`${data.current.length} elements are now hidden.`); + } +}; + +// --- Main message handler for the established port --- +function onShareMessage(event) { + const { action, data } = event.data; + if (action && messageHandlers[action]) { + messageHandlers[action](data); + } else { + logEvent(`Received unhandled action: ${action || 'unknown'}`); + console.log('Received unhandled message:', event.data); + } +} + +// --- Handshake Step 2: Listen for the host's response --- +// This function is the same, but it will now be triggered by our request. +function initPort(event) { + if (event.data === "init" && event.ports[0]) { + logEvent("Connection established with Share host."); + sharePort = event.ports[0]; + sharePort.onmessage = onShareMessage; + + // Enable controls now that we are connected + for (const control of Object.values(controls)) { + control.disabled = false; + } + window.removeEventListener("message", initPort); // Clean up listener + } +} + +// This listener is set up first, ready to catch the response. +window.addEventListener("message", initPort); + +// --- Handshake Step 1: Proactively request the communication channel from the host --- +// This is the NEW and CRITICAL part. +logEvent("Applet loaded. Requesting communication channel from Share host..."); +window.parent.postMessage('request-channel', '*'); + + +// --- UI Event Listeners to send commands to Share --- +controls.loadModelBtn.addEventListener('click', () => { + if (!sharePort) return; + logEvent("Sending 'LoadModel' command..."); + // THIS IS A KEY CORRECTION: The payload must be an object with 'action' and 'data'. + sharePort.postMessage({ + action: 'ai.bldrs-share.LoadModel', + data: { + githubIfcPath: 'Swiss-Property-AG/Momentum-Public/main/Momentum.ifc' + } + }); +}); + +controls.hideBtn.addEventListener('click', () => { + if (!sharePort || currentSelection.length === 0) { + logEvent("Cannot hide. No elements selected."); + return; + } + logEvent(`Sending 'HideElements' for ${currentSelection.length} elements...`); + sharePort.postMessage({ + action: 'ai.bldrs-share.HideElements', + data: { globalIds: currentSelection } + }); +}); + +controls.unhideAllBtn.addEventListener('click', () => { + if (!sharePort) return; + logEvent("Sending 'UnhideElements' command..."); + sharePort.postMessage({ + action: 'ai.bldrs-share.UnhideElements', + data: { globalIds: ['*'] } + }); +}); \ No newline at end of file diff --git a/public/widgets/event-logger.html b/public/widgets/event-logger.html index 40444a6cd..73e1aed3f 100644 --- a/public/widgets/event-logger.html +++ b/public/widgets/event-logger.html @@ -31,6 +31,10 @@